@ryuenn3123/agentic-senior-core 4.3.2 → 4.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.agent-context/prompts/bootstrap-design.md +56 -222
  2. package/.agent-context/rules/api-docs.md +17 -126
  3. package/.agent-context/rules/api-versioning.md +9 -86
  4. package/.agent-context/rules/architecture.md +18 -136
  5. package/.agent-context/rules/background-jobs.md +9 -85
  6. package/.agent-context/rules/config-and-flags.md +8 -71
  7. package/.agent-context/rules/database-design.md +9 -65
  8. package/.agent-context/rules/docker-runtime.md +9 -62
  9. package/.agent-context/rules/efficiency-vs-hype.md +7 -37
  10. package/.agent-context/rules/error-handling.md +8 -33
  11. package/.agent-context/rules/event-driven.md +8 -34
  12. package/.agent-context/rules/frontend-architecture.md +22 -140
  13. package/.agent-context/rules/git-workflow.md +8 -77
  14. package/.agent-context/rules/microservices.md +8 -36
  15. package/.agent-context/rules/migrations.md +8 -76
  16. package/.agent-context/rules/observability.md +7 -60
  17. package/.agent-context/rules/performance.md +8 -28
  18. package/.agent-context/rules/realtime.md +7 -22
  19. package/.agent-context/rules/resilience.md +9 -69
  20. package/.agent-context/rules/security.md +9 -64
  21. package/.agent-context/rules/testing.md +8 -34
  22. package/AGENTS.md +11 -18
  23. package/README.md +1 -1
  24. package/lib/cli/adaptive-context/catalog.mjs +1 -6
  25. package/lib/cli/commands/audit-design-anti-repeat.mjs +26 -185
  26. package/lib/cli/commands/init/project-context.mjs +0 -41
  27. package/lib/cli/commands/init.mjs +12 -41
  28. package/lib/cli/commands/upgrade.mjs +12 -35
  29. package/lib/cli/compiler.mjs +5 -81
  30. package/lib/cli/preflight.mjs +0 -21
  31. package/lib/cli/project-scaffolder/constants.mjs +1 -1
  32. package/lib/cli/project-scaffolder/design-contract.mjs +3 -45
  33. package/lib/cli/project-scaffolder/prompt-builders.mjs +18 -161
  34. package/lib/cli/project-scaffolder/storage.mjs +0 -9
  35. package/lib/cli/project-scaffolder.mjs +0 -1
  36. package/package.json +1 -1
  37. package/scripts/frontend-usability-audit.mjs +4 -45
  38. package/scripts/release-gate/constants.mjs +1 -0
  39. package/scripts/release-gate/static-checks.mjs +0 -36
  40. package/scripts/validate/config.mjs +20 -144
  41. package/scripts/validate/coverage-checks.mjs +2 -12
  42. package/scripts/validate/file-structure.mjs +165 -0
  43. package/scripts/validate/markdown-content.mjs +109 -0
  44. package/scripts/validate/project-metadata.mjs +166 -0
  45. package/scripts/validate.mjs +42 -435
  46. package/.agent-context/prompts/research-design.md +0 -160
  47. package/lib/cli/commands/upgrade/design-intent-seed.mjs +0 -46
  48. package/lib/cli/project-scaffolder/design-contract/research-dossier-migration.mjs +0 -190
