@ryuenn3123/agentic-senior-core 4.0.2 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.agent-context/rules/api-docs.md +14 -0
  2. package/.agent-context/rules/api-versioning.md +93 -0
  3. package/.agent-context/rules/background-jobs.md +93 -0
  4. package/.agent-context/rules/config-and-flags.md +79 -0
  5. package/.agent-context/rules/database-design.md +32 -0
  6. package/.agent-context/rules/frontend-architecture.md +35 -0
  7. package/.agent-context/rules/migrations.md +84 -0
  8. package/.agent-context/rules/observability.md +69 -0
  9. package/.agent-context/rules/resilience.md +78 -0
  10. package/.agent-context/rules/security.md +28 -0
  11. package/AGENTS.md +8 -8
  12. package/README.md +42 -9
  13. package/bin/agentic-senior-core.js +6 -0
  14. package/lib/cli/audits/typography-palette-anti-repeat/color-utils.mjs +156 -0
  15. package/lib/cli/audits/typography-palette-anti-repeat/file-scanner.mjs +103 -0
  16. package/lib/cli/audits/typography-palette-anti-repeat/typography-utils.mjs +70 -0
  17. package/lib/cli/audits/typography-palette-anti-repeat-audit.mjs +255 -0
  18. package/lib/cli/commands/audit-design-anti-repeat.mjs +198 -0
  19. package/lib/cli/commands/upgrade.mjs +1 -0
  20. package/lib/cli/utils.mjs +1 -0
  21. package/package.json +4 -4
  22. package/scripts/audit-cache-layer-contract.mjs +5 -0
  23. package/scripts/audit-caching-scope-hygiene.mjs +5 -0
  24. package/scripts/audit-typography-palette-anti-repeat.mjs +120 -0
  25. package/scripts/clean-local-artifacts.mjs +0 -1
  26. package/scripts/frontend-usability-audit.mjs +5 -8
  27. package/scripts/release-gate/static-checks.mjs +7 -7
  28. package/scripts/validate/config.mjs +0 -2
  29. package/scripts/validate/coverage-checks.mjs +1 -42
  30. package/scripts/validate.mjs +42 -7
  31. package/scripts/migrate-rule-format/id-prefix-table.mjs +0 -37
  32. package/scripts/migrate-rule-format/parse-legacy.mjs +0 -180
  33. package/scripts/migrate-rule-format/render-new.mjs +0 -169
  34. package/scripts/migrate-rule-format/roundtrip-validate.mjs +0 -89
  35. package/scripts/migrate-rule-format.mjs +0 -192
  36. package/scripts/v3-purge-audit.mjs +0 -236
