Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions conductor/tracks.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ This file tracks all major tracks for the project. Each track has its own detail
`src/document-intelligence/`.
[#278](https://github.com/edithatogo/authentext/issues/278).
Contracts are [PR #293](https://github.com/edithatogo/authentext/pull/293).
Registry seed is [PR #296](https://github.com/edithatogo/authentext/pull/296).
_Link: [tracks/pattern-registry-contracts_20260811/index.md](./tracks/pattern-registry-contracts_20260811/index.md)_

- [x] **voice-corpus-calibration_20260811** (P1) - Point Authentext at
Expand Down
4 changes: 2 additions & 2 deletions conductor/tracks/pattern-registry-contracts_20260811/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
registry entry fails `npm run sync` / validate.
Concordance is enforced by `validate-skill-contracts.js` against
Markdown headings rather than by rewriting `compile-skill.js`.
- [ ] Task: Teach `compile-skill.js` to emit the severity tables from
- [x] Task: Teach `compile-skill.js` to emit the severity tables from
`patterns.json`. Keep pattern bodies in modules until a later slice
migrates them.
- [ ] Task: Migrate pattern bodies in small PRs, not one dump.
Expand Down Expand Up @@ -44,5 +44,5 @@
- `src/document-intelligence/patterns-registry.schema.json`
- `src/document-intelligence/patterns.json`
- Concordance check: Markdown headings, severity-table IDs, frontmatter count
- Compiler path that emits severity tables (deferred if `compile-skill.js` is busy)
- Compiler path that emits severity tables from `patterns.json`
- Documented Agent Skills contract
4 changes: 2 additions & 2 deletions docs/agent-skills-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ Schemas live next to the other document-intelligence contracts:
`patterns.json` is the machine-readable seed for the 40 core patterns. The
validator checks it against `src/modules/SKILL_CORE_PATTERNS.md` headings,
body severities, the severity-table IDs, and the frontmatter `patterns`
count. Pattern bodies stay in the Markdown module until a later compile
slice.
count. `npm run sync` emits the severity tables from that registry. Pattern
bodies stay in the Markdown module until a later compile slice.
23 changes: 17 additions & 6 deletions scripts/compile-skill.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import {
loadContractJson,
PATTERNS_REGISTRY,
renderSeverityClassification,
replaceSeveritySection,
} from './lib/skill-contracts.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down Expand Up @@ -271,6 +277,11 @@ policy:
`;
}

function renderedSeverityFromRegistry() {
const registry = loadContractJson(ROOT_DIR, PATTERNS_REGISTRY);
return renderSeverityClassification(registry.patterns);
}

/**

* @param {Record<string, string|null>} modules
Expand All @@ -285,7 +296,11 @@ function writeReferenceTree(modules) {
continue;
}

const body = addReferenceNavigation(stripFrontmatter(moduleContent).trim());
let stripped = stripFrontmatter(moduleContent).trim();
if (key === 'core') {
stripped = replaceSeveritySection(stripped, renderedSeverityFromRegistry());
}
const body = addReferenceNavigation(stripped);
const targetPath = path.join(referencesDir, filename);
fs.writeFileSync(targetPath, `${body}\n`, 'utf-8');
console.log(`✓ Written: ${OUTPUT.referencesDir}/${filename}`);
Expand All @@ -306,11 +321,7 @@ function compileStandardSkill(modules) {
const strippedCore = stripFrontmatter(modules.core);

const intro = buildStandardIntro(strippedCore);
const severity = extractSection(
strippedCore,
'SEVERITY CLASSIFICATION',
'\n---\n\n_Module Version'
);
const severity = renderedSeverityFromRegistry();
const detection = extractSection(strippedCore, 'DETECTION GUIDANCE');

const referenceLinks = [
Expand Down
48 changes: 48 additions & 0 deletions scripts/lib/skill-contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,54 @@ export function validatePatternRegistryConcordance(registry, source) {
return errors;
}

const SEVERITY_TABLE_HEADINGS = Object.freeze({
critical: '### Critical (immediate AI detection)',
high: '### High (strong AI signals)',
medium: '### Medium (moderate AI signals)',
low: '### Low (weak AI signals)',
});

/**
* @param {unknown[]} records
* @returns {string}
*/
export function renderSeverityClassification(records) {
if (!Array.isArray(records)) {
throw new TypeError('pattern records must be an array');
}
const buckets = { critical: [], high: [], medium: [], low: [] };
for (const record of [...records].sort((left, right) => left.number - right.number)) {
const label = record.table_label || record.title;
const suffix =
record.must_preserve && !/\(must preserve\)/i.test(label) ? ' (must preserve)' : '';
buckets[record.severity].push(`- Pattern ${record.number}: ${label}${suffix}`);
}
const parts = ['## SEVERITY CLASSIFICATION', ''];
for (const severity of Object.keys(SEVERITY_TABLE_HEADINGS)) {
parts.push(SEVERITY_TABLE_HEADINGS[severity], '', ...buckets[severity], '');
}
return parts.join('\n').trim();
}

/**
* Replace the compiled severity block while leaving pattern bodies in place.
* @param {string} content
* @param {string} rendered
* @returns {string}
*/
export function replaceSeveritySection(content, rendered) {
const startToken = '\n## SEVERITY CLASSIFICATION\n';
const start = content.indexOf(startToken);
if (start === -1) {
throw new Error('missing ## SEVERITY CLASSIFICATION section');
}
const endMarker = '\n---\n\n_Module Version';
const end = content.indexOf(endMarker, start + 1);
const prefix = content.slice(0, start + 1);
const suffix = end === -1 ? '\n' : content.slice(end);
return `${prefix}${rendered}\n${suffix}`;
}

/**
* @param {unknown[]} records
* @param {Record<string, unknown>} schema
Expand Down
1 change: 1 addition & 0 deletions src/document-intelligence/pattern.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"id": { "type": "string", "pattern": "^pattern-[1-9][0-9]*$" },
"number": { "type": "integer", "minimum": 1 },
"title": { "type": "string", "minLength": 1 },
"table_label": { "type": "string", "minLength": 1 },
"severity": { "$ref": "#/$defs/severity" },
"must_preserve": { "type": "boolean" },
"domain_applicability": {
Expand Down
Loading
Loading