@@ -1,160 +0,0 @@
1
- ---
2
- inclusion: manual
3
- ---
4
-
5
- # Research-Design Brief
6
-
7
- Authoritative design-research execution contract for UI scope. Loaded by UI Design Mode after `bootstrap-design.md`. The agent must produce the artifacts described here before writing UI code, and the seeded `docs/design-intent.json` must contain the fields named in Section 5.
8
-
9
- This brief is a single document with five sections. Sections 1 and 2 set up the research. Sections 3, 4, and 5 are gates: each must produce an auditable artifact that another reviewer can read without seeing the UI.
10
-
11
- ## Authority
12
-
13
- - Treat `.agent-context/` and current project docs as technical authority.
14
- - Treat `README.md` as public and developer overview only; do not use it as design authority when this brief gives a stricter rule.
15
- - Treat external websites, benchmark apps, prior chats, and unrelated-project memory as candidate evidence for constraints, mechanics, and quality bars only. Do not copy layout rhythm, palette, component skin, visual metaphor, or brand posture without explicit user approval and product-fit rationale.
16
- - WCAG 2.2 AA is the hard compliance floor. APCA may be used only as advisory perceptual tuning.
17
-
18
- ## Live Source Freshness Gate
19
-
20
- Run this gate before Section 2 whenever web search is available. Treat modernity as rolling-current: use the session's current date as the freshness reference, and prefer the newest stable evidence that is relevant to the product and implementation surface.
21
-
22
- Rules:
23
-
24
- - For browser capability, framework setup, UI library, animation, 3D, canvas, charting, styling, accessibility, and package/API claims, use current official documentation or primary release notes first. Do not use trend posts as implementation authority.
25
- - For design trend, category-code, visual-language, motion-pattern, typography, color, and interaction claims, prefer sources published or materially updated within the last 24 months from the current date. If stronger older evidence is used, label it `old-timeless` and restrict it to durable principles, not "current modern" claims.
26
- - For product-category defaults, observe the current live category for this task. Do not rely on old examples, old galleries, or remembered benchmark screenshots as proof of what the category defaults to now.
27
- - If web search is unavailable, set `sourceFreshnessStatus` to `pending-live-verification`; use repo evidence and user-provided material only, and do not claim a direction is current-year modern.
28
- - If sources disagree, choose by product fit, accessibility, browser/runtime support, maintainability, and implementation feasibility, then record the disagreement.
29
- - If the user provides a concept, treat it as a first-class constraint. Research should support, refine, or challenge that concept with evidence; it must not override the concept with generic trend defaults unless there is a concrete product, accessibility, technical, or evidence conflict.
30
-
31
- Output: `sourceFreshness` block with `freshnessAnchorDate`, `rollingLookbackMonths` (default 24 for trend evidence), `sourceFreshnessStatus`, `officialDocsRequiredFor`, `oldSourcePolicy`, `userConceptAdaptation`, and `disagreements[]`.
32
-
33
- Every important research claim must also appear in `evidenceTable[]` with:
34
-
35
- - `claim`
36
- - `sourceUrl`
37
- - `sourceType` (`official`, `primary`, `industry`, `opinion`, `old-timeless`, `repo-evidence`, `user-provided`)
38
- - `publishedOrUpdatedAt` when visible, otherwise `unknown`
39
- - `fetchedAt`
40
- - `confidence` (`high`, `medium`, `low`)
41
- - `decisionImpact`
42
-
43
- Research vocabulary is internal-only. Use `sourceFreshness`, `evidenceTable[]`, `researchDossier`, `anchor`, `categoryCodes`, `morphologicalExploration`, and `renameTest` to audit decisions, but do not expose those labels in UI copy, public-facing docs, section headings, or final user-facing rationale unless the user explicitly asks for the research trace.
44
-
45
- ## Anti-Repeat Ledger Gate (read first)
46
-
47
- If `docs/design-intent.json` already exists and carries `researchDossier.metadata.antiRepeatLedger`, treat every entry under `previousAnchors`, `previousPalettes`, `previousMotionSignatures`, and `previousStructuralFingerprints` as a hard blocklist before producing any candidate in Sections 3-5.
48
-
49
- Rules:
50
-
51
- - The five Section 5 anchor candidates must each differ from every blocklisted entry on at least conceptual family, hierarchy implication, and motion implication.
52
- - Restating an existing direction with new wording is REVISE, not pass.
53
- - A user-explicit redesign request ("redesign from zero", "redesain dari 0", "ulang dari 0", "research ulang", or any explicit reset) bypasses the freshness gate but does not weaken the ledger; previously shipped direction stays blocklisted unless the user explicitly says "revive existing direction".
54
- - Ledger entries are signature-level descriptors, not raw token dumps; treat them as direction summaries.
55
-
56
- If the ledger is empty or `researchDossier.metadata.researchVerifiedAt` is null because the contract is a fresh seed, the ledger is informational only and does not add blocklist entries.
57
-
58
- ## Section 1 — Product Reading
59
-
60
- Before any visual choice, write a structured product reading:
61
-
62
- - Product type and core verb (what the user does, not what the UI shows).
63
- - Three highest-stakes user moments, ordered by frequency.
64
- - Data shapes that dominate the screen (timeseries, ledger, list, document, control, telemetry, conversational, spatial, other).
65
- - Latency profile (real-time, soft real-time, batch, ambient).
66
- - Failure modes the UI must absorb visibly (partial, stale, optimistic, conflict, offline, permission, rate-limit, none).
67
- - Context of use (one-shot, sustained focus, glance-and-go, background monitor, shared display, embedded).
68
- - Known constraints (device, runtime, accessibility, regulatory, performance budget, brand continuity).
69
-
70
- Output: `productReading` block. Each field must be one sentence, evidence-backed from repo or brief. Speculation is not allowed; if a field is unknown, name it as such and stop until the user resolves it.
71
-
72
- ## Default Detection Protocol
73
-
74
- Before fetching any reference, use the `productReading` output to derive search direction. Do not self-select from a fixed category list.
75
-
76
- 1. **Name defaults.** Name three design directions the agent is most tempted to use for this brief. For each, write one sentence naming the specific visual pattern (layout, palette, density, motion, typography, composition).
77
-
78
- 2. **Argue against each default.** For each default, write one sentence on why it would flatten what is specific about THIS product's core verb, data shape, or context of use. The argument must reference a `productReading` field, not a generic aesthetic opinion.
79
-
80
- 3. **Derive search direction from the argument.** The rejection argument reveals what the product needs that the default cannot provide. Use that need as the search query for `web_search` and `web_fetch`. Search by the product's core characteristic pair (medium x intent), not by product category name. Example: core verb "demonstrate" + data shape "narrative" -> search "editorial portfolio interaction design" not "portfolio sites."
81
-
82
- 4. **Cross-domain pull.** Identify one domain that shares the same interaction model but a different visual vocabulary. Pull one reference from there. Example: portfolio -> scientific publication, exhibition catalog, film press kit -- anything with "demonstrate body of work" as the core verb but a different surface expectation.
83
-
84
- 5. **Gallery check.** Find state-of-the-art quality references for the derived direction by searching the web. CRITICAL: DO NOT use gallery homepages or discovery platforms as references. You must drill down to find a specific featured product/website, fetch THAT specific product's URL, and use it as evidence.
85
-
86
- DO NOT fetch: Wikipedia, Dribbble templates, or generic blog posts about "best UI design practices."
87
-
88
- For each reference: fetch the actual product page this session, record what mechanic or hierarchy is borrowed, and explicitly state what is NOT borrowed (palette, component skin, layout rhythm).
89
-
90
- If the anchor concept you're considering would make sense to someone from 1920, it's too archival. The anchor must be legible to someone who uses the web today.
91
-
92
- ## Section 2 — Reference Intake
93
-
94
- Reference material is fuel for variance, not a style source.
95
-
96
- - Capture between three and seven references per dimension that needs exploration: hierarchy, density, type system, motion, state language, material logic, color behavior.
97
- - For each reference, record: source URL or citation, what is borrowed (mechanic, behavior, hierarchy, density, type pairing, motion choreography), and what is explicitly not borrowed (palette, component skin, layout rhythm, brand posture).
98
- - References live in `referenceIntake[]`. The agent may not select an anchor in Section 5 that copies a reference's surface; only the borrowed mechanic is allowed to flow downstream.
99
-
100
- If references are not provided by the user and web search is unavailable, set `referenceIntakeStatus` to `internal-evidence-only` and constrain Sections 3 to 5 to repo evidence and project docs.
101
-
102
- ## Section 3 — Creative Direction
103
-
104
- Before UI code, commit to a specific visual direction. The agent tends to converge toward generic, "on distribution" outputs. In frontend design this creates what users call the "AI slop" aesthetic. This section exists to prevent that.
105
-
106
- ### 3a. Category defaults to avoid
107
-
108
- Name at least three specific cliches your product category will fall into without intervention. Be specific enough that someone unfamiliar with the project can visualize the exact aesthetic trap from the text alone.
109
-
110
- Common AI-safe cliches to name and reject when your product sits anywhere near them:
111
-
112
- - `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`
113
- - `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`
114
- - `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`
115
- - `marketing site default: hero image with one-line headline plus subhead, three feature tiles below, two pricing tiers, testimonial carousel, footer link grid`
116
-
117
- Each category default must include a one-sentence rejection note explaining the trap it sets.
118
-
119
- Output: `categoryCodes[]` with at least three entries in `docs/design-intent.json`. Each entry has `description`, `categoryDefaultReason`, and `rejectionNote`.
120
-
121
- ### 3b. Anchor reference
122
-
123
- Pick one concrete, googleable real-world reference whose mechanics (not surface) translate to your UI. The anchor must be specific enough that renaming the product to a different category breaks coherence.
124
-
125
- Hard constraints:
126
- - Reject generic quality words as anchors: "modern", "clean", "premium", "expressive", "minimal", "bold", "futuristic", "elegant" are not anchors.
127
- - Specific premium digital products are valid anchors when the borrowed element is an interaction mechanic, hierarchy pattern, or motion behavior -- not a palette or brand.
128
- - Do not default to spatial place metaphors (room, darkroom, control room, studio, lab, cockpit, command center). Prefer artifacts, workflows, instruments, data behaviors, materials, editorial systems, or interaction mechanisms.
129
- - Draw from IDE themes, cultural aesthetics, cinematic campaigns, editorial systems, material artifacts, or specific product experiences for inspiration.
130
-
131
- Output: `conceptualAnchor.anchorReference` in `docs/design-intent.json`.
132
-
133
- ### 3c. Four creative commitments (record before coding)
134
-
135
- 1. **Typography**: Choose distinctive fonts with meaningful role contrast. Avoid overused families (Inter, Roboto, Arial, Space Grotesk, system fonts). Pick choices that elevate the aesthetic. Record one variable-axis or pairing decision.
136
- 2. **Color and palette**: Commit to a cohesive product-derived palette. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. Name what product evidence makes the palette fit and one color behavior that would not transfer to another category.
137
- 3. **Motion and interaction**: Define one signature motion behavior more specific than "smooth." Focus on high-impact moments: one well-orchestrated page load with staggered reveals creates more delight than scattered micro-interactions. Use CSS-only or a modern motion library.
138
- 4. **Composition**: Make one composition choice that avoids interchangeable card stacks. Create atmosphere and depth rather than defaulting to solid backgrounds.
139
-
140
- Avoid generic AI-generated aesthetics:
141
- - Overused font families and cliched color schemes (particularly purple gradients)
142
- - Predictable layouts and cookie-cutter component patterns
143
- - Solid-color backgrounds without atmosphere or depth
144
-
145
- Interpret creatively and make unexpected choices that feel genuinely designed for the context. Vary between light and dark themes, different fonts, different aesthetics. The agent still tends to converge on common choices across generations; resist this.
146
-
147
- Output: Record all four commitments in `docs/design-intent.json` before UI code. The `derivedTokenLogic.tokenContinuityClassification` must classify each of typography, palette, motion, and spacing as `anchor-derived`, `continuity-retained`, or `newly-introduced` with rationale.
148
-
149
- ## Done Criteria
150
-
151
- The brief is complete when:
152
-
153
- 1. `productReading` is filled with evidence-backed sentences.
154
- 2. `referenceIntake[]` records the borrowed mechanic and the explicit non-copy boundary per reference (or `referenceIntakeStatus: internal-evidence-only` is set).
155
- 3. `categoryCodes[]` has at least three entries with rejection notes.
156
- 4. One anchor reference is recorded that passes the specificity test (renaming the product to a different category breaks coherence).
157
- 5. Four creative commitments are recorded with product-derived rationale.
158
- 6. Generic anchors and spatial-place defaults are rejected with the rejection reason recorded.
159
-
160
- Only after the brief is complete does the agent move on to `docs/DESIGN.md` and the rest of `docs/design-intent.json` (token logic, motion budget, accessibility policy, review rubric, library decisions, etc., per `bootstrap-design.md`).
@@ -1,46 +0,0 @@
1
- import path from 'node:path';
2
-
3
- import { buildDesignIntentSeedFromSignals } from '../../project-scaffolder.mjs';
4
-
5
- export function buildUpgradeDesignIntentSeed({
6
- targetDirectoryPath,
7
- packageManifest,
8
- selectedStackFileName,
9
- selectedBlueprintFileName,
10
- uiScopeSignals,
11
- }) {
12
- const projectName = String(packageManifest?.name || path.basename(targetDirectoryPath)).trim()
13
- || 'existing-ui-project';
14
- const isMobileUiProject = String(selectedStackFileName || '').toLowerCase().includes('react-native')
15
- || String(selectedStackFileName || '').toLowerCase().includes('flutter')
16
- || uiScopeSignals.signalReasons.some((signalReason) => {
17
- return signalReason.includes('android') || signalReason.includes('ios');
18
- });
19
- const resolvedDomain = isMobileUiProject ? 'Mobile app' : 'Web application';
20
- const projectDescription = String(packageManifest?.description || '').trim()
21
- || `Existing ${resolvedDomain.toLowerCase()} detected during upgrade. Create a project-specific dynamic design contract before shipping new UI work.`;
22
-
23
- return buildDesignIntentSeedFromSignals({
24
- projectName,
25
- projectDescription,
26
- primaryDomain: resolvedDomain,
27
- features: [],
28
- initContext: {
29
- stackFileName: selectedStackFileName,
30
- blueprintFileName: selectedBlueprintFileName,
31
- },
32
- status: 'seed-generated-during-upgrade',
33
- supplementalFields: {
34
- upgradeSignals: {
35
- detectedFrom: uiScopeSignals.signalReasons,
36
- generatedBy: 'upgrade-seed',
37
- },
38
- repoEvidence: {
39
- uiSignalReasons: uiScopeSignals.signalReasons,
40
- frontendMetrics: uiScopeSignals.frontendEvidenceMetrics || null,
41
- designEvidenceSummary: uiScopeSignals.designEvidenceSummary || null,
42
- workspaceUiEntries: uiScopeSignals.workspaceUiEntries || [],
43
- },
44
- },
45
- });
46
- }
@@ -1,190 +0,0 @@
1
- /**
2
- * Research-dossier migration helper.
3
- *
4
- * Adds the `researchDossier.metadata` block to existing `docs/design-intent.json`
5
- * files when it is absent, populating `antiRepeatLedger` from the existing
6
- * conceptual anchor, palette, and motion fields so future UI work cannot
7
- * unknowingly repeat shipped direction.
8
- *
9
- * Idempotent: if the metadata block already exists, the file is left untouched.
10
- * Additive: never overwrites existing fields.
11
- */
12
-
13
- import fs from 'node:fs/promises';
14
-
15
- import { pathExists } from '../../utils.mjs';
16
-
17
- const FRESHNESS_WINDOW_DAYS = 90;
18
- const FRESHNESS_RULE = 'Research dossier is stale when researchVerifiedAt is null or older than freshnessWindowDays. Stale dossiers must run research-design.md before UI implementation. User-explicit redesign requests bypass freshness and force fresh research regardless of age.';
19
-
20
- function takeFirstNonEmpty(...candidateValues) {
21
- for (const candidateValue of candidateValues) {
22
- if (typeof candidateValue === 'string' && candidateValue.trim().length > 0) {
23
- return candidateValue.trim();
24
- }
25
- }
26
- return null;
27
- }
28
-
29
- function buildPreviousAnchorEntry(designIntentContract) {
30
- const conceptualAnchor = designIntentContract?.conceptualAnchor;
31
- if (!conceptualAnchor || typeof conceptualAnchor !== 'object') {
32
- return [];
33
- }
34
- const anchorReference = takeFirstNonEmpty(conceptualAnchor.anchorReference);
35
- if (!anchorReference || anchorReference === 'agent-defined-anchor-reference') {
36
- return [];
37
- }
38
- const specificReferencePoint = takeFirstNonEmpty(conceptualAnchor.specificReferencePoint);
39
- const summary = specificReferencePoint
40
- ? `${anchorReference} (${specificReferencePoint})`
41
- : anchorReference;
42
- return [{
43
- summary,
44
- source: 'migrated-from-existing-design-intent',
45
- blockedBecause: 'previously-shipped-direction',
46
- }];
47
- }
48
-
49
- function buildPreviousPaletteEntry(designIntentContract) {
50
- const derivedTokenLogic = designIntentContract?.derivedTokenLogic;
51
- const colorTruth = designIntentContract?.colorTruth;
52
- const colorDerivationSummary = takeFirstNonEmpty(derivedTokenLogic?.colorDerivationSource);
53
- const colorIntent = takeFirstNonEmpty(colorTruth?.intent);
54
- const summary = takeFirstNonEmpty(colorIntent, colorDerivationSummary);
55
- if (!summary) {
56
- return [];
57
- }
58
- return [{
59
- summary,
60
- source: 'migrated-from-existing-design-intent',
61
- blockedBecause: 'previously-shipped-palette-behavior',
62
- }];
63
- }
64
-
65
- function buildPreviousMotionEntry(designIntentContract) {
66
- const motionPaletteDecision = designIntentContract?.motionPaletteDecision;
67
- const motionSystem = designIntentContract?.motionSystem;
68
- const derivedTokenLogic = designIntentContract?.derivedTokenLogic;
69
-
70
- const motionSignature = takeFirstNonEmpty(
71
- motionPaletteDecision?.signatureMotion,
72
- motionPaletteDecision?.motion,
73
- motionSystem?.signature,
74
- motionSystem?.purpose,
75
- derivedTokenLogic?.motionBudget,
76
- );
77
- if (!motionSignature) {
78
- return [];
79
- }
80
- return [{
81
- summary: motionSignature,
82
- source: 'migrated-from-existing-design-intent',
83
- blockedBecause: 'previously-shipped-motion-signature',
84
- }];
85
- }
86
-
87
- function buildPreviousTypographyEntry(designIntentContract) {
88
- if (!designIntentContract || typeof designIntentContract !== 'object') {
89
- return [];
90
- }
91
- const tokenSystem = designIntentContract?.tokenSystem;
92
- const typographyTokens = tokenSystem && typeof tokenSystem === 'object' ? tokenSystem.typographyTokens : null;
93
- if (!typographyTokens || typeof typographyTokens !== 'object') {
94
- return [];
95
- }
96
- const tokenEntries = Object.entries(typographyTokens)
97
- .filter(([, value]) => typeof value === 'string' && value.trim().length > 0)
98
- .map(([role, value]) => `${role}: ${value.trim()}`);
99
- if (tokenEntries.length === 0) {
100
- return [];
101
- }
102
- return [{
103
- summary: tokenEntries.join('; '),
104
- source: 'migrated-from-existing-design-intent',
105
- blockedBecause: 'previously-shipped-typography-trio',
106
- }];
107
- }
108
-
109
- export function buildResearchDossierMetadata({
110
- designIntentContract = null,
111
- populateLedgerFromExistingContract = false,
112
- } = {}) {
113
- const metadata = {
114
- researchVerifiedAt: null,
115
- freshnessWindowDays: FRESHNESS_WINDOW_DAYS,
116
- freshnessRule: FRESHNESS_RULE,
117
- antiRepeatLedger: {
118
- blocklistFromHistory: true,
119
- ledgerScope: 'signature-level-descriptors-only',
120
- ledgerMaxEntriesPerCategory: 3,
121
- previousAnchors: [],
122
- previousPalettes: [],
123
- previousMotionSignatures: [],
124
- previousTypographyChoices: [],
125
- previousStructuralFingerprints: [],
126
- },
127
- userExplicitRedesignBypassesFreshness: true,
128
- statusAwareValidation: {
129
- seedStatuses: [
130
- 'seed-needs-design-synthesis',
131
- 'seed-generated-during-init',
132
- 'seed-generated-during-upgrade',
133
- ],
134
- seedSkipsDossierShape: true,
135
- activeRequiresFreshOrExplicitRedesign: true,
136
- },
137
- };
138
-
139
- if (populateLedgerFromExistingContract && designIntentContract && typeof designIntentContract === 'object') {
140
- metadata.antiRepeatLedger.previousAnchors = buildPreviousAnchorEntry(designIntentContract).slice(0, metadata.antiRepeatLedger.ledgerMaxEntriesPerCategory);
141
- metadata.antiRepeatLedger.previousPalettes = buildPreviousPaletteEntry(designIntentContract).slice(0, metadata.antiRepeatLedger.ledgerMaxEntriesPerCategory);
142
- metadata.antiRepeatLedger.previousMotionSignatures = buildPreviousMotionEntry(designIntentContract).slice(0, metadata.antiRepeatLedger.ledgerMaxEntriesPerCategory);
143
- metadata.antiRepeatLedger.previousTypographyChoices = buildPreviousTypographyEntry(designIntentContract).slice(0, metadata.antiRepeatLedger.ledgerMaxEntriesPerCategory);
144
- }
145
-
146
- return metadata;
147
- }
148
-
149
- /**
150
- * Migrates an existing design-intent.json file in place by adding
151
- * `researchDossier.metadata` when absent. Idempotent.
152
- *
153
- * @param {string} designIntentFilePath
154
- * @returns {Promise<{ migrated: boolean, reason: string }>}
155
- */
156
- export async function migrateExistingDesignIntentToResearchDossierSchema(designIntentFilePath) {
157
- if (!(await pathExists(designIntentFilePath))) {
158
- return { migrated: false, reason: 'design-intent-file-absent' };
159
- }
160
-
161
- const fileContent = await fs.readFile(designIntentFilePath, 'utf8');
162
- let designIntentContract;
163
- try {
164
- designIntentContract = JSON.parse(fileContent);
165
- } catch {
166
- return { migrated: false, reason: 'design-intent-file-not-valid-json' };
167
- }
168
-
169
- if (!designIntentContract || typeof designIntentContract !== 'object') {
170
- return { migrated: false, reason: 'design-intent-file-not-an-object' };
171
- }
172
-
173
- const existingResearchDossier = designIntentContract.researchDossier;
174
- if (existingResearchDossier && typeof existingResearchDossier === 'object' && existingResearchDossier.metadata) {
175
- return { migrated: false, reason: 'research-dossier-metadata-already-present' };
176
- }
177
-
178
- const populatedMetadata = buildResearchDossierMetadata({
179
- designIntentContract,
180
- populateLedgerFromExistingContract: true,
181
- });
182
-
183
- designIntentContract.researchDossier = {
184
- ...(existingResearchDossier && typeof existingResearchDossier === 'object' ? existingResearchDossier : {}),
185
- metadata: populatedMetadata,
186
- };
187
-
188
- await fs.writeFile(designIntentFilePath, `${JSON.stringify(designIntentContract, null, 2)}\n`, 'utf8');
189
- return { migrated: true, reason: 'research-dossier-metadata-injected' };
190
- }