@codeyam/codeyam-cli 0.1.0-staging.8778565 → 0.1.0-staging.8b51541
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/analyzer-template/.build-info.json +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +56 -0
- package/analyzer-template/packages/database/src/lib/loadEntities.ts +0 -6
- package/analyzer-template/packages/database/src/lib/updateCommitMetadata.ts +0 -65
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +3 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +56 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +0 -6
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +0 -25
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +45 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +101 -47
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +799 -211
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/init.js +68 -34
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js +173 -0
- package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorApi.test.js +18 -8
- package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +353 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +128 -1
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +88 -1
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +47 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +785 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +63 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js +30 -2
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +227 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +426 -218
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +1 -0
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +9 -0
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/analyzerFinalization.js +100 -0
- package/codeyam-cli/src/utils/analyzerFinalization.js.map +1 -0
- package/codeyam-cli/src/utils/backgroundServer.js +2 -8
- package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/utils/database.js +37 -2
- package/codeyam-cli/src/utils/database.js.map +1 -1
- package/codeyam-cli/src/utils/editorApi.js +11 -5
- package/codeyam-cli/src/utils/editorApi.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +51 -0
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorLoaderHelpers.js +32 -0
- package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -1
- package/codeyam-cli/src/utils/editorPreview.js +31 -0
- package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +261 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.js +4 -2
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +34 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/parseRegisterArg.js.map +1 -1
- package/codeyam-cli/src/utils/progress.js +2 -2
- package/codeyam-cli/src/utils/progress.js.map +1 -1
- package/codeyam-cli/src/utils/scenarioCoverage.js +75 -0
- package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -0
- package/codeyam-cli/src/utils/scenariosManifest.js +204 -75
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +1 -0
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
- package/codeyam-cli/src/utils/simulationGateMiddleware.js +8 -1
- package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
- package/codeyam-cli/src/utils/slugUtils.js +25 -0
- package/codeyam-cli/src/utils/slugUtils.js.map +1 -0
- package/codeyam-cli/src/utils/syncMocksMiddleware.js +2 -2
- package/codeyam-cli/src/utils/syncMocksMiddleware.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +40 -0
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +157 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +146 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +65 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/git.js +3 -2
- package/codeyam-cli/src/webserver/app/lib/git.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-0DY_NKil.js → ScenarioViewer-TSD3C211.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-coverage-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-session-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-Csi0_PMl.js → dev.empty-Ii3inc0_.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor-16o0AIFV.js +15 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-7Uga8I59.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BF4oLwaE.js → entity._sha._-DwCV5__E.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-C7YX6r3H.js → entity._sha.scenarios._scenarioId.dev-BwKcai0j.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CHMiAog3.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/globals-CQPR0pFR.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/manifest-76e7b62c.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/memory-9gnxSZlb.js +101 -0
- package/codeyam-cli/src/webserver/build/client/assets/{root-ClvYBUSA.js → root-DBjt6o04.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-C-_hOl_g.js +1 -0
- package/codeyam-cli/src/webserver/build/client/sound-test.html +98 -0
- package/codeyam-cli/src/webserver/build/server/assets/index-DsZjKspK.js +1 -0
- package/codeyam-cli/src/webserver/build/server/assets/init-DdqKD2p4.js +10 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-CKKeWtVK.js +444 -0
- package/codeyam-cli/src/webserver/build/server/index.js +1 -1
- package/codeyam-cli/src/webserver/build-info.json +5 -5
- package/codeyam-cli/src/webserver/editorProxy.js +99 -7
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/idleDetector.js +73 -0
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -0
- package/codeyam-cli/src/webserver/public/sound-test.html +98 -0
- package/codeyam-cli/src/webserver/server.js +46 -4
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +68 -29
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/chrome-extension-react/README.md +46 -0
- package/codeyam-cli/templates/chrome-extension-react/package.json +1 -0
- package/codeyam-cli/templates/codeyam-editor-claude.md +84 -5
- package/codeyam-cli/templates/editor-step-hook.py +14 -8
- package/codeyam-cli/templates/expo-react-native/README.md +41 -0
- package/codeyam-cli/templates/expo-react-native/package.json +1 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/DATABASE.md +14 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/README.md +53 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +1 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/README.md +52 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +1 -0
- package/codeyam-cli/templates/skills/codeyam-dev-mode/SKILL.md +1 -1
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +14 -10
- package/package.json +1 -1
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +56 -0
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/packages/database/src/lib/loadEntities.js +0 -6
- package/packages/database/src/lib/loadEntities.js.map +1 -1
- package/packages/database/src/lib/updateCommitMetadata.js +0 -25
- package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor-DgN1LTTt.js +0 -10
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-BLQMSKZa.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CF164ouH.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/globals-BkWJ_UNc.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-c26eb85b.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/memory-Bl2rpw8u.js +0 -96
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-CrAK28Bc.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/index-DflIr5SD.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-OhKy839M.js +0 -416
|
@@ -11,12 +11,12 @@ import { IS_INTERNAL_BUILD } from "../utils/buildFlags.js";
|
|
|
11
11
|
import { startBackgroundServer } from "../utils/backgroundServer.js";
|
|
12
12
|
import { installClaudeCodeSkills } from "../utils/install-skills.js";
|
|
13
13
|
import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
|
|
14
|
-
import {
|
|
14
|
+
import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
|
|
15
15
|
import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
|
|
16
16
|
import { getProjectRoot as getStateProjectRoot } from "../state.js";
|
|
17
17
|
import initCommand from "./init.js";
|
|
18
|
-
import {
|
|
19
|
-
import { clearEditorState, clearEditorUserPrompt, } from "../utils/editorScenarios.js";
|
|
18
|
+
import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
|
|
19
|
+
import { clearEditorState, clearEditorUserPrompt, validateStepTransition, } from "../utils/editorScenarios.js";
|
|
20
20
|
import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
|
|
21
21
|
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
22
22
|
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
@@ -36,6 +36,9 @@ const STEP_LABELS = {
|
|
|
36
36
|
11: 'Journal',
|
|
37
37
|
12: 'Review',
|
|
38
38
|
13: 'Present',
|
|
39
|
+
14: 'Commit',
|
|
40
|
+
15: 'Finalize',
|
|
41
|
+
16: 'Push',
|
|
39
42
|
};
|
|
40
43
|
/**
|
|
41
44
|
* Append a JSONL log entry to .codeyam/logs/editor-log.jsonl
|
|
@@ -56,6 +59,18 @@ function logEvent(root, event, data) {
|
|
|
56
59
|
// Logging is best-effort
|
|
57
60
|
}
|
|
58
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Read the design system file if it exists.
|
|
64
|
+
*/
|
|
65
|
+
function readDesignSystem(root) {
|
|
66
|
+
const designSystemPath = path.join(root, '.codeyam', 'design-system.md');
|
|
67
|
+
try {
|
|
68
|
+
return fs.readFileSync(designSystemPath, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
59
74
|
/**
|
|
60
75
|
* Get the project root (where .codeyam/ lives) or cwd.
|
|
61
76
|
*/
|
|
@@ -128,6 +143,39 @@ function getServerPort() {
|
|
|
128
143
|
}
|
|
129
144
|
return process.env.CODEYAM_PORT || '3111';
|
|
130
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Read the project's default dimension name and available screen size names.
|
|
148
|
+
* Used by step instructions to show project-specific dimension examples
|
|
149
|
+
* instead of hardcoded "Desktop".
|
|
150
|
+
*/
|
|
151
|
+
function getProjectDimensions(root) {
|
|
152
|
+
try {
|
|
153
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
154
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
155
|
+
const defaultName = config.defaultScreenSize?.name ||
|
|
156
|
+
(config.screenSizes ? Object.keys(config.screenSizes)[0] : null) ||
|
|
157
|
+
'Desktop';
|
|
158
|
+
const names = config.screenSizes ? Object.keys(config.screenSizes) : [];
|
|
159
|
+
return { defaultName, names };
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return { defaultName: 'Desktop', names: [] };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Print dimension guidance when the project has multiple screen sizes.
|
|
167
|
+
* Tells Claude to pick the right dimension for the content being previewed
|
|
168
|
+
* instead of blindly using the default for everything.
|
|
169
|
+
*/
|
|
170
|
+
function printDimensionGuidance(defaultName, names) {
|
|
171
|
+
if (names.length <= 1)
|
|
172
|
+
return;
|
|
173
|
+
// Find a non-default dimension to suggest as the "other" option
|
|
174
|
+
const otherName = names.find((n) => n !== defaultName) || names[1];
|
|
175
|
+
console.log(chalk.yellow(` IMPORTANT: Choose the dimension that matches what you're previewing. Available: ${names.map((n) => `"${n}"`).join(', ')}.`));
|
|
176
|
+
console.log(chalk.yellow(` Do NOT always use "${defaultName}". Full pages, standalone views, and desktop layouts should use "${otherName}".`));
|
|
177
|
+
console.log(chalk.yellow(` Think about the CONTENT — a full-page library view needs a large viewport, not a popup-sized one.`));
|
|
178
|
+
}
|
|
131
179
|
/**
|
|
132
180
|
* Print a checklist item.
|
|
133
181
|
* Inline backtick-wrapped text is highlighted in cyan for visibility.
|
|
@@ -149,13 +197,13 @@ function stepHeader(step, title, feature) {
|
|
|
149
197
|
console.log();
|
|
150
198
|
}
|
|
151
199
|
/**
|
|
152
|
-
* Print a colored progress tracker showing all
|
|
200
|
+
* Print a colored progress tracker showing all 16 steps.
|
|
153
201
|
* Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
|
|
154
202
|
*/
|
|
155
203
|
function printProgressTracker(current) {
|
|
156
204
|
console.log();
|
|
157
|
-
console.log(chalk.dim('┌─────────────────────────────────────┐'));
|
|
158
|
-
for (let i = 1; i <=
|
|
205
|
+
console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
|
|
206
|
+
for (let i = 1; i <= 16; i++) {
|
|
159
207
|
const label = STEP_LABELS[i];
|
|
160
208
|
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
161
209
|
const content = `${num}. ${label.padEnd(28)}`;
|
|
@@ -193,7 +241,7 @@ function stopGate(current, opts) {
|
|
|
193
241
|
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
194
242
|
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
195
243
|
console.log();
|
|
196
|
-
if (current <
|
|
244
|
+
if (current < 16) {
|
|
197
245
|
console.log(chalk.green('When done, run: ') +
|
|
198
246
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
199
247
|
}
|
|
@@ -237,6 +285,14 @@ function printResumptionHeader(step) {
|
|
|
237
285
|
],
|
|
238
286
|
12: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
239
287
|
13: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
288
|
+
14: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
289
|
+
15: [
|
|
290
|
+
'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
|
|
291
|
+
'Check if commit was already amended:\n `git log --oneline -3`',
|
|
292
|
+
],
|
|
293
|
+
16: [
|
|
294
|
+
'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
|
|
295
|
+
],
|
|
240
296
|
};
|
|
241
297
|
const label = STEP_LABELS[step] || 'Unknown';
|
|
242
298
|
console.log(chalk.bold.yellow(`━━━ RESUMING Step ${step}: ${label} ━━━`));
|
|
@@ -341,7 +397,7 @@ function parseDebugTargets(target) {
|
|
|
341
397
|
}
|
|
342
398
|
if (entry.startsWith('step-')) {
|
|
343
399
|
const num = parseInt(entry.replace('step-', ''), 10);
|
|
344
|
-
if (!isNaN(num) && num >= 1 && num <=
|
|
400
|
+
if (!isNaN(num) && num >= 1 && num <= 16) {
|
|
345
401
|
valid.add(`step-${num}`);
|
|
346
402
|
continue;
|
|
347
403
|
}
|
|
@@ -425,6 +481,15 @@ function printSetup(root) {
|
|
|
425
481
|
console.log();
|
|
426
482
|
console.log("No project detected. Let's get started.");
|
|
427
483
|
console.log();
|
|
484
|
+
// ── Design System ────────────────────────────────────────────────
|
|
485
|
+
console.log(chalk.bold('Design System (ask FIRST):'));
|
|
486
|
+
console.log(chalk.dim(' Ask: "Do you have a design system, brand guidelines, or style preferences you\'d like me to follow?"'));
|
|
487
|
+
console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
|
|
488
|
+
console.log(chalk.yellow(' Option 1 label: "Yes, I\'ll paste my design system"'));
|
|
489
|
+
console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
|
|
490
|
+
console.log(chalk.yellow(' Option 2 label: "No, use sensible defaults"'));
|
|
491
|
+
console.log(chalk.dim(' → Skip'));
|
|
492
|
+
console.log();
|
|
428
493
|
console.log(chalk.bold('Checklist:'));
|
|
429
494
|
checkbox('Read `.codeyam/editor-mode-context.md` for session state');
|
|
430
495
|
checkbox('Ask the user what they want to build');
|
|
@@ -479,7 +544,15 @@ function printSetup(root) {
|
|
|
479
544
|
console.log();
|
|
480
545
|
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → Mobile, chrome-extension → Custom (400×600).'));
|
|
481
546
|
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
482
|
-
console.log(chalk.dim(
|
|
547
|
+
console.log(chalk.dim(` Save the choice via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"defaultScreenSize":{"name":"Desktop","width":1440,"height":900}}'`));
|
|
548
|
+
console.log();
|
|
549
|
+
console.log(chalk.bold('Named Screen Sizes (for multi-resolution apps):'));
|
|
550
|
+
console.log(chalk.dim(' For mobile-responsive web apps or apps that need screenshots at multiple resolutions,'));
|
|
551
|
+
console.log(chalk.dim(' also save named screen sizes. Each scenario can reference a dimension name.'));
|
|
552
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
|
|
553
|
+
console.log(chalk.dim(' -d \'{"screenSizes":{"Desktop":{"width":1440,"height":900},"Mobile":{"width":375,"height":667}}}\''));
|
|
554
|
+
console.log(chalk.dim(' If you discover a new viewport is needed mid-workflow, add it to screenSizes the same way and reference by name.'));
|
|
555
|
+
console.log(chalk.dim(' NOTE: This REPLACES all screenSizes — always include every named size, not just the new one.'));
|
|
483
556
|
console.log();
|
|
484
557
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
485
558
|
console.log();
|
|
@@ -514,7 +587,7 @@ function printCycleOverview(root, state) {
|
|
|
514
587
|
console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
|
|
515
588
|
}
|
|
516
589
|
else {
|
|
517
|
-
console.log('Each feature follows
|
|
590
|
+
console.log('Each feature follows 16 steps. You MUST run each command in order:');
|
|
518
591
|
console.log();
|
|
519
592
|
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
|
|
520
593
|
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
|
|
@@ -529,6 +602,9 @@ function printCycleOverview(root, state) {
|
|
|
529
602
|
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('Journal')} — Create/update journal entry`);
|
|
530
603
|
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Review')} — Verify screenshots and audit`);
|
|
531
604
|
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Present')} — Present summary, get approval`);
|
|
605
|
+
console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Commit')} — Commit all changes`);
|
|
606
|
+
console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
|
|
607
|
+
console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Push')} — Push to remote`);
|
|
532
608
|
console.log();
|
|
533
609
|
console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
|
|
534
610
|
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
@@ -543,19 +619,19 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
543
619
|
if (!isResuming) {
|
|
544
620
|
clearState(root);
|
|
545
621
|
}
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
622
|
+
// Always persist state so step 2's validation sees that step 1 ran.
|
|
623
|
+
// The feature name may not be known yet (it's an output of planning) —
|
|
624
|
+
// use an empty string as placeholder; step 2 requires --feature anyway.
|
|
625
|
+
const now = new Date().toISOString();
|
|
626
|
+
writeState(root, {
|
|
627
|
+
feature: feature || prevState?.feature || '',
|
|
628
|
+
step: 1,
|
|
629
|
+
label: STEP_LABELS[1],
|
|
630
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
631
|
+
featureStartedAt: isResuming ? prevState.featureStartedAt : now,
|
|
632
|
+
appFormats: options?.appFormats || prevState?.appFormats,
|
|
633
|
+
techStackId: options?.techStackId || prevState?.techStackId,
|
|
634
|
+
});
|
|
559
635
|
// Save the user's original prompt to a separate file
|
|
560
636
|
if (userPrompt) {
|
|
561
637
|
const promptPath = path.join(root, '.codeyam', 'editor-user-prompt.txt');
|
|
@@ -580,11 +656,21 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
580
656
|
console.log(chalk.dim(' Bad: Free-form text asking "What do you think about the data model?"'));
|
|
581
657
|
console.log(chalk.dim(' You can ask up to 4 questions at once. Bundle related questions into a single AskUserQuestion call.'));
|
|
582
658
|
checkbox("Summarize what you'll build in plain language the user can verify against their vision");
|
|
659
|
+
checkbox('Include a brief note on what interesting data states the scenarios should demonstrate');
|
|
660
|
+
console.log(chalk.dim(' Think: what seed data would put this feature through its paces? Diverse content, edge cases, empty vs rich.'));
|
|
583
661
|
checkbox('Set the project title and description for the App tab:');
|
|
584
662
|
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
|
|
585
663
|
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
586
664
|
console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
|
|
587
665
|
console.log();
|
|
666
|
+
const designSystem = readDesignSystem(root);
|
|
667
|
+
if (designSystem) {
|
|
668
|
+
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
669
|
+
console.log(chalk.dim(' Keep these design tokens in mind during planning.'));
|
|
670
|
+
console.log();
|
|
671
|
+
console.log(designSystem);
|
|
672
|
+
console.log();
|
|
673
|
+
}
|
|
588
674
|
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
589
675
|
console.log(chalk.green(' Option 1 label: "This plan is accurate, let\'s prototype it!"') + chalk.dim(' — proceed to step 2'));
|
|
590
676
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
@@ -612,6 +698,7 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
612
698
|
// ─── Step 2: Prototype ────────────────────────────────────────────────
|
|
613
699
|
function printStep2(root, feature) {
|
|
614
700
|
const port = getServerPort();
|
|
701
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
615
702
|
const projectExists = hasProject(root);
|
|
616
703
|
const prevState = readState(root);
|
|
617
704
|
const isResuming = prevState?.step === 2;
|
|
@@ -649,7 +736,13 @@ function printStep2(root, feature) {
|
|
|
649
736
|
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
650
737
|
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
651
738
|
console.log();
|
|
652
|
-
console.log(chalk.
|
|
739
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
740
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
741
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
742
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
743
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
744
|
+
console.log();
|
|
745
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
653
746
|
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
654
747
|
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
655
748
|
console.log();
|
|
@@ -673,14 +766,33 @@ function printStep2(root, feature) {
|
|
|
673
766
|
console.log(chalk.dim(' Use Tailwind responsive prefixes (sm:, md:, lg:) for layout shifts.'));
|
|
674
767
|
console.log(chalk.dim(' Test at both Desktop (1280×800) and Mobile (390×844) sizes before presenting.'));
|
|
675
768
|
}
|
|
769
|
+
const designSystem = readDesignSystem(root);
|
|
770
|
+
if (designSystem) {
|
|
771
|
+
console.log();
|
|
772
|
+
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
773
|
+
console.log();
|
|
774
|
+
console.log(designSystem);
|
|
775
|
+
console.log();
|
|
776
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
777
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
778
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
779
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
780
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
781
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
782
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
783
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
784
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
785
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
786
|
+
}
|
|
676
787
|
console.log();
|
|
677
788
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
678
789
|
console.log(chalk.cyan(' The user is watching the preview. Refresh it after each meaningful change:'));
|
|
679
|
-
console.log(chalk.cyan(` codeyam editor preview`));
|
|
790
|
+
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
680
791
|
console.log(chalk.cyan(' Refresh after: first visible page, adding each UI section, seeding data, styling.'));
|
|
681
792
|
console.log(chalk.cyan(' Aim for 4-8+ refreshes during prototyping — not one big reveal at the end.'));
|
|
682
793
|
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
683
|
-
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1"}'`));
|
|
794
|
+
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
|
|
795
|
+
printDimensionGuidance(dim, dimNames);
|
|
684
796
|
console.log();
|
|
685
797
|
console.log(chalk.bold('Verify the dev server:'));
|
|
686
798
|
console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
|
|
@@ -701,12 +813,19 @@ function printStep2(root, feature) {
|
|
|
701
813
|
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
702
814
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
703
815
|
console.log();
|
|
816
|
+
console.log(chalk.bold('Update README and setup script:'));
|
|
817
|
+
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
818
|
+
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
819
|
+
console.log(chalk.dim(' The README and setup script must stay accurate as you make changes.'));
|
|
820
|
+
console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
|
|
821
|
+
console.log();
|
|
704
822
|
console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
|
|
705
823
|
stopGate(2);
|
|
706
824
|
}
|
|
707
825
|
// ─── Step 3: Confirm ──────────────────────────────────────────────────
|
|
708
826
|
function printStep3(root, feature) {
|
|
709
827
|
const port = getServerPort();
|
|
828
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
710
829
|
const prevState = readState(root);
|
|
711
830
|
const isResuming = prevState?.step === 3;
|
|
712
831
|
const now = new Date().toISOString();
|
|
@@ -725,7 +844,7 @@ function printStep3(root, feature) {
|
|
|
725
844
|
console.log('Summarize what was built and get user confirmation.');
|
|
726
845
|
console.log();
|
|
727
846
|
console.log(chalk.bold('Before presenting — verify everything works:'));
|
|
728
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\` — check the \`preview\` field for \`healthy: false\``);
|
|
847
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
|
|
729
848
|
checkbox('Verify API routes return valid data (curl each route)');
|
|
730
849
|
console.log();
|
|
731
850
|
console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
|
|
@@ -739,7 +858,8 @@ function printStep3(root, feature) {
|
|
|
739
858
|
console.log();
|
|
740
859
|
console.log(chalk.bold('Then present to the user:'));
|
|
741
860
|
checkbox('Summarize what was built (routes, components, data)');
|
|
742
|
-
checkbox(
|
|
861
|
+
checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
|
|
862
|
+
printDimensionGuidance(dim, dimNames);
|
|
743
863
|
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
744
864
|
console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
|
|
745
865
|
console.log();
|
|
@@ -751,7 +871,7 @@ function printStep3(root, feature) {
|
|
|
751
871
|
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
752
872
|
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 4'));
|
|
753
873
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
754
|
-
chalk.dim(' —
|
|
874
|
+
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 3`'));
|
|
755
875
|
console.log();
|
|
756
876
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
757
877
|
stopGate(3, { confirm: true });
|
|
@@ -809,6 +929,7 @@ function printStep4(root, feature) {
|
|
|
809
929
|
// ─── Step 5: Extract ──────────────────────────────────────────────────
|
|
810
930
|
function printStep5(root, feature) {
|
|
811
931
|
const port = getServerPort();
|
|
932
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
812
933
|
const prevState = readState(root);
|
|
813
934
|
const isResuming = prevState?.step === 5;
|
|
814
935
|
const now = new Date().toISOString();
|
|
@@ -832,12 +953,13 @@ function printStep5(root, feature) {
|
|
|
832
953
|
checkbox('Every component that renders multiple sections must be split into sub-components');
|
|
833
954
|
console.log(chalk.dim(' No tests needed — visual verification happens in step 7'));
|
|
834
955
|
console.log();
|
|
835
|
-
console.log(chalk.bold('Library functions (TDD):'));
|
|
836
|
-
checkbox('For each function: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
956
|
+
console.log(chalk.bold('Library functions AND hooks (TDD):'));
|
|
957
|
+
checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
837
958
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
838
959
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
960
|
+
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
839
961
|
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
840
|
-
console.log(chalk.yellow(' Tests ARE the only coverage for library functions — step 7 only captures component screenshots.'));
|
|
962
|
+
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 7 only captures component screenshots.'));
|
|
841
963
|
console.log();
|
|
842
964
|
console.log(chalk.bold('Recursive pass:'));
|
|
843
965
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
@@ -849,7 +971,8 @@ function printStep5(root, feature) {
|
|
|
849
971
|
checkbox('Run all tests and verify they pass');
|
|
850
972
|
checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
|
|
851
973
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
852
|
-
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview\``);
|
|
974
|
+
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
975
|
+
printDimensionGuidance(dim, dimNames);
|
|
853
976
|
console.log(chalk.dim(' The user should see the preview stay healthy as you refactor — refresh periodically, not just at the end.'));
|
|
854
977
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
855
978
|
console.log();
|
|
@@ -879,7 +1002,8 @@ function printStep6(root, feature) {
|
|
|
879
1002
|
checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
|
|
880
1003
|
checkbox('Add an entry for each new function/component extracted in step 5');
|
|
881
1004
|
checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
|
|
882
|
-
checkbox('
|
|
1005
|
+
checkbox('Every non-component entry MUST have `testFile` set — hooks and functions all need tests');
|
|
1006
|
+
console.log(chalk.yellow(' If a function/hook has no test yet, go back and write one (TDD). The audit will block you.'));
|
|
883
1007
|
console.log();
|
|
884
1008
|
console.log(chalk.bold('Entry format:'));
|
|
885
1009
|
console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
|
|
@@ -895,6 +1019,7 @@ function printStep6(root, feature) {
|
|
|
895
1019
|
// ─── Step 7: Analyze ──────────────────────────────────────────────────
|
|
896
1020
|
function printStep7(root, feature) {
|
|
897
1021
|
const port = getServerPort();
|
|
1022
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
898
1023
|
const prevState = readState(root);
|
|
899
1024
|
const isResuming = prevState?.step === 7;
|
|
900
1025
|
const now = new Date().toISOString();
|
|
@@ -946,12 +1071,20 @@ function printStep7(root, feature) {
|
|
|
946
1071
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
947
1072
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
948
1073
|
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
1074
|
+
console.log(chalk.dim(` "dimensions":["${dim}"],`));
|
|
949
1075
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
950
|
-
console.log(chalk.
|
|
951
|
-
console.log(chalk.dim(
|
|
1076
|
+
console.log(chalk.yellow(' ALWAYS include "dimensions" — use the named screen size that matches the component\'s context.'));
|
|
1077
|
+
console.log(chalk.dim(dimNames.length > 0
|
|
1078
|
+
? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
|
|
1079
|
+
: ` Use "${dim}" for most components. Names must match a key in screenSizes from setup.`));
|
|
1080
|
+
console.log(chalk.dim(' Names must match a key in screenSizes from setup. If you need a new size, add it first:'));
|
|
1081
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
|
|
1082
|
+
console.log(chalk.dim(` -d '{"screenSizes":{...existing..., "New Name":{"width":W,"height":H}}}' (replaces all — include every size)`));
|
|
952
1083
|
console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
|
|
953
1084
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
954
1085
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
1086
|
+
console.log(chalk.dim(' For large JSON: use your Write tool to create .codeyam/tmp/scenario.json,'));
|
|
1087
|
+
console.log(chalk.dim(' then: codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
955
1088
|
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
956
1089
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
957
1090
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
@@ -965,17 +1098,16 @@ function printStep7(root, feature) {
|
|
|
965
1098
|
console.log();
|
|
966
1099
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
967
1100
|
console.log();
|
|
968
|
-
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions have tests');
|
|
1101
|
+
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1102
|
+
console.log(chalk.red.bold(' The audit is a HARD GATE — step 8 will refuse to run until the audit passes.'));
|
|
1103
|
+
console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
|
|
969
1104
|
console.log();
|
|
970
|
-
console.log(chalk.bold('Build import graph (for change tracking):'));
|
|
971
|
-
checkbox('Run `codeyam editor analyze-imports`');
|
|
972
|
-
console.log(chalk.dim(' This populates the import graph so the system can detect impacted entities when code changes.'));
|
|
973
|
-
console.log(chalk.dim(' It must run AFTER the audit passes so the glossary and entity data are complete.'));
|
|
974
1105
|
stopGate(7);
|
|
975
1106
|
}
|
|
976
1107
|
// ─── Step 8: App Scenarios ────────────────────────────────────────────
|
|
977
1108
|
function printStep8(root, feature) {
|
|
978
1109
|
const port = getServerPort();
|
|
1110
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
979
1111
|
const prevState = readState(root);
|
|
980
1112
|
const isResuming = prevState?.step === 8;
|
|
981
1113
|
const now = new Date().toISOString();
|
|
@@ -991,43 +1123,83 @@ function printStep8(root, feature) {
|
|
|
991
1123
|
if (isResuming) {
|
|
992
1124
|
printResumptionHeader(8);
|
|
993
1125
|
}
|
|
994
|
-
console.log('Create app-level scenarios
|
|
1126
|
+
console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
|
|
1127
|
+
console.log();
|
|
1128
|
+
console.log(chalk.bold('Goal: Every scenario should thoroughly exercise the feature with realistic data.'));
|
|
1129
|
+
console.log(chalk.dim(' Scenarios with minimal or generic data ("Test Item 1") won\'t reveal whether the feature works.'));
|
|
1130
|
+
console.log(chalk.dim(' Use rich, diverse seed data that puts the feature through its paces.'));
|
|
1131
|
+
console.log();
|
|
1132
|
+
console.log(chalk.bold.cyan('Make seed data work hard:'));
|
|
1133
|
+
console.log(chalk.cyan(' Every scenario — new or existing — should have data that EXERCISES the feature:'));
|
|
1134
|
+
console.log(chalk.cyan(' • Add data that uses new fields, relationships, or states the feature introduces'));
|
|
1135
|
+
console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
|
|
1136
|
+
console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
|
|
1137
|
+
console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
|
|
1138
|
+
console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
|
|
1139
|
+
console.log();
|
|
1140
|
+
console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
|
|
1141
|
+
console.log(chalk.cyan(' • New pages or pages with no scenarios yet → create new scenarios'));
|
|
1142
|
+
console.log(chalk.cyan(' • Existing scenarios on affected pages → re-register with enhanced data'));
|
|
1143
|
+
console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
|
|
1144
|
+
console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
|
|
995
1145
|
console.log();
|
|
996
1146
|
console.log(chalk.bold('Checklist:'));
|
|
997
|
-
checkbox('
|
|
998
|
-
|
|
999
|
-
console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
|
|
1000
|
-
checkbox('Ensure scenarios clearly demonstrate what changed in this session');
|
|
1001
|
-
console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
|
|
1002
|
-
console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
|
|
1003
|
-
console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
|
|
1004
|
-
checkbox('Cover key data states for EVERY page/route (at least 2-3 scenarios per page)');
|
|
1147
|
+
checkbox('Run `codeyam editor scenarios` — review all existing scenarios');
|
|
1148
|
+
checkbox("Create new scenarios for any pages that don't have scenarios yet");
|
|
1005
1149
|
console.log(chalk.yellow(' Each page needs its own scenarios — a detail page needs different data than the catalog'));
|
|
1006
|
-
|
|
1007
|
-
console.log(chalk.dim('
|
|
1008
|
-
console.log(chalk.
|
|
1150
|
+
checkbox('Re-register every existing scenario whose page was affected by this feature');
|
|
1151
|
+
console.log(chalk.dim(' Use the SAME name to update. Enhance seed data to showcase this feature where relevant.'));
|
|
1152
|
+
console.log(chalk.yellow(" Don't just re-register unchanged data — enrich it so the feature is thoroughly demonstrated."));
|
|
1153
|
+
checkbox('ALWAYS include "dimensions" — pick the viewport that best demonstrates the page');
|
|
1154
|
+
console.log(chalk.yellow(' Every app scenario MUST have a "dimensions" field referencing a named screen size from setup.'));
|
|
1155
|
+
console.log(chalk.dim(dimNames.length > 0
|
|
1156
|
+
? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
|
|
1157
|
+
: ` Use "${dim}" for most scenarios. Names must match a key in screenSizes from setup.`));
|
|
1158
|
+
if (dimNames.length > 1) {
|
|
1159
|
+
const otherDim = dimNames.find((n) => n !== dim) || dimNames[1];
|
|
1160
|
+
console.log(chalk.yellow(` Think about what fits each scenario: full pages/standalone views → "${otherDim}", popups/widgets → "${dim}".`));
|
|
1161
|
+
}
|
|
1162
|
+
console.log(chalk.dim(' For responsive apps, include multiple dimensions: "dimensions":["Desktop","Mobile"] captures both viewports.'));
|
|
1163
|
+
console.log(chalk.dim(' If you need a new named size mid-workflow, add it via editor-project-info API (include ALL existing sizes — the API replaces, not merges).'));
|
|
1009
1164
|
checkbox('If the project has a database + seed adapter, use seed-based scenarios:');
|
|
1010
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","seed":{"products":[...],"categories":[...]}}'`));
|
|
1165
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","dimensions":["${dim}"],"seed":{"products":[...],"categories":[...]}}'`));
|
|
1011
1166
|
console.log(chalk.dim(' Seed data is written to the DB via the seed adapter. Real app renders real data.'));
|
|
1012
|
-
checkbox('Optional: add "viewportWidth" and "viewportHeight" to override the project default screen size');
|
|
1013
|
-
console.log(chalk.dim(' If omitted, captures use the project default from setup. Only override for scenarios that need a different size.'));
|
|
1014
1167
|
checkbox('IMPORTANT: Always include "url" — the page path to screenshot for this scenario');
|
|
1015
1168
|
console.log(chalk.dim(' Use "/" for home page, "/drinks/1" for detail pages, etc.'));
|
|
1016
1169
|
console.log(chalk.dim(' Without url, the screenshot captures the root page regardless of the scenario.'));
|
|
1017
1170
|
console.log(chalk.yellow(' Create separate scenarios for each page that shows different data.'));
|
|
1018
|
-
checkbox('For large seed data,
|
|
1019
|
-
console.log(chalk.dim(' Write JSON to .codeyam/tmp/scenario.json then register with:'));
|
|
1171
|
+
checkbox('For large seed data, use your Write tool to create .codeyam/tmp/scenario.json, then:');
|
|
1020
1172
|
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
1173
|
+
console.log(chalk.yellow(' IMPORTANT: Use the Write tool — NOT cat/heredoc — to avoid permission prompts'));
|
|
1021
1174
|
checkbox('For external API mocks (Stripe, weather, etc.), add externalApis:');
|
|
1022
1175
|
console.log(chalk.dim(` "externalApis":{"GET https://api.stripe.com/v1/prices":{"body":[...],"status":200}}`));
|
|
1023
1176
|
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
1024
|
-
checkbox('If
|
|
1025
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"
|
|
1177
|
+
checkbox('If the app uses localStorage/sessionStorage instead of a database, use localStorage scenarios:');
|
|
1178
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"Full Library","type":"application","url":"/","dimensions":["${dim}"],"localStorage":{"articles":[...],"collections":[...]}}'`));
|
|
1179
|
+
console.log(chalk.dim(' The proxy injects data into localStorage before the app loads. Fully interactive — the app can read and write normally.'));
|
|
1180
|
+
console.log(chalk.dim(' For an empty state, register with an empty localStorage or omit it entirely.'));
|
|
1181
|
+
checkbox('If no database and no localStorage, use component-style mock scenarios:');
|
|
1182
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","url":"/","dimensions":["${dim}"],"mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
1026
1183
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
1027
1184
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
1028
1185
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1186
|
+
checkbox('CRITICAL: After EACH registration, view the captured screenshot to verify it');
|
|
1187
|
+
console.log(chalk.yellow(' Read the screenshot file path from the registration response and view it.'));
|
|
1188
|
+
console.log(chalk.yellow(' Does the screenshot actually SHOW the data you put in? If you seeded 5 articles'));
|
|
1189
|
+
console.log(chalk.yellow(' but the screenshot shows an empty page, the scenario is broken — fix the code or data.'));
|
|
1190
|
+
console.log(chalk.yellow(' Do NOT create scenarios with data the app cannot yet render.'));
|
|
1029
1191
|
console.log();
|
|
1030
1192
|
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 10 if needed.'));
|
|
1193
|
+
console.log();
|
|
1194
|
+
console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
|
|
1195
|
+
console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
|
|
1196
|
+
console.log(chalk.cyan(' • A "Library with Articles" screenshot showing an empty page means the scenario is BROKEN.'));
|
|
1197
|
+
console.log(chalk.cyan(' • Is the seed data diverse — different lengths, categories, counts — or just placeholders?'));
|
|
1198
|
+
console.log(chalk.cyan(' • Only create scenarios for states the CURRENT code can render.'));
|
|
1199
|
+
console.log();
|
|
1200
|
+
console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
|
|
1201
|
+
console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
|
|
1202
|
+
console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
|
|
1031
1203
|
stopGate(8);
|
|
1032
1204
|
}
|
|
1033
1205
|
// ─── Step 9: User Scenarios ───────────────────────────────────────────
|
|
@@ -1048,34 +1220,37 @@ function printStep9(root, feature) {
|
|
|
1048
1220
|
if (isResuming) {
|
|
1049
1221
|
printResumptionHeader(9);
|
|
1050
1222
|
}
|
|
1051
|
-
console.log('Create per-persona
|
|
1223
|
+
console.log('Create per-persona variations of existing scenarios. Skip to step 10 if no users.');
|
|
1052
1224
|
console.log();
|
|
1053
|
-
console.log(chalk.bold('If the app has users:'));
|
|
1054
|
-
|
|
1055
|
-
console.log(
|
|
1056
|
-
console.log(chalk.
|
|
1057
|
-
|
|
1225
|
+
console.log(chalk.bold('If the app has NO users/auth:'));
|
|
1226
|
+
console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
|
|
1227
|
+
console.log();
|
|
1228
|
+
console.log(chalk.bold('If the app has users/auth:'));
|
|
1229
|
+
console.log();
|
|
1230
|
+
console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
|
|
1231
|
+
console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
|
|
1232
|
+
console.log(chalk.dim(' a variation of an existing app scenario from step 8, with user-specific state layered on.'));
|
|
1233
|
+
console.log();
|
|
1234
|
+
console.log(chalk.bold('Checklist:'));
|
|
1235
|
+
checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
|
|
1236
|
+
checkbox('For EACH existing app scenario, create a logged-in variation:');
|
|
1237
|
+
console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
|
|
1238
|
+
console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
|
|
1239
|
+
console.log(chalk.dim(' Step 8 scenarios already serve as logged-out versions (no session cookie)'));
|
|
1240
|
+
checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
|
|
1058
1241
|
console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
|
|
1059
|
-
checkbox('
|
|
1060
|
-
console.log(chalk.dim(
|
|
1061
|
-
console.log(chalk.dim(' Always include "url" — the page to screenshot (e.g. "/", "/dashboard", "/drinks/1")'));
|
|
1062
|
-
console.log(chalk.dim(" The base scenario's seed data is merged with this scenario's seed data (overlay wins)."));
|
|
1063
|
-
checkbox('If the app uses auth or other patterns: see FEATURE_PATTERNS.md for per-persona scenario guidance');
|
|
1064
|
-
checkbox('If using mock-based scenarios, register with mockData as before:');
|
|
1065
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"...","description":"...","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
1242
|
+
checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
|
|
1243
|
+
console.log(chalk.dim(' e.g. a mobile-first user persona should use "dimensions":["Mobile"] even if the base scenario is Desktop.'));
|
|
1066
1244
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
1067
1245
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
1068
|
-
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1069
1246
|
console.log();
|
|
1070
|
-
console.log(chalk.
|
|
1071
|
-
console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
|
|
1072
|
-
console.log();
|
|
1073
|
-
console.log(chalk.dim('Focus on creating user-persona scenarios (or skip if no users). Code fixes happen in step 10 if needed.'));
|
|
1247
|
+
console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
|
|
1074
1248
|
stopGate(9);
|
|
1075
1249
|
}
|
|
1076
1250
|
// ─── Step 10: Verify ──────────────────────────────────────────────────
|
|
1077
1251
|
function printStep10(root, feature) {
|
|
1078
1252
|
const port = getServerPort();
|
|
1253
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1079
1254
|
const prevState = readState(root);
|
|
1080
1255
|
const isResuming = prevState?.step === 10;
|
|
1081
1256
|
const now = new Date().toISOString();
|
|
@@ -1100,7 +1275,8 @@ function printStep10(root, feature) {
|
|
|
1100
1275
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",...}'`));
|
|
1101
1276
|
console.log();
|
|
1102
1277
|
console.log(chalk.bold('Editor scenarios (App tab) — visual + error check:'));
|
|
1103
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1278
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1279
|
+
printDimensionGuidance(dim, dimNames);
|
|
1104
1280
|
checkbox('Click through each app-level and user-persona scenario in the preview');
|
|
1105
1281
|
checkbox('For seed-based scenarios: verify data renders correctly after switching');
|
|
1106
1282
|
console.log(chalk.dim(' Switch between scenarios and confirm the app reflects the seeded data each time'));
|
|
@@ -1153,6 +1329,7 @@ function printStep11(root, feature) {
|
|
|
1153
1329
|
// ─── Step 12: Review ──────────────────────────────────────────────────
|
|
1154
1330
|
function printStep12(root, feature) {
|
|
1155
1331
|
const port = getServerPort();
|
|
1332
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1156
1333
|
const prevState = readState(root);
|
|
1157
1334
|
const isResuming = prevState?.step === 12;
|
|
1158
1335
|
const now = new Date().toISOString();
|
|
@@ -1171,7 +1348,8 @@ function printStep12(root, feature) {
|
|
|
1171
1348
|
console.log('Verify all screenshots and checks pass before presenting to the user.');
|
|
1172
1349
|
console.log();
|
|
1173
1350
|
console.log(chalk.bold('Checklist (do all of this silently):'));
|
|
1174
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1351
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1352
|
+
printDimensionGuidance(dim, dimNames);
|
|
1175
1353
|
checkbox('Verify each component has screenshots in the App tab (grouped under Components)');
|
|
1176
1354
|
checkbox('If any are missing, re-register them using `codeyam editor register`');
|
|
1177
1355
|
checkbox(`Check for client errors: \`codeyam editor client-errors\``);
|
|
@@ -1185,6 +1363,7 @@ function printStep12(root, feature) {
|
|
|
1185
1363
|
// ─── Step 13: Present ─────────────────────────────────────────────────
|
|
1186
1364
|
function printStep13(root, feature) {
|
|
1187
1365
|
const port = getServerPort();
|
|
1366
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1188
1367
|
const prevState = readState(root);
|
|
1189
1368
|
const isResuming = prevState?.step === 13;
|
|
1190
1369
|
const now = new Date().toISOString();
|
|
@@ -1209,8 +1388,10 @@ function printStep13(root, feature) {
|
|
|
1209
1388
|
console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
|
|
1210
1389
|
console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
|
|
1211
1390
|
console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
|
|
1212
|
-
checkbox('Switch the preview to that scenario using its `id`:');
|
|
1213
|
-
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>"}'`));
|
|
1391
|
+
checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
|
|
1392
|
+
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
1393
|
+
console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
|
|
1394
|
+
printDimensionGuidance(dim, dimNames);
|
|
1214
1395
|
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1215
1396
|
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1216
1397
|
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
@@ -1224,17 +1405,7 @@ function printStep13(root, feature) {
|
|
|
1224
1405
|
chalk.dim(' — describe changes, then re-verify'));
|
|
1225
1406
|
console.log();
|
|
1226
1407
|
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1227
|
-
checkbox(
|
|
1228
|
-
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
1229
|
-
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
1230
|
-
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
1231
|
-
console.log(chalk.green(' Then run: ') +
|
|
1232
|
-
chalk.bold('codeyam editor steps') +
|
|
1233
|
-
chalk.green(' to start the next feature'));
|
|
1234
|
-
console.log();
|
|
1235
|
-
console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
|
|
1236
|
-
console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
|
|
1237
|
-
console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
|
|
1408
|
+
checkbox('Advance to the commit step: `codeyam editor 14`');
|
|
1238
1409
|
console.log();
|
|
1239
1410
|
console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
|
|
1240
1411
|
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
@@ -1244,6 +1415,89 @@ function printStep13(root, feature) {
|
|
|
1244
1415
|
console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
|
|
1245
1416
|
stopGate(13, { confirm: true });
|
|
1246
1417
|
}
|
|
1418
|
+
// ─── Step 14: Commit ─────────────────────────────────────────────────
|
|
1419
|
+
function printStep14(root, feature) {
|
|
1420
|
+
const prevState = readState(root);
|
|
1421
|
+
const isResuming = prevState?.step === 14;
|
|
1422
|
+
const now = new Date().toISOString();
|
|
1423
|
+
writeState(root, {
|
|
1424
|
+
feature,
|
|
1425
|
+
step: 14,
|
|
1426
|
+
label: STEP_LABELS[14],
|
|
1427
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1428
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1429
|
+
});
|
|
1430
|
+
logEvent(root, 'step', { step: 14, label: 'Commit', feature });
|
|
1431
|
+
stepHeader(14, 'Commit', feature);
|
|
1432
|
+
if (isResuming) {
|
|
1433
|
+
printResumptionHeader(14);
|
|
1434
|
+
}
|
|
1435
|
+
console.log('Commit all changes for this feature.');
|
|
1436
|
+
console.log();
|
|
1437
|
+
console.log(chalk.bold('Checklist:'));
|
|
1438
|
+
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
1439
|
+
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
1440
|
+
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
1441
|
+
stopGate(14);
|
|
1442
|
+
}
|
|
1443
|
+
// ─── Step 15: Finalize ───────────────────────────────────────────────
|
|
1444
|
+
function printStep15(root, feature) {
|
|
1445
|
+
const prevState = readState(root);
|
|
1446
|
+
const isResuming = prevState?.step === 15;
|
|
1447
|
+
const now = new Date().toISOString();
|
|
1448
|
+
writeState(root, {
|
|
1449
|
+
feature,
|
|
1450
|
+
step: 15,
|
|
1451
|
+
label: STEP_LABELS[15],
|
|
1452
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1453
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1454
|
+
});
|
|
1455
|
+
logEvent(root, 'step', { step: 15, label: 'Finalize', feature });
|
|
1456
|
+
stepHeader(15, 'Finalize', feature);
|
|
1457
|
+
if (isResuming) {
|
|
1458
|
+
printResumptionHeader(15);
|
|
1459
|
+
}
|
|
1460
|
+
console.log('Update the journal with the commit SHA and amend the commit.');
|
|
1461
|
+
console.log();
|
|
1462
|
+
console.log(chalk.bold('Checklist:'));
|
|
1463
|
+
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
1464
|
+
checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
|
|
1465
|
+
console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
|
|
1466
|
+
stopGate(15);
|
|
1467
|
+
}
|
|
1468
|
+
// ─── Step 16: Push ───────────────────────────────────────────────────
|
|
1469
|
+
function printStep16(root, feature) {
|
|
1470
|
+
const prevState = readState(root);
|
|
1471
|
+
const isResuming = prevState?.step === 16;
|
|
1472
|
+
const now = new Date().toISOString();
|
|
1473
|
+
writeState(root, {
|
|
1474
|
+
feature,
|
|
1475
|
+
step: 16,
|
|
1476
|
+
label: STEP_LABELS[16],
|
|
1477
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1478
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1479
|
+
});
|
|
1480
|
+
logEvent(root, 'step', { step: 16, label: 'Push', feature });
|
|
1481
|
+
stepHeader(16, 'Push', feature);
|
|
1482
|
+
if (isResuming) {
|
|
1483
|
+
printResumptionHeader(16);
|
|
1484
|
+
}
|
|
1485
|
+
console.log('Push the commit to the remote repository.');
|
|
1486
|
+
console.log();
|
|
1487
|
+
console.log(chalk.bold('Checklist:'));
|
|
1488
|
+
checkbox('Check if a git remote is configured: `git remote -v`');
|
|
1489
|
+
checkbox("Offer to push to remote (AskUserQuestion — STOP and wait for the user's answer before proceeding):");
|
|
1490
|
+
console.log(chalk.dim(' If a remote exists, ask (AskUserQuestion): "Would you like me to push this commit to the remote?" with options "Yes, push" and "No, skip"'));
|
|
1491
|
+
console.log(chalk.dim(' If the user says yes, run: `git push`'));
|
|
1492
|
+
console.log(chalk.dim(' If NO remote exists, ask (AskUserQuestion): "This project doesn\'t have a git remote yet. Would you like help setting one up? I can walk you through creating a GitHub repository." with options "Yes, set up remote" and "No, skip"'));
|
|
1493
|
+
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
1494
|
+
checkbox('After the user responds, run: `codeyam editor steps` to start the next feature');
|
|
1495
|
+
console.log();
|
|
1496
|
+
console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
|
|
1497
|
+
console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
|
|
1498
|
+
console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
|
|
1499
|
+
stopGate(16, { confirm: true });
|
|
1500
|
+
}
|
|
1247
1501
|
// ─── Command definition ───────────────────────────────────────────────
|
|
1248
1502
|
// ─── Analyze-imports subcommand ────────────────────────────────────────
|
|
1249
1503
|
/**
|
|
@@ -1252,11 +1506,15 @@ function printStep13(root, feature) {
|
|
|
1252
1506
|
* Runs data-structure-only analysis for all glossary entities, then outputs
|
|
1253
1507
|
* an import graph and entity data structures as JSON to stdout.
|
|
1254
1508
|
*/
|
|
1255
|
-
async function handleAnalyzeImports() {
|
|
1509
|
+
async function handleAnalyzeImports(options = {}) {
|
|
1256
1510
|
const root = getProjectRoot();
|
|
1257
1511
|
// Read glossary
|
|
1258
1512
|
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
1259
1513
|
if (!fs.existsSync(glossaryPath)) {
|
|
1514
|
+
if (options.silent) {
|
|
1515
|
+
// Internal caller — glossary doesn't exist yet, nothing to analyze
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1260
1518
|
console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
|
|
1261
1519
|
console.error(chalk.dim(' Run codeyam editor 6 to create the glossary first.'));
|
|
1262
1520
|
process.exit(1);
|
|
@@ -1274,15 +1532,24 @@ async function handleAnalyzeImports() {
|
|
|
1274
1532
|
process.exit(1);
|
|
1275
1533
|
}
|
|
1276
1534
|
const filePaths = glossaryEntries.map((e) => e.filePath);
|
|
1277
|
-
|
|
1535
|
+
// Include page files so pages get analyzed as entities too.
|
|
1536
|
+
// This allows the import graph to map page names to their dependencies.
|
|
1537
|
+
const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
|
|
1538
|
+
const pageFilePaths = scanPageFilePaths(root);
|
|
1539
|
+
for (const pageFile of Object.values(pageFilePaths)) {
|
|
1540
|
+
if (!filePaths.includes(pageFile)) {
|
|
1541
|
+
filePaths.push(pageFile);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1278
1544
|
const progress = new ProgressReporter();
|
|
1279
|
-
// Run data-structure-only analysis for all entities
|
|
1280
|
-
|
|
1545
|
+
// Run data-structure-only analysis for all entities (glossary + pages)
|
|
1546
|
+
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
1547
|
+
// The analyzer will discover and create them from file paths.
|
|
1548
|
+
progress.start('Running import analysis for all entities...');
|
|
1281
1549
|
try {
|
|
1282
1550
|
await runAnalysisForEntities({
|
|
1283
1551
|
projectRoot: root,
|
|
1284
1552
|
filePaths,
|
|
1285
|
-
entityNames,
|
|
1286
1553
|
progress,
|
|
1287
1554
|
onlyDataStructure: true,
|
|
1288
1555
|
});
|
|
@@ -1300,7 +1567,9 @@ async function handleAnalyzeImports() {
|
|
|
1300
1567
|
const entities = await loadEntities({});
|
|
1301
1568
|
if (!entities || entities.length === 0) {
|
|
1302
1569
|
progress.succeed('No entities found in database yet — skipping import analysis. This is normal on the first feature.');
|
|
1303
|
-
|
|
1570
|
+
if (!options.silent) {
|
|
1571
|
+
console.log(JSON.stringify({ imports: {}, entities: {} }));
|
|
1572
|
+
}
|
|
1304
1573
|
return;
|
|
1305
1574
|
}
|
|
1306
1575
|
// Deduplicate to latest versions
|
|
@@ -1315,7 +1584,7 @@ async function handleAnalyzeImports() {
|
|
|
1315
1584
|
latestByKey.set(key, entity);
|
|
1316
1585
|
}
|
|
1317
1586
|
}
|
|
1318
|
-
const entityNameSet = new Set(
|
|
1587
|
+
const entityNameSet = new Set(glossaryEntries.map((e) => e.name));
|
|
1319
1588
|
const latestEntities = [...latestByKey.values()].filter((e) => entityNameSet.has(e.name));
|
|
1320
1589
|
// Build import graph from importedExports metadata
|
|
1321
1590
|
const imports = {};
|
|
@@ -1353,9 +1622,14 @@ async function handleAnalyzeImports() {
|
|
|
1353
1622
|
};
|
|
1354
1623
|
}
|
|
1355
1624
|
progress.succeed('Done');
|
|
1356
|
-
// Output combined JSON
|
|
1357
|
-
|
|
1358
|
-
|
|
1625
|
+
// Output combined JSON (suppressed when called internally during startup)
|
|
1626
|
+
if (!options.silent) {
|
|
1627
|
+
const summary = formatApiSubcommandResult('analyze-imports', {
|
|
1628
|
+
imports,
|
|
1629
|
+
entities: entityData,
|
|
1630
|
+
});
|
|
1631
|
+
console.log(summary || JSON.stringify({ imports, entities: entityData }));
|
|
1632
|
+
}
|
|
1359
1633
|
}
|
|
1360
1634
|
// ─── Validate-seed subcommand ─────────────────────────────────────────
|
|
1361
1635
|
/**
|
|
@@ -1538,6 +1812,17 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
1538
1812
|
}
|
|
1539
1813
|
return parts.join(' ');
|
|
1540
1814
|
}
|
|
1815
|
+
case 'analyze-imports': {
|
|
1816
|
+
const importEntries = Object.entries(data.imports || {});
|
|
1817
|
+
const entityEntries = Object.entries(data.entities || {});
|
|
1818
|
+
const parts = [];
|
|
1819
|
+
parts.push(`entities=${entityEntries.length}`);
|
|
1820
|
+
parts.push(`withImports=${importEntries.length}`);
|
|
1821
|
+
if (importEntries.length > 0) {
|
|
1822
|
+
parts.push(`imports: ${importEntries.map(([name, deps]) => `${name}→[${deps.join(',')}]`).join(' ')}`);
|
|
1823
|
+
}
|
|
1824
|
+
return parts.join(' ');
|
|
1825
|
+
}
|
|
1541
1826
|
default:
|
|
1542
1827
|
return null; // journal-list, show/hide-results: keep full JSON
|
|
1543
1828
|
}
|
|
@@ -1550,8 +1835,9 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
1550
1835
|
*/
|
|
1551
1836
|
async function handleRegister(jsonArg) {
|
|
1552
1837
|
if (!jsonArg) {
|
|
1838
|
+
const { defaultName: dim } = getProjectDimensions(getProjectRoot());
|
|
1553
1839
|
console.error(chalk.red('Error: JSON argument required.'));
|
|
1554
|
-
console.error(chalk.dim(
|
|
1840
|
+
console.error(chalk.dim(` Usage: codeyam editor register '{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default","dimensions":["${dim}"]}'`));
|
|
1555
1841
|
console.error(chalk.dim(' For large payloads: codeyam editor register @/tmp/scenario.json'));
|
|
1556
1842
|
process.exit(1);
|
|
1557
1843
|
}
|
|
@@ -1563,55 +1849,74 @@ async function handleRegister(jsonArg) {
|
|
|
1563
1849
|
}
|
|
1564
1850
|
process.exit(1);
|
|
1565
1851
|
}
|
|
1566
|
-
|
|
1852
|
+
// Normalize to array for uniform handling
|
|
1853
|
+
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
1567
1854
|
const port = getServerPort();
|
|
1568
1855
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
});
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
parts
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1856
|
+
const isBatch = items.length > 1;
|
|
1857
|
+
let succeeded = 0;
|
|
1858
|
+
let failed = 0;
|
|
1859
|
+
for (let i = 0; i < items.length; i++) {
|
|
1860
|
+
const body = items[i];
|
|
1861
|
+
const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
|
|
1862
|
+
try {
|
|
1863
|
+
const res = await fetch(url, {
|
|
1864
|
+
method: 'POST',
|
|
1865
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1866
|
+
body: JSON.stringify(body),
|
|
1867
|
+
});
|
|
1868
|
+
const data = await res.json();
|
|
1869
|
+
// Print concise summary instead of raw JSON so Claude doesn't need python3
|
|
1870
|
+
const parts = [];
|
|
1871
|
+
parts.push(`success=${data.success}`);
|
|
1872
|
+
if (data.scenario?.name)
|
|
1873
|
+
parts.push(`name="${data.scenario.name}"`);
|
|
1874
|
+
parts.push(`screenshot=${!!data.screenshotCaptured}`);
|
|
1875
|
+
if (data.capturedViewport) {
|
|
1876
|
+
parts.push(`viewport=${data.capturedViewport.width}×${data.capturedViewport.height}`);
|
|
1877
|
+
}
|
|
1878
|
+
if (data.scenario?.dimension)
|
|
1879
|
+
parts.push(`dimension="${data.scenario.dimension}"`);
|
|
1880
|
+
if (data.clientErrors?.length > 0) {
|
|
1881
|
+
parts.push(chalk.red(`errors=${data.clientErrors.length}`));
|
|
1882
|
+
}
|
|
1883
|
+
else {
|
|
1884
|
+
parts.push(`errors=0`);
|
|
1885
|
+
}
|
|
1886
|
+
if (data.seedResult)
|
|
1887
|
+
parts.push(`seed=${data.seedResult.success}`);
|
|
1888
|
+
if (data.captureError)
|
|
1889
|
+
parts.push(chalk.yellow(`captureError="${data.captureError}"`));
|
|
1890
|
+
console.log(prefix + parts.join(' '));
|
|
1891
|
+
// Surface client errors prominently so they can't be missed
|
|
1892
|
+
if (data.clientErrors && data.clientErrors.length > 0) {
|
|
1893
|
+
console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
|
|
1894
|
+
for (const err of data.clientErrors) {
|
|
1895
|
+
console.log(chalk.red(` → ${err}`));
|
|
1896
|
+
}
|
|
1897
|
+
console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
|
|
1898
|
+
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
1899
|
+
}
|
|
1900
|
+
if (!res.ok) {
|
|
1901
|
+
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
1902
|
+
failed++;
|
|
1903
|
+
}
|
|
1904
|
+
else {
|
|
1905
|
+
succeeded++;
|
|
1601
1906
|
}
|
|
1602
|
-
console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
|
|
1603
|
-
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
1604
1907
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1908
|
+
catch (error) {
|
|
1909
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1910
|
+
console.error(prefix + chalk.red(`Error: Could not reach editor server at ${url}`));
|
|
1911
|
+
console.error(chalk.dim(` ${msg}`));
|
|
1912
|
+
console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
|
|
1913
|
+
failed++;
|
|
1608
1914
|
}
|
|
1609
1915
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
|
|
1916
|
+
if (isBatch) {
|
|
1917
|
+
console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
|
|
1918
|
+
}
|
|
1919
|
+
if (failed > 0) {
|
|
1615
1920
|
process.exit(1);
|
|
1616
1921
|
}
|
|
1617
1922
|
}
|
|
@@ -1632,11 +1937,23 @@ async function handleDependents(entityName) {
|
|
|
1632
1937
|
const progress = new ProgressReporter();
|
|
1633
1938
|
progress.start('Loading entities from database...');
|
|
1634
1939
|
await initializeEnvironment();
|
|
1635
|
-
|
|
1940
|
+
let allEntities = await loadEntities({});
|
|
1636
1941
|
if (!allEntities || allEntities.length === 0) {
|
|
1637
|
-
progress.
|
|
1638
|
-
|
|
1639
|
-
|
|
1942
|
+
progress.succeed('No entities found — running analyze-imports first...');
|
|
1943
|
+
try {
|
|
1944
|
+
await handleAnalyzeImports({ silent: true });
|
|
1945
|
+
}
|
|
1946
|
+
catch {
|
|
1947
|
+
console.error(chalk.red('Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
1948
|
+
process.exit(1);
|
|
1949
|
+
}
|
|
1950
|
+
// Reload entities after analysis
|
|
1951
|
+
const reloaded = await loadEntities({});
|
|
1952
|
+
if (!reloaded || reloaded.length === 0) {
|
|
1953
|
+
progress.fail('No entities found even after analyze-imports');
|
|
1954
|
+
process.exit(1);
|
|
1955
|
+
}
|
|
1956
|
+
allEntities = reloaded;
|
|
1640
1957
|
}
|
|
1641
1958
|
// Find the target entity by name (case-insensitive match)
|
|
1642
1959
|
const targetEntities = allEntities.filter((e) => e.name.toLowerCase() === entityName.toLowerCase());
|
|
@@ -1723,15 +2040,16 @@ async function handleDependents(entityName) {
|
|
|
1723
2040
|
* `codeyam editor change <feature>`
|
|
1724
2041
|
*
|
|
1725
2042
|
* Prints a condensed post-change checklist that guides Claude through
|
|
1726
|
-
* re-verifying after user-requested modifications.
|
|
1727
|
-
*
|
|
1728
|
-
*
|
|
2043
|
+
* re-verifying after user-requested modifications. When called from
|
|
2044
|
+
* step 13+, this loops back to step 13 (present). When called from an
|
|
2045
|
+
* earlier step, it returns to that step so the normal flow continues.
|
|
1729
2046
|
*/
|
|
1730
2047
|
function handleChange(feature) {
|
|
1731
2048
|
const root = getProjectRoot();
|
|
2049
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
2050
|
+
const state = readState(root);
|
|
1732
2051
|
if (!feature) {
|
|
1733
2052
|
// Try to read feature from state
|
|
1734
|
-
const state = readState(root);
|
|
1735
2053
|
if (state?.feature) {
|
|
1736
2054
|
feature = state.feature;
|
|
1737
2055
|
}
|
|
@@ -1741,6 +2059,7 @@ function handleChange(feature) {
|
|
|
1741
2059
|
process.exit(1);
|
|
1742
2060
|
}
|
|
1743
2061
|
}
|
|
2062
|
+
const currentStep = state?.step ?? 13;
|
|
1744
2063
|
const port = getServerPort();
|
|
1745
2064
|
console.log();
|
|
1746
2065
|
console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
|
|
@@ -1748,9 +2067,19 @@ function handleChange(feature) {
|
|
|
1748
2067
|
console.log();
|
|
1749
2068
|
console.log('The user has requested changes. Follow this checklist after making them.');
|
|
1750
2069
|
console.log();
|
|
2070
|
+
const designSystem = readDesignSystem(root);
|
|
2071
|
+
if (designSystem) {
|
|
2072
|
+
console.log(chalk.bold.magenta('Design System (active):'));
|
|
2073
|
+
console.log(chalk.magenta(' Use existing CSS custom property tokens when making visual changes — no hardcoded px or hex values.'));
|
|
2074
|
+
console.log(chalk.magenta(' Use var(--text-sm) not 14, var(--spacing-lg) not 16px, var(--text-primary) not #1A1B25.'));
|
|
2075
|
+
console.log();
|
|
2076
|
+
console.log(designSystem);
|
|
2077
|
+
console.log();
|
|
2078
|
+
}
|
|
1751
2079
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
1752
2080
|
console.log(chalk.cyan(` Refresh after EACH individual change — not after all changes are done:`));
|
|
1753
|
-
console.log(chalk.cyan(` codeyam editor preview`));
|
|
2081
|
+
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
2082
|
+
printDimensionGuidance(dim, dimNames);
|
|
1754
2083
|
console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
|
|
1755
2084
|
console.log();
|
|
1756
2085
|
console.log(chalk.bold('0. Close the results panel:'));
|
|
@@ -1758,7 +2087,7 @@ function handleChange(feature) {
|
|
|
1758
2087
|
console.log();
|
|
1759
2088
|
console.log(chalk.bold('1. Re-register affected component scenarios:'));
|
|
1760
2089
|
checkbox('For each component you modified, re-register ALL its scenarios');
|
|
1761
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName"}'`));
|
|
2090
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName","dimensions":["${dim}"]}'`));
|
|
1762
2091
|
checkbox('For any NEW components: `codeyam editor isolate NewComponent` then create isolation routes and register scenarios');
|
|
1763
2092
|
console.log();
|
|
1764
2093
|
console.log(chalk.bold('2. Re-run affected tests:'));
|
|
@@ -1769,7 +2098,6 @@ function handleChange(feature) {
|
|
|
1769
2098
|
console.log(chalk.dim(` codeyam editor dev-server '{"action":"restart"}'`));
|
|
1770
2099
|
console.log();
|
|
1771
2100
|
console.log(chalk.bold('3. Re-capture app-level scenarios:'));
|
|
1772
|
-
checkbox('Run `codeyam editor analyze-imports` to refresh the import graph after code changes');
|
|
1773
2101
|
checkbox('For each changed component, run `codeyam editor dependents ComponentName` to find affected pages');
|
|
1774
2102
|
checkbox('Run `codeyam editor scenarios` to list all existing scenarios');
|
|
1775
2103
|
checkbox('For EACH page listed by dependents: find its existing scenarios in the list and re-register every one');
|
|
@@ -1777,15 +2105,22 @@ function handleChange(feature) {
|
|
|
1777
2105
|
console.log(chalk.dim(' Re-register with the SAME name to update — do NOT create new/duplicate scenarios.'));
|
|
1778
2106
|
console.log(chalk.dim(' Example: if Header changed and Home, Catalog, Detail pages use it,'));
|
|
1779
2107
|
console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
|
|
2108
|
+
checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
|
|
2109
|
+
console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
|
|
2110
|
+
checkbox('After each re-registration, view the screenshot to verify data is visible');
|
|
2111
|
+
console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
|
|
1780
2112
|
console.log();
|
|
1781
2113
|
console.log(chalk.bold('4. Verify completeness:'));
|
|
1782
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1783
|
-
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route"}'\``);
|
|
2114
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
2115
|
+
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
|
|
2116
|
+
printDimensionGuidance(dim, dimNames);
|
|
2117
|
+
checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
|
|
1784
2118
|
checkbox('Run `codeyam editor audit` — all checks must pass');
|
|
1785
2119
|
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
1786
2120
|
console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
|
|
1787
2121
|
console.log(chalk.dim(' If `liveErrors>0`, there are JS errors in the preview — fix them.'));
|
|
1788
2122
|
checkbox('Fix any errors, then re-register affected scenarios');
|
|
2123
|
+
checkbox('If changes affect setup, new dependencies, or new scripts: update `README.md` and `npm run setup`');
|
|
1789
2124
|
console.log();
|
|
1790
2125
|
console.log(chalk.bold('5. Update the existing journal entry:'));
|
|
1791
2126
|
checkbox('Get existing entry: `codeyam editor journal-list`');
|
|
@@ -1794,14 +2129,46 @@ function handleChange(feature) {
|
|
|
1794
2129
|
console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
|
|
1795
2130
|
console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
|
|
1796
2131
|
console.log();
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2132
|
+
// If the change was initiated from a step before 13, return to that step
|
|
2133
|
+
// instead of jumping to step 13. The change workflow should only loop to
|
|
2134
|
+
// step 13 when changes are requested FROM step 13.
|
|
2135
|
+
if (currentStep < 13) {
|
|
2136
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2137
|
+
console.log(chalk.red.bold(' REQUIRED: Return to current step'));
|
|
2138
|
+
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
2139
|
+
chalk.bold(`codeyam editor ${currentStep}`));
|
|
2140
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2141
|
+
}
|
|
2142
|
+
else {
|
|
2143
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2144
|
+
console.log(chalk.red.bold(' REQUIRED: Show Results'));
|
|
2145
|
+
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
2146
|
+
chalk.bold(`codeyam editor 13`));
|
|
2147
|
+
console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
|
|
2148
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2149
|
+
}
|
|
1803
2150
|
console.log();
|
|
1804
2151
|
}
|
|
2152
|
+
// ─── Audit gate ─────────────────────────────────────────────────────
|
|
2153
|
+
/**
|
|
2154
|
+
* Silently check whether the audit passes. Returns true if allPassing,
|
|
2155
|
+
* false if any issues remain or the server is unreachable.
|
|
2156
|
+
* Used as a hard gate before steps 8+.
|
|
2157
|
+
*/
|
|
2158
|
+
async function checkAuditGate() {
|
|
2159
|
+
const port = getServerPort();
|
|
2160
|
+
try {
|
|
2161
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
2162
|
+
if (!res.ok)
|
|
2163
|
+
return false;
|
|
2164
|
+
const data = await res.json();
|
|
2165
|
+
return data?.summary?.allPassing === true;
|
|
2166
|
+
}
|
|
2167
|
+
catch {
|
|
2168
|
+
// Server not running or unreachable — can't verify, so don't block
|
|
2169
|
+
return true;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
1805
2172
|
// ─── Audit subcommand ────────────────────────────────────────────────
|
|
1806
2173
|
/**
|
|
1807
2174
|
* `codeyam editor audit`
|
|
@@ -1914,6 +2281,17 @@ async function handleAudit() {
|
|
|
1914
2281
|
if (!allOk) {
|
|
1915
2282
|
process.exit(1);
|
|
1916
2283
|
}
|
|
2284
|
+
// Auto-run analyze-imports when audit passes — this builds the import graph
|
|
2285
|
+
// so the App tab can show component/function dependencies for each page.
|
|
2286
|
+
// Previously this was a manual step that Claude had to remember to run.
|
|
2287
|
+
console.log(chalk.dim('Building import graph...'));
|
|
2288
|
+
try {
|
|
2289
|
+
await handleAnalyzeImports({ silent: true });
|
|
2290
|
+
}
|
|
2291
|
+
catch {
|
|
2292
|
+
// Non-fatal — audit passed, import graph is a bonus
|
|
2293
|
+
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
2294
|
+
}
|
|
1917
2295
|
}
|
|
1918
2296
|
// ─── Scenarios subcommand ─────────────────────────────────────────────
|
|
1919
2297
|
async function handleScenarios() {
|
|
@@ -2001,6 +2379,77 @@ async function handleScenarios() {
|
|
|
2001
2379
|
const total = scenarios.length;
|
|
2002
2380
|
const groupCount = groups.size;
|
|
2003
2381
|
console.log(chalk.dim(`${total} scenario${total !== 1 ? 's' : ''} across ${groupCount} component${groupCount !== 1 ? 's' : ''}`));
|
|
2382
|
+
// Show screenshot paths so Claude can verify images with the Read tool
|
|
2383
|
+
const withScreenshots = scenarios.filter((s) => s.screenshotPath);
|
|
2384
|
+
if (withScreenshots.length > 0) {
|
|
2385
|
+
console.log();
|
|
2386
|
+
console.log(chalk.bold.cyan('Screenshots:'));
|
|
2387
|
+
for (const s of withScreenshots) {
|
|
2388
|
+
console.log(chalk.dim(` ${s.name}: ${s.screenshotPath}`));
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
console.log();
|
|
2392
|
+
}
|
|
2393
|
+
// ─── Scenario Coverage subcommand ───────────────────────────────────
|
|
2394
|
+
async function handleScenarioCoverage() {
|
|
2395
|
+
const port = getServerPort();
|
|
2396
|
+
const url = `http://localhost:${port}/api/editor-scenario-coverage`;
|
|
2397
|
+
let data;
|
|
2398
|
+
try {
|
|
2399
|
+
const res = await fetch(url);
|
|
2400
|
+
if (!res.ok) {
|
|
2401
|
+
console.error(chalk.red(`Error: Scenario coverage endpoint returned ${res.status}`));
|
|
2402
|
+
process.exit(1);
|
|
2403
|
+
}
|
|
2404
|
+
data = await res.json();
|
|
2405
|
+
}
|
|
2406
|
+
catch (err) {
|
|
2407
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
2408
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
2409
|
+
process.exit(1);
|
|
2410
|
+
}
|
|
2411
|
+
console.log();
|
|
2412
|
+
console.log(chalk.bold.cyan('━━━ Scenario Coverage ━━━'));
|
|
2413
|
+
console.log();
|
|
2414
|
+
if (data.note) {
|
|
2415
|
+
console.log(chalk.yellow(data.note));
|
|
2416
|
+
console.log();
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
// Fresh scenarios (already recaptured this feature)
|
|
2420
|
+
if (data.freshScenarios.length > 0) {
|
|
2421
|
+
console.log(chalk.green(`✓ ${data.freshScenarios.length} scenario(s) captured this feature:`));
|
|
2422
|
+
for (const s of data.freshScenarios) {
|
|
2423
|
+
console.log(chalk.dim(` ${s.name} (${s.url || '/'})`));
|
|
2424
|
+
}
|
|
2425
|
+
console.log();
|
|
2426
|
+
}
|
|
2427
|
+
// Stale scenarios (need recapturing)
|
|
2428
|
+
if (data.staleScenarios.length > 0) {
|
|
2429
|
+
console.log(chalk.red(`✗ ${data.staleScenarios.length} scenario(s) need re-registering (stale screenshots):`));
|
|
2430
|
+
for (const s of data.staleScenarios) {
|
|
2431
|
+
console.log(chalk.yellow(` ${s.name} (${s.url || '/'}) — ${s.changeStatus} but not recaptured`));
|
|
2432
|
+
}
|
|
2433
|
+
console.log();
|
|
2434
|
+
console.log(chalk.yellow('Re-register each stale scenario with the SAME name to update its screenshot.'));
|
|
2435
|
+
console.log(chalk.yellow("Enhance the seed data to reflect this feature's changes where relevant."));
|
|
2436
|
+
console.log();
|
|
2437
|
+
}
|
|
2438
|
+
// Uncovered pages (changed but no scenarios at all)
|
|
2439
|
+
if (data.uncoveredPages.length > 0) {
|
|
2440
|
+
console.log(chalk.red(`✗ ${data.uncoveredPages.length} page(s) affected by changes with no scenarios:`));
|
|
2441
|
+
for (const p of data.uncoveredPages) {
|
|
2442
|
+
console.log(chalk.yellow(` ${p.entityName} — ${p.changeStatus}`));
|
|
2443
|
+
}
|
|
2444
|
+
console.log();
|
|
2445
|
+
}
|
|
2446
|
+
// Summary
|
|
2447
|
+
if (data.pass) {
|
|
2448
|
+
console.log(chalk.green.bold('PASS — all affected scenarios are fresh'));
|
|
2449
|
+
}
|
|
2450
|
+
else {
|
|
2451
|
+
console.log(chalk.red.bold('FAIL — re-register stale scenarios before proceeding'));
|
|
2452
|
+
}
|
|
2004
2453
|
console.log();
|
|
2005
2454
|
}
|
|
2006
2455
|
// ─── Template subcommand ─────────────────────────────────────────────
|
|
@@ -2090,6 +2539,9 @@ async function handleTemplate() {
|
|
|
2090
2539
|
// Config parse error is non-fatal
|
|
2091
2540
|
}
|
|
2092
2541
|
}
|
|
2542
|
+
// 5b. Write a fresh prototypeId so the proxy clears stale localStorage
|
|
2543
|
+
const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
|
|
2544
|
+
fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
|
|
2093
2545
|
// 6. Trigger editor-refresh so the server picks up the new project
|
|
2094
2546
|
console.log(chalk.bold('Refreshing editor...'));
|
|
2095
2547
|
try {
|
|
@@ -2108,17 +2560,14 @@ async function handleTemplate() {
|
|
|
2108
2560
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
2109
2561
|
/**
|
|
2110
2562
|
* `codeyam editor sync`
|
|
2111
|
-
* Import scenarios from scenarios
|
|
2563
|
+
* Import scenarios from editor-scenarios/ files into the local database.
|
|
2564
|
+
* Falls back to legacy scenarios-manifest.json if no _metadata files found.
|
|
2112
2565
|
*/
|
|
2113
2566
|
async function handleSync() {
|
|
2114
2567
|
const root = getProjectRoot();
|
|
2115
|
-
const
|
|
2116
|
-
if (
|
|
2117
|
-
console.log(chalk.yellow('No
|
|
2118
|
-
return;
|
|
2119
|
-
}
|
|
2120
|
-
if (manifest.scenarios.length === 0) {
|
|
2121
|
-
console.log(chalk.dim('Manifest is empty. Nothing to sync.'));
|
|
2568
|
+
const entries = scanScenarioFiles(root);
|
|
2569
|
+
if (entries.length === 0) {
|
|
2570
|
+
console.log(chalk.yellow('No scenario files with metadata found. Nothing to sync.'));
|
|
2122
2571
|
return;
|
|
2123
2572
|
}
|
|
2124
2573
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
@@ -2142,13 +2591,12 @@ async function handleSync() {
|
|
|
2142
2591
|
const { project } = await requireBranchAndProject(projectSlug);
|
|
2143
2592
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2144
2593
|
const db = getDatabase();
|
|
2145
|
-
// Fetch existing editor scenarios
|
|
2146
2594
|
const existingRows = await db
|
|
2147
2595
|
.selectFrom('editor_scenarios')
|
|
2148
2596
|
.select(['id', 'updated_at'])
|
|
2149
2597
|
.where('project_id', '=', project.id)
|
|
2150
2598
|
.execute();
|
|
2151
|
-
const result = await
|
|
2599
|
+
const result = await syncScenarioFilesToDatabase(root, project.id, existingRows, async (row) => {
|
|
2152
2600
|
await db
|
|
2153
2601
|
.insertInto('editor_scenarios')
|
|
2154
2602
|
.values(row)
|
|
@@ -2173,6 +2621,34 @@ async function handleSync() {
|
|
|
2173
2621
|
parts.push(`${result.skipped} unchanged`);
|
|
2174
2622
|
console.log(chalk.green(`Synced scenarios: ${parts.join(', ')}`));
|
|
2175
2623
|
}
|
|
2624
|
+
// Migrate legacy scenario formats after sync, then re-sync if anything was fixed
|
|
2625
|
+
try {
|
|
2626
|
+
const migrateResult = migrateScenarioFormats(root);
|
|
2627
|
+
if (migrateResult.fixed > 0) {
|
|
2628
|
+
console.log(chalk.green(`Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
|
|
2629
|
+
// Re-sync so the DB reflects the corrected file metadata
|
|
2630
|
+
const refreshedRows = await db
|
|
2631
|
+
.selectFrom('editor_scenarios')
|
|
2632
|
+
.select(['id', 'updated_at'])
|
|
2633
|
+
.where('project_id', '=', project.id)
|
|
2634
|
+
.execute();
|
|
2635
|
+
await syncScenarioFilesToDatabase(root, project.id, refreshedRows, async (row) => {
|
|
2636
|
+
await db
|
|
2637
|
+
.insertInto('editor_scenarios')
|
|
2638
|
+
.values(row)
|
|
2639
|
+
.execute();
|
|
2640
|
+
}, async (id, row) => {
|
|
2641
|
+
await db
|
|
2642
|
+
.updateTable('editor_scenarios')
|
|
2643
|
+
.set(row)
|
|
2644
|
+
.where('id', '=', id)
|
|
2645
|
+
.execute();
|
|
2646
|
+
});
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
catch {
|
|
2650
|
+
// Non-fatal
|
|
2651
|
+
}
|
|
2176
2652
|
}
|
|
2177
2653
|
// ─── Verify Images subcommand ─────────────────────────────────────────
|
|
2178
2654
|
async function handleVerifyImages(jsonArg) {
|
|
@@ -2305,8 +2781,11 @@ function handleEditorDebug(args) {
|
|
|
2305
2781
|
11: printStep11,
|
|
2306
2782
|
12: printStep12,
|
|
2307
2783
|
13: printStep13,
|
|
2784
|
+
14: printStep14,
|
|
2785
|
+
15: printStep15,
|
|
2786
|
+
16: printStep16,
|
|
2308
2787
|
};
|
|
2309
|
-
for (let step = 1; step <=
|
|
2788
|
+
for (let step = 1; step <= 16; step++) {
|
|
2310
2789
|
const stepId = `step-${step}`;
|
|
2311
2790
|
if (!wants(stepId))
|
|
2312
2791
|
continue;
|
|
@@ -2385,8 +2864,8 @@ const editorCommand = {
|
|
|
2385
2864
|
describe: 'Editor mode guided workflow',
|
|
2386
2865
|
builder: (yargs) => {
|
|
2387
2866
|
const stepDescription = IS_INTERNAL_BUILD
|
|
2388
|
-
? 'Step number (1-
|
|
2389
|
-
: 'Step number (1-
|
|
2867
|
+
? 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)'
|
|
2868
|
+
: 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)';
|
|
2390
2869
|
let builder = yargs
|
|
2391
2870
|
.positional('step', {
|
|
2392
2871
|
type: 'string',
|
|
@@ -2422,7 +2901,7 @@ const editorCommand = {
|
|
|
2422
2901
|
builder = builder
|
|
2423
2902
|
.option('target', {
|
|
2424
2903
|
type: 'string',
|
|
2425
|
-
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-
|
|
2904
|
+
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-16, or comma-separated list)',
|
|
2426
2905
|
})
|
|
2427
2906
|
.option('resume', {
|
|
2428
2907
|
type: 'boolean',
|
|
@@ -2497,6 +2976,11 @@ const editorCommand = {
|
|
|
2497
2976
|
await handleScenarios();
|
|
2498
2977
|
return;
|
|
2499
2978
|
}
|
|
2979
|
+
// Subcommand: codeyam editor scenario-coverage
|
|
2980
|
+
if (argv.step === 'scenario-coverage') {
|
|
2981
|
+
await handleScenarioCoverage();
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2500
2984
|
// Subcommand: codeyam editor change <feature>
|
|
2501
2985
|
if (argv.step === 'change') {
|
|
2502
2986
|
handleChange(argv.json || '');
|
|
@@ -2549,9 +3033,9 @@ const editorCommand = {
|
|
|
2549
3033
|
}
|
|
2550
3034
|
else {
|
|
2551
3035
|
const state = readState(root);
|
|
2552
|
-
// Clear prompt file when feature is done (step
|
|
3036
|
+
// Clear prompt file when feature is done (step 16) so the hook
|
|
2553
3037
|
// can capture the next feature request from the user.
|
|
2554
|
-
if (state?.step ===
|
|
3038
|
+
if (state?.step === 16) {
|
|
2555
3039
|
clearEditorUserPrompt(root);
|
|
2556
3040
|
}
|
|
2557
3041
|
printCycleOverview(root, state);
|
|
@@ -2559,8 +3043,8 @@ const editorCommand = {
|
|
|
2559
3043
|
return;
|
|
2560
3044
|
}
|
|
2561
3045
|
const step = argv.step ? parseInt(argv.step, 10) : undefined;
|
|
2562
|
-
if (step != null && (isNaN(step) || step < 1 || step >
|
|
2563
|
-
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-
|
|
3046
|
+
if (step != null && (isNaN(step) || step < 1 || step > 16)) {
|
|
3047
|
+
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-16.`));
|
|
2564
3048
|
process.exit(1);
|
|
2565
3049
|
}
|
|
2566
3050
|
if (step == null) {
|
|
@@ -2593,9 +3077,42 @@ const editorCommand = {
|
|
|
2593
3077
|
return;
|
|
2594
3078
|
}
|
|
2595
3079
|
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
2596
|
-
//
|
|
2597
|
-
|
|
2598
|
-
|
|
3080
|
+
// Backfill _metadata into existing scenario files that predate the embedded metadata feature.
|
|
3081
|
+
// This reads metadata from the DB and writes it into files that don't have _metadata yet.
|
|
3082
|
+
try {
|
|
3083
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3084
|
+
const db = getDatabase();
|
|
3085
|
+
const dbScenarios = await db
|
|
3086
|
+
.selectFrom('editor_scenarios')
|
|
3087
|
+
.select([
|
|
3088
|
+
'id',
|
|
3089
|
+
'name',
|
|
3090
|
+
'description',
|
|
3091
|
+
'component_name',
|
|
3092
|
+
'component_path',
|
|
3093
|
+
'url',
|
|
3094
|
+
'type',
|
|
3095
|
+
'screenshot_path',
|
|
3096
|
+
'viewport_width',
|
|
3097
|
+
'viewport_height',
|
|
3098
|
+
'created_at',
|
|
3099
|
+
'updated_at',
|
|
3100
|
+
])
|
|
3101
|
+
.where('project_id', '=', project.id)
|
|
3102
|
+
.execute();
|
|
3103
|
+
if (dbScenarios.length > 0) {
|
|
3104
|
+
const backfilled = backfillScenarioMetadata(projectRoot, dbScenarios);
|
|
3105
|
+
if (backfilled > 0) {
|
|
3106
|
+
console.log(chalk.green(` Backfilled metadata into ${backfilled} scenario file${backfilled > 1 ? 's' : ''}`));
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
catch {
|
|
3111
|
+
// Non-fatal — backfill is best-effort
|
|
3112
|
+
}
|
|
3113
|
+
// Auto-sync scenario files (with _metadata) to database
|
|
3114
|
+
const scenarioEntries = scanScenarioFiles(projectRoot);
|
|
3115
|
+
if (scenarioEntries.length > 0) {
|
|
2599
3116
|
try {
|
|
2600
3117
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2601
3118
|
const db = getDatabase();
|
|
@@ -2604,7 +3121,7 @@ const editorCommand = {
|
|
|
2604
3121
|
.select(['id', 'updated_at'])
|
|
2605
3122
|
.where('project_id', '=', project.id)
|
|
2606
3123
|
.execute();
|
|
2607
|
-
const syncResult = await
|
|
3124
|
+
const syncResult = await syncScenarioFilesToDatabase(projectRoot, project.id, existingRows, async (row) => {
|
|
2608
3125
|
await db
|
|
2609
3126
|
.insertInto('editor_scenarios')
|
|
2610
3127
|
.values(row)
|
|
@@ -2622,13 +3139,50 @@ const editorCommand = {
|
|
|
2622
3139
|
parts.push(`${syncResult.inserted} imported`);
|
|
2623
3140
|
if (syncResult.updated > 0)
|
|
2624
3141
|
parts.push(`${syncResult.updated} updated`);
|
|
2625
|
-
console.log(chalk.green(` Synced scenarios from
|
|
3142
|
+
console.log(chalk.green(` Synced scenarios from files: ${parts.join(', ')}`));
|
|
2626
3143
|
}
|
|
2627
3144
|
}
|
|
2628
3145
|
catch {
|
|
2629
3146
|
// Non-fatal — sync failure shouldn't block editor startup
|
|
2630
3147
|
}
|
|
2631
3148
|
}
|
|
3149
|
+
// Migrate legacy scenario formats: resolve null viewports, populate
|
|
3150
|
+
// dimensions arrays, and build screenshotPaths maps from single values.
|
|
3151
|
+
// If files were fixed, re-sync to update the database with corrected values.
|
|
3152
|
+
try {
|
|
3153
|
+
const migrateResult = migrateScenarioFormats(projectRoot);
|
|
3154
|
+
if (migrateResult.fixed > 0) {
|
|
3155
|
+
console.log(chalk.green(` Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
|
|
3156
|
+
// Re-sync so the DB reflects the fixed file metadata
|
|
3157
|
+
try {
|
|
3158
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3159
|
+
const db = getDatabase();
|
|
3160
|
+
const rows = await db
|
|
3161
|
+
.selectFrom('editor_scenarios')
|
|
3162
|
+
.select(['id', 'updated_at'])
|
|
3163
|
+
.where('project_id', '=', project.id)
|
|
3164
|
+
.execute();
|
|
3165
|
+
await syncScenarioFilesToDatabase(projectRoot, project.id, rows, async (row) => {
|
|
3166
|
+
await db
|
|
3167
|
+
.insertInto('editor_scenarios')
|
|
3168
|
+
.values(row)
|
|
3169
|
+
.execute();
|
|
3170
|
+
}, async (id, row) => {
|
|
3171
|
+
await db
|
|
3172
|
+
.updateTable('editor_scenarios')
|
|
3173
|
+
.set(row)
|
|
3174
|
+
.where('id', '=', id)
|
|
3175
|
+
.execute();
|
|
3176
|
+
});
|
|
3177
|
+
}
|
|
3178
|
+
catch {
|
|
3179
|
+
// Non-fatal — DB re-sync failure shouldn't block startup
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
catch {
|
|
3184
|
+
// Non-fatal — migration failure shouldn't block editor startup
|
|
3185
|
+
}
|
|
2632
3186
|
// `codeyam editor` (no step) always implies editor mode.
|
|
2633
3187
|
// The empty-folder heuristic is no longer needed here — running this
|
|
2634
3188
|
// command IS the signal. We still detect empty folders so that
|
|
@@ -2667,29 +3221,23 @@ const editorCommand = {
|
|
|
2667
3221
|
editorMode,
|
|
2668
3222
|
});
|
|
2669
3223
|
// Auto-finalize analyzer so codeyam analyze works
|
|
2670
|
-
if (editorMode
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
});
|
|
2684
|
-
execSync('npm run build', {
|
|
2685
|
-
cwd: templatePath,
|
|
2686
|
-
stdio: 'pipe',
|
|
2687
|
-
});
|
|
2688
|
-
fs.writeFileSync(path.join(templatePath, '.finalized'), new Date().toISOString());
|
|
3224
|
+
if (editorMode) {
|
|
3225
|
+
const stepLabels = {
|
|
3226
|
+
'npm-install': 'Installing simulation dependencies...',
|
|
3227
|
+
'playwright-install': 'Installing browser (Chromium)...',
|
|
3228
|
+
build: 'Building simulation engine...',
|
|
3229
|
+
};
|
|
3230
|
+
const simProgress = new ProgressReporter();
|
|
3231
|
+
const finalization = ensureAnalyzerFinalized({
|
|
3232
|
+
onProgress: (step) => simProgress.start(stepLabels[step]),
|
|
3233
|
+
});
|
|
3234
|
+
if (finalization.errors.length > 0) {
|
|
3235
|
+
for (const err of finalization.errors) {
|
|
3236
|
+
simProgress.warn(`${stepLabels[err.step].replace('...', '')} failed: ${err.message}`);
|
|
2689
3237
|
}
|
|
2690
3238
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
3239
|
+
else if (finalization.needed) {
|
|
3240
|
+
simProgress.succeed('Simulation engine ready');
|
|
2693
3241
|
}
|
|
2694
3242
|
}
|
|
2695
3243
|
// Start background server (handles killing existing servers internally)
|
|
@@ -2700,6 +3248,19 @@ const editorCommand = {
|
|
|
2700
3248
|
project,
|
|
2701
3249
|
branch,
|
|
2702
3250
|
});
|
|
3251
|
+
// Build import graph on first startup if glossary exists but no entities yet
|
|
3252
|
+
const glossaryPath = path.join(projectRoot, '.codeyam', 'glossary.json');
|
|
3253
|
+
if (fs.existsSync(glossaryPath)) {
|
|
3254
|
+
const entities = await loadEntities({});
|
|
3255
|
+
if (!entities || entities.length === 0) {
|
|
3256
|
+
try {
|
|
3257
|
+
await handleAnalyzeImports({ silent: true });
|
|
3258
|
+
}
|
|
3259
|
+
catch {
|
|
3260
|
+
// Non-fatal
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
2703
3264
|
console.log();
|
|
2704
3265
|
console.log(` Dashboard: ${url}`);
|
|
2705
3266
|
console.log(' Run "codeyam --help" for all commands');
|
|
@@ -2727,6 +3288,18 @@ const editorCommand = {
|
|
|
2727
3288
|
return;
|
|
2728
3289
|
}
|
|
2729
3290
|
const state = readState(root);
|
|
3291
|
+
// Validate step transition — prevent skipping steps.
|
|
3292
|
+
// Exception: step 2 with --feature is always allowed because step 1's
|
|
3293
|
+
// instructions explicitly tell Claude to run `codeyam editor 2 --feature "..."`.
|
|
3294
|
+
// Step 1 is planning-only and may not persist state (no --feature flag).
|
|
3295
|
+
const skipValidation = step === 2 && argv.feature;
|
|
3296
|
+
if (!skipValidation) {
|
|
3297
|
+
const stepError = validateStepTransition(step, state?.step ?? null);
|
|
3298
|
+
if (stepError) {
|
|
3299
|
+
console.error(chalk.red(`Error: ${stepError}`));
|
|
3300
|
+
process.exit(1);
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
2730
3303
|
switch (step) {
|
|
2731
3304
|
case 1: {
|
|
2732
3305
|
const feature = argv.feature || undefined;
|
|
@@ -2758,12 +3331,24 @@ const editorCommand = {
|
|
|
2758
3331
|
case 10:
|
|
2759
3332
|
case 11:
|
|
2760
3333
|
case 12:
|
|
2761
|
-
case 13:
|
|
3334
|
+
case 13:
|
|
3335
|
+
case 14:
|
|
3336
|
+
case 15:
|
|
3337
|
+
case 16: {
|
|
2762
3338
|
const feature = argv.feature || state?.feature;
|
|
2763
3339
|
if (!feature) {
|
|
2764
3340
|
console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
|
|
2765
3341
|
process.exit(1);
|
|
2766
3342
|
}
|
|
3343
|
+
// Hard gate: steps 8+ require audit to have passed
|
|
3344
|
+
if (step >= 8) {
|
|
3345
|
+
const auditOk = await checkAuditGate();
|
|
3346
|
+
if (!auditOk) {
|
|
3347
|
+
console.error(chalk.red.bold('BLOCKED: The audit has not passed. Run `codeyam editor audit` and fix all issues before proceeding.'));
|
|
3348
|
+
console.error(chalk.yellow('Every function and hook must have a test file. Every component must have scenarios.'));
|
|
3349
|
+
process.exit(1);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
2767
3352
|
const stepFns = {
|
|
2768
3353
|
3: printStep3,
|
|
2769
3354
|
4: printStep4,
|
|
@@ -2776,6 +3361,9 @@ const editorCommand = {
|
|
|
2776
3361
|
11: printStep11,
|
|
2777
3362
|
12: printStep12,
|
|
2778
3363
|
13: printStep13,
|
|
3364
|
+
14: printStep14,
|
|
3365
|
+
15: printStep15,
|
|
3366
|
+
16: printStep16,
|
|
2779
3367
|
};
|
|
2780
3368
|
stepFns[step](root, feature);
|
|
2781
3369
|
break;
|