@ryuenn3123/agentic-senior-core 3.0.50 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.agent-context/prompts/bootstrap-design.md +3 -1
  2. package/.agent-context/prompts/research-design.md +165 -0
  3. package/.agent-context/review-checklists/pr-checklist.md +1 -0
  4. package/.agent-context/rules/api-docs.md +63 -47
  5. package/.agent-context/rules/architecture.md +133 -120
  6. package/.agent-context/rules/database-design.md +36 -18
  7. package/.agent-context/rules/docker-runtime.md +66 -43
  8. package/.agent-context/rules/efficiency-vs-hype.md +38 -17
  9. package/.agent-context/rules/error-handling.md +35 -16
  10. package/.agent-context/rules/event-driven.md +35 -18
  11. package/.agent-context/rules/frontend-architecture.md +103 -76
  12. package/.agent-context/rules/git-workflow.md +81 -197
  13. package/.agent-context/rules/microservices.md +42 -41
  14. package/.agent-context/rules/naming-conv.md +27 -8
  15. package/.agent-context/rules/performance.md +32 -12
  16. package/.agent-context/rules/realtime.md +26 -9
  17. package/.agent-context/rules/security.md +39 -20
  18. package/.agent-context/rules/testing.md +36 -16
  19. package/AGENTS.md +21 -20
  20. package/README.md +10 -1
  21. package/lib/cli/commands/init.mjs +12 -0
  22. package/lib/cli/commands/upgrade.mjs +11 -0
  23. package/lib/cli/compiler.mjs +1 -0
  24. package/lib/cli/detector/constants.mjs +135 -0
  25. package/lib/cli/detector/design-evidence/collector.mjs +256 -0
  26. package/lib/cli/detector/design-evidence/constants.mjs +39 -0
  27. package/lib/cli/detector/design-evidence/file-traversal.mjs +83 -0
  28. package/lib/cli/detector/design-evidence/structured-attribute-evidence.mjs +117 -0
  29. package/lib/cli/detector/design-evidence/summary.mjs +109 -0
  30. package/lib/cli/detector/design-evidence/utility-helpers.mjs +122 -0
  31. package/lib/cli/detector/design-evidence.mjs +25 -610
  32. package/lib/cli/detector/stack-detection.mjs +243 -0
  33. package/lib/cli/detector/ui-signals.mjs +150 -0
  34. package/lib/cli/detector/workspace-scan.mjs +177 -0
  35. package/lib/cli/detector.mjs +20 -688
  36. package/lib/cli/memory-continuity.mjs +1 -0
  37. package/lib/cli/project-scaffolder/design-contract/research-dossier-migration.mjs +165 -0
  38. package/lib/cli/project-scaffolder/design-contract/sections/audits.mjs +96 -0
  39. package/lib/cli/project-scaffolder/design-contract/sections/conceptual-anchor.mjs +233 -0
  40. package/lib/cli/project-scaffolder/design-contract/sections/execution-handoff.mjs +211 -0
  41. package/lib/cli/project-scaffolder/design-contract/seed-signals.mjs +79 -0
  42. package/lib/cli/project-scaffolder/design-contract/signal-vocab.mjs +64 -0
  43. package/lib/cli/project-scaffolder/design-contract/validation/anchor-validators.mjs +456 -0
  44. package/lib/cli/project-scaffolder/design-contract/validation/audit-validators.mjs +117 -0
  45. package/lib/cli/project-scaffolder/design-contract/validation/completeness.mjs +83 -0
  46. package/lib/cli/project-scaffolder/design-contract/validation/execution-validators.mjs +328 -0
  47. package/lib/cli/project-scaffolder/design-contract/validation/helpers.mjs +8 -0
  48. package/lib/cli/project-scaffolder/design-contract/validation/research-dossier-validators.mjs +104 -0
  49. package/lib/cli/project-scaffolder/design-contract/validation/structural-validators.mjs +79 -0
  50. package/lib/cli/project-scaffolder/design-contract/validation/system-validators.mjs +256 -0
  51. package/lib/cli/project-scaffolder/design-contract/validation.mjs +61 -896
  52. package/lib/cli/project-scaffolder/design-contract.mjs +151 -556
  53. package/lib/cli/project-scaffolder/prompt-builders.mjs +9 -0
  54. package/mcp.json +30 -9
  55. package/package.json +17 -2
  56. package/scripts/audit-cache-layer-contract.mjs +258 -0
  57. package/scripts/audit-caching-scope-hygiene.mjs +263 -0
  58. package/scripts/audit-file-size.mjs +219 -0
  59. package/scripts/audit-reflection-citations.mjs +163 -0
  60. package/scripts/audit-release-bundle.mjs +170 -0
  61. package/scripts/audit-rule-id-uniqueness.mjs +313 -0
  62. package/scripts/benchmark-evidence-bundle.mjs +1 -0
  63. package/scripts/build-release-benchmark-bundle.mjs +204 -0
  64. package/scripts/context-triggered-audit.mjs +1 -0
  65. package/scripts/documentation-boundary-audit.mjs +1 -0
  66. package/scripts/explain-on-demand-audit.mjs +2 -1
  67. package/scripts/frontend-usability-audit.mjs +10 -10
  68. package/scripts/llm-judge/checklist-loader.mjs +45 -0
  69. package/scripts/llm-judge/constants.mjs +66 -0
  70. package/scripts/llm-judge/diff-collection.mjs +74 -0
  71. package/scripts/llm-judge/prompting.mjs +78 -0
  72. package/scripts/llm-judge/providers.mjs +111 -0
  73. package/scripts/llm-judge/verdict.mjs +134 -0
  74. package/scripts/llm-judge.mjs +21 -482
  75. package/scripts/mcp-server/tool-registry.mjs +55 -0
  76. package/scripts/mcp-server/tools.mjs +137 -1
  77. package/scripts/migrate-rule-format/id-prefix-table.mjs +37 -0
  78. package/scripts/migrate-rule-format/parse-legacy.mjs +180 -0
  79. package/scripts/migrate-rule-format/render-new.mjs +169 -0
  80. package/scripts/migrate-rule-format/roundtrip-validate.mjs +89 -0
  81. package/scripts/migrate-rule-format.mjs +192 -0
  82. package/scripts/release-gate/constants.mjs +1 -1
  83. package/scripts/release-gate/static-checks.mjs +1 -1
  84. package/scripts/rules-guardian-audit.mjs +5 -2
  85. package/scripts/single-source-lazy-loading-audit.mjs +2 -1
  86. package/scripts/ui-design-judge/git-input.mjs +3 -0
  87. package/scripts/validate/config.mjs +27 -2
  88. package/scripts/validate/coverage-checks.mjs +1 -1
  89. package/scripts/validate.mjs +94 -1
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Validators for the design execution policy, the structured execution
3
+ * handoff, the review rubric, the required design sections list, and the
4
+ * forbidden patterns list. These collectively gate the structured handoff
5
+ * payload that downstream UI judges and review tooling consume.
6
+ */
7
+
8
+ import { hasNonEmptyString } from './helpers.mjs';
9
+ import { DESIGN_REQUIRED_SECTIONS } from '../../constants.mjs';
10
+
11
+ export function validateDesignExecutionPolicy(designIntentContract, validationErrors) {
12
+ if (!designIntentContract.designExecutionPolicy || typeof designIntentContract.designExecutionPolicy !== 'object') {
13
+ validationErrors.push('designIntent.designExecutionPolicy must exist.');
14
+ return validationErrors;
15
+ }
16
+ const designExecutionPolicy = designIntentContract.designExecutionPolicy;
17
+ if (designExecutionPolicy.representationStrategy !== 'surface-plan-v1') {
18
+ validationErrors.push('designIntent.designExecutionPolicy.representationStrategy must equal "surface-plan-v1".');
19
+ }
20
+ for (const requiredFlagName of [
21
+ 'requireSurfacePlan',
22
+ 'requireComponentGraph',
23
+ 'requireViewportMutationPlan',
24
+ 'requireInteractionStateMatrix',
25
+ 'requireContentPriorityMap',
26
+ 'requireTaskFlowNarrative',
27
+ 'requireSignatureMoveRationale',
28
+ 'requireCreativeCommitmentGate',
29
+ 'requireStructuredHandoff',
30
+ 'requireRepoEvidenceAlignment',
31
+ 'forbidScreenshotDependency',
32
+ 'separateRequiredOutcomesFromCandidateMoves',
33
+ 'forbidCandidateMovesAsLockedRequirements',
34
+ 'forbidLibraryThemeAsVisualAuthority',
35
+ 'forbidLiteralAnchorChromeWithoutProductFunction',
36
+ 'requirePerSurfaceMutationOps',
37
+ 'forbidUniformSiblingSurfaceTreatment',
38
+ 'zeroBasedRedesignResetsPriorVisualsWhenRequested',
39
+ ]) {
40
+ if (designExecutionPolicy[requiredFlagName] !== true) {
41
+ validationErrors.push(`designIntent.designExecutionPolicy.${requiredFlagName} must equal true.`);
42
+ }
43
+ }
44
+ if (designExecutionPolicy.handoffFormatVersion !== 'ui-handoff-v1') {
45
+ validationErrors.push('designIntent.designExecutionPolicy.handoffFormatVersion must equal "ui-handoff-v1".');
46
+ }
47
+ if (
48
+ !Array.isArray(designExecutionPolicy.semanticReviewFocus)
49
+ || designExecutionPolicy.semanticReviewFocus.length < 4
50
+ ) {
51
+ validationErrors.push('designIntent.designExecutionPolicy.semanticReviewFocus must list the required review dimensions.');
52
+ }
53
+ return validationErrors;
54
+ }
55
+
56
+ function validateHandoffComponentGraph(designExecutionHandoff, validationErrors) {
57
+ const componentGraph = designExecutionHandoff.componentGraph;
58
+ if (!componentGraph || typeof componentGraph !== 'object') {
59
+ validationErrors.push('designIntent.designExecutionHandoff.componentGraph must exist.');
60
+ return;
61
+ }
62
+ if (!Array.isArray(componentGraph.nodes) || componentGraph.nodes.length < 2) {
63
+ validationErrors.push('designIntent.designExecutionHandoff.componentGraph.nodes must list the primary execution nodes.');
64
+ }
65
+ if (!Array.isArray(componentGraph.edges) || componentGraph.edges.length < 1) {
66
+ validationErrors.push('designIntent.designExecutionHandoff.componentGraph.edges must define relationships between UI nodes.');
67
+ }
68
+ }
69
+
70
+ function validateHandoffContentPriorityMap(designExecutionHandoff, validationErrors) {
71
+ const contentPriorityMap = designExecutionHandoff.contentPriorityMap;
72
+ if (!contentPriorityMap || typeof contentPriorityMap !== 'object') {
73
+ validationErrors.push('designIntent.designExecutionHandoff.contentPriorityMap must exist.');
74
+ return;
75
+ }
76
+ for (const priorityBucket of ['primary', 'secondary', 'deferred']) {
77
+ if (!Array.isArray(contentPriorityMap[priorityBucket]) || contentPriorityMap[priorityBucket].length < 1) {
78
+ validationErrors.push(`designIntent.designExecutionHandoff.contentPriorityMap.${priorityBucket} must contain at least one item.`);
79
+ }
80
+ }
81
+ }
82
+
83
+ function validateHandoffViewportMutationPlan(designExecutionHandoff, validationErrors) {
84
+ const viewportMutationPlan = designExecutionHandoff.viewportMutationPlan;
85
+ if (!viewportMutationPlan || typeof viewportMutationPlan !== 'object') {
86
+ validationErrors.push('designIntent.designExecutionHandoff.viewportMutationPlan must exist.');
87
+ return;
88
+ }
89
+ for (const viewportKey of ['mobile', 'tablet', 'desktop']) {
90
+ const viewportPlan = viewportMutationPlan[viewportKey];
91
+ if (!viewportPlan || typeof viewportPlan !== 'object') {
92
+ validationErrors.push(`designIntent.designExecutionHandoff.viewportMutationPlan.${viewportKey} must be an object.`);
93
+ continue;
94
+ }
95
+ if (!String(viewportPlan.primaryOperation || '').trim()) {
96
+ validationErrors.push(`designIntent.designExecutionHandoff.viewportMutationPlan.${viewportKey}.primaryOperation must be a non-empty string.`);
97
+ }
98
+ if (!Array.isArray(viewportPlan.requiredSurfaceActions) || viewportPlan.requiredSurfaceActions.length < 2) {
99
+ validationErrors.push(`designIntent.designExecutionHandoff.viewportMutationPlan.${viewportKey}.requiredSurfaceActions must contain at least two actions.`);
100
+ }
101
+ if (!Array.isArray(viewportPlan.forbiddenPatterns) || viewportPlan.forbiddenPatterns.length < 1) {
102
+ validationErrors.push(`designIntent.designExecutionHandoff.viewportMutationPlan.${viewportKey}.forbiddenPatterns must contain at least one anti-pattern.`);
103
+ }
104
+ if (!String(viewportPlan.rationale || '').trim()) {
105
+ validationErrors.push(`designIntent.designExecutionHandoff.viewportMutationPlan.${viewportKey}.rationale must be a non-empty string.`);
106
+ }
107
+ }
108
+ }
109
+
110
+ function validateHandoffExpressionFlexibility(designExecutionHandoff, validationErrors) {
111
+ const expressionFlexibility = designExecutionHandoff.expressionFlexibility;
112
+ if (!expressionFlexibility || typeof expressionFlexibility !== 'object') {
113
+ validationErrors.push('designIntent.designExecutionHandoff.expressionFlexibility must exist.');
114
+ return;
115
+ }
116
+ if (!Array.isArray(expressionFlexibility.lockedOutcomes) || expressionFlexibility.lockedOutcomes.length < 3) {
117
+ validationErrors.push('designIntent.designExecutionHandoff.expressionFlexibility.lockedOutcomes must list locked outcomes.');
118
+ }
119
+ if (!Array.isArray(expressionFlexibility.candidateSignatureMoves) || expressionFlexibility.candidateSignatureMoves.length < 1) {
120
+ validationErrors.push('designIntent.designExecutionHandoff.expressionFlexibility.candidateSignatureMoves must include at least one candidate move placeholder.');
121
+ }
122
+ if (!Array.isArray(expressionFlexibility.flexibleAxes) || expressionFlexibility.flexibleAxes.length < 4) {
123
+ validationErrors.push('designIntent.designExecutionHandoff.expressionFlexibility.flexibleAxes must list flexible implementation axes.');
124
+ }
125
+ if (!String(expressionFlexibility.lockingRule || '').includes('candidate')) {
126
+ validationErrors.push('designIntent.designExecutionHandoff.expressionFlexibility.lockingRule must explain when candidate moves become required.');
127
+ }
128
+ }
129
+
130
+ function validateHandoffCreativeCommitment(designExecutionHandoff, validationErrors) {
131
+ const creativeCommitment = designExecutionHandoff.creativeCommitment;
132
+ if (!creativeCommitment || typeof creativeCommitment !== 'object') {
133
+ validationErrors.push('designIntent.designExecutionHandoff.creativeCommitment must exist.');
134
+ return;
135
+ }
136
+ if (creativeCommitment.status !== 'agent-must-complete-before-ui-implementation') {
137
+ validationErrors.push('designIntent.designExecutionHandoff.creativeCommitment.status must equal "agent-must-complete-before-ui-implementation".');
138
+ }
139
+ if (
140
+ !Array.isArray(creativeCommitment.requiredFields)
141
+ || !creativeCommitment.requiredFields.includes('specificReferencePoint')
142
+ || !creativeCommitment.requiredFields.includes('signatureMotion')
143
+ || !creativeCommitment.requiredFields.includes('typographicDecision')
144
+ ) {
145
+ validationErrors.push('designIntent.designExecutionHandoff.creativeCommitment.requiredFields must include specificReferencePoint, signatureMotion, and typographicDecision.');
146
+ }
147
+ if (!hasNonEmptyString(creativeCommitment.failureMode)) {
148
+ validationErrors.push('designIntent.designExecutionHandoff.creativeCommitment.failureMode must be a non-empty string.');
149
+ }
150
+ }
151
+
152
+ function validateHandoffImplementationGuardrails(designExecutionHandoff, validationErrors) {
153
+ const implementationGuardrails = designExecutionHandoff.implementationGuardrails;
154
+ if (!implementationGuardrails || typeof implementationGuardrails !== 'object') {
155
+ validationErrors.push('designIntent.designExecutionHandoff.implementationGuardrails must exist.');
156
+ return;
157
+ }
158
+ for (const requiredFlagName of [
159
+ 'requireBuildFromHandoff',
160
+ 'requireGapNotesBeforeFallback',
161
+ 'forbidGenericLayoutFallbackWithoutReason',
162
+ 'requireLockedVsFlexibleDecisionReview',
163
+ 'forbidCandidateMoveHardcoding',
164
+ 'forbidTestingDemoCopyInUi',
165
+ 'forbidTerminalOnlyUserFlows',
166
+ ]) {
167
+ if (implementationGuardrails[requiredFlagName] !== true) {
168
+ validationErrors.push(`designIntent.designExecutionHandoff.implementationGuardrails.${requiredFlagName} must equal true.`);
169
+ }
170
+ }
171
+ }
172
+
173
+ export function validateDesignExecutionHandoff(designIntentContract, validationErrors) {
174
+ if (!designIntentContract.designExecutionHandoff || typeof designIntentContract.designExecutionHandoff !== 'object') {
175
+ validationErrors.push('designIntent.designExecutionHandoff must exist.');
176
+ return validationErrors;
177
+ }
178
+
179
+ const designExecutionHandoff = designIntentContract.designExecutionHandoff;
180
+ if (designExecutionHandoff.version !== 'ui-handoff-v1') {
181
+ validationErrors.push('designIntent.designExecutionHandoff.version must equal "ui-handoff-v1".');
182
+ }
183
+ if (designExecutionHandoff.seedMode !== 'structure-first-scaffold') {
184
+ validationErrors.push('designIntent.designExecutionHandoff.seedMode must equal "structure-first-scaffold".');
185
+ }
186
+ if (designExecutionHandoff.requiresTaskSpecificRefinement !== true) {
187
+ validationErrors.push('designIntent.designExecutionHandoff.requiresTaskSpecificRefinement must equal true.');
188
+ }
189
+ if (!String(designExecutionHandoff.primaryExperienceGoal || '').trim()) {
190
+ validationErrors.push('designIntent.designExecutionHandoff.primaryExperienceGoal must be a non-empty string.');
191
+ }
192
+ if (!Array.isArray(designExecutionHandoff.surfacePlan) || designExecutionHandoff.surfacePlan.length < 1) {
193
+ validationErrors.push('designIntent.designExecutionHandoff.surfacePlan must define at least one planned surface.');
194
+ }
195
+
196
+ validateHandoffComponentGraph(designExecutionHandoff, validationErrors);
197
+ validateHandoffContentPriorityMap(designExecutionHandoff, validationErrors);
198
+ validateHandoffViewportMutationPlan(designExecutionHandoff, validationErrors);
199
+
200
+ if (!Array.isArray(designExecutionHandoff.interactionStateMatrix) || designExecutionHandoff.interactionStateMatrix.length < 1) {
201
+ validationErrors.push('designIntent.designExecutionHandoff.interactionStateMatrix must list key component state expectations.');
202
+ }
203
+
204
+ validateHandoffExpressionFlexibility(designExecutionHandoff, validationErrors);
205
+
206
+ if (!Array.isArray(designExecutionHandoff.taskFlowNarrative) || designExecutionHandoff.taskFlowNarrative.length < 2) {
207
+ validationErrors.push('designIntent.designExecutionHandoff.taskFlowNarrative must describe the key UI task flow in sequence.');
208
+ }
209
+ if (!String(designExecutionHandoff.signatureMoveRationale || '').trim()) {
210
+ validationErrors.push('designIntent.designExecutionHandoff.signatureMoveRationale must explain the chosen authored move.');
211
+ }
212
+
213
+ validateHandoffCreativeCommitment(designExecutionHandoff, validationErrors);
214
+ validateHandoffImplementationGuardrails(designExecutionHandoff, validationErrors);
215
+ return validationErrors;
216
+ }
217
+
218
+ export function validateReviewRubric(designIntentContract, validationErrors) {
219
+ if (!designIntentContract.reviewRubric || typeof designIntentContract.reviewRubric !== 'object') {
220
+ validationErrors.push('designIntent.reviewRubric must exist.');
221
+ return validationErrors;
222
+ }
223
+ const reviewRubric = designIntentContract.reviewRubric;
224
+ if (reviewRubric.version !== 'ui-rubric-v1') {
225
+ validationErrors.push('designIntent.reviewRubric.version must equal "ui-rubric-v1".');
226
+ }
227
+ if (reviewRubric.genericityAutoFail !== true) {
228
+ validationErrors.push('designIntent.reviewRubric.genericityAutoFail must equal true.');
229
+ }
230
+ if (!Array.isArray(reviewRubric.dimensions) || reviewRubric.dimensions.length < 5) {
231
+ validationErrors.push('designIntent.reviewRubric.dimensions must define the required rubric dimensions.');
232
+ } else {
233
+ for (const requiredRubricKey of [
234
+ 'distinctiveness',
235
+ 'contractFidelity',
236
+ 'visualConsistency',
237
+ 'heuristicUxQuality',
238
+ 'motionDiscipline',
239
+ ]) {
240
+ if (!reviewRubric.dimensions.some((dimension) => dimension?.key === requiredRubricKey)) {
241
+ validationErrors.push(`designIntent.reviewRubric.dimensions is missing "${requiredRubricKey}".`);
242
+ }
243
+ }
244
+ }
245
+ if (!Array.isArray(reviewRubric.genericitySignals) || reviewRubric.genericitySignals.length < 3) {
246
+ validationErrors.push('designIntent.reviewRubric.genericitySignals must list common genericity drift signals.');
247
+ } else {
248
+ for (const requiredSignal of [
249
+ 'ai-safe-ui-template-look',
250
+ 'ai-color-default-palette-without-product-role-behavior',
251
+ 'interchangeable-product-renaming-test-fails',
252
+ 'decorative-grid-or-glow-wallpaper-without-product-function',
253
+ 'decorative-line-or-calibration-wallpaper-without-product-function',
254
+ 'measurement-or-calibration-marks-used-as-page-background',
255
+ 'testing-demo-or-placeholder-copy-shipped-to-ui',
256
+ 'terminal-only-user-flow-without-product-reason',
257
+ 'motion-or-3d-omitted-from-fear-without-fit-analysis',
258
+ ]) {
259
+ if (!reviewRubric.genericitySignals.includes(requiredSignal)) {
260
+ validationErrors.push(`designIntent.reviewRubric.genericitySignals must include "${requiredSignal}".`);
261
+ }
262
+ }
263
+ }
264
+ if (!Array.isArray(reviewRubric.validBoldSignals) || reviewRubric.validBoldSignals.length < 3) {
265
+ validationErrors.push('designIntent.reviewRubric.validBoldSignals must list legitimate authored signals.');
266
+ } else {
267
+ for (const requiredSignal of [
268
+ 'three-at-a-glance-product-specific-signals',
269
+ 'visually-exploratory-accessible-palette-derived-from-product',
270
+ 'audacious-accessible-palette-with-product-role-behavior',
271
+ 'motion-or-spatial-experience-derived-from-anchor',
272
+ ]) {
273
+ if (!reviewRubric.validBoldSignals.includes(requiredSignal)) {
274
+ validationErrors.push(`designIntent.reviewRubric.validBoldSignals must include "${requiredSignal}".`);
275
+ }
276
+ }
277
+ }
278
+ if (!reviewRubric.reportingRules || typeof reviewRubric.reportingRules !== 'object') {
279
+ validationErrors.push('designIntent.reviewRubric.reportingRules must exist.');
280
+ return validationErrors;
281
+ }
282
+ for (const requiredFlagName of [
283
+ 'mustExplainGenericity',
284
+ 'mustSeparateTasteFromFailure',
285
+ 'contractFidelityOverridesPersonalTaste',
286
+ ]) {
287
+ if (reviewRubric.reportingRules[requiredFlagName] !== true) {
288
+ validationErrors.push(`designIntent.reviewRubric.reportingRules.${requiredFlagName} must equal true.`);
289
+ }
290
+ }
291
+ return validationErrors;
292
+ }
293
+
294
+ export function validateRequiredSectionsAndForbiddenPatterns(designIntentContract, validationErrors) {
295
+ if (
296
+ !Array.isArray(designIntentContract.requiredDesignSections)
297
+ || designIntentContract.requiredDesignSections.length !== DESIGN_REQUIRED_SECTIONS.length
298
+ ) {
299
+ validationErrors.push('designIntent.requiredDesignSections must match the required design contract sections.');
300
+ } else {
301
+ for (const requiredSectionName of DESIGN_REQUIRED_SECTIONS) {
302
+ if (!designIntentContract.requiredDesignSections.includes(requiredSectionName)) {
303
+ validationErrors.push(`designIntent.requiredDesignSections is missing "${requiredSectionName}".`);
304
+ }
305
+ }
306
+ }
307
+
308
+ if (!Array.isArray(designIntentContract.forbiddenPatterns) || designIntentContract.forbiddenPatterns.length < 4) {
309
+ validationErrors.push('designIntent.forbiddenPatterns must list concrete anti-generic patterns.');
310
+ return validationErrors;
311
+ }
312
+ for (const requiredPattern of [
313
+ 'ai-safe-ui-template-look',
314
+ 'ai-color-default-palette-without-product-role-behavior',
315
+ 'interchangeable-product-renaming-test-fails',
316
+ 'decorative-grid-or-glow-wallpaper-without-product-function',
317
+ 'decorative-line-or-calibration-wallpaper-without-product-function',
318
+ 'measurement-or-calibration-marks-used-as-page-background',
319
+ 'testing-demo-or-placeholder-copy-shipped-to-ui',
320
+ 'terminal-only-user-flow-without-product-reason',
321
+ 'motion-or-3d-omitted-from-fear-without-fit-analysis',
322
+ ]) {
323
+ if (!designIntentContract.forbiddenPatterns.includes(requiredPattern)) {
324
+ validationErrors.push(`designIntent.forbiddenPatterns must include "${requiredPattern}".`);
325
+ }
326
+ }
327
+ return validationErrors;
328
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tiny validator helpers shared across the design contract validation
3
+ * sub-modules. Pure, side-effect free.
4
+ */
5
+
6
+ export function hasNonEmptyString(value) {
7
+ return typeof value === 'string' && value.trim().length > 0;
8
+ }
@@ -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'];
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
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Structural-shape validators for the design intent contract: top-level mode,
3
+ * project block, design philosophy, external research intake, and the design
4
+ * flexibility policy. Each validator pushes its findings onto the shared error
5
+ * accumulator and returns it.
6
+ */
7
+
8
+ import { hasNonEmptyString } from './helpers.mjs';
9
+
10
+ export function validateModeAndProjectShape(designIntentContract, validationErrors) {
11
+ if (designIntentContract.mode !== 'dynamic') {
12
+ validationErrors.push('designIntent.mode must equal "dynamic".');
13
+ }
14
+
15
+ if (!designIntentContract.project || typeof designIntentContract.project !== 'object') {
16
+ validationErrors.push('designIntent.project must exist.');
17
+ }
18
+
19
+ if (!designIntentContract.designPhilosophy || typeof designIntentContract.designPhilosophy !== 'string') {
20
+ validationErrors.push('designIntent.designPhilosophy must be a non-empty string.');
21
+ }
22
+ return validationErrors;
23
+ }
24
+
25
+ export function validateExternalResearchIntake(designIntentContract, validationErrors) {
26
+ if (!designIntentContract.externalResearchIntake || typeof designIntentContract.externalResearchIntake !== 'object') {
27
+ validationErrors.push('designIntent.externalResearchIntake must exist.');
28
+ return validationErrors;
29
+ }
30
+
31
+ const externalResearchIntake = designIntentContract.externalResearchIntake;
32
+ if (externalResearchIntake.userSuppliedResearchPolicy !== 'read-as-candidate-evidence-not-final-prescription') {
33
+ validationErrors.push('designIntent.externalResearchIntake.userSuppliedResearchPolicy must preserve user research as candidate evidence.');
34
+ }
35
+ if (externalResearchIntake.requireOfficialDocsVerificationForTechnologyClaims !== true) {
36
+ validationErrors.push('designIntent.externalResearchIntake.requireOfficialDocsVerificationForTechnologyClaims must equal true.');
37
+ }
38
+ if (
39
+ !Array.isArray(externalResearchIntake.candidateDomains)
40
+ || !externalResearchIntake.candidateDomains.includes('motion-and-scroll')
41
+ ) {
42
+ validationErrors.push('designIntent.externalResearchIntake.candidateDomains must include motion-and-scroll.');
43
+ }
44
+ return validationErrors;
45
+ }
46
+
47
+ export function validateDesignFlexibilityPolicy(designIntentContract, validationErrors) {
48
+ if (!designIntentContract.designFlexibilityPolicy || typeof designIntentContract.designFlexibilityPolicy !== 'object') {
49
+ validationErrors.push('designIntent.designFlexibilityPolicy must exist.');
50
+ return validationErrors;
51
+ }
52
+
53
+ const designFlexibilityPolicy = designIntentContract.designFlexibilityPolicy;
54
+ if (designFlexibilityPolicy.mode !== 'locked-outcomes-flexible-expression') {
55
+ validationErrors.push('designIntent.designFlexibilityPolicy.mode must equal "locked-outcomes-flexible-expression".');
56
+ }
57
+ if (!hasNonEmptyString(designFlexibilityPolicy.contractRole)) {
58
+ validationErrors.push('designIntent.designFlexibilityPolicy.contractRole must be a non-empty string.');
59
+ }
60
+ if (!Array.isArray(designFlexibilityPolicy.lockedOutcomeTypes) || designFlexibilityPolicy.lockedOutcomeTypes.length < 4) {
61
+ validationErrors.push('designIntent.designFlexibilityPolicy.lockedOutcomeTypes must list the locked outcome categories.');
62
+ }
63
+ if (!Array.isArray(designFlexibilityPolicy.flexibleExpressionAxes) || designFlexibilityPolicy.flexibleExpressionAxes.length < 4) {
64
+ validationErrors.push('designIntent.designFlexibilityPolicy.flexibleExpressionAxes must list flexible expression axes.');
65
+ }
66
+ if (!hasNonEmptyString(designFlexibilityPolicy.tokenLockingRule)) {
67
+ validationErrors.push('designIntent.designFlexibilityPolicy.tokenLockingRule must be a non-empty string.');
68
+ }
69
+ if (!String(designFlexibilityPolicy.signatureMovePolicy || '').includes('candidate')) {
70
+ validationErrors.push('designIntent.designFlexibilityPolicy.signatureMovePolicy must separate candidate moves from required outcomes.');
71
+ }
72
+ if (!String(designFlexibilityPolicy.libraryVisualLanguagePolicy || '').includes('Libraries supply')) {
73
+ validationErrors.push('designIntent.designFlexibilityPolicy.libraryVisualLanguagePolicy must keep libraries from dictating visual language.');
74
+ }
75
+ if (!String(designFlexibilityPolicy.literalAnchorPolicy || '').includes('Translate anchors')) {
76
+ validationErrors.push('designIntent.designFlexibilityPolicy.literalAnchorPolicy must require non-literal anchor translation.');
77
+ }
78
+ return validationErrors;
79
+ }