@@ -0,0 +1,255 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Typography and palette anti-repeat audit.
5
+ *
6
+ * Scans CSS-like files for font-family declarations and color values, then
7
+ * cross-checks them against
8
+ * `docs/design-intent.json`'s
9
+ * `researchDossier.metadata.antiRepeatLedger.previousTypographyChoices` and
10
+ * `previousPalettes` so a redesign cannot leak the previous direction's
11
+ * exact font trio or perceptually-identical palette colors through CSS
12
+ * implementation files alone.
13
+ *
14
+ * Severity model:
15
+ * - Typography matches are EXACT family-name matches and are always
16
+ * blocking (kind: typography.previously-shipped-family,
17
+ * diagnosticCode: BOUNDARY_TYPOGRAPHY_LEDGER_VIOLATION).
18
+ * - Palette matches are HEURISTIC. OKLCH-to-OKLCH distance is perceptually
19
+ * uniform (deltaE < threshold => match). Hex-to-hex matches are exact
20
+ * normalized equality. Cross-type matches (hex declared in CSS vs OKLCH
21
+ * in the ledger, or vice versa) are NOT performed; reporting cross-type
22
+ * would require a color-space conversion that is its own correctness
23
+ * surface (tracked in docs/deep-analysis-and-roadmap-backlog.md).
24
+ * - Palette findings BLOCK by default with kind:
25
+ * palette.previously-shipped-color and diagnosticCode:
26
+ * BOUNDARY_PALETTE_LEDGER_VIOLATION. Pass
27
+ * `treatPaletteAsAdvisory: true` (or `--palette-advisory` on the CLI)
28
+ * to opt a project into advisory-only palette reporting; severity drops
29
+ * to "advisory" and the diagnostic code becomes
30
+ * BOUNDARY_PALETTE_LEDGER_ADVISORY. Use the advisory mode only when a
31
+ * project knowingly accepts the false-positive risk of OKLCH distance
32
+ * matching at its chosen threshold.
33
+ */
34
+
35
+ import { existsSync, readFileSync } from 'node:fs';
36
+ import { join, relative } from 'node:path';
37
+
38
+ import {
39
+ collectScannableFilePaths,
40
+ lineNumberFromIndex,
41
+ } from './typography-palette-anti-repeat/file-scanner.mjs';
42
+ import {
43
+ extractFontFamiliesFromText,
44
+ extractLedgerTypographyFamilies,
45
+ } from './typography-palette-anti-repeat/typography-utils.mjs';
46
+ import {
47
+ extractColorOccurrencesFromText,
48
+ extractLedgerColors,
49
+ oklchPerceptualDistance,
50
+ } from './typography-palette-anti-repeat/color-utils.mjs';
51
+
52
+ const DEFAULT_OKLCH_DISTANCE_THRESHOLD = 0.04;
53
+
54
+ function findTypographyViolationsForFile({ relativeFilePath, sourceText, ledgerFamilies }) {
55
+ const violations = [];
56
+ const fontFamilyOccurrences = extractFontFamiliesFromText(sourceText);
57
+ for (const familyOccurrence of fontFamilyOccurrences) {
58
+ const ledgerEntry = ledgerFamilies.get(familyOccurrence.family);
59
+ if (!ledgerEntry) {
60
+ continue;
61
+ }
62
+ violations.push({
63
+ file: relativeFilePath,
64
+ line: lineNumberFromIndex(sourceText, familyOccurrence.index),
65
+ kind: 'typography.previously-shipped-family',
66
+ severity: 'blocking',
67
+ diagnosticCode: 'BOUNDARY_TYPOGRAPHY_LEDGER_VIOLATION',
68
+ detail: `CSS declares font-family "${familyOccurrence.family}" which appears in researchDossier.metadata.antiRepeatLedger.previousTypographyChoices ("${ledgerEntry.summary}"). Either escape the previously-shipped trio, or set derivedTokenLogic.tokenContinuityClassification.typography to continuity-retained with explicit rationale.`,
69
+ });
70
+ }
71
+ return violations;
72
+ }
73
+
74
+ function buildHexAdvisory({ relativeFilePath, sourceText, colorOccurrence, ledgerColor, paletteSeverity, paletteDiagnosticCode }) {
75
+ return {
76
+ file: relativeFilePath,
77
+ line: lineNumberFromIndex(sourceText, colorOccurrence.index),
78
+ kind: 'palette.previously-shipped-color',
79
+ severity: paletteSeverity,
80
+ diagnosticCode: paletteDiagnosticCode,
81
+ detail: `CSS hex "${colorOccurrence.raw}" matches a hex color in researchDossier.metadata.antiRepeatLedger.previousPalettes ("${ledgerColor.sourceEntry.summary}"). Either escape the previously-shipped palette or set derivedTokenLogic.tokenContinuityClassification.palette to continuity-retained with explicit rationale.`,
82
+ };
83
+ }
84
+
85
+ function buildOklchAdvisory({ relativeFilePath, sourceText, colorOccurrence, ledgerColor, perceptualDistance, oklchDistanceThreshold, paletteSeverity, paletteDiagnosticCode }) {
86
+ return {
87
+ file: relativeFilePath,
88
+ line: lineNumberFromIndex(sourceText, colorOccurrence.index),
89
+ kind: 'palette.previously-shipped-color',
90
+ severity: paletteSeverity,
91
+ diagnosticCode: paletteDiagnosticCode,
92
+ detail: `CSS OKLCH "${colorOccurrence.raw}" is within ${perceptualDistance.toFixed(4)} of an OKLCH color in researchDossier.metadata.antiRepeatLedger.previousPalettes ("${ledgerColor.sourceEntry.summary}") (threshold ${oklchDistanceThreshold}). Heuristic match on perceptually-uniform OKLCH distance; either escape the previously-shipped palette, raise the threshold via --threshold, or set derivedTokenLogic.tokenContinuityClassification.palette to continuity-retained with explicit rationale.`,
93
+ };
94
+ }
95
+
96
+ function findPaletteFindingsForFile({ relativeFilePath, sourceText, ledgerColors, oklchDistanceThreshold, paletteSeverity, paletteDiagnosticCode }) {
97
+ const findings = [];
98
+ const colorOccurrences = extractColorOccurrencesFromText(sourceText);
99
+ for (const colorOccurrence of colorOccurrences) {
100
+ for (const ledgerColor of ledgerColors) {
101
+ if (colorOccurrence.kind === 'hex' && ledgerColor.kind === 'hex') {
102
+ if (
103
+ colorOccurrence.normalized
104
+ && ledgerColor.normalized
105
+ && colorOccurrence.normalized === ledgerColor.normalized
106
+ ) {
107
+ findings.push(buildHexAdvisory({
108
+ relativeFilePath, sourceText, colorOccurrence, ledgerColor,
109
+ paletteSeverity, paletteDiagnosticCode,
110
+ }));
111
+ }
112
+ continue;
113
+ }
114
+ if (colorOccurrence.kind === 'oklch' && ledgerColor.kind === 'oklch' && colorOccurrence.oklch && ledgerColor.oklch) {
115
+ const perceptualDistance = oklchPerceptualDistance(colorOccurrence.oklch, ledgerColor.oklch);
116
+ if (perceptualDistance <= oklchDistanceThreshold) {
117
+ findings.push(buildOklchAdvisory({
118
+ relativeFilePath, sourceText, colorOccurrence, ledgerColor, perceptualDistance, oklchDistanceThreshold,
119
+ paletteSeverity, paletteDiagnosticCode,
120
+ }));
121
+ }
122
+ }
123
+ // Cross-type (hex declared vs OKLCH ledger or vice versa) is intentionally
124
+ // skipped here; reporting cross-type would require a color-space conversion
125
+ // pass that is its own correctness surface. Tracked in
126
+ // docs/deep-analysis-and-roadmap-backlog.md.
127
+ }
128
+ }
129
+ return findings;
130
+ }
131
+
132
+ function loadDesignIntentContract(repositoryRootPath, designIntentRelativePath) {
133
+ const designIntentAbsolutePath = join(repositoryRootPath, designIntentRelativePath);
134
+ if (!existsSync(designIntentAbsolutePath)) {
135
+ return { contract: null, reason: 'design-intent-file-absent' };
136
+ }
137
+ let contract;
138
+ try {
139
+ contract = JSON.parse(readFileSync(designIntentAbsolutePath, 'utf8'));
140
+ } catch (parseError) {
141
+ const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
142
+ return { contract: null, reason: `design-intent-file-not-valid-json: ${errorMessage}` };
143
+ }
144
+ return { contract, reason: 'ok' };
145
+ }
146
+
147
+ function buildSkippedReport(reportShell, reason) {
148
+ return {
149
+ ...reportShell,
150
+ passed: true,
151
+ skipped: true,
152
+ reason,
153
+ filesScanned: 0,
154
+ typographyViolationCount: 0,
155
+ paletteFindingCount: 0,
156
+ typographyViolations: [],
157
+ paletteFindings: [],
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Runs the typography and palette anti-repeat audit.
163
+ *
164
+ * @param {object} [auditOptions]
165
+ * @param {string} [auditOptions.repositoryRootPath]
166
+ * @param {string[]} [auditOptions.scanRoots]
167
+ * @param {string} [auditOptions.designIntentRelativePath]
168
+ * @param {number} [auditOptions.oklchDistanceThreshold]
169
+ * @param {boolean} [auditOptions.treatPaletteAsAdvisory]
170
+ */
171
+ export function runTypographyPaletteAntiRepeatAudit(auditOptions = {}) {
172
+ const repositoryRootPath = auditOptions.repositoryRootPath || process.cwd();
173
+ const scanRoots = Array.isArray(auditOptions.scanRoots) && auditOptions.scanRoots.length > 0
174
+ ? auditOptions.scanRoots
175
+ : ['.'];
176
+ const designIntentRelativePath = auditOptions.designIntentRelativePath || 'docs/design-intent.json';
177
+ const oklchDistanceThreshold = typeof auditOptions.oklchDistanceThreshold === 'number'
178
+ ? auditOptions.oklchDistanceThreshold
179
+ : DEFAULT_OKLCH_DISTANCE_THRESHOLD;
180
+ const treatPaletteAsAdvisory = auditOptions.treatPaletteAsAdvisory === true;
181
+ const paletteSeverity = treatPaletteAsAdvisory ? 'advisory' : 'blocking';
182
+ const paletteDiagnosticCode = treatPaletteAsAdvisory
183
+ ? 'BOUNDARY_PALETTE_LEDGER_ADVISORY'
184
+ : 'BOUNDARY_PALETTE_LEDGER_VIOLATION';
185
+
186
+ const reportShell = {
187
+ auditName: 'audit-typography-palette-anti-repeat',
188
+ reportVersion: '2.0.0',
189
+ generatedAt: new Date().toISOString(),
190
+ designIntentRelativePath,
191
+ paletteSeverity,
192
+ paletteDiagnosticCode,
193
+ oklchDistanceThreshold,
194
+ scope: {
195
+ typographyMatching: 'exact-family-name-after-normalization',
196
+ paletteHexMatching: 'exact-hex-after-7-digit-normalization',
197
+ paletteOklchMatching: 'l*c*h-distance-under-threshold',
198
+ paletteCrossTypeMatching: 'not-supported-color-conversion-out-of-scope',
199
+ paletteCrossTypeMatchingTrackedIn: 'docs/deep-analysis-and-roadmap-backlog.md',
200
+ },
201
+ };
202
+
203
+ const designIntentLoad = loadDesignIntentContract(repositoryRootPath, designIntentRelativePath);
204
+ if (!designIntentLoad.contract) {
205
+ return buildSkippedReport(reportShell, designIntentLoad.reason);
206
+ }
207
+
208
+ const antiRepeatLedger = designIntentLoad.contract?.researchDossier?.metadata?.antiRepeatLedger;
209
+ if (!antiRepeatLedger || typeof antiRepeatLedger !== 'object') {
210
+ return buildSkippedReport(reportShell, 'anti-repeat-ledger-absent');
211
+ }
212
+
213
+ const ledgerFamilies = extractLedgerTypographyFamilies(antiRepeatLedger);
214
+ const ledgerColors = extractLedgerColors(antiRepeatLedger);
215
+ const scannableFilePaths = collectScannableFilePaths(repositoryRootPath, scanRoots);
216
+
217
+ /** @type {any[]} */
218
+ const typographyViolations = [];
219
+ /** @type {any[]} */
220
+ const paletteFindings = [];
221
+
222
+ for (const absoluteFilePath of scannableFilePaths) {
223
+ const relativeFilePath = relative(repositoryRootPath, absoluteFilePath).replace(/\\/g, '/');
224
+ let sourceText;
225
+ try {
226
+ sourceText = readFileSync(absoluteFilePath, 'utf8');
227
+ } catch {
228
+ continue;
229
+ }
230
+ if (ledgerFamilies.size > 0) {
231
+ typographyViolations.push(...findTypographyViolationsForFile({
232
+ relativeFilePath, sourceText, ledgerFamilies,
233
+ }));
234
+ }
235
+ if (ledgerColors.length > 0) {
236
+ paletteFindings.push(...findPaletteFindingsForFile({
237
+ relativeFilePath, sourceText, ledgerColors, oklchDistanceThreshold,
238
+ paletteSeverity, paletteDiagnosticCode,
239
+ }));
240
+ }
241
+ }
242
+
243
+ const blockingViolationCount = typographyViolations.length + (treatPaletteAsAdvisory ? 0 : paletteFindings.length);
244
+
245
+ return {
246
+ ...reportShell,
247
+ passed: blockingViolationCount === 0,
248
+ skipped: false,
249
+ filesScanned: scannableFilePaths.length,
250
+ typographyViolationCount: typographyViolations.length,
251
+ paletteFindingCount: paletteFindings.length,
252
+ typographyViolations,
253
+ paletteFindings,
254
+ };
255
+ }
@@ -0,0 +1,198 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * `agentic-senior-core audit:design-anti-repeat`
5
+ *
6
+ * User-facing CLI subcommand that runs the typography/palette anti-repeat
7
+ * audit against a user project's working directory. The default scan root is
8
+ * `process.cwd()` so `npx @ryuenn3123/agentic-senior-core audit:design-anti-repeat`
9
+ * inside a user project scans that project (not the package install).
10
+ *
11
+ * Skip behavior is friendly: missing design-intent.json or missing ledger
12
+ * print actionable next steps and exit 0. Typography violations and palette
13
+ * findings (when palette is blocking, which is the default) exit 1.
14
+ */
15
+
16
+ import { resolve } from 'node:path';
17
+
18
+ import { runTypographyPaletteAntiRepeatAudit } from '../audits/typography-palette-anti-repeat-audit.mjs';
19
+
20
+ const HELP_TEXT_LINES = [
21
+ 'agentic-senior-core audit:design-anti-repeat',
22
+ '',
23
+ 'Scan CSS, SCSS, SASS, LESS, and Tailwind/theme/design-token config files in',
24
+ 'the current project for typography or palette values that match the',
25
+ 'anti-repeat ledger in docs/design-intent.json. Catches redesigns that leak',
26
+ "the previous direction's font trio or palette through CSS implementation",
27
+ 'even when the JSON design contract claims a fresh anchor.',
28
+ '',
29
+ 'Usage:',
30
+ ' agentic-senior-core audit:design-anti-repeat',
31
+ ' agentic-senior-core audit:design-anti-repeat [target-directory]',
32
+ ' agentic-senior-core audit:design-anti-repeat --json',
33
+ ' agentic-senior-core audit:design-anti-repeat --palette-advisory',
34
+ ' agentic-senior-core audit:design-anti-repeat --threshold 0.05',
35
+ '',
36
+ 'Defaults:',
37
+ ' Target directory: current working directory',
38
+ ' Typography matches: blocking (BOUNDARY_TYPOGRAPHY_LEDGER_VIOLATION)',
39
+ ' Palette matches: blocking (BOUNDARY_PALETTE_LEDGER_VIOLATION)',
40
+ ' OKLCH distance: 0.04 in L*C*H space',
41
+ '',
42
+ 'Options:',
43
+ ' --json Print the full report as JSON only (no human summary).',
44
+ ' --palette-advisory Opt out of blocking palette severity. Findings still',
45
+ ' print but do not fail the run. Typography stays blocking.',
46
+ ' --threshold <num> Override the OKLCH perceptual distance threshold.',
47
+ ' Higher values are more permissive.',
48
+ ' --help Show this help.',
49
+ '',
50
+ 'Skip behavior (exit 0):',
51
+ ' - docs/design-intent.json absent. Run `npx @ryuenn3123/agentic-senior-core init`',
52
+ ' in a fresh project, or `... upgrade` in an existing project to seed the',
53
+ ' design contract.',
54
+ ' - docs/design-intent.json present but researchDossier metadata absent.',
55
+ " Run `... upgrade` to migrate the contract, then run `.agent-context/prompts/research-design.md`",
56
+ ' so the dossier and anti-repeat ledger get populated before the next UI work.',
57
+ '',
58
+ 'Exit codes:',
59
+ ' 0 = no blocking violations (or audit skipped with a friendly note).',
60
+ ' 1 = at least one BOUNDARY_TYPOGRAPHY_LEDGER_VIOLATION or, in default mode,',
61
+ ' BOUNDARY_PALETTE_LEDGER_VIOLATION.',
62
+ ];
63
+
64
+ function buildHelpText() {
65
+ return HELP_TEXT_LINES.join('\n');
66
+ }
67
+
68
+ function readNumericFlag(commandLineArgs, flagName) {
69
+ const flagIndex = commandLineArgs.indexOf(flagName);
70
+ if (flagIndex === -1) {
71
+ return null;
72
+ }
73
+ const flagValue = commandLineArgs[flagIndex + 1];
74
+ if (typeof flagValue !== 'string' || flagValue.length === 0) {
75
+ return null;
76
+ }
77
+ const numericValue = Number(flagValue);
78
+ return Number.isFinite(numericValue) ? numericValue : null;
79
+ }
80
+
81
+ const VALUE_FLAGS = new Set(['--threshold']);
82
+
83
+ function extractTargetDirectoryArgument(commandLineArgs) {
84
+ for (let argumentIndex = 0; argumentIndex < commandLineArgs.length; argumentIndex += 1) {
85
+ const candidateArgument = commandLineArgs[argumentIndex];
86
+ if (VALUE_FLAGS.has(candidateArgument)) {
87
+ argumentIndex += 1;
88
+ continue;
89
+ }
90
+ if (!candidateArgument.startsWith('--')) {
91
+ return candidateArgument;
92
+ }
93
+ }
94
+ return null;
95
+ }
96
+
97
+ function buildSkipMessage(reason) {
98
+ if (reason === 'design-intent-file-absent') {
99
+ return [
100
+ 'Audit skipped: docs/design-intent.json is missing.',
101
+ 'Next step:',
102
+ ' - Fresh project: run `npx @ryuenn3123/agentic-senior-core init` to seed the design contract.',
103
+ ' - Existing project: run `npx @ryuenn3123/agentic-senior-core upgrade` to seed it without touching app code.',
104
+ 'Then re-run this audit.',
105
+ ];
106
+ }
107
+ if (reason === 'anti-repeat-ledger-absent') {
108
+ return [
109
+ 'Audit skipped: docs/design-intent.json is present but researchDossier metadata is absent.',
110
+ 'Next step:',
111
+ ' - Run `npx @ryuenn3123/agentic-senior-core upgrade` to migrate the contract; the upgrade injects',
112
+ ' `researchDossier.metadata` and seeds the anti-repeat ledger from existing anchor, palette,',
113
+ ' motion, and typography fields without overwriting them.',
114
+ " - Then run the design-research dossier prompt at `.agent-context/prompts/research-design.md`",
115
+ ' so the ledger and `researchVerifiedAt` get populated before the next UI work.',
116
+ 'After that, re-run this audit to enforce the ledger against your CSS.',
117
+ ];
118
+ }
119
+ if (typeof reason === 'string' && reason.startsWith('design-intent-file-not-valid-json')) {
120
+ return [
121
+ 'Audit skipped: docs/design-intent.json is present but cannot be parsed as JSON.',
122
+ ` ${reason}`,
123
+ 'Next step: fix the JSON syntax, then re-run this audit.',
124
+ ];
125
+ }
126
+ return [`Audit skipped: ${reason || 'unknown reason'}`];
127
+ }
128
+
129
+ function printHumanReport(auditReport) {
130
+ console.log('===============================================');
131
+ console.log(' audit:design-anti-repeat');
132
+ console.log('===============================================');
133
+ console.log(` Target directory: ${auditReport.targetDirectoryPath}`);
134
+ console.log(` Files scanned: ${auditReport.filesScanned}`);
135
+ console.log(` Typography violations: ${auditReport.typographyViolationCount} (blocking)`);
136
+ console.log(` Palette findings: ${auditReport.paletteFindingCount} (${auditReport.paletteSeverity})`);
137
+ console.log(` OKLCH distance threshold: ${auditReport.oklchDistanceThreshold}`);
138
+ console.log('');
139
+
140
+ if (auditReport.skipped) {
141
+ for (const skipLine of buildSkipMessage(auditReport.reason)) {
142
+ console.log(` ${skipLine}`);
143
+ }
144
+ return;
145
+ }
146
+
147
+ if (auditReport.typographyViolations.length > 0) {
148
+ console.log(' Typography violations (blocking):');
149
+ for (const violation of auditReport.typographyViolations) {
150
+ console.log(` [${violation.kind}] ${violation.file}:${violation.line} ${violation.detail}`);
151
+ }
152
+ console.log('');
153
+ }
154
+
155
+ if (auditReport.paletteFindings.length > 0) {
156
+ console.log(` Palette findings (${auditReport.paletteSeverity}):`);
157
+ for (const finding of auditReport.paletteFindings) {
158
+ console.log(` [${finding.kind}] ${finding.file}:${finding.line} ${finding.detail}`);
159
+ }
160
+ console.log('');
161
+ }
162
+
163
+ if (auditReport.passed) {
164
+ console.log(' No blocking violations against the anti-repeat ledger.');
165
+ return;
166
+ }
167
+
168
+ const blockingPaletteCount = auditReport.paletteSeverity === 'blocking' ? auditReport.paletteFindingCount : 0;
169
+ console.log(` ${auditReport.typographyViolationCount} typography violation(s) and ${blockingPaletteCount} palette violation(s) found; release blocked.`);
170
+ }
171
+
172
+ export async function runDesignAntiRepeatAuditCommand(commandLineArgs = []) {
173
+ if (commandLineArgs.includes('--help') || commandLineArgs.includes('-h')) {
174
+ console.log(buildHelpText());
175
+ return 0;
176
+ }
177
+
178
+ const shouldOutputJsonOnly = commandLineArgs.includes('--json');
179
+ const shouldTreatPaletteAsAdvisory = commandLineArgs.includes('--palette-advisory');
180
+ const oklchDistanceThreshold = readNumericFlag(commandLineArgs, '--threshold');
181
+ const targetDirectoryArgument = extractTargetDirectoryArgument(commandLineArgs);
182
+ const targetDirectoryPath = resolve(targetDirectoryArgument || process.cwd());
183
+
184
+ const auditReport = runTypographyPaletteAntiRepeatAudit({
185
+ repositoryRootPath: targetDirectoryPath,
186
+ treatPaletteAsAdvisory: shouldTreatPaletteAsAdvisory,
187
+ ...(typeof oklchDistanceThreshold === 'number' ? { oklchDistanceThreshold } : {}),
188
+ });
189
+ const auditReportWithRoot = { ...auditReport, targetDirectoryPath };
190
+
191
+ if (shouldOutputJsonOnly) {
192
+ process.stdout.write(`${JSON.stringify(auditReportWithRoot, null, 2)}\n`);
193
+ return auditReportWithRoot.passed ? 0 : 1;
194
+ }
195
+
196
+ printHumanReport(auditReportWithRoot);
197
+ return auditReportWithRoot.passed ? 0 : 1;
198
+ }
@@ -464,6 +464,7 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
464
464
  }
465
465
 
466
466
  console.log('\nRefreshed files: AGENTS.md, CLAUDE.md, GEMINI.md, .agent-context/, and .agent-context/state/onboarding-report.json');
467
+ console.log('\nNext-step suggestion (UI scope): run `npx @ryuenn3123/agentic-senior-core audit:design-anti-repeat` to scan CSS, SCSS, SASS, LESS, Tailwind config, and design-token files in this project for typography or palette values that match the anti-repeat ledger in docs/design-intent.json. Add it to your CI alongside `npm test` once the design dossier is populated.');
467
468
  } catch (error) {
468
469
  console.error('\n[FATAL] An error occurred during upgrade. Attempting automatic rollback...');
469
470
  try {
package/lib/cli/utils.mjs CHANGED
@@ -58,6 +58,7 @@ export function printUsage() {
58
58
  console.log(' agentic-senior-core optimize [target-directory] [--agent <copilot|claude|cursor|windsurf|gemini|codex|cline>] [--enable|--disable] [--show]');
59
59
  console.log(' agentic-senior-core mcp');
60
60
  console.log(' agentic-senior-core rollback [target-directory]');
61
+ console.log(' agentic-senior-core audit:design-anti-repeat [target-directory] [--json] [--palette-advisory] [--threshold <number>]');
61
62
  console.log(' agentic-senior-core --version');
62
63
  console.log('');
63
64
  console.log('Options:');
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@ryuenn3123/agentic-senior-core",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "type": "module",
5
- "description": "Force your AI Agent to code like a Staff Engineer, not a Junior.",
5
+ "description": "Change your AI Agent to code like a Staff Engineer, not a Junior.",
6
6
  "bin": {
7
7
  "agentic-senior-core": "bin/agentic-senior-core.js"
8
8
  },
@@ -59,10 +59,10 @@
59
59
  "audit:cache-layer-contract": "node ./scripts/audit-cache-layer-contract.mjs",
60
60
  "audit:reflection-citations": "node ./scripts/audit-reflection-citations.mjs",
61
61
  "audit:caching-scope-hygiene": "node ./scripts/audit-caching-scope-hygiene.mjs",
62
+ "audit:typography-palette-anti-repeat": "node ./scripts/audit-typography-palette-anti-repeat.mjs",
62
63
  "audit:release-bundle": "node ./scripts/audit-release-bundle.mjs",
63
64
  "audit:file-size": "node ./scripts/audit-file-size.mjs",
64
65
  "audit:rule-id-uniqueness": "node ./scripts/audit-rule-id-uniqueness.mjs",
65
- "audit:v3-purge": "node ./scripts/v3-purge-audit.mjs",
66
66
  "build:release-bundle": "node ./scripts/build-release-benchmark-bundle.mjs",
67
67
  "sync:adapters": "node ./scripts/sync-thin-adapters.mjs",
68
68
  "check:adapters": "node ./scripts/sync-thin-adapters.mjs --check",
@@ -83,7 +83,7 @@
83
83
  "report:governance-weekly": "node ./scripts/governance-weekly-report.mjs",
84
84
  "clean:local": "node ./scripts/clean-local-artifacts.mjs",
85
85
  "validate": "node ./scripts/validate.mjs",
86
- "test": "node --test ./tests/cli-smoke.test.mjs ./tests/mcp-server.test.mjs ./tests/llm-judge.test.mjs ./tests/ui-rubric-calibration.test.mjs ./tests/operations.test.mjs ./tests/knowledge-injection.test.mjs ./tests/migrate-rule-format.test.mjs ./tests/audit-caching-scope-hygiene.test.mjs ./tests/research-dossier-migration.test.mjs ./benchmarks/token-usage/lib/token-counter.test.mjs ./benchmarks/token-usage/lib/provider-cache-matrix.test.mjs ./benchmarks/token-usage/lib/cache-layer-contract.test.mjs ./benchmarks/token-usage/lib/cache-economics.test.mjs"
86
+ "test": "node --test ./tests/cli-smoke.test.mjs ./tests/mcp-server.test.mjs ./tests/llm-judge.test.mjs ./tests/ui-rubric-calibration.test.mjs ./tests/operations.test.mjs ./tests/knowledge-injection.test.mjs ./tests/audit-caching-scope-hygiene.test.mjs ./tests/research-dossier-migration.test.mjs ./tests/typography-palette-anti-repeat-audit.test.mjs ./tests/audit-design-anti-repeat-command.test.mjs ./benchmarks/token-usage/lib/token-counter.test.mjs ./benchmarks/token-usage/lib/provider-cache-matrix.test.mjs ./benchmarks/token-usage/lib/cache-layer-contract.test.mjs ./benchmarks/token-usage/lib/cache-economics.test.mjs"
87
87
  },
88
88
  "devDependencies": {
89
89
  "@anthropic-ai/sdk": "^0.96.0",
@@ -5,6 +5,11 @@
5
5
  * Phase 2 cache-layer integrity gate. Validates provider cache metadata,
6
6
  * fixture segmentation, and the emitted cache simulation JSON without calling
7
7
  * provider APIs.
8
+ *
9
+ * Boundary: this audit covers the TECHNICAL contract (provider matrix,
10
+ * layer definitions, fixture segmentation, simulation result JSON shape).
11
+ * For public-prose hygiene around caching saving figures see
12
+ * audit-caching-scope-hygiene.mjs.
8
13
  */
9
14
 
10
15
  import { existsSync, readFileSync } from 'node:fs';
@@ -9,6 +9,11 @@
9
9
  * that each claim is integration-scoped per `docs/architecture/decisions-foundation.md`
10
10
  * D4 "Per-Tool Caching Scope Matrix".
11
11
  *
12
+ * Boundary: this audit covers PUBLIC-PROSE hygiene only (preventing
13
+ * universal "X% caching saving" claims that mix integration modes). For the
14
+ * technical provider/fixture/result-JSON contract see
15
+ * audit-cache-layer-contract.mjs.
16
+ *
12
17
  * The rule: never publish a single universal "X% caching saving" figure that
13
18
  * mixes integration modes. Every numerical caching saving claim on a public
14
19
  * surface must either:
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ // @ts-check
3
+
4
+ /**
5
+ * audit-typography-palette-anti-repeat.mjs
6
+ *
7
+ * Thin CLI wrapper around lib/cli/audits/typography-palette-anti-repeat-audit.mjs.
8
+ * The wrapper defaults to scanning the current working directory so it works
9
+ * for both repo-internal `npm run audit:typography-palette-anti-repeat` (npm
10
+ * sets cwd to the repo root) and direct invocation from a user project. The
11
+ * user-facing entry point is the `audit:design-anti-repeat` subcommand on
12
+ * the published bin (see lib/cli/commands/audit-design-anti-repeat.mjs).
13
+ *
14
+ * Usage:
15
+ * node scripts/audit-typography-palette-anti-repeat.mjs
16
+ * node scripts/audit-typography-palette-anti-repeat.mjs --json
17
+ * node scripts/audit-typography-palette-anti-repeat.mjs --palette-advisory
18
+ * node scripts/audit-typography-palette-anti-repeat.mjs --threshold 0.05
19
+ * node scripts/audit-typography-palette-anti-repeat.mjs --root /path/to/project
20
+ *
21
+ * Default severity:
22
+ * - Typography matches always block.
23
+ * - Palette matches block by default. Pass --palette-advisory to opt a
24
+ * project into advisory-only palette reporting (release stays unblocked
25
+ * by palette findings; typography still blocks).
26
+ *
27
+ * Exit codes:
28
+ * 0 = no blocking violations
29
+ * 1 = at least one BOUNDARY_TYPOGRAPHY_LEDGER_VIOLATION or, in default
30
+ * mode, BOUNDARY_PALETTE_LEDGER_VIOLATION
31
+ */
32
+
33
+ import { resolve } from 'node:path';
34
+
35
+ import { runTypographyPaletteAntiRepeatAudit } from '../lib/cli/audits/typography-palette-anti-repeat-audit.mjs';
36
+
37
+ const COMMAND_LINE_ARGS = process.argv.slice(2);
38
+ const SHOULD_OUTPUT_JSON_ONLY = COMMAND_LINE_ARGS.includes('--json');
39
+ const SHOULD_TREAT_PALETTE_AS_ADVISORY = COMMAND_LINE_ARGS.includes('--palette-advisory');
40
+
41
+ function readStringFlag(flagName) {
42
+ const flagIndex = COMMAND_LINE_ARGS.indexOf(flagName);
43
+ if (flagIndex === -1) {
44
+ return null;
45
+ }
46
+ const flagValue = COMMAND_LINE_ARGS[flagIndex + 1];
47
+ if (typeof flagValue !== 'string' || flagValue.length === 0 || flagValue.startsWith('--')) {
48
+ return null;
49
+ }
50
+ return flagValue;
51
+ }
52
+
53
+ function readNumericFlag(flagName) {
54
+ const stringValue = readStringFlag(flagName);
55
+ if (stringValue === null) {
56
+ return null;
57
+ }
58
+ const numericValue = Number(stringValue);
59
+ return Number.isFinite(numericValue) ? numericValue : null;
60
+ }
61
+
62
+ function main() {
63
+ const oklchDistanceThreshold = readNumericFlag('--threshold');
64
+ const explicitRootPath = readStringFlag('--root');
65
+ const repositoryRootPath = resolve(explicitRootPath || process.cwd());
66
+
67
+ const auditReport = runTypographyPaletteAntiRepeatAudit({
68
+ repositoryRootPath,
69
+ treatPaletteAsAdvisory: SHOULD_TREAT_PALETTE_AS_ADVISORY,
70
+ ...(typeof oklchDistanceThreshold === 'number' ? { oklchDistanceThreshold } : {}),
71
+ });
72
+
73
+ if (SHOULD_OUTPUT_JSON_ONLY) {
74
+ process.stdout.write(`${JSON.stringify({ ...auditReport, repositoryRootPath }, null, 2)}\n`);
75
+ process.exit(auditReport.passed ? 0 : 1);
76
+ }
77
+
78
+ console.log('===============================================');
79
+ console.log(' audit:typography-palette-anti-repeat');
80
+ console.log('===============================================');
81
+ console.log(` Target directory: ${repositoryRootPath}`);
82
+ console.log(` Files scanned: ${auditReport.filesScanned}`);
83
+ console.log(` Typography violations: ${auditReport.typographyViolationCount} (blocking)`);
84
+ console.log(` Palette findings: ${auditReport.paletteFindingCount} (${auditReport.paletteSeverity})`);
85
+ console.log(` OKLCH distance threshold: ${auditReport.oklchDistanceThreshold}`);
86
+ console.log('');
87
+
88
+ if (auditReport.skipped) {
89
+ console.log(` Audit skipped: ${auditReport.reason}`);
90
+ process.exit(0);
91
+ }
92
+
93
+ if (auditReport.typographyViolations.length > 0) {
94
+ console.log(' Typography violations (blocking):');
95
+ for (const violation of auditReport.typographyViolations) {
96
+ console.log(` [${violation.kind}] ${violation.file}:${violation.line} ${violation.detail}`);
97
+ }
98
+ console.log('');
99
+ }
100
+
101
+ if (auditReport.paletteFindings.length > 0) {
102
+ console.log(` Palette findings (${auditReport.paletteSeverity}):`);
103
+ for (const finding of auditReport.paletteFindings) {
104
+ console.log(` [${finding.kind}] ${finding.file}:${finding.line} ${finding.detail}`);
105
+ }
106
+ console.log('');
107
+ }
108
+
109
+ if (auditReport.passed) {
110
+ console.log(' No blocking violations against the anti-repeat ledger.');
111
+ process.exit(0);
112
+ }
113
+ const blockingPaletteCount = auditReport.paletteSeverity === 'blocking' ? auditReport.paletteFindingCount : 0;
114
+ console.log(` ${auditReport.typographyViolationCount} typography violation(s) and ${blockingPaletteCount} palette violation(s) found; release blocked.`);
115
+ process.exit(1);
116
+ }
117
+
118
+ if (process.argv[1] && (import.meta.url === `file://${process.argv[1].replace(/\\/g, '/')}` || process.argv[1].endsWith('audit-typography-palette-anti-repeat.mjs'))) {
119
+ main();
120
+ }
@@ -15,7 +15,6 @@ const LOCAL_ARTIFACT_PATHS = [
15
15
  '.zed',
16
16
  '.agentic-backup',
17
17
  '.agent-context/state/active-memory.json',
18
- '.agent-context/state/v3-purge-audit.json',
19
18
  '.agent-context/state/llm-judge-report.json',
20
19
  '.agent-context/state/benchmark-analysis.json',
21
20
  '.agent-context/state/benchmark-evidence-bundle.json',