@ryuenn3123/agentic-senior-core 3.0.14 → 3.0.16
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 +30 -16
- package/.agent-context/prompts/init-project.md +4 -0
- package/.agent-context/rules/architecture.md +13 -0
- package/.agent-context/rules/docker-runtime.md +12 -0
- package/.agent-context/rules/efficiency-vs-hype.md +17 -6
- package/.agent-context/rules/frontend-architecture.md +5 -0
- package/.agent-context/state/memory-continuity-benchmark.json +1 -1
- package/.agent-context/state/onboarding-report.json +0 -1
- package/.cursorrules +66 -29
- package/.gemini/instructions.md +1 -1
- package/.github/copilot-instructions.md +1 -1
- package/.instructions.md +4 -3
- package/.windsurfrules +66 -29
- package/AGENTS.md +1 -1
- package/lib/cli/architect.mjs +71 -784
- package/lib/cli/commands/init.mjs +32 -98
- package/lib/cli/commands/optimize.mjs +0 -4
- package/lib/cli/commands/upgrade.mjs +2 -5
- package/lib/cli/compiler.mjs +3 -11
- package/lib/cli/constants.mjs +3 -73
- package/lib/cli/detector/design-evidence.mjs +427 -0
- package/lib/cli/detector.mjs +13 -116
- package/lib/cli/init-options.mjs +0 -118
- package/lib/cli/project-scaffolder/constants.mjs +67 -0
- package/lib/cli/project-scaffolder/design-contract.mjs +554 -0
- package/lib/cli/project-scaffolder/discovery.mjs +315 -0
- package/lib/cli/project-scaffolder/prompt-builders.mjs +196 -0
- package/lib/cli/project-scaffolder/storage.mjs +154 -0
- package/lib/cli/project-scaffolder.mjs +32 -1160
- package/lib/cli/utils.mjs +2 -11
- package/package.json +1 -1
- package/scripts/frontend-usability-audit.mjs +53 -0
- package/scripts/validate/config.mjs +401 -0
- package/scripts/validate/coverage-checks.mjs +429 -0
- package/scripts/validate.mjs +44 -754
- package/lib/cli/init-architecture-flow.mjs +0 -233
- package/lib/cli/profile-packs.mjs +0 -108
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { toTitleCase } from '../utils.mjs';
|
|
2
|
+
import { DESIGN_REQUIRED_SECTIONS } from './constants.mjs';
|
|
3
|
+
|
|
4
|
+
export function shouldBootstrapDesignDocument(discoveryAnswers, initContext) {
|
|
5
|
+
const normalizedDomain = String(discoveryAnswers.primaryDomain || '').trim().toLowerCase();
|
|
6
|
+
const normalizedBlueprint = String(initContext.blueprintFileName || '').trim().toLowerCase();
|
|
7
|
+
|
|
8
|
+
const isUiDomain = normalizedDomain.includes('web')
|
|
9
|
+
|| normalizedDomain.includes('mobile')
|
|
10
|
+
|| normalizedDomain.includes('frontend')
|
|
11
|
+
|| normalizedDomain.includes('ui');
|
|
12
|
+
|
|
13
|
+
const isBackendOnlyDomain = normalizedDomain.includes('api service')
|
|
14
|
+
|| normalizedDomain.includes('cli tool')
|
|
15
|
+
|| normalizedDomain.includes('library');
|
|
16
|
+
|
|
17
|
+
const blueprintLooksUi = normalizedBlueprint.includes('frontend')
|
|
18
|
+
|| normalizedBlueprint.includes('landing')
|
|
19
|
+
|| normalizedBlueprint.includes('ui');
|
|
20
|
+
|
|
21
|
+
if (isUiDomain) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!isBackendOnlyDomain && blueprintLooksUi) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function inferDesignKeywords(discoveryAnswers) {
|
|
33
|
+
const normalizedDescription = String(discoveryAnswers.projectDescription || '').toLowerCase();
|
|
34
|
+
const normalizedDomain = String(discoveryAnswers.primaryDomain || '').toLowerCase();
|
|
35
|
+
const normalizedFeatures = Array.isArray(discoveryAnswers.features)
|
|
36
|
+
? discoveryAnswers.features.map((featureValue) => String(featureValue).toLowerCase()).join(' ')
|
|
37
|
+
: '';
|
|
38
|
+
const aggregateText = `${normalizedDescription} ${normalizedDomain} ${normalizedFeatures}`;
|
|
39
|
+
|
|
40
|
+
if (aggregateText.includes('commerce') || aggregateText.includes('catalog') || aggregateText.includes('checkout')) {
|
|
41
|
+
return {
|
|
42
|
+
designPhilosophy: 'Conversion clarity with premium restraint.',
|
|
43
|
+
brandAdjectives: ['clear', 'desirable', 'confident'],
|
|
44
|
+
antiAdjectives: ['cluttered', 'hesitant', 'coupon-noisy'],
|
|
45
|
+
typographyScaleRatio: '1.200',
|
|
46
|
+
baseGridUnit: 8,
|
|
47
|
+
densityMode: 'conversion-focused',
|
|
48
|
+
colorIntent: 'Use a restrained neutral foundation with one controlled accent reserved for buying cues and trust moments.',
|
|
49
|
+
distinctiveMoves: [
|
|
50
|
+
'Use product hierarchy and buying cues without turning the interface into a discount template.',
|
|
51
|
+
'Keep decision-critical information prominent while secondary merchandising stays quiet.',
|
|
52
|
+
'Let imagery and spacing create premium perception before decorative effects do.',
|
|
53
|
+
],
|
|
54
|
+
motionPurpose: 'Use motion to reinforce buying confidence, product continuity, and premium delight. It may be theatrical at key moments if it stays fast, legible, and supportive of decision-making.',
|
|
55
|
+
motionChoreography: 'Favor fast hover and focus feedback, confident sheet choreography, product-media continuity, and one or two signature reveal moments. Avoid autoplay spectacle that distracts from purchase decisions.',
|
|
56
|
+
motionDurations: {
|
|
57
|
+
desktop: 180,
|
|
58
|
+
mobile: 240,
|
|
59
|
+
},
|
|
60
|
+
componentMorphology: {
|
|
61
|
+
mobile: 'Product cards should compress supporting metadata, pin purchase actions closer to the thumb zone, and move comparison into progressive disclosure or bottom sheets.',
|
|
62
|
+
tablet: 'Cards and merch modules should preserve comparison affordances while reducing tertiary chrome and keeping visual hierarchy stable.',
|
|
63
|
+
desktop: 'Cards can expand media, comparison, and reassurance copy while keeping buying cues dominant and visually disciplined.',
|
|
64
|
+
},
|
|
65
|
+
mutationRules: {
|
|
66
|
+
mobile: 'Convert browsing into vertically stacked product cards, move cart and filter actions into sticky or bottom-sheet patterns, and keep thumb-reach actions persistent.',
|
|
67
|
+
tablet: 'Preserve browsing flow with a two-column rhythm, collapse tertiary filters, and keep comparison moments visible without forcing desktop density.',
|
|
68
|
+
desktop: 'Expose multi-column merchandising, comparison views, and richer product context while keeping the purchase path visually dominant.',
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (aggregateText.includes('dashboard') || aggregateText.includes('operations') || aggregateText.includes('report')) {
|
|
74
|
+
return {
|
|
75
|
+
designPhilosophy: 'Operational calm under high information density.',
|
|
76
|
+
brandAdjectives: ['calm', 'precise', 'trustworthy'],
|
|
77
|
+
antiAdjectives: ['chaotic', 'gimmicky', 'visually exhausting'],
|
|
78
|
+
typographyScaleRatio: '1.125',
|
|
79
|
+
baseGridUnit: 4,
|
|
80
|
+
densityMode: 'high-density-scanning',
|
|
81
|
+
colorIntent: 'Use neutrals for structure and reserve accent saturation for status shifts, risky actions, and alerts.',
|
|
82
|
+
distinctiveMoves: [
|
|
83
|
+
'Prioritize scanning clarity and status recognition over decorative density.',
|
|
84
|
+
'Use visual weight to separate signal from operational noise.',
|
|
85
|
+
'Reserve strong accents for alerts, decisions, and state transitions only.',
|
|
86
|
+
],
|
|
87
|
+
motionPurpose: 'Use motion as operational feedback and state continuity, while still allowing decisive state transitions that make dense workflows feel alive and controlled.',
|
|
88
|
+
motionChoreography: 'Prefer fast transitions for filters, drawers, status reveals, and row expansion, but allow strong confirmation moments when they improve confidence and scan clarity.',
|
|
89
|
+
motionDurations: {
|
|
90
|
+
desktop: 160,
|
|
91
|
+
mobile: 220,
|
|
92
|
+
},
|
|
93
|
+
componentMorphology: {
|
|
94
|
+
mobile: 'Data rows should become prioritized cards or grouped summaries, with filters and secondary tools moving into sheets or drawers.',
|
|
95
|
+
tablet: 'Operational panels should retain split-view logic where possible, while tertiary panels collapse behind explicit toggles.',
|
|
96
|
+
desktop: 'Dense tables, side panels, and comparison surfaces can remain visible simultaneously, with state treatments optimized for rapid scanning.',
|
|
97
|
+
},
|
|
98
|
+
mutationRules: {
|
|
99
|
+
mobile: 'Collapse dense tables into prioritized cards or row groups, move filters into drawers or sheets, and pin the most critical actions to the bottom reach zone.',
|
|
100
|
+
tablet: 'Keep two-column or split-pane workflows, collapse tertiary panels, and maintain fast scan paths for operators using touch or keyboard.',
|
|
101
|
+
desktop: 'Expose the highest-density views with visible navigation, comparison surfaces, and simultaneous context panels for power users.',
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (aggregateText.includes('developer') || aggregateText.includes('api') || aggregateText.includes('platform')) {
|
|
107
|
+
return {
|
|
108
|
+
designPhilosophy: 'Technical precision with explicit structure and honest feedback.',
|
|
109
|
+
brandAdjectives: ['precise', 'technical', 'transparent'],
|
|
110
|
+
antiAdjectives: ['vague', 'marketing-heavy', 'template-polished'],
|
|
111
|
+
typographyScaleRatio: '1.125',
|
|
112
|
+
baseGridUnit: 4,
|
|
113
|
+
densityMode: 'technical-utility',
|
|
114
|
+
colorIntent: 'Anchor the interface in disciplined neutrals and use accent color only where state, feedback, or code-adjacent interaction needs emphasis.',
|
|
115
|
+
distinctiveMoves: [
|
|
116
|
+
'Make structure and feedback feel exact without becoming sterile.',
|
|
117
|
+
'Use code-adjacent rhythm and hierarchy to build trust with technical users.',
|
|
118
|
+
'Keep complexity legible through spacing, grouping, and explicit interaction states.',
|
|
119
|
+
],
|
|
120
|
+
motionPurpose: 'Use motion to clarify causality, reveal system state, preserve context, and give technical workflows a sense of precision instead of dead stillness.',
|
|
121
|
+
motionChoreography: 'Prefer sharp panel transitions, command feedback, disclosure motion, and occasional signature transitions that feel exact rather than ornamental.',
|
|
122
|
+
motionDurations: {
|
|
123
|
+
desktop: 170,
|
|
124
|
+
mobile: 230,
|
|
125
|
+
},
|
|
126
|
+
componentMorphology: {
|
|
127
|
+
mobile: 'Technical panes should flatten into sequential sections, with commands and diagnostics colocated near the content they affect.',
|
|
128
|
+
tablet: 'Split views should survive where useful, with explicit panel toggles and condensed chrome for code-adjacent tasks.',
|
|
129
|
+
desktop: 'Navigation, documentation, diagnostics, and active work surfaces can remain concurrently visible when it improves expert comprehension.',
|
|
130
|
+
},
|
|
131
|
+
mutationRules: {
|
|
132
|
+
mobile: 'Switch multi-pane technical layouts into stacked sections, turn secondary navigation into segmented or sheet-based controls, and keep commands near the content they affect.',
|
|
133
|
+
tablet: 'Retain split-view comprehension where possible, compress chrome, and keep documentation or diagnostics adjacent to the active task.',
|
|
134
|
+
desktop: 'Expose full navigation, dense comparison surfaces, and multi-pane workflows for expert scanning and debugging.',
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (aggregateText.includes('content') || aggregateText.includes('community') || aggregateText.includes('publish')) {
|
|
140
|
+
return {
|
|
141
|
+
designPhilosophy: 'Editorial flow with warm but controlled expression.',
|
|
142
|
+
brandAdjectives: ['editorial', 'warm', 'expressive'],
|
|
143
|
+
antiAdjectives: ['flat', 'anonymous', 'feed-generic'],
|
|
144
|
+
typographyScaleRatio: '1.200',
|
|
145
|
+
baseGridUnit: 8,
|
|
146
|
+
densityMode: 'reading-first',
|
|
147
|
+
colorIntent: 'Let typography and surface contrast lead while chroma supports hierarchy and key participation actions.',
|
|
148
|
+
distinctiveMoves: [
|
|
149
|
+
'Build a strong reading rhythm so content feels curated rather than dumped into cards.',
|
|
150
|
+
'Use contrast and spacing to guide attention between creation, moderation, and discovery.',
|
|
151
|
+
'Give key interaction moments personality without sacrificing clarity.',
|
|
152
|
+
],
|
|
153
|
+
motionPurpose: 'Use motion to support reading rhythm, reveal structure, and reward contribution moments with visible craft, not generic restraint.',
|
|
154
|
+
motionChoreography: 'Favor reveal choreography for section transitions, expressive but measured feedback on participation, and media behavior that feels alive without becoming noisy.',
|
|
155
|
+
motionDurations: {
|
|
156
|
+
desktop: 190,
|
|
157
|
+
mobile: 250,
|
|
158
|
+
},
|
|
159
|
+
componentMorphology: {
|
|
160
|
+
mobile: 'Reading surfaces should dominate while secondary discovery and community tools collapse behind sheets, tabs, or segmented controls.',
|
|
161
|
+
tablet: 'Editorial modules can balance reading and discovery, provided the primary narrative flow remains obvious.',
|
|
162
|
+
desktop: 'Long-form content, secondary navigation, and related discovery modules can coexist without fragmenting the reading rhythm.',
|
|
163
|
+
},
|
|
164
|
+
mutationRules: {
|
|
165
|
+
mobile: 'Prioritize reading and contribution flows in a single-column narrative stack, tuck secondary discovery tools behind sheets, and keep primary creation actions within reach.',
|
|
166
|
+
tablet: 'Balance narrative reading with supporting discovery modules, using two-column compositions only where hierarchy stays obvious.',
|
|
167
|
+
desktop: 'Use wider editorial compositions, visible secondary navigation, and modular discovery surfaces without breaking reading rhythm.',
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
designPhilosophy: 'Project-specific clarity with one authored tension and one memorable visual bet.',
|
|
174
|
+
brandAdjectives: ['clear', 'human', 'distinct'],
|
|
175
|
+
antiAdjectives: ['generic', 'template-like', 'trend-chasing'],
|
|
176
|
+
typographyScaleRatio: '1.200',
|
|
177
|
+
baseGridUnit: 8,
|
|
178
|
+
densityMode: 'balanced-authored',
|
|
179
|
+
colorIntent: 'Use a restrained perceptual palette with one deliberate accent budget instead of interchangeable template colors.',
|
|
180
|
+
distinctiveMoves: [
|
|
181
|
+
'Create a visual direction with one memorable tension instead of stacking fashionable effects.',
|
|
182
|
+
'Use rhythm, hierarchy, and motion intentionally so the interface feels authored.',
|
|
183
|
+
'Keep the system flexible enough to evolve with product scope without losing identity.',
|
|
184
|
+
],
|
|
185
|
+
motionPurpose: 'Allow motion to create continuity, feedback, perceived craft, and memorability. Optimize bold choreography instead of defaulting to restraint. Reject only motion that harms comprehension, accessibility, or runtime performance.',
|
|
186
|
+
motionChoreography: 'Use fast purposeful transitions for most interactions, but allow a small number of signature transitions or reveal moments when they strengthen product identity and remain technically cheap.',
|
|
187
|
+
motionDurations: {
|
|
188
|
+
desktop: 180,
|
|
189
|
+
mobile: 240,
|
|
190
|
+
},
|
|
191
|
+
componentMorphology: {
|
|
192
|
+
mobile: 'Primary components should simplify structure, prioritize direct tasks, and collapse supporting detail into explicit disclosure.',
|
|
193
|
+
tablet: 'Components should preserve hierarchy and task continuity while reducing density and compressing tertiary chrome.',
|
|
194
|
+
desktop: 'Components can expose richer states, denser supporting information, and broader navigation affordances without losing clarity.',
|
|
195
|
+
},
|
|
196
|
+
mutationRules: {
|
|
197
|
+
mobile: 'Stack primary tasks vertically, convert secondary navigation into thumb-friendly overlays or sheets, and simplify dense comparison layouts into progressive disclosure.',
|
|
198
|
+
tablet: 'Preserve hierarchy with fewer columns, condensed chrome, and adaptive navigation that maintains task continuity.',
|
|
199
|
+
desktop: 'Expose the full layout system, highest information density, and broadest navigation affordances without sacrificing clarity.',
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function buildDesignIntentContractObject({
|
|
205
|
+
projectName,
|
|
206
|
+
projectDescription,
|
|
207
|
+
primaryDomain,
|
|
208
|
+
features = [],
|
|
209
|
+
initContext,
|
|
210
|
+
architectureRecommendation = null,
|
|
211
|
+
status = 'seed-needs-design-synthesis',
|
|
212
|
+
supplementalFields = {},
|
|
213
|
+
}) {
|
|
214
|
+
const inferredKeywords = inferDesignKeywords({
|
|
215
|
+
projectDescription,
|
|
216
|
+
primaryDomain,
|
|
217
|
+
features,
|
|
218
|
+
});
|
|
219
|
+
const normalizedPrimaryDomain = String(primaryDomain || '').trim().toLowerCase();
|
|
220
|
+
const resolvedSpacingPattern = inferredKeywords.densityMode === 'dense'
|
|
221
|
+
? 'compact-grid'
|
|
222
|
+
: normalizedPrimaryDomain.includes('mobile')
|
|
223
|
+
? 'mobile-first-single-axis'
|
|
224
|
+
: inferredKeywords.densityMode === 'focused'
|
|
225
|
+
? 'high-contrast-rhythm'
|
|
226
|
+
: 'balanced-grid';
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
mode: 'dynamic',
|
|
230
|
+
status,
|
|
231
|
+
project: {
|
|
232
|
+
name: projectName,
|
|
233
|
+
context: projectDescription,
|
|
234
|
+
domain: primaryDomain,
|
|
235
|
+
stack: toTitleCase(initContext.stackFileName),
|
|
236
|
+
blueprint: toTitleCase(initContext.blueprintFileName),
|
|
237
|
+
},
|
|
238
|
+
designPhilosophy: inferredKeywords.designPhilosophy,
|
|
239
|
+
brandAdjectives: inferredKeywords.brandAdjectives,
|
|
240
|
+
antiAdjectives: inferredKeywords.antiAdjectives,
|
|
241
|
+
visualDirection: {
|
|
242
|
+
trendStance: 'trend-aware-not-trend-chasing',
|
|
243
|
+
distinctiveMoves: inferredKeywords.distinctiveMoves,
|
|
244
|
+
copiedReferenceAllowed: false,
|
|
245
|
+
},
|
|
246
|
+
mathSystems: {
|
|
247
|
+
typographyScaleRatio: inferredKeywords.typographyScaleRatio,
|
|
248
|
+
baseGridUnit: inferredKeywords.baseGridUnit,
|
|
249
|
+
spacingPattern: resolvedSpacingPattern,
|
|
250
|
+
densityMode: inferredKeywords.densityMode,
|
|
251
|
+
},
|
|
252
|
+
tokenSystem: {
|
|
253
|
+
sourceOfTruth: 'docs/design-intent.json',
|
|
254
|
+
taxonomyOrder: ['primitive', 'semantic', 'component'],
|
|
255
|
+
primitiveColorSpace: 'OKLCH',
|
|
256
|
+
requireSemanticAliases: true,
|
|
257
|
+
semanticAliasesMutableWithoutComponentRewrite: true,
|
|
258
|
+
componentTokensConsumeSemantic: true,
|
|
259
|
+
forbidDirectComponentPrimitiveBypass: true,
|
|
260
|
+
aliasReferenceStyle: 'brace-reference',
|
|
261
|
+
aliasingStrategy: 'Primitive tokens hold raw values, semantic tokens carry intent, and component tokens consume semantic aliases instead of raw values.',
|
|
262
|
+
fallbackPolicy: {
|
|
263
|
+
forbidRawHexOutsidePrimitives: true,
|
|
264
|
+
forbidRawSpacingOutsidePrimitives: true,
|
|
265
|
+
requireDocumentedExceptionForLegacyBypass: true,
|
|
266
|
+
},
|
|
267
|
+
namingConstraints: {
|
|
268
|
+
forbidCurlyBracesInNames: true,
|
|
269
|
+
forbidDotsInNames: true,
|
|
270
|
+
forbidSquareBracketsInNames: true,
|
|
271
|
+
},
|
|
272
|
+
tokenLayerRoles: {
|
|
273
|
+
primitive: 'Raw values such as colors, spacing, radius, typography, and motion primitives.',
|
|
274
|
+
semantic: 'Contextual intent tokens such as primary action, muted surface, emphasis text, or critical state.',
|
|
275
|
+
component: 'Component-scoped tokens that consume semantic aliases and preserve local consistency without redefining the system.',
|
|
276
|
+
},
|
|
277
|
+
platformOutputs: ['json-contract', 'css-variables'],
|
|
278
|
+
},
|
|
279
|
+
colorTruth: {
|
|
280
|
+
format: 'OKLCH',
|
|
281
|
+
allowHexDerivatives: true,
|
|
282
|
+
requirePerceptualLightnessCurve: true,
|
|
283
|
+
paletteRoles: ['base', 'surface', 'accent'],
|
|
284
|
+
intent: inferredKeywords.colorIntent,
|
|
285
|
+
},
|
|
286
|
+
crossViewportAdaptation: {
|
|
287
|
+
adaptByRecomposition: true,
|
|
288
|
+
touchTargetMinPx: 44,
|
|
289
|
+
mutationRules: inferredKeywords.mutationRules,
|
|
290
|
+
},
|
|
291
|
+
motionSystem: {
|
|
292
|
+
allowMeaningfulMotion: true,
|
|
293
|
+
purpose: inferredKeywords.motionPurpose,
|
|
294
|
+
choreography: inferredKeywords.motionChoreography,
|
|
295
|
+
desktopDurationMs: inferredKeywords.motionDurations.desktop,
|
|
296
|
+
mobileDurationMs: inferredKeywords.motionDurations.mobile,
|
|
297
|
+
respectReducedMotion: true,
|
|
298
|
+
preferTransformAndOpacity: true,
|
|
299
|
+
avoidDecorativeMotionForItsOwnSake: true,
|
|
300
|
+
},
|
|
301
|
+
componentMorphology: {
|
|
302
|
+
requireStateBehaviorMatrix: true,
|
|
303
|
+
preserveIdentityAcrossViewports: true,
|
|
304
|
+
stateKeys: ['default', 'hover', 'focus', 'active', 'disabled', 'loading', 'error'],
|
|
305
|
+
viewportBehavior: inferredKeywords.componentMorphology,
|
|
306
|
+
},
|
|
307
|
+
experiencePrinciples: [
|
|
308
|
+
'Design must feel project-specific, not interchangeable with generic SaaS templates.',
|
|
309
|
+
'Major interface decisions must be explainable in product and user terms.',
|
|
310
|
+
'Accessibility, responsiveness, and implementation realism are non-negotiable.',
|
|
311
|
+
'Cross-viewport behavior must reorganize tasks and navigation, not just scale the desktop layout down.',
|
|
312
|
+
'Motion may add character, memorability, and continuity when it improves the product experience, but it must stay purposeful, performant, and optional for reduced-motion users.',
|
|
313
|
+
'At least one surface, compositional move, typographic decision, or motion motif should be recognizable at a glance.',
|
|
314
|
+
],
|
|
315
|
+
forbiddenPatterns: [
|
|
316
|
+
'generic-saas-hero',
|
|
317
|
+
'copycat-brand-system',
|
|
318
|
+
'unjustified-default-gradients',
|
|
319
|
+
'placeholder-design-language',
|
|
320
|
+
'scale-only-responsive-layout',
|
|
321
|
+
],
|
|
322
|
+
validationHints: {
|
|
323
|
+
rejectArbitraryHexOnlyPalette: true,
|
|
324
|
+
requireViewportMutationRules: true,
|
|
325
|
+
requirePerceptualColorRationale: true,
|
|
326
|
+
requireTokenLayering: true,
|
|
327
|
+
requireTokenAliasingPlan: true,
|
|
328
|
+
allowHexDerivatives: true,
|
|
329
|
+
requireMotionRationale: true,
|
|
330
|
+
requireStateMorphology: true,
|
|
331
|
+
requireSignatureMove: true,
|
|
332
|
+
rejectTemplateNeutralLayout: true,
|
|
333
|
+
},
|
|
334
|
+
requiredDesignSections: DESIGN_REQUIRED_SECTIONS,
|
|
335
|
+
implementation: {
|
|
336
|
+
requiredDeliverables: ['docs/DESIGN.md', 'docs/design-intent.json'],
|
|
337
|
+
requireDesignRationale: true,
|
|
338
|
+
requireDistinctVisualDirection: true,
|
|
339
|
+
requireMachineReadableContract: true,
|
|
340
|
+
requireViewportMutationRules: true,
|
|
341
|
+
requirePurposefulMotionGuidelines: true,
|
|
342
|
+
requireRecognizableVisualBet: true,
|
|
343
|
+
bootstrapPrompt: '.agent-context/prompts/bootstrap-design.md',
|
|
344
|
+
autoLoadedRuleFiles: [
|
|
345
|
+
'.agent-context/prompts/bootstrap-design.md',
|
|
346
|
+
'.agent-context/rules/frontend-architecture.md',
|
|
347
|
+
],
|
|
348
|
+
disallowedAutoLoadedRuleFiles: [
|
|
349
|
+
'.agent-context/rules/database-design.md',
|
|
350
|
+
'.agent-context/rules/docker-runtime.md',
|
|
351
|
+
'.agent-context/rules/microservices.md',
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
...supplementalFields,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function validateDesignIntentContract(designIntentContract) {
|
|
359
|
+
const validationErrors = [];
|
|
360
|
+
|
|
361
|
+
if (!designIntentContract || typeof designIntentContract !== 'object') {
|
|
362
|
+
return ['Design intent contract must be an object.'];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (designIntentContract.mode !== 'dynamic') {
|
|
366
|
+
validationErrors.push('designIntent.mode must equal "dynamic".');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (!designIntentContract.project || typeof designIntentContract.project !== 'object') {
|
|
370
|
+
validationErrors.push('designIntent.project must exist.');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!designIntentContract.designPhilosophy || typeof designIntentContract.designPhilosophy !== 'string') {
|
|
374
|
+
validationErrors.push('designIntent.designPhilosophy must be a non-empty string.');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!designIntentContract.mathSystems || typeof designIntentContract.mathSystems !== 'object') {
|
|
378
|
+
validationErrors.push('designIntent.mathSystems must exist.');
|
|
379
|
+
} else {
|
|
380
|
+
if (!/^\d+(\.\d+)?$/.test(String(designIntentContract.mathSystems.typographyScaleRatio || '').trim())) {
|
|
381
|
+
validationErrors.push('designIntent.mathSystems.typographyScaleRatio must be numeric text.');
|
|
382
|
+
}
|
|
383
|
+
if (!Number.isInteger(designIntentContract.mathSystems.baseGridUnit) || designIntentContract.mathSystems.baseGridUnit <= 0) {
|
|
384
|
+
validationErrors.push('designIntent.mathSystems.baseGridUnit must be a positive integer.');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (!designIntentContract.tokenSystem || typeof designIntentContract.tokenSystem !== 'object') {
|
|
389
|
+
validationErrors.push('designIntent.tokenSystem must exist.');
|
|
390
|
+
} else {
|
|
391
|
+
const taxonomyOrder = designIntentContract.tokenSystem.taxonomyOrder;
|
|
392
|
+
if (!Array.isArray(taxonomyOrder) || taxonomyOrder.join('|') !== 'primitive|semantic|component') {
|
|
393
|
+
validationErrors.push('designIntent.tokenSystem.taxonomyOrder must equal ["primitive","semantic","component"].');
|
|
394
|
+
}
|
|
395
|
+
if (designIntentContract.tokenSystem.primitiveColorSpace !== 'OKLCH') {
|
|
396
|
+
validationErrors.push('designIntent.tokenSystem.primitiveColorSpace must equal "OKLCH".');
|
|
397
|
+
}
|
|
398
|
+
if (designIntentContract.tokenSystem.requireSemanticAliases !== true) {
|
|
399
|
+
validationErrors.push('designIntent.tokenSystem.requireSemanticAliases must equal true.');
|
|
400
|
+
}
|
|
401
|
+
if (designIntentContract.tokenSystem.semanticAliasesMutableWithoutComponentRewrite !== true) {
|
|
402
|
+
validationErrors.push('designIntent.tokenSystem.semanticAliasesMutableWithoutComponentRewrite must equal true.');
|
|
403
|
+
}
|
|
404
|
+
if (designIntentContract.tokenSystem.componentTokensConsumeSemantic !== true) {
|
|
405
|
+
validationErrors.push('designIntent.tokenSystem.componentTokensConsumeSemantic must equal true.');
|
|
406
|
+
}
|
|
407
|
+
const fallbackPolicy = designIntentContract.tokenSystem.fallbackPolicy;
|
|
408
|
+
if (!fallbackPolicy || typeof fallbackPolicy !== 'object') {
|
|
409
|
+
validationErrors.push('designIntent.tokenSystem.fallbackPolicy must exist.');
|
|
410
|
+
} else {
|
|
411
|
+
if (fallbackPolicy.forbidRawHexOutsidePrimitives !== true) {
|
|
412
|
+
validationErrors.push('designIntent.tokenSystem.fallbackPolicy.forbidRawHexOutsidePrimitives must equal true.');
|
|
413
|
+
}
|
|
414
|
+
if (fallbackPolicy.forbidRawSpacingOutsidePrimitives !== true) {
|
|
415
|
+
validationErrors.push('designIntent.tokenSystem.fallbackPolicy.forbidRawSpacingOutsidePrimitives must equal true.');
|
|
416
|
+
}
|
|
417
|
+
if (fallbackPolicy.requireDocumentedExceptionForLegacyBypass !== true) {
|
|
418
|
+
validationErrors.push('designIntent.tokenSystem.fallbackPolicy.requireDocumentedExceptionForLegacyBypass must equal true.');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const namingConstraints = designIntentContract.tokenSystem.namingConstraints;
|
|
422
|
+
if (!namingConstraints || typeof namingConstraints !== 'object') {
|
|
423
|
+
validationErrors.push('designIntent.tokenSystem.namingConstraints must exist.');
|
|
424
|
+
} else {
|
|
425
|
+
if (namingConstraints.forbidCurlyBracesInNames !== true) {
|
|
426
|
+
validationErrors.push('designIntent.tokenSystem.namingConstraints.forbidCurlyBracesInNames must equal true.');
|
|
427
|
+
}
|
|
428
|
+
if (namingConstraints.forbidDotsInNames !== true) {
|
|
429
|
+
validationErrors.push('designIntent.tokenSystem.namingConstraints.forbidDotsInNames must equal true.');
|
|
430
|
+
}
|
|
431
|
+
if (namingConstraints.forbidSquareBracketsInNames !== true) {
|
|
432
|
+
validationErrors.push('designIntent.tokenSystem.namingConstraints.forbidSquareBracketsInNames must equal true.');
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!designIntentContract.colorTruth || typeof designIntentContract.colorTruth !== 'object') {
|
|
438
|
+
validationErrors.push('designIntent.colorTruth must exist.');
|
|
439
|
+
} else {
|
|
440
|
+
if (designIntentContract.colorTruth.format !== 'OKLCH') {
|
|
441
|
+
validationErrors.push('designIntent.colorTruth.format must equal "OKLCH".');
|
|
442
|
+
}
|
|
443
|
+
if (designIntentContract.colorTruth.allowHexDerivatives !== true) {
|
|
444
|
+
validationErrors.push('designIntent.colorTruth.allowHexDerivatives must equal true.');
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (!designIntentContract.crossViewportAdaptation || typeof designIntentContract.crossViewportAdaptation !== 'object') {
|
|
449
|
+
validationErrors.push('designIntent.crossViewportAdaptation must exist.');
|
|
450
|
+
} else {
|
|
451
|
+
const mutationRules = designIntentContract.crossViewportAdaptation.mutationRules;
|
|
452
|
+
if (!mutationRules || typeof mutationRules !== 'object') {
|
|
453
|
+
validationErrors.push('designIntent.crossViewportAdaptation.mutationRules must exist.');
|
|
454
|
+
} else {
|
|
455
|
+
for (const viewportKey of ['mobile', 'tablet', 'desktop']) {
|
|
456
|
+
if (!String(mutationRules[viewportKey] || '').trim()) {
|
|
457
|
+
validationErrors.push(`designIntent.crossViewportAdaptation.mutationRules.${viewportKey} must be a non-empty string.`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (!designIntentContract.motionSystem || typeof designIntentContract.motionSystem !== 'object') {
|
|
464
|
+
validationErrors.push('designIntent.motionSystem must exist.');
|
|
465
|
+
} else {
|
|
466
|
+
if (designIntentContract.motionSystem.allowMeaningfulMotion !== true) {
|
|
467
|
+
validationErrors.push('designIntent.motionSystem.allowMeaningfulMotion must equal true.');
|
|
468
|
+
}
|
|
469
|
+
if (!String(designIntentContract.motionSystem.purpose || '').trim()) {
|
|
470
|
+
validationErrors.push('designIntent.motionSystem.purpose must be a non-empty string.');
|
|
471
|
+
}
|
|
472
|
+
if (designIntentContract.motionSystem.respectReducedMotion !== true) {
|
|
473
|
+
validationErrors.push('designIntent.motionSystem.respectReducedMotion must equal true.');
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (!designIntentContract.componentMorphology || typeof designIntentContract.componentMorphology !== 'object') {
|
|
478
|
+
validationErrors.push('designIntent.componentMorphology must exist.');
|
|
479
|
+
} else {
|
|
480
|
+
if (designIntentContract.componentMorphology.requireStateBehaviorMatrix !== true) {
|
|
481
|
+
validationErrors.push('designIntent.componentMorphology.requireStateBehaviorMatrix must equal true.');
|
|
482
|
+
}
|
|
483
|
+
if (!Array.isArray(designIntentContract.componentMorphology.stateKeys) || designIntentContract.componentMorphology.stateKeys.length < 4) {
|
|
484
|
+
validationErrors.push('designIntent.componentMorphology.stateKeys must contain multiple interaction states.');
|
|
485
|
+
}
|
|
486
|
+
const viewportBehavior = designIntentContract.componentMorphology.viewportBehavior;
|
|
487
|
+
if (!viewportBehavior || typeof viewportBehavior !== 'object') {
|
|
488
|
+
validationErrors.push('designIntent.componentMorphology.viewportBehavior must exist.');
|
|
489
|
+
} else {
|
|
490
|
+
for (const viewportKey of ['mobile', 'tablet', 'desktop']) {
|
|
491
|
+
if (!String(viewportBehavior[viewportKey] || '').trim()) {
|
|
492
|
+
validationErrors.push(`designIntent.componentMorphology.viewportBehavior.${viewportKey} must be a non-empty string.`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (!Array.isArray(designIntentContract.requiredDesignSections) || designIntentContract.requiredDesignSections.length !== DESIGN_REQUIRED_SECTIONS.length) {
|
|
499
|
+
validationErrors.push('designIntent.requiredDesignSections must match the required design contract sections.');
|
|
500
|
+
} else {
|
|
501
|
+
for (const requiredSectionName of DESIGN_REQUIRED_SECTIONS) {
|
|
502
|
+
if (!designIntentContract.requiredDesignSections.includes(requiredSectionName)) {
|
|
503
|
+
validationErrors.push(`designIntent.requiredDesignSections is missing "${requiredSectionName}".`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return validationErrors;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export function buildDesignIntentSeedFromSignals({
|
|
512
|
+
projectName,
|
|
513
|
+
projectDescription,
|
|
514
|
+
primaryDomain,
|
|
515
|
+
features = [],
|
|
516
|
+
initContext,
|
|
517
|
+
architectureRecommendation = null,
|
|
518
|
+
status = 'seed-needs-design-synthesis',
|
|
519
|
+
supplementalFields = {},
|
|
520
|
+
}) {
|
|
521
|
+
const designIntentContract = buildDesignIntentContractObject({
|
|
522
|
+
projectName,
|
|
523
|
+
projectDescription,
|
|
524
|
+
primaryDomain,
|
|
525
|
+
features,
|
|
526
|
+
initContext,
|
|
527
|
+
architectureRecommendation,
|
|
528
|
+
status,
|
|
529
|
+
supplementalFields,
|
|
530
|
+
});
|
|
531
|
+
const validationErrors = validateDesignIntentContract(designIntentContract);
|
|
532
|
+
|
|
533
|
+
if (validationErrors.length > 0) {
|
|
534
|
+
throw new Error(`Invalid design intent contract seed: ${validationErrors.join(' ')}`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return `${JSON.stringify(designIntentContract, null, 2)}\n`;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export function buildDesignIntentSeed({
|
|
541
|
+
discoveryAnswers,
|
|
542
|
+
initContext,
|
|
543
|
+
architectureRecommendation,
|
|
544
|
+
}) {
|
|
545
|
+
return buildDesignIntentSeedFromSignals({
|
|
546
|
+
projectName: discoveryAnswers.projectName,
|
|
547
|
+
projectDescription: discoveryAnswers.projectDescription,
|
|
548
|
+
primaryDomain: discoveryAnswers.primaryDomain,
|
|
549
|
+
features: discoveryAnswers.features,
|
|
550
|
+
initContext,
|
|
551
|
+
architectureRecommendation,
|
|
552
|
+
status: 'seed-needs-design-synthesis',
|
|
553
|
+
});
|
|
554
|
+
}
|