@ryuenn3123/agentic-senior-core 4.3.4 → 4.3.5
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.
- package/AGENTS.md +3 -3
- package/lib/cli/commands/audit-design-anti-repeat.mjs +26 -185
- package/lib/cli/commands/init/project-context.mjs +0 -41
- package/lib/cli/commands/init.mjs +12 -41
- package/lib/cli/commands/upgrade.mjs +12 -35
- package/lib/cli/compiler.mjs +4 -79
- package/lib/cli/preflight.mjs +0 -21
- package/lib/cli/project-scaffolder/constants.mjs +1 -1
- package/lib/cli/project-scaffolder/design-contract.mjs +3 -45
- package/lib/cli/project-scaffolder/prompt-builders.mjs +3 -18
- package/lib/cli/project-scaffolder/storage.mjs +0 -9
- package/lib/cli/project-scaffolder.mjs +0 -1
- package/package.json +1 -1
- package/scripts/release-gate/static-checks.mjs +0 -36
- package/scripts/validate/config.mjs +0 -10
- package/lib/cli/commands/upgrade/design-intent-seed.mjs +0 -46
- package/lib/cli/project-scaffolder/design-contract/research-dossier-migration.mjs +0 -190
package/AGENTS.md
CHANGED
|
@@ -137,8 +137,8 @@ Load `pr-checklist.md` and `architecture-review.md`, then report defects, risks,
|
|
|
137
137
|
Trigger: ui, ux, layout, screen, tailwind, frontend, redesign.
|
|
138
138
|
|
|
139
139
|
1. Read `bootstrap-design.md` and `frontend-architecture.md`. Read UI-relevant repo evidence from state, current UI code, and `docs/*`.
|
|
140
|
-
2. Follow the three-step direction process in `bootstrap-design.md`: name defaults, choose anchor, commit to creative direction. If `docs/
|
|
141
|
-
3. Generate or refine `docs/DESIGN.md`
|
|
140
|
+
2. Follow the three-step direction process in `bootstrap-design.md`: name defaults, choose anchor, commit to creative direction. If `docs/DESIGN.md` has an anti-repeat ledger, load previous directions as blocklist.
|
|
141
|
+
3. Generate or refine `docs/DESIGN.md` before UI implementation. Keep context isolated; do not eagerly load unrelated backend-only rules.
|
|
142
142
|
4. External websites are evidence for constraints and mechanics only. Do not copy layout rhythm, palette, component skin, or brand posture without explicit user approval.
|
|
143
143
|
|
|
144
144
|
## Bounded Reflection
|
|
@@ -156,7 +156,7 @@ Use valid rule IDs only; do not quote full rule prose, expose hidden chain-of-th
|
|
|
156
156
|
Never claim done without:
|
|
157
157
|
1. Relevant rules applied.
|
|
158
158
|
2. PR and architecture checklists considered.
|
|
159
|
-
3. Universal SOP gates satisfied: public and developer root `README.md`; `docs/doc-index.md` when `docs/` exists; `docs/project-brief.md`; `docs/architecture-decision-record.md`; `docs/flow-overview.md`; `docs/database-schema.md` when persistent data exists; `docs/api-contract.md` when API or web application flows exist; plus `docs/DESIGN.md`
|
|
159
|
+
3. Universal SOP gates satisfied: public and developer root `README.md`; `docs/doc-index.md` when `docs/` exists; `docs/project-brief.md`; `docs/architecture-decision-record.md`; `docs/flow-overview.md`; `docs/database-schema.md` when persistent data exists; `docs/api-contract.md` when API or web application flows exist; plus `docs/DESIGN.md` for UI scope.
|
|
160
160
|
4. If `.agent-context/state/active-memory.json` exists and material project progress happened, refresh it while preserving privacy rules and user-owned entries.
|
|
161
161
|
5. Project validation passed through `npm run validate`.
|
|
162
162
|
|
|
@@ -3,196 +3,37 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* `agentic-senior-core audit:design-anti-repeat`
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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.
|
|
6
|
+
* [DEPRECATED in 2026 Context Ops Paradigm]
|
|
7
|
+
* This script is retired because the anti-repeat ledger is now maintained
|
|
8
|
+
* as plain-text inside docs/DESIGN.md. The Adaptive Context AI agent enforces
|
|
9
|
+
* this naturally without needing regex-based JSON compliance theater.
|
|
14
10
|
*/
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
];
|
|
12
|
+
export async function runDesignAntiRepeatAuditCommand(commandLineArgs = []) {
|
|
13
|
+
if (commandLineArgs.includes('--json')) {
|
|
14
|
+
process.stdout.write(JSON.stringify({
|
|
15
|
+
passed: true,
|
|
16
|
+
skipped: true,
|
|
17
|
+
reason: 'audit-design-anti-repeat is deprecated in Context Ops 2026. Plain text ledgers in DESIGN.md are handled by the agent contextually.',
|
|
18
|
+
targetDirectoryPath: process.cwd(),
|
|
19
|
+
filesScanned: 0,
|
|
20
|
+
typographyViolationCount: 0,
|
|
21
|
+
paletteFindingCount: 0,
|
|
22
|
+
paletteSeverity: 'advisory',
|
|
23
|
+
oklchDistanceThreshold: 0.04,
|
|
24
|
+
typographyViolations: [],
|
|
25
|
+
paletteFindings: []
|
|
26
|
+
}, null, 2) + '\n');
|
|
27
|
+
return 0;
|
|
125
28
|
}
|
|
126
|
-
return [`Audit skipped: ${reason || 'unknown reason'}`];
|
|
127
|
-
}
|
|
128
29
|
|
|
129
|
-
function printHumanReport(auditReport) {
|
|
130
30
|
console.log('===============================================');
|
|
131
|
-
console.log(' audit:design-anti-repeat');
|
|
31
|
+
console.log(' audit:design-anti-repeat (DEPRECATED)');
|
|
132
32
|
console.log('===============================================');
|
|
133
|
-
console.log(
|
|
134
|
-
console.log(
|
|
135
|
-
console.log(
|
|
136
|
-
console.log(` Palette findings: ${auditReport.paletteFindingCount} (${auditReport.paletteSeverity})`);
|
|
137
|
-
console.log(` OKLCH distance threshold: ${auditReport.oklchDistanceThreshold}`);
|
|
33
|
+
console.log(' Audit skipped: audit-design-anti-repeat is retired in Context Ops 2026.');
|
|
34
|
+
console.log(' Plain text ledgers in docs/DESIGN.md are handled by the agent contextually.');
|
|
35
|
+
console.log(' You can safely remove this check from your CI pipeline.');
|
|
138
36
|
console.log('');
|
|
139
|
-
|
|
140
|
-
|
|
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;
|
|
37
|
+
|
|
38
|
+
return 0;
|
|
198
39
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import { buildDesignIntentSeedFromSignals } from '../../project-scaffolder.mjs';
|
|
5
4
|
|
|
6
5
|
function normalizeContextLine(rawText) {
|
|
7
6
|
return String(rawText || '')
|
|
@@ -59,43 +58,3 @@ export async function inferExistingProjectDescriptionHint(targetDirectoryPath) {
|
|
|
59
58
|
return evidence.slice(0, 3).join(' | ');
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
export function buildInitExistingProjectDesignIntentSeed({
|
|
63
|
-
targetDirectoryPath,
|
|
64
|
-
packageManifest,
|
|
65
|
-
selectedStackFileName,
|
|
66
|
-
selectedBlueprintFileName,
|
|
67
|
-
uiScopeSignals,
|
|
68
|
-
projectDescriptionHint,
|
|
69
|
-
}) {
|
|
70
|
-
const projectName = String(packageManifest?.name || path.basename(targetDirectoryPath)).trim() || 'existing-ui-project';
|
|
71
|
-
const isMobileUiProject = String(selectedStackFileName || '').toLowerCase().includes('react-native')
|
|
72
|
-
|| String(selectedStackFileName || '').toLowerCase().includes('flutter')
|
|
73
|
-
|| uiScopeSignals.signalReasons.some((signalReason) => signalReason.includes('android') || signalReason.includes('ios'));
|
|
74
|
-
const resolvedDomain = isMobileUiProject ? 'Mobile app' : 'Web application';
|
|
75
|
-
const projectDescription = String(packageManifest?.description || projectDescriptionHint || '').trim()
|
|
76
|
-
|| `Existing ${resolvedDomain.toLowerCase()} detected during init. Create a project-specific dynamic design contract before shipping new UI work.`;
|
|
77
|
-
|
|
78
|
-
return buildDesignIntentSeedFromSignals({
|
|
79
|
-
projectName,
|
|
80
|
-
projectDescription,
|
|
81
|
-
primaryDomain: resolvedDomain,
|
|
82
|
-
features: [],
|
|
83
|
-
initContext: {
|
|
84
|
-
stackFileName: selectedStackFileName,
|
|
85
|
-
blueprintFileName: selectedBlueprintFileName,
|
|
86
|
-
},
|
|
87
|
-
status: 'seed-generated-during-init',
|
|
88
|
-
supplementalFields: {
|
|
89
|
-
initSignals: {
|
|
90
|
-
detectedFrom: uiScopeSignals.signalReasons,
|
|
91
|
-
generatedBy: 'init-existing-project-seed',
|
|
92
|
-
},
|
|
93
|
-
repoEvidence: {
|
|
94
|
-
uiSignalReasons: uiScopeSignals.signalReasons,
|
|
95
|
-
frontendMetrics: uiScopeSignals.frontendEvidenceMetrics || null,
|
|
96
|
-
designEvidenceSummary: uiScopeSignals.designEvidenceSummary || null,
|
|
97
|
-
workspaceUiEntries: uiScopeSignals.workspaceUiEntries || [],
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
}
|
|
@@ -59,7 +59,6 @@ import {
|
|
|
59
59
|
loadProjectConfig,
|
|
60
60
|
normalizeDocsLanguage,
|
|
61
61
|
} from '../project-scaffolder.mjs';
|
|
62
|
-
import { migrateExistingDesignIntentToResearchDossierSchema } from '../project-scaffolder/design-contract/research-dossier-migration.mjs';
|
|
63
62
|
import { performRollback } from '../rollback.mjs';
|
|
64
63
|
import {
|
|
65
64
|
createTokenOptimizationState,
|
|
@@ -72,10 +71,7 @@ import {
|
|
|
72
71
|
ensureActiveMemorySnapshot,
|
|
73
72
|
writeMemoryContinuityState,
|
|
74
73
|
} from '../memory-continuity.mjs';
|
|
75
|
-
|
|
76
|
-
buildInitExistingProjectDesignIntentSeed,
|
|
77
|
-
inferExistingProjectDescriptionHint,
|
|
78
|
-
} from './init/project-context.mjs';
|
|
74
|
+
|
|
79
75
|
import {
|
|
80
76
|
detectRuntimeEnvironment,
|
|
81
77
|
resolveRuntimeEnvironmentKeyFromLabel,
|
|
@@ -87,6 +83,7 @@ import {
|
|
|
87
83
|
resolveProjectScopeLabelFromKey,
|
|
88
84
|
resolveSilentCiGuardrailsDefault,
|
|
89
85
|
} from './init/setup-decisions.mjs';
|
|
86
|
+
import { inferExistingProjectDescriptionHint } from './init/project-context.mjs';
|
|
90
87
|
|
|
91
88
|
export { REPOSITORY_ROOT } from '../constants.mjs';
|
|
92
89
|
export {
|
|
@@ -493,36 +490,14 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
493
490
|
selectedBlueprintFileName: selectedResolvedBlueprintFileName,
|
|
494
491
|
})
|
|
495
492
|
: null;
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
505
|
-
packageManifest: existingProjectUiScopeSignals.packageManifest,
|
|
506
|
-
selectedStackFileName: selectedResolvedStackFileName,
|
|
507
|
-
selectedBlueprintFileName: selectedResolvedBlueprintFileName,
|
|
508
|
-
uiScopeSignals: existingProjectUiScopeSignals,
|
|
509
|
-
projectDescriptionHint,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
await ensureDirectory(docsDirectoryPath);
|
|
513
|
-
await fs.writeFile(designIntentTargetPath, designIntentSeedContent, 'utf8');
|
|
514
|
-
supplementalMaterializedDocFileNames.push('design-intent.json');
|
|
515
|
-
|
|
516
|
-
console.log('\nExisting UI/frontend scope detected. Seeded docs/design-intent.json so the machine-readable design contract exists before UI implementation work continues.');
|
|
517
|
-
} else if (projectDetection.hasExistingProjectFiles && (await pathExists(designIntentTargetPath))) {
|
|
518
|
-
// Scenario E: existing project being initialized for the first time
|
|
519
|
-
// already has docs/design-intent.json. Migrate it to carry researchDossier.metadata
|
|
520
|
-
// so the anti-repeat ledger and freshness gate become available without
|
|
521
|
-
// touching existing tokens, anchor, or palette.
|
|
522
|
-
const migrationResult = await migrateExistingDesignIntentToResearchDossierSchema(designIntentTargetPath);
|
|
523
|
-
if (migrationResult.migrated) {
|
|
524
|
-
supplementalMaterializedDocFileNames.push('design-intent.json (research-dossier metadata migrated)');
|
|
525
|
-
console.log('\n[MIGRATED] docs/design-intent.json now carries researchDossier.metadata. Run research-design.md before next UI implementation to populate the dossier and refresh researchVerifiedAt.');
|
|
493
|
+
const obsoleteDesignIntentPath = path.join(resolvedTargetDirectoryPath, 'docs', 'design-intent.json');
|
|
494
|
+
if (await pathExists(obsoleteDesignIntentPath)) {
|
|
495
|
+
try {
|
|
496
|
+
await fs.unlink(obsoleteDesignIntentPath);
|
|
497
|
+
supplementalMaterializedDocFileNames.push('design-intent.json (DELETED - Obsolete Compliance Theater)');
|
|
498
|
+
console.log('\n[CLEANUP] Removed obsolete docs/design-intent.json. The 2026 Context Ops Paradigm uses plain-text DESIGN.md only.');
|
|
499
|
+
} catch (err) {
|
|
500
|
+
// ignore error
|
|
526
501
|
}
|
|
527
502
|
}
|
|
528
503
|
|
|
@@ -644,7 +619,7 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
644
619
|
console.log('\nPrompt starter examples (copy and adapt in your IDE):');
|
|
645
620
|
console.log('- If docs/project-brief.md is missing, execute .agent-context/prompts/bootstrap-project-context.md now and create all required docs files.');
|
|
646
621
|
if ((scaffoldingResult.generatedPromptFileNames || []).includes('bootstrap-design.md')) {
|
|
647
|
-
console.log('- If docs/DESIGN.md
|
|
622
|
+
console.log('- If docs/DESIGN.md is missing, execute .agent-context/prompts/bootstrap-design.md now before building UI components.');
|
|
648
623
|
}
|
|
649
624
|
console.log(`- Build an MVP for ${promptProjectName} using the newly synthesized docs as strict project context.`);
|
|
650
625
|
console.log('- When scope changes, update docs/* in the same change so future prompts stay aligned.');
|
|
@@ -656,12 +631,8 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
656
631
|
console.log(`- Build an MVP for ${promptProjectName}. Follow Layer 9 docs and keep the current stack, database, and auth constraints.`);
|
|
657
632
|
console.log('- Add [new feature] and update docs/project-brief.md plus docs/flow-overview.md in the same change.');
|
|
658
633
|
console.log('- If this change needs architecture migration, propose a migration plan first, then implement after approval.');
|
|
659
|
-
} else if (supplementalMaterializedDocFileNames.includes('design-intent.json')) {
|
|
660
|
-
console.log('I also seeded docs/design-intent.json for this existing UI repository so future UI work starts from a machine-readable design contract without forcing a canned visual concept.');
|
|
661
|
-
console.log('\nPrompt starter examples (copy and adapt in your IDE):');
|
|
662
|
-
console.log('- If docs/DESIGN.md is missing, execute .agent-context/prompts/bootstrap-design.md now and refine docs/design-intent.json into a complete design contract before building UI components.');
|
|
663
|
-
console.log('- Keep docs/design-intent.json and docs/DESIGN.md synchronized whenever the UI direction changes.');
|
|
664
634
|
}
|
|
635
|
+
|
|
665
636
|
console.log('Your AI tools will now receive AGENTS.md, native import bridges, and the lazy-loaded .agent-context rule library. Your review threshold is stored in .agent-context/policies/llm-judge-threshold.json.');
|
|
666
637
|
console.log('MCP server registration is manual inside your IDE settings, even when mcp.json exists.');
|
|
667
638
|
} catch (error) {
|
|
@@ -46,10 +46,8 @@ import {
|
|
|
46
46
|
} from '../backup.mjs';
|
|
47
47
|
import { performRollback } from '../rollback.mjs';
|
|
48
48
|
import { detectProjectDocTemplateStaleness } from '../project-scaffolder.mjs';
|
|
49
|
-
import { migrateExistingDesignIntentToResearchDossierSchema } from '../project-scaffolder/design-contract/research-dossier-migration.mjs';
|
|
50
49
|
import { ensureActiveMemorySnapshot } from '../memory-continuity.mjs';
|
|
51
50
|
import { buildExistingProjectMajorConstraints } from '../init-detection-flow.mjs';
|
|
52
|
-
import { buildUpgradeDesignIntentSeed } from './upgrade/design-intent-seed.mjs';
|
|
53
51
|
import {
|
|
54
52
|
applyUpgradeTokenOptimizationPlan,
|
|
55
53
|
resolveUpgradeTokenOptimizationPlan,
|
|
@@ -184,7 +182,7 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
184
182
|
projectScopeSourceLabel: 'onboarding project scope',
|
|
185
183
|
});
|
|
186
184
|
const packageManifest = uiScopeSignals.packageManifest;
|
|
187
|
-
const designContractPaths = ['docs/DESIGN.md'
|
|
185
|
+
const designContractPaths = ['docs/DESIGN.md'];
|
|
188
186
|
const missingDesignContractPaths = [];
|
|
189
187
|
|
|
190
188
|
for (const designContractPath of designContractPaths) {
|
|
@@ -193,17 +191,6 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
193
191
|
missingDesignContractPaths.push(designContractPath);
|
|
194
192
|
}
|
|
195
193
|
}
|
|
196
|
-
const shouldSeedDesignIntentOnApply = uiScopeSignals.isUiScopeLikely
|
|
197
|
-
&& missingDesignContractPaths.includes('docs/design-intent.json');
|
|
198
|
-
const designIntentSeedContent = shouldSeedDesignIntentOnApply
|
|
199
|
-
? buildUpgradeDesignIntentSeed({
|
|
200
|
-
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
201
|
-
packageManifest,
|
|
202
|
-
selectedStackFileName,
|
|
203
|
-
selectedBlueprintFileName,
|
|
204
|
-
uiScopeSignals,
|
|
205
|
-
})
|
|
206
|
-
: null;
|
|
207
194
|
|
|
208
195
|
const detectionMajorConstraints = buildExistingProjectMajorConstraints({ mode: 'upgrade' });
|
|
209
196
|
const detectionTransparency = {
|
|
@@ -320,11 +307,8 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
320
307
|
if (Array.isArray(uiScopeSignals.workspaceUiEntries) && uiScopeSignals.workspaceUiEntries.length > 0) {
|
|
321
308
|
console.log(`- Nested UI workspaces: ${uiScopeSignals.workspaceUiEntries.map((workspaceUiEntry) => workspaceUiEntry.relativePath).join(', ')}`);
|
|
322
309
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
console.log('Required action: create or refresh docs/DESIGN.md and docs/design-intent.json before allowing UI implementation work.');
|
|
327
|
-
console.log('Upgrade synchronizes governance assets and can seed docs/design-intent.json, but it does not author project-specific docs/DESIGN.md automatically.');
|
|
310
|
+
console.log('Required action: create or refresh docs/DESIGN.md before allowing UI implementation work.');
|
|
311
|
+
console.log('Upgrade synchronizes governance assets, but it does not author project-specific docs/DESIGN.md automatically.');
|
|
328
312
|
}
|
|
329
313
|
|
|
330
314
|
if (upgradeOptions.dryRun) {
|
|
@@ -369,21 +353,14 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
369
353
|
supplementalCreatedFileNames.push(tokenOptimizationApplyResult.createdFileName);
|
|
370
354
|
}
|
|
371
355
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
// Inject researchDossier.metadata when absent so the anti-repeat ledger
|
|
381
|
-
// becomes available and active validation can enforce freshness.
|
|
382
|
-
const existingDesignIntentPath = path.join(resolvedTargetDirectoryPath, 'docs', 'design-intent.json');
|
|
383
|
-
const migrationResult = await migrateExistingDesignIntentToResearchDossierSchema(existingDesignIntentPath);
|
|
384
|
-
if (migrationResult.migrated) {
|
|
385
|
-
supplementalCreatedFileNames.push('docs/design-intent.json (research-dossier metadata migrated)');
|
|
386
|
-
console.log('\n[MIGRATED] docs/design-intent.json now carries researchDossier.metadata. Run research-design.md before next UI implementation to populate the dossier and refresh researchVerifiedAt.');
|
|
356
|
+
const obsoleteDesignIntentPath = path.join(resolvedTargetDirectoryPath, 'docs', 'design-intent.json');
|
|
357
|
+
if (await pathExists(obsoleteDesignIntentPath)) {
|
|
358
|
+
try {
|
|
359
|
+
await fs.unlink(obsoleteDesignIntentPath);
|
|
360
|
+
supplementalCreatedFileNames.push('docs/design-intent.json (DELETED - Obsolete Compliance Theater)');
|
|
361
|
+
console.log('\n[CLEANUP] Removed obsolete docs/design-intent.json. The 2026 Context Ops Paradigm uses plain-text DESIGN.md only.');
|
|
362
|
+
} catch (err) {
|
|
363
|
+
// ignore error
|
|
387
364
|
}
|
|
388
365
|
}
|
|
389
366
|
|
|
@@ -453,7 +430,7 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
453
430
|
console.log('\nRefreshed files: AGENTS.md, CLAUDE.md, GEMINI.md, .agent-context/, and .agent-context/state/onboarding-report.json');
|
|
454
431
|
console.log('Default activation cues remain Adaptive Context bootstrap, ASCX command wrappers, and Compact Natural final replies.');
|
|
455
432
|
console.log('Default response mode remains Compact Natural Mode through .agent-context/prompts/compact-natural-mode.md.');
|
|
456
|
-
|
|
433
|
+
|
|
457
434
|
} catch (error) {
|
|
458
435
|
console.error('\n[FATAL] An error occurred during upgrade. Attempting automatic rollback...');
|
|
459
436
|
try {
|
package/lib/cli/compiler.mjs
CHANGED
|
@@ -32,78 +32,8 @@ function toSingleLine(value, fallbackValue) {
|
|
|
32
32
|
return normalizedValue || fallbackValue;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
async function readDesignIntentIfExists(targetDirectoryPath) {
|
|
36
|
-
const designIntentPath = path.join(targetDirectoryPath, 'docs', 'design-intent.json');
|
|
37
|
-
if (!(await pathExists(designIntentPath))) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
return JSON.parse(await fs.readFile(designIntentPath, 'utf8'));
|
|
43
|
-
} catch {
|
|
44
|
-
return {
|
|
45
|
-
parseError: true,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
35
|
function buildAnchorCommitmentHeader(designIntent) {
|
|
51
|
-
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (designIntent.parseError) {
|
|
56
|
-
return [
|
|
57
|
-
'## [UI TASK ANCHOR - READ FIRST]',
|
|
58
|
-
'Source: docs/design-intent.json',
|
|
59
|
-
'Status: design-intent.json could not be parsed.',
|
|
60
|
-
'Required action: fix docs/design-intent.json before UI implementation so the creative commitment can be loaded.',
|
|
61
|
-
].join('\n');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const conceptualAnchor = designIntent.conceptualAnchor || {};
|
|
65
|
-
const derivedTokenLogic = designIntent.derivedTokenLogic || {};
|
|
66
|
-
const creativeCommitment = designIntent.designExecutionHandoff?.creativeCommitment || {};
|
|
67
|
-
const designFlexibilityPolicy = designIntent.designFlexibilityPolicy || {};
|
|
68
|
-
const expressionFlexibility = designIntent.designExecutionHandoff?.expressionFlexibility || {};
|
|
69
|
-
const anchorReference = conceptualAnchor.anchorReference || derivedTokenLogic.anchorReference;
|
|
70
|
-
const conceptualAnchorName = conceptualAnchor.name || anchorReference;
|
|
71
|
-
const signatureMove = conceptualAnchor.signatureMove
|
|
72
|
-
|| designIntent.designExecutionHandoff?.signatureMoveRationale;
|
|
73
|
-
const signatureMotion = conceptualAnchor.signatureMotion
|
|
74
|
-
|| creativeCommitment.signatureMotion
|
|
75
|
-
|| derivedTokenLogic.motionDerivationSource;
|
|
76
|
-
const typographicDecision = conceptualAnchor.typographicDecision
|
|
77
|
-
|| creativeCommitment.typographicDecision
|
|
78
|
-
|| derivedTokenLogic.typographyDerivationSource;
|
|
79
|
-
const flexibleAxes = Array.isArray(designFlexibilityPolicy.flexibleExpressionAxes)
|
|
80
|
-
? designFlexibilityPolicy.flexibleExpressionAxes
|
|
81
|
-
: expressionFlexibility.flexibleAxes;
|
|
82
|
-
const lockedOutcomes = Array.isArray(expressionFlexibility.lockedOutcomes)
|
|
83
|
-
? expressionFlexibility.lockedOutcomes
|
|
84
|
-
: designFlexibilityPolicy.lockedOutcomeTypes;
|
|
85
|
-
|
|
86
|
-
if (!conceptualAnchorName && !anchorReference) {
|
|
87
|
-
return [
|
|
88
|
-
'## [UI TASK ANCHOR - READ FIRST]',
|
|
89
|
-
'Source: docs/design-intent.json',
|
|
90
|
-
'Status: Creative commitment is missing.',
|
|
91
|
-
'Required action: define conceptualAnchor.anchorReference, a concrete real-world anchor, signature motion, typographic decision, derivedTokenLogic, and libraryDecisions before UI implementation.',
|
|
92
|
-
].join('\n');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return [
|
|
96
|
-
'## [UI TASK ANCHOR - READ FIRST]',
|
|
97
|
-
'Source: docs/design-intent.json',
|
|
98
|
-
`Conceptual Anchor: ${toSingleLine(conceptualAnchorName, 'MISSING - complete Creative Commitment Gate before UI implementation')}`,
|
|
99
|
-
`Anchor Reference: ${toSingleLine(anchorReference, 'MISSING - required for deterministic token validation')}`,
|
|
100
|
-
`Signature Move: ${toSingleLine(signatureMove, 'MISSING - define one concrete authored move before UI implementation')}`,
|
|
101
|
-
`Motion Signature: ${toSingleLine(signatureMotion, 'MISSING - define timing/easing/choreography before UI implementation')}`,
|
|
102
|
-
`Typographic Decision: ${toSingleLine(typographicDecision, 'MISSING - define role contrast before UI implementation')}`,
|
|
103
|
-
`Locked Outcomes: ${toSingleLine(Array.isArray(lockedOutcomes) ? lockedOutcomes.join(', ') : '', 'user goals, accessibility, production readiness, forbidden patterns')}`,
|
|
104
|
-
`Flexible Expression: ${toSingleLine(Array.isArray(flexibleAxes) ? flexibleAxes.join(', ') : '', 'exact primitives, typeface, surface treatment, component skin, candidate moves')}`,
|
|
105
|
-
'If the UI output does not reflect these lines, stop and revise the design contract before continuing.',
|
|
106
|
-
].join('\n');
|
|
36
|
+
return null;
|
|
107
37
|
}
|
|
108
38
|
|
|
109
39
|
function buildContainerizationStrategySnapshot(dockerStrategy) {
|
|
@@ -400,7 +330,7 @@ export async function buildCompiledRulesContent({
|
|
|
400
330
|
'3. .agent-context/prompts/review-code.md -> review, audit, check, analyze',
|
|
401
331
|
'4. .agent-context/prompts/bootstrap-design.md -> ui, ux, layout, screen, tailwind, frontend, redesign',
|
|
402
332
|
'Documentation-first policy:',
|
|
403
|
-
'- Create or refine required project docs before implementation: README.md for every fresh or existing project; docs/doc-index.md whenever docs/ exists; docs/project-brief.md; docs/architecture-decision-record.md; docs/flow-overview.md; docs/api-contract.md when APIs, firmware endpoints, CLI commands, or web application flows exist; docs/database-schema.md when persistent data exists; and docs/DESIGN.md
|
|
333
|
+
'- Create or refine required project docs before implementation: README.md for every fresh or existing project; docs/doc-index.md whenever docs/ exists; docs/project-brief.md; docs/architecture-decision-record.md; docs/flow-overview.md; docs/api-contract.md when APIs, firmware endpoints, CLI commands, or web application flows exist; docs/database-schema.md when persistent data exists; and docs/DESIGN.md for UI scope.',
|
|
404
334
|
'- Keep README.md public and developer friendly, including for private projects: explain what the project is, who it is for, setup, usage, configuration, and links to deeper docs. Do not put secrets, internal agent notes, private reasoning, or governance policy dumps in it.',
|
|
405
335
|
'- Use docs/doc-index.md as the compact read-routing map before selecting deeper docs. Do not broad-read docs/*.md by default.',
|
|
406
336
|
'- Add PRD, SRS, technical-design, or separate ERD only when project evidence justifies them.',
|
|
@@ -566,7 +496,7 @@ export async function buildCompiledRulesContent({
|
|
|
566
496
|
'- Stop implementation if docs/flow-overview.md is missing.',
|
|
567
497
|
'- If the product uses persistent data, docs/database-schema.md must exist before coding continues.',
|
|
568
498
|
'- If the product exposes API or web application flows, docs/api-contract.md must exist before coding continues.',
|
|
569
|
-
'- For UI scope, stop implementation if docs/DESIGN.md
|
|
499
|
+
'- For UI scope, stop implementation if docs/DESIGN.md is missing.',
|
|
570
500
|
'- Keep README.md overview-level, public, and developer friendly; do not put secrets, internal agent notes, private reasoning, or governance policy dumps in it.',
|
|
571
501
|
'- Use README.md plus docs/doc-index.md to choose the smallest relevant read set before loading deeper docs.',
|
|
572
502
|
'- Add PRD, SRS, technical-design, or separate ERD only when project evidence justifies them.',
|
|
@@ -611,7 +541,7 @@ export async function buildCompiledRulesContent({
|
|
|
611
541
|
'- For docs-only/docs-first requests, stop after docs unless the user asks for implementation or approves an implementation plan.',
|
|
612
542
|
'- If docs/project-brief.md is missing, execute bootstrap-project-context prompt immediately.',
|
|
613
543
|
hasBootstrapDesignPrompt
|
|
614
|
-
? '- For UI scope: if docs/DESIGN.md
|
|
544
|
+
? '- For UI scope: if docs/DESIGN.md is missing, execute bootstrap-design prompt before implementing UI surfaces.'
|
|
615
545
|
: '- For UI scope: add a design bootstrap prompt before implementing UI surfaces.',
|
|
616
546
|
'- Bootstrap docs from repo evidence and the latest user request. Do not use generic placeholder templates.',
|
|
617
547
|
'- Separate confirmed facts from assumptions, include an Assumptions to Validate section when context is incomplete, and end with the next validation action.',
|
|
@@ -622,11 +552,6 @@ export async function buildCompiledRulesContent({
|
|
|
622
552
|
);
|
|
623
553
|
}
|
|
624
554
|
|
|
625
|
-
const designIntent = await readDesignIntentIfExists(resolvedTargetDirectoryPath);
|
|
626
|
-
const anchorCommitmentHeader = buildAnchorCommitmentHeader(designIntent);
|
|
627
|
-
if (anchorCommitmentHeader) {
|
|
628
|
-
contextBlocks.unshift(anchorCommitmentHeader);
|
|
629
|
-
}
|
|
630
555
|
|
|
631
556
|
return [
|
|
632
557
|
'# AGENTIC-SENIOR-CORE DYNAMIC GOVERNANCE RULESET',
|
package/lib/cli/preflight.mjs
CHANGED
|
@@ -71,27 +71,6 @@ export async function runPreflightChecks(targetDirectoryPath, operationMode) {
|
|
|
71
71
|
result.checks.push(`Skipping disk space check due to standard library limitation: ${error.message}`);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
// Check 4: Conflicting Files
|
|
75
|
-
if (operationMode === 'init') {
|
|
76
|
-
const potentiallyConflictingPaths = [
|
|
77
|
-
...entryPointFiles.map((entryPointFileName) => path.join(targetDirectoryPath, entryPointFileName)),
|
|
78
|
-
path.join(targetDirectoryPath, '.agent-context'),
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
const conflictingFound = [];
|
|
82
|
-
for (const conflictPath of potentiallyConflictingPaths) {
|
|
83
|
-
if (await pathExists(conflictPath)) {
|
|
84
|
-
conflictingFound.push(path.relative(targetDirectoryPath, conflictPath).replace(/\\/g, '/'));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (conflictingFound.length > 0) {
|
|
89
|
-
result.passed = false;
|
|
90
|
-
result.errors.push(`Conflicting governance files already exist during init: ${conflictingFound.join(', ')}. Use upgrade instead.`);
|
|
91
|
-
} else {
|
|
92
|
-
result.checks.push('No conflicting governance files found.');
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
74
|
|
|
96
75
|
return result;
|
|
97
76
|
}
|
|
@@ -7,7 +7,7 @@ export const PROJECT_DOC_FILE_NAMES = [
|
|
|
7
7
|
'flow-overview.md',
|
|
8
8
|
];
|
|
9
9
|
export const DOC_INDEX_FILE_NAME = 'doc-index.md';
|
|
10
|
-
export const UI_DESIGN_CONTRACT_FILE_NAMES = ['DESIGN.md'
|
|
10
|
+
export const UI_DESIGN_CONTRACT_FILE_NAMES = ['DESIGN.md'];
|
|
11
11
|
|
|
12
12
|
// Legacy project docs may still carry this version header; keep for upgrade staleness checks.
|
|
13
13
|
export const PROJECT_DOC_TEMPLATE_VERSION = '1.2.0';
|
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
import { buildStructureFirstSeedSignals, shouldBootstrapDesignDocument } from './design-contract/seed-signals.mjs';
|
|
24
24
|
import { FORBIDDEN_PATTERN_SIGNALS } from './design-contract/signal-vocab.mjs';
|
|
25
25
|
import { buildConceptualAnchorSection } from './design-contract/sections/conceptual-anchor.mjs';
|
|
26
|
-
import { buildResearchDossierMetadata } from './design-contract/research-dossier-migration.mjs';
|
|
27
26
|
import {
|
|
28
27
|
buildAccessibilityPolicySection,
|
|
29
28
|
buildAiSafeUiAuditSection,
|
|
@@ -135,7 +134,7 @@ function buildDesignIntentContractObject({
|
|
|
135
134
|
},
|
|
136
135
|
designFlexibilityPolicy: {
|
|
137
136
|
mode: 'locked-outcomes-flexible-expression',
|
|
138
|
-
contractRole: 'Use docs/
|
|
137
|
+
contractRole: 'Use docs/DESIGN.md as review invariants and handoff structure, not as a frozen visual recipe unless the user explicitly locks a decision.',
|
|
139
138
|
lockedOutcomeTypes: [
|
|
140
139
|
'confirmed-user-goals',
|
|
141
140
|
'repo-evidence-and-runtime-constraints',
|
|
@@ -161,7 +160,6 @@ function buildDesignIntentContractObject({
|
|
|
161
160
|
},
|
|
162
161
|
conceptualAnchor: buildConceptualAnchorSection(),
|
|
163
162
|
researchDossier: {
|
|
164
|
-
metadata: buildResearchDossierMetadata(),
|
|
165
163
|
sourceFreshness: {
|
|
166
164
|
status: 'agent-must-complete-before-ui-implementation',
|
|
167
165
|
freshnessAnchorDate: 'session-current-date',
|
|
@@ -241,7 +239,7 @@ function buildDesignIntentContractObject({
|
|
|
241
239
|
seedValuesRequireCalibration: true,
|
|
242
240
|
},
|
|
243
241
|
tokenSystem: {
|
|
244
|
-
sourceOfTruth: 'docs/
|
|
242
|
+
sourceOfTruth: 'docs/DESIGN.md',
|
|
245
243
|
taxonomyOrder: ['primitive', 'semantic', 'component'],
|
|
246
244
|
primitiveColorSpace: 'OKLCH',
|
|
247
245
|
requireSemanticAliases: true,
|
|
@@ -343,7 +341,7 @@ function buildDesignIntentContractObject({
|
|
|
343
341
|
],
|
|
344
342
|
requiredDesignSections: DESIGN_REQUIRED_SECTIONS,
|
|
345
343
|
implementation: {
|
|
346
|
-
requiredDeliverables: ['docs/DESIGN.md'
|
|
344
|
+
requiredDeliverables: ['docs/DESIGN.md'],
|
|
347
345
|
requireDesignRationale: true,
|
|
348
346
|
requireDistinctVisualDirection: true,
|
|
349
347
|
requireMachineReadableContract: true,
|
|
@@ -367,46 +365,6 @@ function buildDesignIntentContractObject({
|
|
|
367
365
|
};
|
|
368
366
|
}
|
|
369
367
|
|
|
370
|
-
export function buildDesignIntentSeedFromSignals({
|
|
371
|
-
projectName,
|
|
372
|
-
projectDescription,
|
|
373
|
-
primaryDomain,
|
|
374
|
-
features = [],
|
|
375
|
-
initContext,
|
|
376
|
-
status = 'seed-needs-design-synthesis',
|
|
377
|
-
supplementalFields = {},
|
|
378
|
-
}) {
|
|
379
|
-
const designIntentContract = buildDesignIntentContractObject({
|
|
380
|
-
projectName,
|
|
381
|
-
projectDescription,
|
|
382
|
-
primaryDomain,
|
|
383
|
-
features,
|
|
384
|
-
initContext,
|
|
385
|
-
status,
|
|
386
|
-
supplementalFields,
|
|
387
|
-
});
|
|
388
|
-
const validationErrors = validateDesignIntentContract(designIntentContract);
|
|
389
|
-
|
|
390
|
-
if (validationErrors.length > 0) {
|
|
391
|
-
throw new Error(`Invalid design intent contract seed: ${validationErrors.join(' ')}`);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return `${JSON.stringify(designIntentContract, null, 2)}\n`;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export function buildDesignIntentSeed({
|
|
398
|
-
discoveryAnswers,
|
|
399
|
-
initContext,
|
|
400
|
-
}) {
|
|
401
|
-
return buildDesignIntentSeedFromSignals({
|
|
402
|
-
projectName: discoveryAnswers.projectName,
|
|
403
|
-
projectDescription: discoveryAnswers.projectDescription,
|
|
404
|
-
primaryDomain: discoveryAnswers.primaryDomain,
|
|
405
|
-
features: discoveryAnswers.features,
|
|
406
|
-
initContext,
|
|
407
|
-
status: 'seed-needs-design-synthesis',
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
368
|
|
|
411
369
|
/**
|
|
412
370
|
* Public schema index. Lists the field and signal names that this aggregator
|
|
@@ -2,7 +2,6 @@ import { toTitleCase } from '../utils.mjs';
|
|
|
2
2
|
import {
|
|
3
3
|
PROJECT_DOC_SYNTHESIS_PROMPT_VERSION,
|
|
4
4
|
} from './constants.mjs';
|
|
5
|
-
import { buildDesignIntentSeed } from './design-contract.mjs';
|
|
6
5
|
|
|
7
6
|
function buildDockerStrategyExecutionBlock(dockerStrategy) {
|
|
8
7
|
const selectedDockerStrategy = String(dockerStrategy || '').trim();
|
|
@@ -124,10 +123,6 @@ export function buildDesignBootstrapPrompt({
|
|
|
124
123
|
initContext,
|
|
125
124
|
docsLanguage,
|
|
126
125
|
}) {
|
|
127
|
-
const designIntentSeed = buildDesignIntentSeed({
|
|
128
|
-
discoveryAnswers,
|
|
129
|
-
initContext,
|
|
130
|
-
});
|
|
131
126
|
|
|
132
127
|
return [
|
|
133
128
|
'# Bootstrap Prompt: Dynamic Design Contract Synthesis',
|
|
@@ -139,11 +134,9 @@ export function buildDesignBootstrapPrompt({
|
|
|
139
134
|
'',
|
|
140
135
|
'## Mission',
|
|
141
136
|
`Author docs/DESIGN.md in ${docsLanguage.toUpperCase()} language with strong art direction and engineering-ready guidance.`,
|
|
142
|
-
'Keep docs/design-intent.json synchronized as the machine-readable source of design intent.',
|
|
143
137
|
'',
|
|
144
138
|
'## Deliverables',
|
|
145
139
|
'1. docs/DESIGN.md',
|
|
146
|
-
'2. docs/design-intent.json',
|
|
147
140
|
'',
|
|
148
141
|
'## Design Direction Process',
|
|
149
142
|
'Follow `.agent-context/prompts/bootstrap-design.md` for the three-step design direction:',
|
|
@@ -169,19 +162,11 @@ export function buildDesignBootstrapPrompt({
|
|
|
169
162
|
`- Runtime constraint: ${initContext.stackFileName === 'agent-decision-runtime.md' ? 'agent recommendation required before coding' : toTitleCase(initContext.stackFileName)}`,
|
|
170
163
|
`- Architecture constraint: ${initContext.blueprintFileName === 'agent-decision-architecture.md' ? 'agent recommendation required before coding' : toTitleCase(initContext.blueprintFileName)}`,
|
|
171
164
|
'',
|
|
172
|
-
'## Seed Machine Contract',
|
|
173
|
-
'Refine this scaffold seed instead of discarding it. Replace placeholder direction with agent-chosen decisions grounded in repo evidence and the active brief.',
|
|
174
|
-
'```json',
|
|
175
|
-
designIntentSeed.trim(),
|
|
176
|
-
'```',
|
|
177
|
-
'',
|
|
178
165
|
'## Required Execution',
|
|
179
166
|
'1. Create or update docs/DESIGN.md with complete content covering: design intent, audience signals, visual direction, color/typography/spacing decisions, responsive strategy, motion rules, component language, accessibility non-negotiables, and anti-patterns.',
|
|
180
|
-
'2.
|
|
181
|
-
'3.
|
|
182
|
-
'4.
|
|
183
|
-
'5. Preserve repoEvidence.designEvidenceSummary when onboarding or detector evidence exists.',
|
|
184
|
-
'6. After the contract exists, use it as a first-class source for future UI tasks.',
|
|
167
|
+
'2. Make the contract executable without screenshot dependency.',
|
|
168
|
+
'3. Preserve repoEvidence.designEvidenceSummary when onboarding or detector evidence exists.',
|
|
169
|
+
'4. After the contract exists, use it as a first-class source for future UI tasks.',
|
|
185
170
|
'',
|
|
186
171
|
].join('\n');
|
|
187
172
|
}
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
resolveProjectDocTargets,
|
|
15
15
|
} from './discovery.mjs';
|
|
16
16
|
import {
|
|
17
|
-
buildDesignIntentSeed,
|
|
18
17
|
shouldBootstrapDesignDocument,
|
|
19
18
|
} from './design-contract.mjs';
|
|
20
19
|
import {
|
|
@@ -76,14 +75,6 @@ export async function generateProjectDocumentation(
|
|
|
76
75
|
});
|
|
77
76
|
await fs.writeFile(path.join(promptsDirectoryPath, designPromptFileName), designPromptContent, 'utf8');
|
|
78
77
|
generatedPromptFileNames.push(designPromptFileName);
|
|
79
|
-
|
|
80
|
-
const designIntentSeedFileName = 'design-intent.json';
|
|
81
|
-
const designIntentSeedContent = buildDesignIntentSeed({
|
|
82
|
-
discoveryAnswers,
|
|
83
|
-
initContext: synthesisContext,
|
|
84
|
-
});
|
|
85
|
-
await fs.writeFile(path.join(docsDirectoryPath, designIntentSeedFileName), designIntentSeedContent, 'utf8');
|
|
86
|
-
materializedFileNames.push(designIntentSeedFileName);
|
|
87
78
|
}
|
|
88
79
|
|
|
89
80
|
return {
|
package/package.json
CHANGED
|
@@ -15,10 +15,6 @@ import {
|
|
|
15
15
|
VERSION_PATTERN,
|
|
16
16
|
} from './constants.mjs';
|
|
17
17
|
import { pushResult, readText } from './runtime.mjs';
|
|
18
|
-
import {
|
|
19
|
-
buildDesignIntentSeedFromSignals,
|
|
20
|
-
validateDesignContractCompleteness,
|
|
21
|
-
} from '../../lib/cli/project-scaffolder.mjs';
|
|
22
18
|
|
|
23
19
|
export function runStaticReleaseChecks(results, diagnostics) {
|
|
24
20
|
const packageJsonPath = 'package.json';
|
|
@@ -70,38 +66,6 @@ export function runStaticReleaseChecks(results, diagnostics) {
|
|
|
70
66
|
pushResult(results, true, 'history-v18', 'Project history includes V1.8 release track');
|
|
71
67
|
}
|
|
72
68
|
|
|
73
|
-
try {
|
|
74
|
-
const designIntentSeed = JSON.parse(buildDesignIntentSeedFromSignals({
|
|
75
|
-
projectName: 'Release Gate UI Contract',
|
|
76
|
-
projectDescription: 'Validates deterministic UI design contract completeness before release',
|
|
77
|
-
primaryDomain: 'Web application',
|
|
78
|
-
initContext: {
|
|
79
|
-
stackFileName: 'agent-decision-runtime.md',
|
|
80
|
-
blueprintFileName: 'agent-decision-architecture.md',
|
|
81
|
-
},
|
|
82
|
-
// Seed status so `tokenContinuityClassification: pending-research`
|
|
83
|
-
// is a valid placeholder. This probe asserts seed-shape completeness,
|
|
84
|
-
// not active-contract behavior; an agent or user fills the contract
|
|
85
|
-
// with anchor-derived / continuity-retained / newly-introduced once
|
|
86
|
-
// the dossier is filled.
|
|
87
|
-
status: 'seed-needs-design-synthesis',
|
|
88
|
-
}));
|
|
89
|
-
const designContractIssues = validateDesignContractCompleteness(designIntentSeed);
|
|
90
|
-
|
|
91
|
-
if (designContractIssues.length === 0) {
|
|
92
|
-
pushResult(results, true, 'ui-design-contract-completeness', 'Design intent seed includes deterministic token derivation and library verification gates');
|
|
93
|
-
} else {
|
|
94
|
-
pushResult(
|
|
95
|
-
results,
|
|
96
|
-
false,
|
|
97
|
-
'ui-design-contract-completeness',
|
|
98
|
-
`Design intent seed completeness issues: ${designContractIssues.join('; ')}`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
} catch (designContractError) {
|
|
102
|
-
const designContractMessage = designContractError instanceof Error ? designContractError.message : 'Unknown design contract error';
|
|
103
|
-
pushResult(results, false, 'ui-design-contract-completeness', `Cannot validate design intent seed: ${designContractMessage}`);
|
|
104
|
-
}
|
|
105
69
|
|
|
106
70
|
const requiredOperationsFiles = [
|
|
107
71
|
'.agent-context/review-checklists/architecture-review.md',
|
|
@@ -267,18 +267,9 @@ export const REQUIRED_UPGRADE_UI_CONTRACT_WARNING_SNIPPETS = [
|
|
|
267
267
|
path: 'lib/cli/commands/upgrade.mjs',
|
|
268
268
|
snippets: [
|
|
269
269
|
'UI/frontend scope was detected, but the dynamic design contract is incomplete:',
|
|
270
|
-
'docs/design-intent.json',
|
|
271
|
-
'Planned seed on apply: docs/design-intent.json',
|
|
272
|
-
'Upgrade synchronizes governance assets and can seed docs/design-intent.json, but it does not author project-specific docs/DESIGN.md automatically.',
|
|
273
270
|
'detectUiScopeSignals',
|
|
274
271
|
],
|
|
275
272
|
},
|
|
276
|
-
{
|
|
277
|
-
path: 'lib/cli/commands/upgrade/design-intent-seed.mjs',
|
|
278
|
-
snippets: [
|
|
279
|
-
'seed-generated-during-upgrade',
|
|
280
|
-
],
|
|
281
|
-
},
|
|
282
273
|
];
|
|
283
274
|
export const REQUIRED_UI_DESIGN_AUTOMATION_SNIPPETS = [
|
|
284
275
|
{
|
|
@@ -288,7 +279,6 @@ export const REQUIRED_UI_DESIGN_AUTOMATION_SNIPPETS = [
|
|
|
288
279
|
'bootstrap-design.md',
|
|
289
280
|
'frontend-architecture.md',
|
|
290
281
|
'docs/DESIGN.md',
|
|
291
|
-
'docs/design-intent.json',
|
|
292
282
|
'Documentation-First Mode',
|
|
293
283
|
'English by default',
|
|
294
284
|
'do not write application, firmware, or UI code',
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
import { buildDesignIntentSeedFromSignals } from '../../project-scaffolder.mjs';
|
|
4
|
-
|
|
5
|
-
export function buildUpgradeDesignIntentSeed({
|
|
6
|
-
targetDirectoryPath,
|
|
7
|
-
packageManifest,
|
|
8
|
-
selectedStackFileName,
|
|
9
|
-
selectedBlueprintFileName,
|
|
10
|
-
uiScopeSignals,
|
|
11
|
-
}) {
|
|
12
|
-
const projectName = String(packageManifest?.name || path.basename(targetDirectoryPath)).trim()
|
|
13
|
-
|| 'existing-ui-project';
|
|
14
|
-
const isMobileUiProject = String(selectedStackFileName || '').toLowerCase().includes('react-native')
|
|
15
|
-
|| String(selectedStackFileName || '').toLowerCase().includes('flutter')
|
|
16
|
-
|| uiScopeSignals.signalReasons.some((signalReason) => {
|
|
17
|
-
return signalReason.includes('android') || signalReason.includes('ios');
|
|
18
|
-
});
|
|
19
|
-
const resolvedDomain = isMobileUiProject ? 'Mobile app' : 'Web application';
|
|
20
|
-
const projectDescription = String(packageManifest?.description || '').trim()
|
|
21
|
-
|| `Existing ${resolvedDomain.toLowerCase()} detected during upgrade. Create a project-specific dynamic design contract before shipping new UI work.`;
|
|
22
|
-
|
|
23
|
-
return buildDesignIntentSeedFromSignals({
|
|
24
|
-
projectName,
|
|
25
|
-
projectDescription,
|
|
26
|
-
primaryDomain: resolvedDomain,
|
|
27
|
-
features: [],
|
|
28
|
-
initContext: {
|
|
29
|
-
stackFileName: selectedStackFileName,
|
|
30
|
-
blueprintFileName: selectedBlueprintFileName,
|
|
31
|
-
},
|
|
32
|
-
status: 'seed-generated-during-upgrade',
|
|
33
|
-
supplementalFields: {
|
|
34
|
-
upgradeSignals: {
|
|
35
|
-
detectedFrom: uiScopeSignals.signalReasons,
|
|
36
|
-
generatedBy: 'upgrade-seed',
|
|
37
|
-
},
|
|
38
|
-
repoEvidence: {
|
|
39
|
-
uiSignalReasons: uiScopeSignals.signalReasons,
|
|
40
|
-
frontendMetrics: uiScopeSignals.frontendEvidenceMetrics || null,
|
|
41
|
-
designEvidenceSummary: uiScopeSignals.designEvidenceSummary || null,
|
|
42
|
-
workspaceUiEntries: uiScopeSignals.workspaceUiEntries || [],
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Research-dossier migration helper.
|
|
3
|
-
*
|
|
4
|
-
* Adds the `researchDossier.metadata` block to existing `docs/design-intent.json`
|
|
5
|
-
* files when it is absent, populating `antiRepeatLedger` from the existing
|
|
6
|
-
* conceptual anchor, palette, and motion fields so future UI work cannot
|
|
7
|
-
* unknowingly repeat shipped direction.
|
|
8
|
-
*
|
|
9
|
-
* Idempotent: if the metadata block already exists, the file is left untouched.
|
|
10
|
-
* Additive: never overwrites existing fields.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import fs from 'node:fs/promises';
|
|
14
|
-
|
|
15
|
-
import { pathExists } from '../../utils.mjs';
|
|
16
|
-
|
|
17
|
-
const FRESHNESS_WINDOW_DAYS = 90;
|
|
18
|
-
const FRESHNESS_RULE = 'Research dossier is stale when researchVerifiedAt is null or older than freshnessWindowDays. Stale dossiers must run research-design.md before UI implementation. User-explicit redesign requests bypass freshness and force fresh research regardless of age.';
|
|
19
|
-
|
|
20
|
-
function takeFirstNonEmpty(...candidateValues) {
|
|
21
|
-
for (const candidateValue of candidateValues) {
|
|
22
|
-
if (typeof candidateValue === 'string' && candidateValue.trim().length > 0) {
|
|
23
|
-
return candidateValue.trim();
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function buildPreviousAnchorEntry(designIntentContract) {
|
|
30
|
-
const conceptualAnchor = designIntentContract?.conceptualAnchor;
|
|
31
|
-
if (!conceptualAnchor || typeof conceptualAnchor !== 'object') {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
const anchorReference = takeFirstNonEmpty(conceptualAnchor.anchorReference);
|
|
35
|
-
if (!anchorReference || anchorReference === 'agent-defined-anchor-reference') {
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
const specificReferencePoint = takeFirstNonEmpty(conceptualAnchor.specificReferencePoint);
|
|
39
|
-
const summary = specificReferencePoint
|
|
40
|
-
? `${anchorReference} (${specificReferencePoint})`
|
|
41
|
-
: anchorReference;
|
|
42
|
-
return [{
|
|
43
|
-
summary,
|
|
44
|
-
source: 'migrated-from-existing-design-intent',
|
|
45
|
-
blockedBecause: 'previously-shipped-direction',
|
|
46
|
-
}];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function buildPreviousPaletteEntry(designIntentContract) {
|
|
50
|
-
const derivedTokenLogic = designIntentContract?.derivedTokenLogic;
|
|
51
|
-
const colorTruth = designIntentContract?.colorTruth;
|
|
52
|
-
const colorDerivationSummary = takeFirstNonEmpty(derivedTokenLogic?.colorDerivationSource);
|
|
53
|
-
const colorIntent = takeFirstNonEmpty(colorTruth?.intent);
|
|
54
|
-
const summary = takeFirstNonEmpty(colorIntent, colorDerivationSummary);
|
|
55
|
-
if (!summary) {
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
58
|
-
return [{
|
|
59
|
-
summary,
|
|
60
|
-
source: 'migrated-from-existing-design-intent',
|
|
61
|
-
blockedBecause: 'previously-shipped-palette-behavior',
|
|
62
|
-
}];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function buildPreviousMotionEntry(designIntentContract) {
|
|
66
|
-
const motionPaletteDecision = designIntentContract?.motionPaletteDecision;
|
|
67
|
-
const motionSystem = designIntentContract?.motionSystem;
|
|
68
|
-
const derivedTokenLogic = designIntentContract?.derivedTokenLogic;
|
|
69
|
-
|
|
70
|
-
const motionSignature = takeFirstNonEmpty(
|
|
71
|
-
motionPaletteDecision?.signatureMotion,
|
|
72
|
-
motionPaletteDecision?.motion,
|
|
73
|
-
motionSystem?.signature,
|
|
74
|
-
motionSystem?.purpose,
|
|
75
|
-
derivedTokenLogic?.motionBudget,
|
|
76
|
-
);
|
|
77
|
-
if (!motionSignature) {
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
return [{
|
|
81
|
-
summary: motionSignature,
|
|
82
|
-
source: 'migrated-from-existing-design-intent',
|
|
83
|
-
blockedBecause: 'previously-shipped-motion-signature',
|
|
84
|
-
}];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function buildPreviousTypographyEntry(designIntentContract) {
|
|
88
|
-
if (!designIntentContract || typeof designIntentContract !== 'object') {
|
|
89
|
-
return [];
|
|
90
|
-
}
|
|
91
|
-
const tokenSystem = designIntentContract?.tokenSystem;
|
|
92
|
-
const typographyTokens = tokenSystem && typeof tokenSystem === 'object' ? tokenSystem.typographyTokens : null;
|
|
93
|
-
if (!typographyTokens || typeof typographyTokens !== 'object') {
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
const tokenEntries = Object.entries(typographyTokens)
|
|
97
|
-
.filter(([, value]) => typeof value === 'string' && value.trim().length > 0)
|
|
98
|
-
.map(([role, value]) => `${role}: ${value.trim()}`);
|
|
99
|
-
if (tokenEntries.length === 0) {
|
|
100
|
-
return [];
|
|
101
|
-
}
|
|
102
|
-
return [{
|
|
103
|
-
summary: tokenEntries.join('; '),
|
|
104
|
-
source: 'migrated-from-existing-design-intent',
|
|
105
|
-
blockedBecause: 'previously-shipped-typography-trio',
|
|
106
|
-
}];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function buildResearchDossierMetadata({
|
|
110
|
-
designIntentContract = null,
|
|
111
|
-
populateLedgerFromExistingContract = false,
|
|
112
|
-
} = {}) {
|
|
113
|
-
const metadata = {
|
|
114
|
-
researchVerifiedAt: null,
|
|
115
|
-
freshnessWindowDays: FRESHNESS_WINDOW_DAYS,
|
|
116
|
-
freshnessRule: FRESHNESS_RULE,
|
|
117
|
-
antiRepeatLedger: {
|
|
118
|
-
blocklistFromHistory: true,
|
|
119
|
-
ledgerScope: 'signature-level-descriptors-only',
|
|
120
|
-
ledgerMaxEntriesPerCategory: 3,
|
|
121
|
-
previousAnchors: [],
|
|
122
|
-
previousPalettes: [],
|
|
123
|
-
previousMotionSignatures: [],
|
|
124
|
-
previousTypographyChoices: [],
|
|
125
|
-
previousStructuralFingerprints: [],
|
|
126
|
-
},
|
|
127
|
-
userExplicitRedesignBypassesFreshness: true,
|
|
128
|
-
statusAwareValidation: {
|
|
129
|
-
seedStatuses: [
|
|
130
|
-
'seed-needs-design-synthesis',
|
|
131
|
-
'seed-generated-during-init',
|
|
132
|
-
'seed-generated-during-upgrade',
|
|
133
|
-
],
|
|
134
|
-
seedSkipsDossierShape: true,
|
|
135
|
-
activeRequiresFreshOrExplicitRedesign: true,
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
if (populateLedgerFromExistingContract && designIntentContract && typeof designIntentContract === 'object') {
|
|
140
|
-
metadata.antiRepeatLedger.previousAnchors = buildPreviousAnchorEntry(designIntentContract).slice(0, metadata.antiRepeatLedger.ledgerMaxEntriesPerCategory);
|
|
141
|
-
metadata.antiRepeatLedger.previousPalettes = buildPreviousPaletteEntry(designIntentContract).slice(0, metadata.antiRepeatLedger.ledgerMaxEntriesPerCategory);
|
|
142
|
-
metadata.antiRepeatLedger.previousMotionSignatures = buildPreviousMotionEntry(designIntentContract).slice(0, metadata.antiRepeatLedger.ledgerMaxEntriesPerCategory);
|
|
143
|
-
metadata.antiRepeatLedger.previousTypographyChoices = buildPreviousTypographyEntry(designIntentContract).slice(0, metadata.antiRepeatLedger.ledgerMaxEntriesPerCategory);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return metadata;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Migrates an existing design-intent.json file in place by adding
|
|
151
|
-
* `researchDossier.metadata` when absent. Idempotent.
|
|
152
|
-
*
|
|
153
|
-
* @param {string} designIntentFilePath
|
|
154
|
-
* @returns {Promise<{ migrated: boolean, reason: string }>}
|
|
155
|
-
*/
|
|
156
|
-
export async function migrateExistingDesignIntentToResearchDossierSchema(designIntentFilePath) {
|
|
157
|
-
if (!(await pathExists(designIntentFilePath))) {
|
|
158
|
-
return { migrated: false, reason: 'design-intent-file-absent' };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const fileContent = await fs.readFile(designIntentFilePath, 'utf8');
|
|
162
|
-
let designIntentContract;
|
|
163
|
-
try {
|
|
164
|
-
designIntentContract = JSON.parse(fileContent);
|
|
165
|
-
} catch {
|
|
166
|
-
return { migrated: false, reason: 'design-intent-file-not-valid-json' };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (!designIntentContract || typeof designIntentContract !== 'object') {
|
|
170
|
-
return { migrated: false, reason: 'design-intent-file-not-an-object' };
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const existingResearchDossier = designIntentContract.researchDossier;
|
|
174
|
-
if (existingResearchDossier && typeof existingResearchDossier === 'object' && existingResearchDossier.metadata) {
|
|
175
|
-
return { migrated: false, reason: 'research-dossier-metadata-already-present' };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const populatedMetadata = buildResearchDossierMetadata({
|
|
179
|
-
designIntentContract,
|
|
180
|
-
populateLedgerFromExistingContract: true,
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
designIntentContract.researchDossier = {
|
|
184
|
-
...(existingResearchDossier && typeof existingResearchDossier === 'object' ? existingResearchDossier : {}),
|
|
185
|
-
metadata: populatedMetadata,
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
await fs.writeFile(designIntentFilePath, `${JSON.stringify(designIntentContract, null, 2)}\n`, 'utf8');
|
|
189
|
-
return { migrated: true, reason: 'research-dossier-metadata-injected' };
|
|
190
|
-
}
|