@ryuenn3123/agentic-senior-core 4.0.0 → 4.0.2
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/.agent-context/prompts/bootstrap-design.md +4 -1
- package/.agent-context/prompts/research-design.md +182 -0
- package/AGENTS.md +12 -11
- package/README.md +3 -1
- package/lib/cli/commands/init.mjs +11 -0
- package/lib/cli/commands/upgrade.mjs +11 -0
- package/lib/cli/project-scaffolder/design-contract/research-dossier-migration.mjs +189 -0
- package/lib/cli/project-scaffolder/design-contract/sections/conceptual-anchor.mjs +117 -0
- package/lib/cli/project-scaffolder/design-contract/validation/anchor-validators.mjs +234 -0
- package/lib/cli/project-scaffolder/design-contract/validation/completeness.mjs +38 -0
- package/lib/cli/project-scaffolder/design-contract/validation/research-dossier-validators.mjs +104 -0
- package/lib/cli/project-scaffolder/design-contract/validation.mjs +2 -0
- package/lib/cli/project-scaffolder/design-contract.mjs +13 -0
- package/lib/cli/project-scaffolder/prompt-builders.mjs +9 -0
- package/package.json +2 -2
- package/scripts/audit-caching-scope-hygiene.mjs +4 -4
- package/scripts/build-release-benchmark-bundle.mjs +6 -6
- package/scripts/documentation-boundary-audit.mjs +13 -0
- package/scripts/migrate-rule-format/id-prefix-table.mjs +1 -1
- package/scripts/migrate-rule-format/render-new.mjs +1 -1
- package/scripts/migrate-rule-format.mjs +1 -1
- package/scripts/release-gate/static-checks.mjs +6 -1
- package/scripts/validate/config.mjs +31 -0
- package/scripts/validate.mjs +1 -0
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* Conceptual anchor section of the design intent contract. Encodes the rules
|
|
3
3
|
* that force agents to commit to a real-world reference, motion, and typography
|
|
4
4
|
* decision before UI implementation, instead of defaulting to spatial cliches.
|
|
5
|
+
*
|
|
6
|
+
* Carries the Section 3-5 dossier from
|
|
7
|
+
* `.agent-context/prompts/research-design.md`:
|
|
8
|
+
* - categoryCodes (Section 3)
|
|
9
|
+
* - morphologicalExploration (Section 4)
|
|
10
|
+
* - anchorCandidates (Section 5)
|
|
5
11
|
*/
|
|
6
12
|
|
|
7
13
|
export function buildConceptualAnchorSection() {
|
|
@@ -9,6 +15,7 @@ export function buildConceptualAnchorSection() {
|
|
|
9
15
|
mode: 'required-when-no-external-research',
|
|
10
16
|
seedMode: 'selection-policy-only',
|
|
11
17
|
anchorReference: 'agent-defined-anchor-reference',
|
|
18
|
+
researchBrief: '.agent-context/prompts/research-design.md',
|
|
12
19
|
requiresAgentSelectionBeforeUiImplementation: true,
|
|
13
20
|
userResearchAbsencePolicy: {
|
|
14
21
|
userSuppliedResearchOnly: true,
|
|
@@ -88,6 +95,116 @@ export function buildConceptualAnchorSection() {
|
|
|
88
95
|
'motion',
|
|
89
96
|
'responsive-composition',
|
|
90
97
|
],
|
|
98
|
+
categoryCodes: {
|
|
99
|
+
mode: 'agent-must-complete-before-ui-implementation',
|
|
100
|
+
blockingByDefault: true,
|
|
101
|
+
researchBriefSection: 'Section 3',
|
|
102
|
+
researchBriefPath: '.agent-context/prompts/research-design.md',
|
|
103
|
+
minimumEntries: 3,
|
|
104
|
+
specificityRule: 'A category code is only valid if a reader unfamiliar with the project can visualize a specific aesthetic direction from the text alone, without seeing the UI and without knowing the product name.',
|
|
105
|
+
antiLeakageRule: 'Listing a cliche identifies a trap; it does not endorse an aesthetic. Examples in the brief are NOT target aesthetics for any project. Each category code must carry an explicit rejection note so the cliche cannot quietly become the target.',
|
|
106
|
+
failingExamples: [
|
|
107
|
+
'clean typography (too abstract, applies to anything)',
|
|
108
|
+
'modern color palette (not falsifiable)',
|
|
109
|
+
'smooth animations (describes nothing specific)',
|
|
110
|
+
],
|
|
111
|
+
passingExamples: [
|
|
112
|
+
"children's storybook illustration site: hand-painted gouache textures with irregular hand-lettered titles, off-grid spreads with whitespace gutters, page-turn pacing rather than scroll (instantly recognizable as kids book category default)",
|
|
113
|
+
'luxury car configurator: full-bleed monochrome photography on black, ultra-thin sans-serif tracked wide, slow horizontal scroll with locked vertical alignment, micro-counters that tick instead of slide (instantly recognizable as luxury auto category default)',
|
|
114
|
+
'academic philosophy journal: high-contrast black-on-cream, book-class serif body at 11pt with generous leading, footnote markers with hover panels, numbered table-of-contents navigation, no hero imagery (instantly recognizable as academic journal category default)',
|
|
115
|
+
],
|
|
116
|
+
passingExamplesPolicy: 'These examples illustrate the description format only. They are AI-defaultable cliches of their categories and must NOT be adopted as target aesthetics for any project.',
|
|
117
|
+
commonAiSafeClichesToReject: [
|
|
118
|
+
'dev-tool default: condensed tabular numerics with minimal chrome and monospace code blocks on dark slate background, sans-serif metadata at 11-12px, monochrome status dots, single-line settings rows',
|
|
119
|
+
'AI-startup landing default: purple-to-pink gradient hero with floating 3D glass cards, sans-serif display type at 700-900 weight, vague hero copy, three-up feature grid below the fold',
|
|
120
|
+
'health/wellness app default: mint accent on white surface with coral status indicators, rounded pill-shaped buttons, friendly sans-serif at high weight, soft drop shadows on cards',
|
|
121
|
+
'SaaS admin default: left-side icon-only nav, top utility bar, three-card KPI row above a single data table, neutral grey-on-white with one accent color, modal-driven detail flows',
|
|
122
|
+
'marketing site default: hero image with one-line headline plus subhead, three feature tiles below, two pricing tiers, testimonial carousel, footer link grid',
|
|
123
|
+
],
|
|
124
|
+
commonAiSafeClichesPolicy: 'If the project sits anywhere near one of these AI-safe defaults, the matching cliche must appear in candidateEntries with an explicit rejection note. Software products pattern-match one of these without intervention; naming the trap is required even when the trap is uncomfortable to admit.',
|
|
125
|
+
selfTestRule: 'Read each category code aloud to someone unfamiliar with the project. If they cannot visualize a specific aesthetic direction from the text alone, the code is too abstract; revise. If they say "that is basically the X cliche", the description is specific enough; the cliche then belongs on the reject list with a rejection note, not as a candidate target.',
|
|
126
|
+
requiredFieldsPerEntry: [
|
|
127
|
+
'description',
|
|
128
|
+
'specificityEvidence',
|
|
129
|
+
'categoryDefaultReason',
|
|
130
|
+
'rejectionNote',
|
|
131
|
+
],
|
|
132
|
+
forbiddenPlaceholderPhrases: [
|
|
133
|
+
'clean typography',
|
|
134
|
+
'modern color palette',
|
|
135
|
+
'smooth animations',
|
|
136
|
+
'best practices',
|
|
137
|
+
'good design',
|
|
138
|
+
],
|
|
139
|
+
candidateEntries: [],
|
|
140
|
+
},
|
|
141
|
+
morphologicalExploration: {
|
|
142
|
+
mode: 'agent-must-complete-before-ui-implementation',
|
|
143
|
+
blockingByDefault: true,
|
|
144
|
+
researchBriefSection: 'Section 4',
|
|
145
|
+
researchBriefPath: '.agent-context/prompts/research-design.md',
|
|
146
|
+
requiredMatrixShape: '5x5-or-6x5',
|
|
147
|
+
minimumDimensions: 5,
|
|
148
|
+
maximumDimensions: 6,
|
|
149
|
+
valuesPerDimension: 5,
|
|
150
|
+
forbidCategoryCodeValuesInMatrix: true,
|
|
151
|
+
requireSelectedCombination: true,
|
|
152
|
+
requireUncomfortableCombination: true,
|
|
153
|
+
uncomfortableCombinationRule: 'Highlight at least one combination that feels instinctively wrong or uncomfortable but can be argued with product logic. The uncomfortable combination proves the matrix actually spans the design space; if every combination feels safe, shippable, and unobjectionable, the matrix is clustering in the safe-creative zone.',
|
|
154
|
+
uncomfortableCombinationRequiredFields: [
|
|
155
|
+
'combinationLabel',
|
|
156
|
+
'discomfortReason',
|
|
157
|
+
'productLogicJustification',
|
|
158
|
+
],
|
|
159
|
+
widenDimensionsWhenUncomfortableCombinationCannotBeProduced: true,
|
|
160
|
+
seedDimensions: [],
|
|
161
|
+
seedMatrix: [],
|
|
162
|
+
selectedCombination: null,
|
|
163
|
+
uncomfortableCombination: null,
|
|
164
|
+
},
|
|
165
|
+
anchorCandidates: {
|
|
166
|
+
mode: 'agent-must-complete-before-ui-implementation',
|
|
167
|
+
blockingByDefault: true,
|
|
168
|
+
researchBriefSection: 'Section 5',
|
|
169
|
+
researchBriefPath: '.agent-context/prompts/research-design.md',
|
|
170
|
+
requiredCandidateCount: 5,
|
|
171
|
+
requiredFieldsPerCandidate: [
|
|
172
|
+
'anchorReference',
|
|
173
|
+
'conceptualFamily',
|
|
174
|
+
'jobFit',
|
|
175
|
+
'hierarchyImplication',
|
|
176
|
+
'densityImplication',
|
|
177
|
+
'typeImplication',
|
|
178
|
+
'stateLanguage',
|
|
179
|
+
'motionImplication',
|
|
180
|
+
'whatItRulesOut',
|
|
181
|
+
'renameTest',
|
|
182
|
+
'categoryCodeOverlap',
|
|
183
|
+
],
|
|
184
|
+
renameTest: {
|
|
185
|
+
mode: 'strengthened',
|
|
186
|
+
requiredTestCategoryCount: 3,
|
|
187
|
+
testCategoryDistanceRule: 'The three test categories must be remote from each other and from the actual product (for example, if the product is a health app, test against fintech dashboard, kids educational game, and industrial equipment monitoring console).',
|
|
188
|
+
testCategoryFreshnessRule: 'Pick fresh test categories per anchor. Reusing the same triple across every anchor lets the agent memorize the pass condition instead of stress-testing the anchor. The illustrative triple in testCategoryDistanceRule is not a fixed test set.',
|
|
189
|
+
verdictScoring: {
|
|
190
|
+
'STRONG PASS': 'UI still coherent in 0 of 3 renamed categories.',
|
|
191
|
+
PASS: 'UI still coherent in 1 of 3. Pass with note.',
|
|
192
|
+
REVISE: 'UI still coherent in 2 of 3. Anchor is too generic. Revise the anchor to add product-specific constraints until it fails in at least 2 of 3.',
|
|
193
|
+
DISCARD: 'UI still coherent in 3 of 3. Anchor is category-agnostic. Discard immediately.',
|
|
194
|
+
},
|
|
195
|
+
requireTestCategoriesRecordedInDossier: true,
|
|
196
|
+
auditableByHumanReviewer: true,
|
|
197
|
+
renameTestRequiredFields: [
|
|
198
|
+
'testCategories',
|
|
199
|
+
'results',
|
|
200
|
+
'verdict',
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
categoryCodeOverlapRule: 'List any Section 3 category codes this candidate accidentally inherits, with reasoning.',
|
|
204
|
+
forbiddenSelectedVerdicts: ['DISCARD'],
|
|
205
|
+
requiredSelectedVerdicts: ['STRONG PASS', 'PASS'],
|
|
206
|
+
candidates: [],
|
|
207
|
+
},
|
|
91
208
|
finalAnchorContract: {
|
|
92
209
|
requiredFields: [
|
|
93
210
|
'name',
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
import { hasNonEmptyString } from './helpers.mjs';
|
|
9
9
|
|
|
10
|
+
const SEED_STATUSES = new Set([
|
|
11
|
+
'seed-needs-design-synthesis',
|
|
12
|
+
'seed-generated-during-init',
|
|
13
|
+
'seed-generated-during-upgrade',
|
|
14
|
+
]);
|
|
15
|
+
|
|
10
16
|
function validateUserResearchAbsencePolicy(conceptualAnchor, validationErrors) {
|
|
11
17
|
const userResearchAbsencePolicy = conceptualAnchor.userResearchAbsencePolicy;
|
|
12
18
|
if (!userResearchAbsencePolicy || typeof userResearchAbsencePolicy !== 'object') {
|
|
@@ -166,6 +172,8 @@ export function validateConceptualAnchor(designIntentContract, validationErrors)
|
|
|
166
172
|
}
|
|
167
173
|
|
|
168
174
|
const conceptualAnchor = designIntentContract.conceptualAnchor;
|
|
175
|
+
const isSeedStatus = SEED_STATUSES.has(designIntentContract.status);
|
|
176
|
+
|
|
169
177
|
if (conceptualAnchor.mode !== 'required-when-no-external-research') {
|
|
170
178
|
validationErrors.push('designIntent.conceptualAnchor.mode must equal "required-when-no-external-research".');
|
|
171
179
|
}
|
|
@@ -204,6 +212,9 @@ export function validateConceptualAnchor(designIntentContract, validationErrors)
|
|
|
204
212
|
|
|
205
213
|
validateVisualRiskBudgetAndLiteralPolicy(conceptualAnchor, validationErrors);
|
|
206
214
|
validateFinalAnchorContract(conceptualAnchor, validationErrors);
|
|
215
|
+
validateCategoryCodes(conceptualAnchor, validationErrors, { isSeedStatus });
|
|
216
|
+
validateMorphologicalExploration(conceptualAnchor, validationErrors, { isSeedStatus });
|
|
217
|
+
validateAnchorCandidates(conceptualAnchor, validationErrors, { isSeedStatus });
|
|
207
218
|
return validationErrors;
|
|
208
219
|
}
|
|
209
220
|
|
|
@@ -220,3 +231,226 @@ export function validateMathSystems(designIntentContract, validationErrors) {
|
|
|
220
231
|
}
|
|
221
232
|
return validationErrors;
|
|
222
233
|
}
|
|
234
|
+
|
|
235
|
+
function validateCategoryCodes(conceptualAnchor, validationErrors, options = {}) {
|
|
236
|
+
const { isSeedStatus = false } = options;
|
|
237
|
+
const categoryCodes = conceptualAnchor.categoryCodes;
|
|
238
|
+
if (!categoryCodes || typeof categoryCodes !== 'object') {
|
|
239
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes must exist (Section 3 of research-design.md).');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (categoryCodes.mode !== 'agent-must-complete-before-ui-implementation') {
|
|
243
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.mode must equal "agent-must-complete-before-ui-implementation".');
|
|
244
|
+
}
|
|
245
|
+
if (categoryCodes.blockingByDefault !== true) {
|
|
246
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.blockingByDefault must equal true.');
|
|
247
|
+
}
|
|
248
|
+
if (categoryCodes.researchBriefSection !== 'Section 3') {
|
|
249
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.researchBriefSection must equal "Section 3".');
|
|
250
|
+
}
|
|
251
|
+
if (!hasNonEmptyString(categoryCodes.researchBriefPath) || !categoryCodes.researchBriefPath.includes('research-design.md')) {
|
|
252
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.researchBriefPath must point to the research-design.md brief.');
|
|
253
|
+
}
|
|
254
|
+
if (!Number.isInteger(categoryCodes.minimumEntries) || categoryCodes.minimumEntries < 3) {
|
|
255
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.minimumEntries must be an integer >= 3.');
|
|
256
|
+
}
|
|
257
|
+
if (!hasNonEmptyString(categoryCodes.specificityRule)) {
|
|
258
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.specificityRule must be a non-empty string.');
|
|
259
|
+
}
|
|
260
|
+
if (!hasNonEmptyString(categoryCodes.antiLeakageRule)) {
|
|
261
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.antiLeakageRule must be a non-empty string that prevents the example cliches from being read as target aesthetics.');
|
|
262
|
+
}
|
|
263
|
+
if (!hasNonEmptyString(categoryCodes.selfTestRule)) {
|
|
264
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.selfTestRule must be a non-empty string.');
|
|
265
|
+
}
|
|
266
|
+
if (!Array.isArray(categoryCodes.failingExamples) || categoryCodes.failingExamples.length < 3) {
|
|
267
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.failingExamples must list at least 3 failing examples to anchor the specificity floor.');
|
|
268
|
+
}
|
|
269
|
+
if (!Array.isArray(categoryCodes.passingExamples) || categoryCodes.passingExamples.length < 3) {
|
|
270
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.passingExamples must list at least 3 passing examples to anchor the specificity floor.');
|
|
271
|
+
}
|
|
272
|
+
if (!hasNonEmptyString(categoryCodes.passingExamplesPolicy)) {
|
|
273
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.passingExamplesPolicy must be a non-empty string clarifying that the passing examples are description-format illustrations, not target aesthetics.');
|
|
274
|
+
}
|
|
275
|
+
if (!Array.isArray(categoryCodes.commonAiSafeClichesToReject) || categoryCodes.commonAiSafeClichesToReject.length < 3) {
|
|
276
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.commonAiSafeClichesToReject must list at least 3 AI-safe defaults the agent has to name and reject when the project sits near them.');
|
|
277
|
+
}
|
|
278
|
+
if (!hasNonEmptyString(categoryCodes.commonAiSafeClichesPolicy)) {
|
|
279
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.commonAiSafeClichesPolicy must be a non-empty string requiring the matching cliche to appear with a rejection note when the project pattern-matches it.');
|
|
280
|
+
}
|
|
281
|
+
if (
|
|
282
|
+
!Array.isArray(categoryCodes.requiredFieldsPerEntry)
|
|
283
|
+
|| !categoryCodes.requiredFieldsPerEntry.includes('description')
|
|
284
|
+
|| !categoryCodes.requiredFieldsPerEntry.includes('specificityEvidence')
|
|
285
|
+
|| !categoryCodes.requiredFieldsPerEntry.includes('categoryDefaultReason')
|
|
286
|
+
|| !categoryCodes.requiredFieldsPerEntry.includes('rejectionNote')
|
|
287
|
+
) {
|
|
288
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.requiredFieldsPerEntry must require description, specificityEvidence, categoryDefaultReason, and rejectionNote.');
|
|
289
|
+
}
|
|
290
|
+
if (!Array.isArray(categoryCodes.forbiddenPlaceholderPhrases) || categoryCodes.forbiddenPlaceholderPhrases.length === 0) {
|
|
291
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.forbiddenPlaceholderPhrases must list the abstract phrases that fail the specificity self-test.');
|
|
292
|
+
}
|
|
293
|
+
if (!Array.isArray(categoryCodes.candidateEntries)) {
|
|
294
|
+
validationErrors.push('designIntent.conceptualAnchor.categoryCodes.candidateEntries must be an array (empty in the seed; populated by the agent).');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function validateMorphologicalExploration(conceptualAnchor, validationErrors, options = {}) {
|
|
299
|
+
const { isSeedStatus = false } = options;
|
|
300
|
+
const morphologicalExploration = conceptualAnchor.morphologicalExploration;
|
|
301
|
+
if (!morphologicalExploration || typeof morphologicalExploration !== 'object') {
|
|
302
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration must exist (Section 4 of research-design.md).');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (morphologicalExploration.mode !== 'agent-must-complete-before-ui-implementation') {
|
|
306
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.mode must equal "agent-must-complete-before-ui-implementation".');
|
|
307
|
+
}
|
|
308
|
+
if (morphologicalExploration.blockingByDefault !== true) {
|
|
309
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.blockingByDefault must equal true.');
|
|
310
|
+
}
|
|
311
|
+
if (morphologicalExploration.researchBriefSection !== 'Section 4') {
|
|
312
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.researchBriefSection must equal "Section 4".');
|
|
313
|
+
}
|
|
314
|
+
if (morphologicalExploration.requiredMatrixShape !== '5x5-or-6x5') {
|
|
315
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.requiredMatrixShape must equal "5x5-or-6x5".');
|
|
316
|
+
}
|
|
317
|
+
if (morphologicalExploration.minimumDimensions !== 5) {
|
|
318
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.minimumDimensions must equal 5.');
|
|
319
|
+
}
|
|
320
|
+
if (morphologicalExploration.maximumDimensions !== 6) {
|
|
321
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.maximumDimensions must equal 6.');
|
|
322
|
+
}
|
|
323
|
+
if (morphologicalExploration.valuesPerDimension !== 5) {
|
|
324
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.valuesPerDimension must equal 5.');
|
|
325
|
+
}
|
|
326
|
+
if (morphologicalExploration.forbidCategoryCodeValuesInMatrix !== true) {
|
|
327
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.forbidCategoryCodeValuesInMatrix must equal true so Section 3 cliches do not re-enter the matrix as values.');
|
|
328
|
+
}
|
|
329
|
+
if (morphologicalExploration.requireSelectedCombination !== true) {
|
|
330
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.requireSelectedCombination must equal true.');
|
|
331
|
+
}
|
|
332
|
+
if (morphologicalExploration.requireUncomfortableCombination !== true) {
|
|
333
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.requireUncomfortableCombination must equal true.');
|
|
334
|
+
}
|
|
335
|
+
if (!hasNonEmptyString(morphologicalExploration.uncomfortableCombinationRule)) {
|
|
336
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.uncomfortableCombinationRule must be a non-empty string explaining why the uncomfortable combination is required.');
|
|
337
|
+
}
|
|
338
|
+
if (
|
|
339
|
+
!Array.isArray(morphologicalExploration.uncomfortableCombinationRequiredFields)
|
|
340
|
+
|| !morphologicalExploration.uncomfortableCombinationRequiredFields.includes('combinationLabel')
|
|
341
|
+
|| !morphologicalExploration.uncomfortableCombinationRequiredFields.includes('discomfortReason')
|
|
342
|
+
|| !morphologicalExploration.uncomfortableCombinationRequiredFields.includes('productLogicJustification')
|
|
343
|
+
) {
|
|
344
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.uncomfortableCombinationRequiredFields must include combinationLabel, discomfortReason, and productLogicJustification.');
|
|
345
|
+
}
|
|
346
|
+
if (morphologicalExploration.widenDimensionsWhenUncomfortableCombinationCannotBeProduced !== true) {
|
|
347
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.widenDimensionsWhenUncomfortableCombinationCannotBeProduced must equal true.');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (morphologicalExploration.uncomfortableCombination !== null && typeof morphologicalExploration.uncomfortableCombination === 'object') {
|
|
351
|
+
const uncomfortableCombination = morphologicalExploration.uncomfortableCombination;
|
|
352
|
+
if (!hasNonEmptyString(uncomfortableCombination.combinationLabel)) {
|
|
353
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.uncomfortableCombination.combinationLabel must be a non-empty string.');
|
|
354
|
+
}
|
|
355
|
+
if (!hasNonEmptyString(uncomfortableCombination.discomfortReason)) {
|
|
356
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.uncomfortableCombination.discomfortReason must be a non-empty string.');
|
|
357
|
+
}
|
|
358
|
+
if (!hasNonEmptyString(uncomfortableCombination.productLogicJustification)) {
|
|
359
|
+
validationErrors.push('designIntent.conceptualAnchor.morphologicalExploration.uncomfortableCombination.productLogicJustification must be a non-empty string.');
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function validateAnchorCandidates(conceptualAnchor, validationErrors, options = {}) {
|
|
365
|
+
const { isSeedStatus = false } = options;
|
|
366
|
+
const anchorCandidates = conceptualAnchor.anchorCandidates;
|
|
367
|
+
if (!anchorCandidates || typeof anchorCandidates !== 'object') {
|
|
368
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates must exist (Section 5 of research-design.md).');
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (anchorCandidates.mode !== 'agent-must-complete-before-ui-implementation') {
|
|
372
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.mode must equal "agent-must-complete-before-ui-implementation".');
|
|
373
|
+
}
|
|
374
|
+
if (anchorCandidates.blockingByDefault !== true) {
|
|
375
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.blockingByDefault must equal true.');
|
|
376
|
+
}
|
|
377
|
+
if (anchorCandidates.researchBriefSection !== 'Section 5') {
|
|
378
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.researchBriefSection must equal "Section 5".');
|
|
379
|
+
}
|
|
380
|
+
if (anchorCandidates.requiredCandidateCount !== 5) {
|
|
381
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.requiredCandidateCount must equal 5.');
|
|
382
|
+
}
|
|
383
|
+
if (
|
|
384
|
+
!Array.isArray(anchorCandidates.requiredFieldsPerCandidate)
|
|
385
|
+
|| !anchorCandidates.requiredFieldsPerCandidate.includes('anchorReference')
|
|
386
|
+
|| !anchorCandidates.requiredFieldsPerCandidate.includes('jobFit')
|
|
387
|
+
|| !anchorCandidates.requiredFieldsPerCandidate.includes('hierarchyImplication')
|
|
388
|
+
|| !anchorCandidates.requiredFieldsPerCandidate.includes('densityImplication')
|
|
389
|
+
|| !anchorCandidates.requiredFieldsPerCandidate.includes('typeImplication')
|
|
390
|
+
|| !anchorCandidates.requiredFieldsPerCandidate.includes('stateLanguage')
|
|
391
|
+
|| !anchorCandidates.requiredFieldsPerCandidate.includes('motionImplication')
|
|
392
|
+
|| !anchorCandidates.requiredFieldsPerCandidate.includes('whatItRulesOut')
|
|
393
|
+
|| !anchorCandidates.requiredFieldsPerCandidate.includes('renameTest')
|
|
394
|
+
|| !anchorCandidates.requiredFieldsPerCandidate.includes('categoryCodeOverlap')
|
|
395
|
+
) {
|
|
396
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.requiredFieldsPerCandidate must list the full anchor dossier fields including renameTest and categoryCodeOverlap.');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const renameTest = anchorCandidates.renameTest;
|
|
400
|
+
if (!renameTest || typeof renameTest !== 'object') {
|
|
401
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.renameTest must exist.');
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (renameTest.mode !== 'strengthened') {
|
|
405
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.renameTest.mode must equal "strengthened".');
|
|
406
|
+
}
|
|
407
|
+
if (renameTest.requiredTestCategoryCount !== 3) {
|
|
408
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.renameTest.requiredTestCategoryCount must equal 3.');
|
|
409
|
+
}
|
|
410
|
+
if (!hasNonEmptyString(renameTest.testCategoryDistanceRule)) {
|
|
411
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.renameTest.testCategoryDistanceRule must be a non-empty string requiring remote categories.');
|
|
412
|
+
}
|
|
413
|
+
if (!hasNonEmptyString(renameTest.testCategoryFreshnessRule)) {
|
|
414
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.renameTest.testCategoryFreshnessRule must be a non-empty string preventing reuse of the same test triple across every anchor.');
|
|
415
|
+
}
|
|
416
|
+
if (!renameTest.verdictScoring || typeof renameTest.verdictScoring !== 'object') {
|
|
417
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.renameTest.verdictScoring must define STRONG PASS, PASS, REVISE, and DISCARD verdicts.');
|
|
418
|
+
} else {
|
|
419
|
+
for (const verdict of ['STRONG PASS', 'PASS', 'REVISE', 'DISCARD']) {
|
|
420
|
+
if (!hasNonEmptyString(renameTest.verdictScoring[verdict])) {
|
|
421
|
+
validationErrors.push(`designIntent.conceptualAnchor.anchorCandidates.renameTest.verdictScoring["${verdict}"] must be a non-empty string.`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (renameTest.requireTestCategoriesRecordedInDossier !== true) {
|
|
426
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.renameTest.requireTestCategoriesRecordedInDossier must equal true.');
|
|
427
|
+
}
|
|
428
|
+
if (renameTest.auditableByHumanReviewer !== true) {
|
|
429
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.renameTest.auditableByHumanReviewer must equal true.');
|
|
430
|
+
}
|
|
431
|
+
if (
|
|
432
|
+
!Array.isArray(renameTest.renameTestRequiredFields)
|
|
433
|
+
|| !renameTest.renameTestRequiredFields.includes('testCategories')
|
|
434
|
+
|| !renameTest.renameTestRequiredFields.includes('results')
|
|
435
|
+
|| !renameTest.renameTestRequiredFields.includes('verdict')
|
|
436
|
+
) {
|
|
437
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.renameTest.renameTestRequiredFields must include testCategories, results, and verdict.');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!hasNonEmptyString(anchorCandidates.categoryCodeOverlapRule)) {
|
|
441
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.categoryCodeOverlapRule must be a non-empty string.');
|
|
442
|
+
}
|
|
443
|
+
if (!Array.isArray(anchorCandidates.forbiddenSelectedVerdicts) || !anchorCandidates.forbiddenSelectedVerdicts.includes('DISCARD')) {
|
|
444
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.forbiddenSelectedVerdicts must include "DISCARD".');
|
|
445
|
+
}
|
|
446
|
+
if (
|
|
447
|
+
!Array.isArray(anchorCandidates.requiredSelectedVerdicts)
|
|
448
|
+
|| !anchorCandidates.requiredSelectedVerdicts.includes('STRONG PASS')
|
|
449
|
+
|| !anchorCandidates.requiredSelectedVerdicts.includes('PASS')
|
|
450
|
+
) {
|
|
451
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.requiredSelectedVerdicts must include "STRONG PASS" and "PASS".');
|
|
452
|
+
}
|
|
453
|
+
if (!Array.isArray(anchorCandidates.candidates)) {
|
|
454
|
+
validationErrors.push('designIntent.conceptualAnchor.anchorCandidates.candidates must be an array (empty in the seed; populated by the agent).');
|
|
455
|
+
}
|
|
456
|
+
}
|
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
|
|
8
8
|
import { hasNonEmptyString } from './helpers.mjs';
|
|
9
9
|
|
|
10
|
+
const SEED_STATUSES = new Set([
|
|
11
|
+
'seed-needs-design-synthesis',
|
|
12
|
+
'seed-generated-during-init',
|
|
13
|
+
'seed-generated-during-upgrade',
|
|
14
|
+
]);
|
|
15
|
+
const REQUIRED_TOKEN_CATEGORIES = ['typography', 'palette', 'motion', 'spacing'];
|
|
16
|
+
const ACTIVE_CLASSIFICATION_VALUES = new Set(['anchor-derived', 'continuity-retained', 'newly-introduced']);
|
|
17
|
+
const SEED_CLASSIFICATION_VALUES = new Set(['anchor-derived', 'continuity-retained', 'newly-introduced', 'pending-research']);
|
|
18
|
+
|
|
10
19
|
export function validateDesignContractCompleteness(designIntentContract) {
|
|
11
20
|
const validationIssues = [];
|
|
12
21
|
const conceptualAnchor = designIntentContract?.conceptualAnchor;
|
|
@@ -47,6 +56,35 @@ export function validateDesignContractCompleteness(designIntentContract) {
|
|
|
47
56
|
) {
|
|
48
57
|
validationIssues.push('designIntent.derivedTokenLogic.validationRule must require traceability to anchorReference.');
|
|
49
58
|
}
|
|
59
|
+
|
|
60
|
+
const tokenContinuityClassification = derivedTokenLogic.tokenContinuityClassification;
|
|
61
|
+
const isSeedStatus = SEED_STATUSES.has(designIntentContract?.status);
|
|
62
|
+
const allowedClassificationValues = isSeedStatus ? SEED_CLASSIFICATION_VALUES : ACTIVE_CLASSIFICATION_VALUES;
|
|
63
|
+
if (!tokenContinuityClassification || typeof tokenContinuityClassification !== 'object') {
|
|
64
|
+
validationIssues.push('designIntent.derivedTokenLogic.tokenContinuityClassification must exist. Each token category (typography, palette, motion, spacing) must declare anchor-derived, continuity-retained, or newly-introduced (pending-research is allowed only on seed contracts).');
|
|
65
|
+
} else {
|
|
66
|
+
if (
|
|
67
|
+
!Array.isArray(tokenContinuityClassification.validValues)
|
|
68
|
+
|| !tokenContinuityClassification.validValues.includes('anchor-derived')
|
|
69
|
+
|| !tokenContinuityClassification.validValues.includes('continuity-retained')
|
|
70
|
+
|| !tokenContinuityClassification.validValues.includes('newly-introduced')
|
|
71
|
+
|| !tokenContinuityClassification.validValues.includes('pending-research')
|
|
72
|
+
) {
|
|
73
|
+
validationIssues.push('designIntent.derivedTokenLogic.tokenContinuityClassification.validValues must include anchor-derived, continuity-retained, newly-introduced, and pending-research.');
|
|
74
|
+
}
|
|
75
|
+
if (!hasNonEmptyString(tokenContinuityClassification.rule)) {
|
|
76
|
+
validationIssues.push('designIntent.derivedTokenLogic.tokenContinuityClassification.rule must explain when each value applies.');
|
|
77
|
+
}
|
|
78
|
+
for (const tokenCategoryName of REQUIRED_TOKEN_CATEGORIES) {
|
|
79
|
+
const classifiedValue = tokenContinuityClassification[tokenCategoryName];
|
|
80
|
+
if (!allowedClassificationValues.has(classifiedValue)) {
|
|
81
|
+
const allowedLabel = isSeedStatus
|
|
82
|
+
? 'anchor-derived, continuity-retained, newly-introduced, or pending-research'
|
|
83
|
+
: 'anchor-derived, continuity-retained, or newly-introduced';
|
|
84
|
+
validationIssues.push(`designIntent.derivedTokenLogic.tokenContinuityClassification.${tokenCategoryName} must be one of: ${allowedLabel}. Got: "${classifiedValue}".`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
50
88
|
}
|
|
51
89
|
|
|
52
90
|
if (!['verified', 'pending-verification', 'no-external-library-needed'].includes(designIntentContract?.libraryResearchStatus)) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research dossier validators. Enforces the `researchDossier.metadata` block
|
|
3
|
+
* shape that powers the freshness gate, status-aware skip, and anti-repeat
|
|
4
|
+
* ledger consumed by `.agent-context/prompts/research-design.md`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { hasNonEmptyString } from './helpers.mjs';
|
|
8
|
+
|
|
9
|
+
const REQUIRED_FRESHNESS_WINDOW_DAYS = 90;
|
|
10
|
+
const REQUIRED_SEED_STATUSES = [
|
|
11
|
+
'seed-needs-design-synthesis',
|
|
12
|
+
'seed-generated-during-init',
|
|
13
|
+
'seed-generated-during-upgrade',
|
|
14
|
+
];
|
|
15
|
+
const ANTI_REPEAT_LEDGER_CATEGORIES = ['previousAnchors', 'previousPalettes', 'previousMotionSignatures', 'previousTypographyChoices'];
|
|
16
|
+
|
|
17
|
+
function validateMetadataBaseShape(metadata, validationErrors) {
|
|
18
|
+
if (
|
|
19
|
+
metadata.researchVerifiedAt !== null
|
|
20
|
+
&& (typeof metadata.researchVerifiedAt !== 'string' || metadata.researchVerifiedAt.trim().length === 0)
|
|
21
|
+
) {
|
|
22
|
+
validationErrors.push('designIntent.researchDossier.metadata.researchVerifiedAt must be null or an ISO date string.');
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isInteger(metadata.freshnessWindowDays) || metadata.freshnessWindowDays < 1) {
|
|
25
|
+
validationErrors.push(`designIntent.researchDossier.metadata.freshnessWindowDays must be a positive integer (recommended ${REQUIRED_FRESHNESS_WINDOW_DAYS}).`);
|
|
26
|
+
}
|
|
27
|
+
if (!hasNonEmptyString(metadata.freshnessRule)) {
|
|
28
|
+
validationErrors.push('designIntent.researchDossier.metadata.freshnessRule must be a non-empty string explaining staleness.');
|
|
29
|
+
}
|
|
30
|
+
if (metadata.userExplicitRedesignBypassesFreshness !== true) {
|
|
31
|
+
validationErrors.push('designIntent.researchDossier.metadata.userExplicitRedesignBypassesFreshness must equal true so explicit redesign requests force fresh research.');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function validateStatusAwareValidation(metadata, validationErrors) {
|
|
36
|
+
const statusAwareValidation = metadata.statusAwareValidation;
|
|
37
|
+
if (!statusAwareValidation || typeof statusAwareValidation !== 'object') {
|
|
38
|
+
validationErrors.push('designIntent.researchDossier.metadata.statusAwareValidation must exist.');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const seedStatuses = statusAwareValidation.seedStatuses;
|
|
42
|
+
const hasAllRequiredSeedStatuses = Array.isArray(seedStatuses)
|
|
43
|
+
&& REQUIRED_SEED_STATUSES.every((requiredSeedStatusName) => seedStatuses.includes(requiredSeedStatusName));
|
|
44
|
+
if (!hasAllRequiredSeedStatuses) {
|
|
45
|
+
validationErrors.push('designIntent.researchDossier.metadata.statusAwareValidation.seedStatuses must include seed-needs-design-synthesis, seed-generated-during-init, and seed-generated-during-upgrade.');
|
|
46
|
+
}
|
|
47
|
+
if (statusAwareValidation.seedSkipsDossierShape !== true) {
|
|
48
|
+
validationErrors.push('designIntent.researchDossier.metadata.statusAwareValidation.seedSkipsDossierShape must equal true.');
|
|
49
|
+
}
|
|
50
|
+
if (statusAwareValidation.activeRequiresFreshOrExplicitRedesign !== true) {
|
|
51
|
+
validationErrors.push('designIntent.researchDossier.metadata.statusAwareValidation.activeRequiresFreshOrExplicitRedesign must equal true.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function validateAntiRepeatLedger(metadata, validationErrors) {
|
|
56
|
+
const antiRepeatLedger = metadata.antiRepeatLedger;
|
|
57
|
+
if (!antiRepeatLedger || typeof antiRepeatLedger !== 'object') {
|
|
58
|
+
validationErrors.push('designIntent.researchDossier.metadata.antiRepeatLedger must exist.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (antiRepeatLedger.blocklistFromHistory !== true) {
|
|
62
|
+
validationErrors.push('designIntent.researchDossier.metadata.antiRepeatLedger.blocklistFromHistory must equal true so previously shipped direction is blocked from repetition.');
|
|
63
|
+
}
|
|
64
|
+
if (antiRepeatLedger.ledgerScope !== 'signature-level-descriptors-only') {
|
|
65
|
+
validationErrors.push('designIntent.researchDossier.metadata.antiRepeatLedger.ledgerScope must equal "signature-level-descriptors-only" so the ledger does not become a raw token dump.');
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
!Number.isInteger(antiRepeatLedger.ledgerMaxEntriesPerCategory)
|
|
69
|
+
|| antiRepeatLedger.ledgerMaxEntriesPerCategory < 1
|
|
70
|
+
|| antiRepeatLedger.ledgerMaxEntriesPerCategory > 5
|
|
71
|
+
) {
|
|
72
|
+
validationErrors.push('designIntent.researchDossier.metadata.antiRepeatLedger.ledgerMaxEntriesPerCategory must be an integer between 1 and 5 to keep the ledger signature-level.');
|
|
73
|
+
}
|
|
74
|
+
for (const ledgerCategoryName of ANTI_REPEAT_LEDGER_CATEGORIES) {
|
|
75
|
+
const ledgerEntries = antiRepeatLedger[ledgerCategoryName];
|
|
76
|
+
if (!Array.isArray(ledgerEntries)) {
|
|
77
|
+
validationErrors.push(`designIntent.researchDossier.metadata.antiRepeatLedger.${ledgerCategoryName} must be an array (may be empty in fresh seeds).`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (
|
|
81
|
+
Number.isInteger(antiRepeatLedger.ledgerMaxEntriesPerCategory)
|
|
82
|
+
&& ledgerEntries.length > antiRepeatLedger.ledgerMaxEntriesPerCategory
|
|
83
|
+
) {
|
|
84
|
+
validationErrors.push(`designIntent.researchDossier.metadata.antiRepeatLedger.${ledgerCategoryName} exceeds ledgerMaxEntriesPerCategory; trim to signature-level descriptors.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function validateResearchDossier(designIntentContract, validationErrors) {
|
|
90
|
+
const researchDossier = designIntentContract?.researchDossier;
|
|
91
|
+
if (!researchDossier || typeof researchDossier !== 'object') {
|
|
92
|
+
validationErrors.push('designIntent.researchDossier must exist (Section 3-5 metadata block from research-design.md).');
|
|
93
|
+
return validationErrors;
|
|
94
|
+
}
|
|
95
|
+
const metadata = researchDossier.metadata;
|
|
96
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
97
|
+
validationErrors.push('designIntent.researchDossier.metadata must exist with researchVerifiedAt, freshnessWindowDays, and antiRepeatLedger.');
|
|
98
|
+
return validationErrors;
|
|
99
|
+
}
|
|
100
|
+
validateMetadataBaseShape(metadata, validationErrors);
|
|
101
|
+
validateStatusAwareValidation(metadata, validationErrors);
|
|
102
|
+
validateAntiRepeatLedger(metadata, validationErrors);
|
|
103
|
+
return validationErrors;
|
|
104
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
validateConceptualAnchor,
|
|
19
19
|
validateMathSystems,
|
|
20
20
|
} from './validation/anchor-validators.mjs';
|
|
21
|
+
import { validateResearchDossier } from './validation/research-dossier-validators.mjs';
|
|
21
22
|
import {
|
|
22
23
|
validateAiSafeUiAudit,
|
|
23
24
|
validateProductionContentPolicy,
|
|
@@ -54,6 +55,7 @@ export function validateDesignIntentContract(designIntentContract) {
|
|
|
54
55
|
validateDesignFlexibilityPolicy(designIntentContract, validationErrors);
|
|
55
56
|
validateConceptualAnchor(designIntentContract, validationErrors);
|
|
56
57
|
validateMathSystems(designIntentContract, validationErrors);
|
|
58
|
+
validateResearchDossier(designIntentContract, validationErrors);
|
|
57
59
|
validateAiSafeUiAudit(designIntentContract, validationErrors);
|
|
58
60
|
validateProductionContentPolicy(designIntentContract, validationErrors);
|
|
59
61
|
validateTokenSystem(designIntentContract, validationErrors);
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { buildStructureFirstSeedSignals, shouldBootstrapDesignDocument } from './design-contract/seed-signals.mjs';
|
|
23
23
|
import { FORBIDDEN_PATTERN_SIGNALS } from './design-contract/signal-vocab.mjs';
|
|
24
24
|
import { buildConceptualAnchorSection } from './design-contract/sections/conceptual-anchor.mjs';
|
|
25
|
+
import { buildResearchDossierMetadata } from './design-contract/research-dossier-migration.mjs';
|
|
25
26
|
import {
|
|
26
27
|
buildAccessibilityPolicySection,
|
|
27
28
|
buildAiSafeUiAuditSection,
|
|
@@ -124,6 +125,9 @@ function buildDesignIntentContractObject({
|
|
|
124
125
|
externalInspirationPolicy: 'External websites and examples are candidate evidence for constraints, mechanics, and quality bars; do not copy their layout rhythm, palette, component skin, brand posture, or visual metaphor.',
|
|
125
126
|
},
|
|
126
127
|
conceptualAnchor: buildConceptualAnchorSection(),
|
|
128
|
+
researchDossier: {
|
|
129
|
+
metadata: buildResearchDossierMetadata(),
|
|
130
|
+
},
|
|
127
131
|
derivedTokenLogic: {
|
|
128
132
|
anchorReference: 'agent-defined-anchor-reference',
|
|
129
133
|
colorDerivationSource: 'Explain semantic color roles from anchorReference; reject generic palettes without anchor evidence.',
|
|
@@ -135,6 +139,14 @@ function buildDesignIntentContractObject({
|
|
|
135
139
|
typeScaleMethod: 'Prefer fluid clamp() type scales when supported; name ratio, role contrast, balance/wrap behavior, and numeric typography needs.',
|
|
136
140
|
motionBudget: 'Name micro, layout, entrance, easing, stagger, and reduced-motion budgets; prefer transform/opacity for high-frequency motion.',
|
|
137
141
|
validationRule: 'Every semantic token role must trace to anchorReference; keep exact primitive values flexible unless locked by repo evidence, accessibility validation, implementation constraints, or explicit user approval.',
|
|
142
|
+
tokenContinuityClassification: {
|
|
143
|
+
typography: 'pending-research',
|
|
144
|
+
palette: 'pending-research',
|
|
145
|
+
motion: 'pending-research',
|
|
146
|
+
spacing: 'pending-research',
|
|
147
|
+
validValues: ['anchor-derived', 'continuity-retained', 'newly-introduced', 'pending-research'],
|
|
148
|
+
rule: 'For each token category, classify whether the choice is anchor-derived (causally tied to anchorReference real-world reality), continuity-retained (kept from a previous design iteration without re-derivation), or newly-introduced (fresh choice not tied to anchor). Continuity-retained is acceptable; pretending continuity is derivation is not.',
|
|
149
|
+
},
|
|
138
150
|
},
|
|
139
151
|
motionPaletteDecision: buildMotionPaletteDecisionSection(),
|
|
140
152
|
aiSafeUiAudit: buildAiSafeUiAuditSection({ projectName }),
|
|
@@ -274,6 +286,7 @@ function buildDesignIntentContractObject({
|
|
|
274
286
|
bootstrapPrompt: '.agent-context/prompts/bootstrap-design.md',
|
|
275
287
|
autoLoadedRuleFiles: [
|
|
276
288
|
'.agent-context/prompts/bootstrap-design.md',
|
|
289
|
+
'.agent-context/prompts/research-design.md',
|
|
277
290
|
'.agent-context/rules/frontend-architecture.md',
|
|
278
291
|
],
|
|
279
292
|
disallowedAutoLoadedRuleFiles: [
|
|
@@ -190,6 +190,7 @@ export function buildDesignBootstrapPrompt({
|
|
|
190
190
|
'29. repoEvidence when onboarding or detector evidence exists',
|
|
191
191
|
'',
|
|
192
192
|
'## Mechanical Gates',
|
|
193
|
+
'0. Read `.agent-context/prompts/research-design.md` first. Sections 3 (Category Code Identification), 4 (Morphological Exploration), and 5 (Anchor Candidates) are gates: each must produce an auditable artifact before UI implementation.',
|
|
193
194
|
'1. Do not copy external style guides.',
|
|
194
195
|
'2. Do not anchor the final design language to famous products, benchmark visuals, or external reference surfaces.',
|
|
195
196
|
'3. Do not choose final style, library, palette, typography, motion, or layout from this offline scaffold.',
|
|
@@ -238,6 +239,13 @@ export function buildDesignBootstrapPrompt({
|
|
|
238
239
|
'40. Translate conceptual anchors non-literally first. Do not turn anchor artifacts into required chrome, decorative props, wallpaper, or theme objects unless they serve a named product function.',
|
|
239
240
|
'41. Use external websites and benchmark examples as candidate evidence for constraints, mechanics, and quality bars only; do not copy layout rhythm, palette, component skin, visual metaphor, or brand posture.',
|
|
240
241
|
'',
|
|
242
|
+
'## Research-Design Brief Gates (research-design.md)',
|
|
243
|
+
'42. Section 3 — Category Code Identification: list at least three category codes per product surface in `conceptualAnchor.categoryCodes.candidateEntries`. Each entry must record `description`, `specificityEvidence`, `categoryDefaultReason`, and `rejectionNote`. The specificity floor is: a reader unfamiliar with the project must be able to visualize a specific aesthetic direction from the text alone, without seeing the UI and without knowing the product name. Reject placeholder phrases like "clean typography", "modern color palette", or "smooth animations". Anti-leakage rule: listing a cliche identifies a trap, not a target; the example cliches in the brief are not aesthetic candidates for any project. If the project pattern-matches a common AI-safe default (dev-tool dark slate, AI-startup purple-pink gradient, health-app mint, SaaS admin three-card KPI, marketing-site three-tile hero), name that cliche in `candidateEntries` with an explicit rejection note.',
|
|
244
|
+
'43. Section 4 — Morphological Exploration: build a 5x5 or 6x5 matrix in `conceptualAnchor.morphologicalExploration`. Choose 5 or 6 dimensions and 5 values per dimension. Do not seed the matrix with the Section 3 category codes as values. Highlight the selected combination, and ALSO highlight at least one uncomfortable combination that feels instinctively wrong but can be argued with product logic. The uncomfortable combination must record `combinationLabel`, `discomfortReason`, and `productLogicJustification`. If you cannot produce an uncomfortable-but-arguable combination, the dimensions are too narrow; widen at least one and regenerate.',
|
|
245
|
+
'44. Section 5 — Anchor Candidates: produce exactly five entries in `conceptualAnchor.anchorCandidates.candidates`. Each candidate must record `anchorReference`, `conceptualFamily`, `jobFit`, `hierarchyImplication`, `densityImplication`, `typeImplication`, `stateLanguage`, `motionImplication`, `whatItRulesOut`, `renameTest`, and `categoryCodeOverlap`.',
|
|
246
|
+
'45. Strengthened rename test: rename the product to three genuinely different categories that are remote from each other and from the actual product. Pick fresh test categories per anchor; do not reuse the same triple across every anchor. Record the three test categories explicitly in the dossier so a human reviewer can audit them. Score the result: 0/3 coherent equals STRONG PASS, 1/3 equals PASS, 2/3 equals REVISE, 3/3 equals DISCARD. Never select an anchor with verdict DISCARD; revise REVISE candidates until they fail in at least 2 of 3 categories.',
|
|
247
|
+
'46. Make `conceptualAnchor.anchorReference` and `derivedTokenLogic.anchorReference` match the selected anchor exactly. The selected anchor must have verdict STRONG PASS or PASS.',
|
|
248
|
+
'',
|
|
241
249
|
'## Creative Ambition Floor',
|
|
242
250
|
'Before implementation, the design contract must name one authored visual bet, one product-derived palette move, one signature motion/spatial/interaction behavior, and one morphology or composition choice that would not appear in a generic AI template.',
|
|
243
251
|
'The ambition floor is not a fixed aesthetic. Quiet, dense, utilitarian, or text-heavy interfaces are allowed when the product requires them, but they still need a project-specific visual decision and a real reason for omitting richer motion, 3D, canvas, WebGL, scroll choreography, or animation libraries.',
|
|
@@ -290,6 +298,7 @@ export function buildDesignBootstrapPrompt({
|
|
|
290
298
|
'13. Preserve externalResearchIntake so user-provided research becomes reviewed evidence without turning into an offline style or dependency preset.',
|
|
291
299
|
'14. Preserve conceptualAnchor so prompt-only UI work has one cohesive non-template concept instead of a mixed collection of bold but unrelated visual decisions.',
|
|
292
300
|
'15. Record conceptualAnchor.agentResearchMode, specificReferencePoint, signatureMotion, typographicDecision, visualRiskBudget, motionRiskBudget, and cohesionChecks so the final UI cannot quietly fall back to a timid dashboard/admin mental model.',
|
|
301
|
+
'15a. Record conceptualAnchor.categoryCodes.candidateEntries (Section 3 of research-design.md), conceptualAnchor.morphologicalExploration with selectedCombination and uncomfortableCombination (Section 4), and conceptualAnchor.anchorCandidates.candidates with full renameTest results (Section 5) before UI implementation.',
|
|
293
302
|
'16. Preserve derivedTokenLogic, libraryResearchStatus, and libraryDecisions so token choices and dependency uncertainty stay visible before implementation.',
|
|
294
303
|
'16a. Preserve designFlexibilityPolicy so the machine contract guides consistency without freezing literal anchor artifacts, exact token primitives, or component-kit visual language.',
|
|
295
304
|
'17. Preserve productionContentPolicy so UI output is ship-ready and not a testing-looking scaffold.',
|