@codeyam/codeyam-cli 0.1.0-staging.25a2014 → 0.1.0-staging.26dc674
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/package.json +2 -2
- package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +5 -1
- package/analyzer-template/packages/analyze/src/lib/files/analyze/findOrCreateEntity.ts +10 -6
- package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +9 -12
- package/analyzer-template/packages/analyze/src/lib/files/analyzeChange.ts +4 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyzeInitial.ts +4 -0
- package/analyzer-template/packages/aws/package.json +1 -1
- package/analyzer-template/packages/database/package.json +1 -1
- package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +19 -15
- package/analyzer-template/packages/database/src/lib/loadEntity.ts +19 -8
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts +4 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js +5 -5
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts +3 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +22 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
- package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +27 -0
- package/analyzer-template/project/analyzeFileEntities.ts +26 -0
- package/background/src/lib/virtualized/project/analyzeFileEntities.js +22 -0
- package/background/src/lib/virtualized/project/analyzeFileEntities.js.map +1 -1
- package/codeyam-cli/src/cli.js +15 -0
- package/codeyam-cli/src/cli.js.map +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +9 -9
- package/codeyam-cli/src/commands/editor.js +981 -344
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/init.js +1 -0
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +1274 -33
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js +137 -0
- package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +66 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js +67 -0
- package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +70 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +71 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +233 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js +177 -0
- package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js +16 -1
- package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
- package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +57 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +41 -0
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js +84 -0
- package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js.map +1 -0
- package/codeyam-cli/src/utils/analysisRunner.js +8 -6
- package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +8 -0
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +277 -37
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorGuard.js +36 -0
- package/codeyam-cli/src/utils/editorGuard.js.map +1 -0
- package/codeyam-cli/src/utils/editorRecapture.js +109 -0
- package/codeyam-cli/src/utils/editorRecapture.js.map +1 -0
- package/codeyam-cli/src/utils/editorScenarioSwitch.js +24 -2
- package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +65 -6
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.js +31 -3
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/glossaryAdd.js +74 -0
- package/codeyam-cli/src/utils/glossaryAdd.js.map +1 -0
- package/codeyam-cli/src/utils/install-skills.js +5 -0
- package/codeyam-cli/src/utils/install-skills.js.map +1 -1
- package/codeyam-cli/src/utils/queue/job.js +6 -3
- package/codeyam-cli/src/utils/queue/job.js.map +1 -1
- package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
- package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
- package/codeyam-cli/src/utils/scenarioCoverage.js +4 -1
- package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -1
- package/codeyam-cli/src/utils/scenariosManifest.js +28 -0
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/screenshotHash.js +26 -0
- package/codeyam-cli/src/utils/screenshotHash.js.map +1 -0
- package/codeyam-cli/src/utils/simulationGateMiddleware.js +9 -0
- package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +68 -1
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +123 -21
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +135 -0
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +22 -1
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-recapture-stale-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-scenario-data-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-schema-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-CJzc4vOH.svg} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DODLxLcw.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-Dx-h1rJK.js +130 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-NTuLi4Xg.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-CUobbQdQ.js → entity._sha.scenarios._scenarioId.dev-BA5L8bU-.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js → entity._sha.scenarios._scenarioId.fullscreen-D4dmRgvO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-BrPXT1iR.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/manifest-5025e428.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{root-DB3O9_9j.js → root-BCx1S8Z3.js} +26 -13
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-C1kjC9UJ.js +13 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-CEaDhUiv.js → index-C91yWWCI.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-DA7guOrE.js → init-Dkas-RUS.js} +2 -2
- package/codeyam-cli/src/webserver/build/server/assets/server-build-pulXLTrG.js +640 -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/idleDetector.js +41 -8
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
- package/codeyam-cli/src/webserver/scripts/journalCapture.ts +53 -0
- package/codeyam-cli/src/webserver/server.js +52 -14
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +141 -27
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/__tests__/editor-step-hook.prompt-capture.test.ts +118 -0
- package/codeyam-cli/templates/codeyam-editor-claude.md +2 -0
- package/codeyam-cli/templates/codeyam-editor-reference.md +214 -0
- package/codeyam-cli/templates/editor-step-hook.py +93 -46
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +19 -1
- package/package.json +1 -1
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +4 -1
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +3 -2
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +9 -7
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
- package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
- package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
- package/packages/database/src/lib/loadAnalysis.js +1 -1
- package/packages/database/src/lib/loadAnalysis.js.map +1 -1
- package/packages/database/src/lib/loadEntity.js +5 -5
- package/packages/database/src/lib/loadEntity.js.map +1 -1
- package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
- package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-B7xQ9Sjy.js +0 -58
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-CxmrE6AF.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/globals-fAqOD9ex.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-5d53342d.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-DcJSnBCE.js +0 -13
- package/codeyam-cli/src/webserver/build/server/assets/server-build-juyiY2m6.js +0 -551
|
@@ -16,32 +16,35 @@ 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
18
|
import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
|
|
19
|
-
import { clearEditorState,
|
|
19
|
+
import { clearEditorState, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
|
|
20
20
|
import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
|
|
21
21
|
import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
|
|
22
22
|
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
23
23
|
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
24
|
+
import { classifyRegistrationResult } from "../utils/registerScenarioResult.js";
|
|
24
25
|
import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
|
|
25
26
|
import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
|
|
26
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
28
|
const __dirname = path.dirname(__filename);
|
|
28
29
|
const STEP_LABELS = {
|
|
29
30
|
1: 'Plan',
|
|
30
|
-
2: '
|
|
31
|
-
3: '
|
|
32
|
-
4: '
|
|
33
|
-
5: '
|
|
34
|
-
6: '
|
|
35
|
-
7: '
|
|
36
|
-
8: '
|
|
37
|
-
9: '
|
|
38
|
-
10: '
|
|
39
|
-
11: '
|
|
40
|
-
12: '
|
|
41
|
-
13: '
|
|
42
|
-
14: '
|
|
43
|
-
15: '
|
|
44
|
-
16: '
|
|
31
|
+
2: 'Prepare',
|
|
32
|
+
3: 'Prototype',
|
|
33
|
+
4: 'Verify Prototype',
|
|
34
|
+
5: 'Confirm',
|
|
35
|
+
6: 'Deconstruct',
|
|
36
|
+
7: 'Extract',
|
|
37
|
+
8: 'Glossary',
|
|
38
|
+
9: 'Analyze',
|
|
39
|
+
10: 'App Scenarios',
|
|
40
|
+
11: 'User Scenarios',
|
|
41
|
+
12: 'Verify',
|
|
42
|
+
13: 'Journal',
|
|
43
|
+
14: 'Review',
|
|
44
|
+
15: 'Present',
|
|
45
|
+
16: 'Commit',
|
|
46
|
+
17: 'Finalize',
|
|
47
|
+
18: 'Push',
|
|
45
48
|
};
|
|
46
49
|
const MIGRATION_STEP_LABELS = {
|
|
47
50
|
1: 'Survey',
|
|
@@ -119,11 +122,19 @@ function writeState(root, state) {
|
|
|
119
122
|
const dir = path.dirname(statePath);
|
|
120
123
|
fs.mkdirSync(dir, { recursive: true });
|
|
121
124
|
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
125
|
+
// Write task tracking file — marks that a task is expected for this step.
|
|
126
|
+
// The step hook sets taskCreated=true when it sees a TaskCreate tool call.
|
|
127
|
+
// Steps 1 and below don't require tasks (feature not named yet).
|
|
128
|
+
if (state.step && state.step >= 2) {
|
|
129
|
+
const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
|
|
130
|
+
fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: false }, null, 2), 'utf8');
|
|
131
|
+
}
|
|
122
132
|
}
|
|
123
133
|
/**
|
|
124
134
|
* Clear the editor state (for starting a new feature).
|
|
125
|
-
* Does NOT clear the user prompt file —
|
|
126
|
-
*
|
|
135
|
+
* Does NOT clear the user prompt file — the prompt is cleared
|
|
136
|
+
* by the journal API after recording, and the hook captures
|
|
137
|
+
* a fresh prompt for the next feature on UserPromptSubmit.
|
|
127
138
|
*/
|
|
128
139
|
function clearState(root) {
|
|
129
140
|
clearEditorState(root);
|
|
@@ -200,6 +211,34 @@ function checkbox(text) {
|
|
|
200
211
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
201
212
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
202
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Instructions for creating/updating .codeyam/data-structure.json.
|
|
216
|
+
* Only prints creation instructions if the file doesn't exist yet.
|
|
217
|
+
* If it exists, reminds Claude to update it if data models changed.
|
|
218
|
+
*/
|
|
219
|
+
function printDataStructureInstructions() {
|
|
220
|
+
const root = getProjectRoot();
|
|
221
|
+
const dsPath = path.join(root, '.codeyam', 'data-structure.json');
|
|
222
|
+
const exists = fs.existsSync(dsPath);
|
|
223
|
+
if (!exists) {
|
|
224
|
+
console.log(chalk.bold('Create the data structure config:'));
|
|
225
|
+
checkbox('Create `.codeyam/data-structure.json` describing all data sources');
|
|
226
|
+
console.log(chalk.dim(" This file tells the editor what data the app uses and how it's stored."));
|
|
227
|
+
console.log(chalk.dim(' The file is a JSON array. Each entry describes one data source:'));
|
|
228
|
+
console.log(chalk.dim(' { "name": "Drink", "description": "...", "category": "datastore", "order": 1,'));
|
|
229
|
+
console.log(chalk.dim(' "fields": [{ "name": "id", "type": "number", "isId": true, "required": true }, ...] }'));
|
|
230
|
+
console.log();
|
|
231
|
+
console.log(chalk.dim(' category: "datastore" for persisted data (DB, localStorage)'));
|
|
232
|
+
console.log(chalk.dim(' "mock-api" for mocked external API responses'));
|
|
233
|
+
console.log(chalk.dim(' order: 1 = primary data store, 2 = secondary, etc.'));
|
|
234
|
+
console.log(chalk.dim(' Do NOT include types only used as component props.'));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
checkbox('If this feature changes data models, update `.codeyam/data-structure.json`');
|
|
238
|
+
console.log(chalk.dim(' Add new tables, update fields, or change descriptions to match.'));
|
|
239
|
+
}
|
|
240
|
+
console.log();
|
|
241
|
+
}
|
|
203
242
|
/**
|
|
204
243
|
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
205
244
|
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
@@ -208,8 +247,9 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
208
247
|
const root = process.cwd();
|
|
209
248
|
const hasSeedAdapter = !!detectSeedAdapter(root);
|
|
210
249
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
211
|
-
console.log(chalk.dim(' Review existing scenarios —
|
|
212
|
-
console.log(chalk.dim('
|
|
250
|
+
console.log(chalk.dim(' Review existing scenarios — enhance their seed data to exercise new features.'));
|
|
251
|
+
console.log(chalk.dim(' A rich scenario that exercises 5 features is better than 5 thin scenarios with one feature each.'));
|
|
252
|
+
console.log(chalk.dim(' Rename scenarios if their data scope has grown beyond the original name.'));
|
|
213
253
|
console.log();
|
|
214
254
|
console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
|
|
215
255
|
console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
|
|
@@ -244,11 +284,15 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
244
284
|
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
245
285
|
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
246
286
|
}
|
|
247
|
-
|
|
248
|
-
console.log(chalk.
|
|
249
|
-
console.log(chalk.dim('
|
|
287
|
+
console.log();
|
|
288
|
+
console.log(chalk.bold('Register ALL scenarios at once (bulk registration):'));
|
|
289
|
+
console.log(chalk.dim(' Write an array of scenario objects to a temp file:'));
|
|
290
|
+
console.log(chalk.dim(' [{"name":"...","type":"application","url":"/","seed":{...}}, {"name":"...","url":"/other",...}]'));
|
|
291
|
+
console.log(chalk.dim(' Then register all at once: codeyam editor register @.codeyam/tmp/scenarios.json'));
|
|
292
|
+
console.log(chalk.yellow(' Bulk registration is preferred — faster and avoids repeated capture overhead.'));
|
|
293
|
+
console.log();
|
|
250
294
|
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
251
|
-
checkbox('After
|
|
295
|
+
checkbox('After registration, check the response for `clientErrors`');
|
|
252
296
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
253
297
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
254
298
|
}
|
|
@@ -305,7 +349,7 @@ function printExtractionPlanInstructions() {
|
|
|
305
349
|
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
306
350
|
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
307
351
|
console.log();
|
|
308
|
-
console.log(chalk.dim('Present the numbered plan, then proceed to step
|
|
352
|
+
console.log(chalk.dim('Present the numbered plan, then proceed to step 7 to execute it.'));
|
|
309
353
|
}
|
|
310
354
|
/**
|
|
311
355
|
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
@@ -349,6 +393,10 @@ function printComponentCaptureInstructions() {
|
|
|
349
393
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
350
394
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
351
395
|
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
396
|
+
console.log();
|
|
397
|
+
console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
|
|
398
|
+
console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/codeyam-isolate/Comp?s=Default"}, ...]'));
|
|
399
|
+
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
|
|
352
400
|
}
|
|
353
401
|
/**
|
|
354
402
|
* Print a section header.
|
|
@@ -362,13 +410,13 @@ function stepHeader(step, title, feature) {
|
|
|
362
410
|
console.log();
|
|
363
411
|
}
|
|
364
412
|
/**
|
|
365
|
-
* Print a colored progress tracker showing all
|
|
413
|
+
* Print a colored progress tracker showing all 18 steps.
|
|
366
414
|
* Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
|
|
367
415
|
*/
|
|
368
416
|
function printProgressTracker(current) {
|
|
369
417
|
console.log();
|
|
370
418
|
console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
|
|
371
|
-
for (let i = 1; i <=
|
|
419
|
+
for (let i = 1; i <= 18; i++) {
|
|
372
420
|
const label = STEP_LABELS[i];
|
|
373
421
|
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
374
422
|
const content = `${num}. ${label.padEnd(28)}`;
|
|
@@ -389,9 +437,31 @@ function printProgressTracker(current) {
|
|
|
389
437
|
*
|
|
390
438
|
* Options:
|
|
391
439
|
* - confirm: true → step requires user confirmation before proceeding (steps 1, 3, 11)
|
|
440
|
+
* - feature: string → feature name for task directive
|
|
392
441
|
*/
|
|
393
442
|
function stopGate(current, opts) {
|
|
394
|
-
|
|
443
|
+
const totalSteps = Object.keys(STEP_LABELS).length;
|
|
444
|
+
const currentLabel = STEP_LABELS[current] || `Step ${current}`;
|
|
445
|
+
const nextLabel = current < totalSteps ? STEP_LABELS[current + 1] : undefined;
|
|
446
|
+
console.log();
|
|
447
|
+
// ━━━ TASK ━━━ — deterministic task lifecycle directive
|
|
448
|
+
// Skip step 1 (no feature name yet — task starts at step 2)
|
|
449
|
+
if (current >= 2) {
|
|
450
|
+
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
451
|
+
console.log(chalk.cyan('Mark your current task (if any) as complete.'));
|
|
452
|
+
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
453
|
+
console.log();
|
|
454
|
+
let taskTitle;
|
|
455
|
+
if (current < totalSteps) {
|
|
456
|
+
taskTitle = `Complete codeyam editor step ${current}: '${currentLabel}' and move on to step ${current + 1}: '${nextLabel}'`;
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
taskTitle =
|
|
460
|
+
'Ask user what to build next and restart codeyam editor workflow';
|
|
461
|
+
}
|
|
462
|
+
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
463
|
+
console.log();
|
|
464
|
+
}
|
|
395
465
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
396
466
|
console.log();
|
|
397
467
|
console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
|
|
@@ -400,20 +470,12 @@ function stopGate(current, opts) {
|
|
|
400
470
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
401
471
|
}
|
|
402
472
|
console.log();
|
|
403
|
-
|
|
404
|
-
printProgressTracker(current);
|
|
405
|
-
console.log();
|
|
406
|
-
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
407
|
-
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
408
|
-
console.log();
|
|
409
|
-
if (current < 16) {
|
|
473
|
+
if (current < totalSteps) {
|
|
410
474
|
console.log(chalk.green('When done, run: ') +
|
|
411
475
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
412
476
|
}
|
|
413
477
|
else {
|
|
414
|
-
console.log(chalk.green('Feature complete!
|
|
415
|
-
chalk.bold('codeyam editor 1') +
|
|
416
|
-
chalk.green(' to start the next feature'));
|
|
478
|
+
console.log(chalk.green('Feature complete! Ask the user what to build next, then run: ') + chalk.bold('codeyam editor 1'));
|
|
417
479
|
}
|
|
418
480
|
console.log();
|
|
419
481
|
}
|
|
@@ -428,34 +490,36 @@ function printResumptionHeader(step) {
|
|
|
428
490
|
'Check if plan was already written to context file:\n `cat .codeyam/editor-mode-context.md`',
|
|
429
491
|
],
|
|
430
492
|
2: ['Check if project files already exist:\n `ls package.json src/`'],
|
|
431
|
-
3: ['
|
|
432
|
-
4: [
|
|
493
|
+
3: ['Re-check is safe — just re-run the step'],
|
|
494
|
+
4: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
495
|
+
5: ['This is a confirmation step — just re-present to user'],
|
|
496
|
+
6: [
|
|
433
497
|
'Check if extraction plan already exists in context file:\n `cat .codeyam/editor-mode-context.md`',
|
|
434
498
|
],
|
|
435
|
-
|
|
499
|
+
7: [
|
|
436
500
|
'Check if components/functions already extracted:\n `ls src/components/ src/lib/`',
|
|
437
501
|
],
|
|
438
|
-
|
|
502
|
+
8: [
|
|
439
503
|
'Check if glossary already populated:\n `cat .codeyam/glossary.json`',
|
|
440
504
|
],
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
505
|
+
9: ['Check if isolation routes and registered scenarios already exist'],
|
|
506
|
+
10: ['Check existing scenarios:\n `codeyam editor scenarios`'],
|
|
507
|
+
11: [
|
|
444
508
|
'Check existing user-persona scenarios:\n `codeyam editor scenarios`',
|
|
445
509
|
],
|
|
446
|
-
|
|
447
|
-
|
|
510
|
+
12: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
511
|
+
13: [
|
|
448
512
|
'Check if a journal entry already exists for this feature:\n `codeyam editor journal-list`',
|
|
449
513
|
'If an entry exists, use PATCH to update it — do NOT create a new one',
|
|
450
514
|
],
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
515
|
+
14: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
516
|
+
15: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
517
|
+
16: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
518
|
+
17: [
|
|
455
519
|
'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
|
|
456
520
|
'Check if commit was already amended:\n `git log --oneline -3`',
|
|
457
521
|
],
|
|
458
|
-
|
|
522
|
+
18: [
|
|
459
523
|
'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
|
|
460
524
|
],
|
|
461
525
|
};
|
|
@@ -562,7 +626,7 @@ function parseDebugTargets(target) {
|
|
|
562
626
|
}
|
|
563
627
|
if (entry.startsWith('step-')) {
|
|
564
628
|
const num = parseInt(entry.replace('step-', ''), 10);
|
|
565
|
-
if (!isNaN(num) && num >= 1 && num <=
|
|
629
|
+
if (!isNaN(num) && num >= 1 && num <= 18) {
|
|
566
630
|
valid.add(`step-${num}`);
|
|
567
631
|
continue;
|
|
568
632
|
}
|
|
@@ -779,37 +843,39 @@ function printCycleOverview(root, state) {
|
|
|
779
843
|
console.log();
|
|
780
844
|
console.log(chalk.green('Continue with: ') +
|
|
781
845
|
chalk.bold(`codeyam editor ${state.step}`));
|
|
782
|
-
console.log(chalk.dim('Or run ')
|
|
783
|
-
chalk.bold('codeyam editor 1') +
|
|
784
|
-
chalk.dim(' to start a new feature'));
|
|
846
|
+
console.log(chalk.dim('Or ask the user what to build next, then run `codeyam editor 1` yourself (never expose this command to the user)'));
|
|
785
847
|
console.log();
|
|
786
848
|
console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
|
|
787
849
|
chalk.bold('codeyam editor change'));
|
|
788
850
|
console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
|
|
789
851
|
}
|
|
790
852
|
else {
|
|
791
|
-
console.log('Each feature follows
|
|
853
|
+
console.log('Each feature follows 18 steps. You MUST run each command in order:');
|
|
792
854
|
console.log();
|
|
793
|
-
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')}
|
|
794
|
-
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('
|
|
795
|
-
console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('
|
|
796
|
-
console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('
|
|
797
|
-
console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('
|
|
798
|
-
console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('
|
|
799
|
-
console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('
|
|
800
|
-
console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('
|
|
801
|
-
console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('
|
|
802
|
-
console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('
|
|
803
|
-
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('
|
|
804
|
-
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('
|
|
805
|
-
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('
|
|
806
|
-
console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('
|
|
807
|
-
console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('
|
|
808
|
-
console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('
|
|
855
|
+
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
|
|
856
|
+
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prepare')} — Set up project and dependencies`);
|
|
857
|
+
console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
|
|
858
|
+
console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('Verify Prototype')} — Verify prototype works correctly`);
|
|
859
|
+
console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('Confirm')} — Confirm prototype with user`);
|
|
860
|
+
console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('Deconstruct')} — Read code, plan all extractions`);
|
|
861
|
+
console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('Extract')} — TDD extraction of functions + components`);
|
|
862
|
+
console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('Glossary')} — Record functions in glossary`);
|
|
863
|
+
console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('Analyze')} — Analyze and verify components`);
|
|
864
|
+
console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('App Scenarios')} — Create app-level scenarios`);
|
|
865
|
+
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('User Scenarios')} — Create user-persona scenarios`);
|
|
866
|
+
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Verify')} — Review screenshots, check for errors`);
|
|
867
|
+
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Journal')} — Create/update journal entry`);
|
|
868
|
+
console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Review')} — Verify screenshots and audit`);
|
|
869
|
+
console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Present')} — Present summary, get approval`);
|
|
870
|
+
console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Commit')} — Commit all changes`);
|
|
871
|
+
console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
|
|
872
|
+
console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
|
|
809
873
|
console.log();
|
|
810
|
-
console.log(chalk.green('Start now: ') +
|
|
874
|
+
console.log(chalk.green('Start now: ') +
|
|
875
|
+
'Ask the user what they want to build, then run `codeyam editor 1` (never expose this command to the user)');
|
|
811
876
|
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
812
877
|
}
|
|
878
|
+
console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
|
|
813
879
|
console.log();
|
|
814
880
|
}
|
|
815
881
|
// ─── Step 1: Plan ─────────────────────────────────────────────────────
|
|
@@ -885,18 +951,12 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
885
951
|
console.log();
|
|
886
952
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
887
953
|
console.log();
|
|
888
|
-
console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
|
|
889
|
-
printProgressTracker(1);
|
|
890
|
-
console.log();
|
|
891
|
-
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
892
|
-
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
893
|
-
console.log();
|
|
894
954
|
console.log(chalk.green('When done, run: ') +
|
|
895
955
|
chalk.bold('codeyam editor 2 --feature "Feature Name"'));
|
|
896
956
|
console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
|
|
897
957
|
console.log();
|
|
898
958
|
}
|
|
899
|
-
// ─── Step 2:
|
|
959
|
+
// ─── Step 2: Prepare ──────────────────────────────────────────────────
|
|
900
960
|
function printStep2(root, feature) {
|
|
901
961
|
const port = getServerPort();
|
|
902
962
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
@@ -913,12 +973,12 @@ function printStep2(root, feature) {
|
|
|
913
973
|
appFormats: prevState?.appFormats,
|
|
914
974
|
techStackId: prevState?.techStackId,
|
|
915
975
|
});
|
|
916
|
-
logEvent(root, 'step', { step: 2, label: '
|
|
917
|
-
stepHeader(2, '
|
|
976
|
+
logEvent(root, 'step', { step: 2, label: 'Prepare', feature });
|
|
977
|
+
stepHeader(2, 'Prepare', feature);
|
|
918
978
|
if (isResuming) {
|
|
919
979
|
printResumptionHeader(2);
|
|
920
980
|
}
|
|
921
|
-
console.log('
|
|
981
|
+
console.log('Get the project ready to build.');
|
|
922
982
|
console.log();
|
|
923
983
|
// If no project exists yet, include scaffolding instructions first
|
|
924
984
|
if (!projectExists) {
|
|
@@ -947,7 +1007,7 @@ function printStep2(root, feature) {
|
|
|
947
1007
|
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
948
1008
|
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
949
1009
|
console.log();
|
|
950
|
-
|
|
1010
|
+
printDataStructureInstructions();
|
|
951
1011
|
}
|
|
952
1012
|
else {
|
|
953
1013
|
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
@@ -962,7 +1022,34 @@ function printStep2(root, feature) {
|
|
|
962
1022
|
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
963
1023
|
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
964
1024
|
console.log();
|
|
1025
|
+
printDataStructureInstructions();
|
|
965
1026
|
}
|
|
1027
|
+
stopGate(2);
|
|
1028
|
+
}
|
|
1029
|
+
// ─── Step 3: Prototype ────────────────────────────────────────────────
|
|
1030
|
+
function printStep3(root, feature) {
|
|
1031
|
+
const port = getServerPort();
|
|
1032
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1033
|
+
const projectExists = hasProject(root);
|
|
1034
|
+
const prevState = readState(root);
|
|
1035
|
+
const isResuming = prevState?.step === 3;
|
|
1036
|
+
const now = new Date().toISOString();
|
|
1037
|
+
writeState(root, {
|
|
1038
|
+
feature,
|
|
1039
|
+
step: 3,
|
|
1040
|
+
label: STEP_LABELS[3],
|
|
1041
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1042
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1043
|
+
appFormats: prevState?.appFormats,
|
|
1044
|
+
techStackId: prevState?.techStackId,
|
|
1045
|
+
});
|
|
1046
|
+
logEvent(root, 'step', { step: 3, label: 'Prototype', feature });
|
|
1047
|
+
stepHeader(3, 'Prototype', feature);
|
|
1048
|
+
if (isResuming) {
|
|
1049
|
+
printResumptionHeader(3);
|
|
1050
|
+
}
|
|
1051
|
+
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
1052
|
+
console.log();
|
|
966
1053
|
console.log(chalk.bold('Checklist:'));
|
|
967
1054
|
checkbox('Create API routes that read from the database via Prisma');
|
|
968
1055
|
if (!projectExists) {
|
|
@@ -971,6 +1058,13 @@ function printStep2(root, feature) {
|
|
|
971
1058
|
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
972
1059
|
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
973
1060
|
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
1061
|
+
console.log();
|
|
1062
|
+
console.log(chalk.bold.cyan('Make seed data visually rich:'));
|
|
1063
|
+
console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
|
|
1064
|
+
console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
|
|
1065
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1066
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1067
|
+
console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
|
|
974
1068
|
}
|
|
975
1069
|
checkbox('Verify the dev server shows the changes');
|
|
976
1070
|
checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
|
|
@@ -1010,6 +1104,30 @@ function printStep2(root, feature) {
|
|
|
1010
1104
|
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
1011
1105
|
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
|
|
1012
1106
|
printDimensionGuidance(dim, dimNames);
|
|
1107
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
1108
|
+
console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
|
|
1109
|
+
console.log();
|
|
1110
|
+
stopGate(3);
|
|
1111
|
+
}
|
|
1112
|
+
// ─── Step 4: Verify Prototype ─────────────────────────────────────────
|
|
1113
|
+
function printStep4(root, feature) {
|
|
1114
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1115
|
+
const prevState = readState(root);
|
|
1116
|
+
const isResuming = prevState?.step === 4;
|
|
1117
|
+
const now = new Date().toISOString();
|
|
1118
|
+
writeState(root, {
|
|
1119
|
+
feature,
|
|
1120
|
+
step: 4,
|
|
1121
|
+
label: STEP_LABELS[4],
|
|
1122
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1123
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1124
|
+
});
|
|
1125
|
+
logEvent(root, 'step', { step: 4, label: 'Verify Prototype', feature });
|
|
1126
|
+
stepHeader(4, 'Verify Prototype', feature);
|
|
1127
|
+
if (isResuming) {
|
|
1128
|
+
printResumptionHeader(4);
|
|
1129
|
+
}
|
|
1130
|
+
console.log('Verify everything works before presenting the prototype.');
|
|
1013
1131
|
console.log();
|
|
1014
1132
|
console.log(chalk.bold('Verify the dev server:'));
|
|
1015
1133
|
console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
|
|
@@ -1037,26 +1155,26 @@ function printStep2(root, feature) {
|
|
|
1037
1155
|
console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
|
|
1038
1156
|
console.log();
|
|
1039
1157
|
console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
|
|
1040
|
-
stopGate(
|
|
1158
|
+
stopGate(4);
|
|
1041
1159
|
}
|
|
1042
|
-
// ─── Step
|
|
1043
|
-
function
|
|
1160
|
+
// ─── Step 5: Confirm ──────────────────────────────────────────────────
|
|
1161
|
+
function printStep5(root, feature) {
|
|
1044
1162
|
const port = getServerPort();
|
|
1045
1163
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1046
1164
|
const prevState = readState(root);
|
|
1047
|
-
const isResuming = prevState?.step ===
|
|
1165
|
+
const isResuming = prevState?.step === 5;
|
|
1048
1166
|
const now = new Date().toISOString();
|
|
1049
1167
|
writeState(root, {
|
|
1050
1168
|
feature,
|
|
1051
|
-
step:
|
|
1052
|
-
label: STEP_LABELS[
|
|
1169
|
+
step: 5,
|
|
1170
|
+
label: STEP_LABELS[5],
|
|
1053
1171
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1054
1172
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1055
1173
|
});
|
|
1056
|
-
logEvent(root, 'step', { step:
|
|
1057
|
-
stepHeader(
|
|
1174
|
+
logEvent(root, 'step', { step: 5, label: 'Confirm', feature });
|
|
1175
|
+
stepHeader(5, 'Confirm', feature);
|
|
1058
1176
|
if (isResuming) {
|
|
1059
|
-
printResumptionHeader(
|
|
1177
|
+
printResumptionHeader(5);
|
|
1060
1178
|
}
|
|
1061
1179
|
console.log('Summarize what was built and get user confirmation.');
|
|
1062
1180
|
console.log();
|
|
@@ -1073,12 +1191,19 @@ function printStep3(root, feature) {
|
|
|
1073
1191
|
console.log(chalk.dim(' If there are errors, fix the underlying issue before presenting.'));
|
|
1074
1192
|
checkbox('Verify `hasContent=true` and `liveErrors=0` — do NOT ask the user to confirm if the preview is broken');
|
|
1075
1193
|
console.log();
|
|
1194
|
+
console.log(chalk.bold('Verify the captured user prompt:'));
|
|
1195
|
+
checkbox("Read `.codeyam/editor-user-prompt.txt` — this is the user's original feature request");
|
|
1196
|
+
checkbox('If the file is missing or does not match what the user actually asked for, write the correct prompt text to `.codeyam/editor-user-prompt.txt`');
|
|
1197
|
+
console.log(chalk.dim(" This must be the user's exact words, not a summary. It gets recorded in the journal."));
|
|
1198
|
+
console.log();
|
|
1076
1199
|
console.log(chalk.bold('Then present to the user:'));
|
|
1077
1200
|
checkbox('Summarize what was built (routes, components, data)');
|
|
1078
1201
|
checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
|
|
1079
1202
|
printDimensionGuidance(dim, dimNames);
|
|
1080
1203
|
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
1081
1204
|
console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
|
|
1205
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
1206
|
+
console.log(chalk.red(' The preview only updates when you explicitly call `codeyam editor preview`. Verify, then describe.'));
|
|
1082
1207
|
console.log();
|
|
1083
1208
|
console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
|
|
1084
1209
|
checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
|
|
@@ -1086,62 +1211,62 @@ function printStep3(root, feature) {
|
|
|
1086
1211
|
checkbox('Ask the user: "Does everything work as expected?"');
|
|
1087
1212
|
console.log();
|
|
1088
1213
|
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
1089
|
-
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step
|
|
1214
|
+
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 6'));
|
|
1090
1215
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
1091
|
-
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor
|
|
1216
|
+
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 5`'));
|
|
1092
1217
|
console.log();
|
|
1093
1218
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
1094
|
-
stopGate(
|
|
1219
|
+
stopGate(5, { confirm: true });
|
|
1095
1220
|
}
|
|
1096
|
-
// ─── Step
|
|
1097
|
-
function
|
|
1221
|
+
// ─── Step 6: Deconstruct ──────────────────────────────────────────────
|
|
1222
|
+
function printStep6(root, feature) {
|
|
1098
1223
|
const prevState = readState(root);
|
|
1099
|
-
const isResuming = prevState?.step ===
|
|
1224
|
+
const isResuming = prevState?.step === 6;
|
|
1100
1225
|
const now = new Date().toISOString();
|
|
1101
1226
|
writeState(root, {
|
|
1102
1227
|
feature,
|
|
1103
|
-
step:
|
|
1104
|
-
label: STEP_LABELS[
|
|
1228
|
+
step: 6,
|
|
1229
|
+
label: STEP_LABELS[6],
|
|
1105
1230
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1106
1231
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1107
1232
|
});
|
|
1108
|
-
logEvent(root, 'step', { step:
|
|
1109
|
-
stepHeader(
|
|
1233
|
+
logEvent(root, 'step', { step: 6, label: 'Deconstruct', feature });
|
|
1234
|
+
stepHeader(6, 'Deconstruct', feature);
|
|
1110
1235
|
if (isResuming) {
|
|
1111
|
-
printResumptionHeader(
|
|
1236
|
+
printResumptionHeader(6);
|
|
1112
1237
|
}
|
|
1113
1238
|
console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
|
|
1114
|
-
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step
|
|
1239
|
+
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
|
|
1115
1240
|
console.log();
|
|
1116
1241
|
printExtractionPlanInstructions();
|
|
1117
|
-
stopGate(
|
|
1242
|
+
stopGate(6);
|
|
1118
1243
|
}
|
|
1119
|
-
// ─── Step
|
|
1120
|
-
function
|
|
1244
|
+
// ─── Step 7: Extract ──────────────────────────────────────────────────
|
|
1245
|
+
function printStep7(root, feature) {
|
|
1121
1246
|
const port = getServerPort();
|
|
1122
1247
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1123
1248
|
const prevState = readState(root);
|
|
1124
|
-
const isResuming = prevState?.step ===
|
|
1249
|
+
const isResuming = prevState?.step === 7;
|
|
1125
1250
|
const now = new Date().toISOString();
|
|
1126
1251
|
writeState(root, {
|
|
1127
1252
|
feature,
|
|
1128
|
-
step:
|
|
1129
|
-
label: STEP_LABELS[
|
|
1253
|
+
step: 7,
|
|
1254
|
+
label: STEP_LABELS[7],
|
|
1130
1255
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1131
1256
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1132
1257
|
});
|
|
1133
|
-
logEvent(root, 'step', { step:
|
|
1134
|
-
stepHeader(
|
|
1258
|
+
logEvent(root, 'step', { step: 7, label: 'Extract', feature });
|
|
1259
|
+
stepHeader(7, 'Extract', feature);
|
|
1135
1260
|
if (isResuming) {
|
|
1136
|
-
printResumptionHeader(
|
|
1261
|
+
printResumptionHeader(7);
|
|
1137
1262
|
}
|
|
1138
|
-
console.log('Execute your extraction plan from step
|
|
1263
|
+
console.log('Execute your extraction plan from step 6.');
|
|
1139
1264
|
console.log();
|
|
1140
1265
|
console.log(chalk.bold('Components:'));
|
|
1141
1266
|
checkbox('Extract each component from your plan into its own file');
|
|
1142
1267
|
checkbox('Page/route files must contain ZERO direct JSX — only imported components');
|
|
1143
1268
|
checkbox('Every component that renders multiple sections must be split into sub-components');
|
|
1144
|
-
console.log(chalk.dim(' No tests needed — visual verification happens in step
|
|
1269
|
+
console.log(chalk.dim(' No tests needed — visual verification happens in step 9'));
|
|
1145
1270
|
console.log();
|
|
1146
1271
|
console.log(chalk.bold('Library functions AND hooks (TDD):'));
|
|
1147
1272
|
checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
@@ -1149,7 +1274,7 @@ function printStep5(root, feature) {
|
|
|
1149
1274
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
1150
1275
|
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
1151
1276
|
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
1152
|
-
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step
|
|
1277
|
+
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
|
|
1153
1278
|
console.log();
|
|
1154
1279
|
console.log(chalk.bold('Recursive pass:'));
|
|
1155
1280
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
@@ -1167,55 +1292,55 @@ function printStep5(root, feature) {
|
|
|
1167
1292
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
1168
1293
|
console.log();
|
|
1169
1294
|
console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
|
|
1170
|
-
stopGate(
|
|
1295
|
+
stopGate(7);
|
|
1171
1296
|
}
|
|
1172
|
-
// ─── Step
|
|
1173
|
-
function
|
|
1297
|
+
// ─── Step 8: Glossary ─────────────────────────────────────────────────
|
|
1298
|
+
function printStep8(root, feature) {
|
|
1174
1299
|
const prevState = readState(root);
|
|
1175
|
-
const isResuming = prevState?.step ===
|
|
1300
|
+
const isResuming = prevState?.step === 8;
|
|
1176
1301
|
const now = new Date().toISOString();
|
|
1177
1302
|
writeState(root, {
|
|
1178
1303
|
feature,
|
|
1179
|
-
step:
|
|
1180
|
-
label: STEP_LABELS[
|
|
1304
|
+
step: 8,
|
|
1305
|
+
label: STEP_LABELS[8],
|
|
1181
1306
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1182
1307
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1183
1308
|
});
|
|
1184
|
-
logEvent(root, 'step', { step:
|
|
1185
|
-
stepHeader(
|
|
1309
|
+
logEvent(root, 'step', { step: 8, label: 'Glossary', feature });
|
|
1310
|
+
stepHeader(8, 'Glossary', feature);
|
|
1186
1311
|
if (isResuming) {
|
|
1187
|
-
printResumptionHeader(
|
|
1312
|
+
printResumptionHeader(8);
|
|
1188
1313
|
}
|
|
1189
1314
|
console.log('Record all new functions/components in `.codeyam/glossary.json`.');
|
|
1190
1315
|
console.log();
|
|
1191
1316
|
printGlossaryInstructions(feature);
|
|
1192
1317
|
console.log();
|
|
1193
1318
|
console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
|
|
1194
|
-
stopGate(
|
|
1319
|
+
stopGate(8);
|
|
1195
1320
|
}
|
|
1196
|
-
// ─── Step
|
|
1197
|
-
function
|
|
1321
|
+
// ─── Step 9: Analyze ──────────────────────────────────────────────────
|
|
1322
|
+
function printStep9(root, feature) {
|
|
1198
1323
|
const port = getServerPort();
|
|
1199
1324
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1200
1325
|
const prevState = readState(root);
|
|
1201
|
-
const isResuming = prevState?.step ===
|
|
1326
|
+
const isResuming = prevState?.step === 9;
|
|
1202
1327
|
const now = new Date().toISOString();
|
|
1203
1328
|
writeState(root, {
|
|
1204
1329
|
feature,
|
|
1205
|
-
step:
|
|
1206
|
-
label: STEP_LABELS[
|
|
1330
|
+
step: 9,
|
|
1331
|
+
label: STEP_LABELS[9],
|
|
1207
1332
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1208
1333
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1209
1334
|
});
|
|
1210
|
-
logEvent(root, 'step', { step:
|
|
1211
|
-
stepHeader(
|
|
1335
|
+
logEvent(root, 'step', { step: 9, label: 'Analyze', feature });
|
|
1336
|
+
stepHeader(9, 'Analyze and Verify', feature);
|
|
1212
1337
|
if (isResuming) {
|
|
1213
|
-
printResumptionHeader(
|
|
1338
|
+
printResumptionHeader(9);
|
|
1214
1339
|
}
|
|
1215
1340
|
console.log('Verify visual components (via isolation routes) and library functions (via tests).');
|
|
1216
1341
|
console.log();
|
|
1217
1342
|
console.log(chalk.bold('Visual Components — Component Isolation:'));
|
|
1218
|
-
checkbox('List all files with new/modified visual components from step
|
|
1343
|
+
checkbox('List all files with new/modified visual components from step 7');
|
|
1219
1344
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
1220
1345
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
1221
1346
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
@@ -1223,7 +1348,7 @@ function printStep7(root, feature) {
|
|
|
1223
1348
|
printComponentCaptureInstructions();
|
|
1224
1349
|
console.log();
|
|
1225
1350
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
1226
|
-
checkbox('Run ALL test files created in step
|
|
1351
|
+
checkbox('Run ALL test files created in step 7');
|
|
1227
1352
|
console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
|
|
1228
1353
|
checkbox('Verify every test passes');
|
|
1229
1354
|
checkbox('If any test fails, fix the source code and re-run');
|
|
@@ -1231,29 +1356,32 @@ function printStep7(root, feature) {
|
|
|
1231
1356
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
1232
1357
|
console.log();
|
|
1233
1358
|
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1234
|
-
console.log(chalk.red.bold(' The audit is a HARD GATE — step
|
|
1235
|
-
console.log(chalk.dim('
|
|
1359
|
+
console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
|
|
1360
|
+
console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
|
|
1361
|
+
console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
|
|
1362
|
+
console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
|
|
1363
|
+
console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
|
|
1236
1364
|
console.log();
|
|
1237
|
-
stopGate(
|
|
1365
|
+
stopGate(9);
|
|
1238
1366
|
}
|
|
1239
|
-
// ─── Step
|
|
1240
|
-
function
|
|
1367
|
+
// ─── Step 10: App Scenarios ────────────────────────────────────────────
|
|
1368
|
+
function printStep10(root, feature) {
|
|
1241
1369
|
const port = getServerPort();
|
|
1242
1370
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1243
1371
|
const prevState = readState(root);
|
|
1244
|
-
const isResuming = prevState?.step ===
|
|
1372
|
+
const isResuming = prevState?.step === 10;
|
|
1245
1373
|
const now = new Date().toISOString();
|
|
1246
1374
|
writeState(root, {
|
|
1247
1375
|
feature,
|
|
1248
|
-
step:
|
|
1249
|
-
label: STEP_LABELS[
|
|
1376
|
+
step: 10,
|
|
1377
|
+
label: STEP_LABELS[10],
|
|
1250
1378
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1251
1379
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1252
1380
|
});
|
|
1253
|
-
logEvent(root, 'step', { step:
|
|
1254
|
-
stepHeader(
|
|
1381
|
+
logEvent(root, 'step', { step: 10, label: 'App Scenarios', feature });
|
|
1382
|
+
stepHeader(10, 'App Scenarios', feature);
|
|
1255
1383
|
if (isResuming) {
|
|
1256
|
-
printResumptionHeader(
|
|
1384
|
+
printResumptionHeader(10);
|
|
1257
1385
|
}
|
|
1258
1386
|
console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
|
|
1259
1387
|
console.log();
|
|
@@ -1267,6 +1395,7 @@ function printStep8(root, feature) {
|
|
|
1267
1395
|
console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
|
|
1268
1396
|
console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
|
|
1269
1397
|
console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
|
|
1398
|
+
console.log(chalk.cyan(' • Use real image URLs (Unsplash, Pravatar) — not broken placeholders or empty strings'));
|
|
1270
1399
|
console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
|
|
1271
1400
|
console.log();
|
|
1272
1401
|
console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
|
|
@@ -1275,13 +1404,19 @@ function printStep8(root, feature) {
|
|
|
1275
1404
|
console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
|
|
1276
1405
|
console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
|
|
1277
1406
|
console.log();
|
|
1407
|
+
console.log(chalk.bold.cyan('Scenario naming — describe data states, not features:'));
|
|
1408
|
+
console.log(chalk.cyan(' • Name scenarios by what they represent: "Rich Data", "Empty", "Mobile", "Minimal"'));
|
|
1409
|
+
console.log(chalk.cyan(' • When enhancing a scenario with new feature data, update the name if it has grown beyond its original scope'));
|
|
1410
|
+
console.log(chalk.cyan(' • A single rich scenario exercising multiple features is more valuable than separate thin scenarios per feature'));
|
|
1411
|
+
console.log(chalk.cyan(' • Re-register with the same name to update; to rename, register with the new name'));
|
|
1412
|
+
console.log();
|
|
1278
1413
|
checkbox('Ensure scenarios clearly demonstrate what changed in this session');
|
|
1279
1414
|
console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
|
|
1280
1415
|
console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
|
|
1281
1416
|
console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
|
|
1282
1417
|
printAppScenarioInstructions();
|
|
1283
1418
|
console.log();
|
|
1284
|
-
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step
|
|
1419
|
+
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 12 if needed.'));
|
|
1285
1420
|
console.log();
|
|
1286
1421
|
console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
|
|
1287
1422
|
console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
|
|
@@ -1292,43 +1427,43 @@ function printStep8(root, feature) {
|
|
|
1292
1427
|
console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
|
|
1293
1428
|
console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
|
|
1294
1429
|
console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
|
|
1295
|
-
stopGate(
|
|
1430
|
+
stopGate(10);
|
|
1296
1431
|
}
|
|
1297
|
-
// ─── Step
|
|
1298
|
-
function
|
|
1432
|
+
// ─── Step 11: User Scenarios ───────────────────────────────────────────
|
|
1433
|
+
function printStep11(root, feature) {
|
|
1299
1434
|
const port = getServerPort();
|
|
1300
1435
|
const prevState = readState(root);
|
|
1301
|
-
const isResuming = prevState?.step ===
|
|
1436
|
+
const isResuming = prevState?.step === 11;
|
|
1302
1437
|
const now = new Date().toISOString();
|
|
1303
1438
|
writeState(root, {
|
|
1304
1439
|
feature,
|
|
1305
|
-
step:
|
|
1306
|
-
label: STEP_LABELS[
|
|
1440
|
+
step: 11,
|
|
1441
|
+
label: STEP_LABELS[11],
|
|
1307
1442
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1308
1443
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1309
1444
|
});
|
|
1310
|
-
logEvent(root, 'step', { step:
|
|
1311
|
-
stepHeader(
|
|
1445
|
+
logEvent(root, 'step', { step: 11, label: 'User Scenarios', feature });
|
|
1446
|
+
stepHeader(11, 'User Scenarios', feature);
|
|
1312
1447
|
if (isResuming) {
|
|
1313
|
-
printResumptionHeader(
|
|
1448
|
+
printResumptionHeader(11);
|
|
1314
1449
|
}
|
|
1315
|
-
console.log('Create per-persona variations of existing scenarios. Skip to step
|
|
1450
|
+
console.log('Create per-persona variations of existing scenarios. Skip to step 12 if no users.');
|
|
1316
1451
|
console.log();
|
|
1317
1452
|
console.log(chalk.bold('If the app has NO users/auth:'));
|
|
1318
|
-
console.log(chalk.dim(' Skip this step and proceed to step
|
|
1453
|
+
console.log(chalk.dim(' Skip this step and proceed to step 12 (Verify).'));
|
|
1319
1454
|
console.log();
|
|
1320
1455
|
console.log(chalk.bold('If the app has users/auth:'));
|
|
1321
1456
|
console.log();
|
|
1322
1457
|
console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
|
|
1323
1458
|
console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
|
|
1324
|
-
console.log(chalk.dim(' a variation of an existing app scenario from step
|
|
1459
|
+
console.log(chalk.dim(' a variation of an existing app scenario from step 10, with user-specific state layered on.'));
|
|
1325
1460
|
console.log();
|
|
1326
1461
|
console.log(chalk.bold('Checklist:'));
|
|
1327
1462
|
checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
|
|
1328
1463
|
checkbox('For EACH existing app scenario, create a logged-in variation:');
|
|
1329
1464
|
console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
|
|
1330
1465
|
console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
|
|
1331
|
-
console.log(chalk.dim(' Step
|
|
1466
|
+
console.log(chalk.dim(' Step 10 scenarios already serve as logged-out versions (no session cookie)'));
|
|
1332
1467
|
checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
|
|
1333
1468
|
console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
|
|
1334
1469
|
checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
|
|
@@ -1337,26 +1472,26 @@ function printStep9(root, feature) {
|
|
|
1337
1472
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
1338
1473
|
console.log();
|
|
1339
1474
|
console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
|
|
1340
|
-
stopGate(
|
|
1475
|
+
stopGate(11);
|
|
1341
1476
|
}
|
|
1342
|
-
// ─── Step
|
|
1343
|
-
function
|
|
1477
|
+
// ─── Step 12: Verify ──────────────────────────────────────────────────
|
|
1478
|
+
function printStep12(root, feature) {
|
|
1344
1479
|
const port = getServerPort();
|
|
1345
1480
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1346
1481
|
const prevState = readState(root);
|
|
1347
|
-
const isResuming = prevState?.step ===
|
|
1482
|
+
const isResuming = prevState?.step === 12;
|
|
1348
1483
|
const now = new Date().toISOString();
|
|
1349
1484
|
writeState(root, {
|
|
1350
1485
|
feature,
|
|
1351
|
-
step:
|
|
1352
|
-
label: STEP_LABELS[
|
|
1486
|
+
step: 12,
|
|
1487
|
+
label: STEP_LABELS[12],
|
|
1353
1488
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1354
1489
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1355
1490
|
});
|
|
1356
|
-
logEvent(root, 'step', { step:
|
|
1357
|
-
stepHeader(
|
|
1491
|
+
logEvent(root, 'step', { step: 12, label: 'Verify', feature });
|
|
1492
|
+
stepHeader(12, 'Verify', feature);
|
|
1358
1493
|
if (isResuming) {
|
|
1359
|
-
printResumptionHeader(
|
|
1494
|
+
printResumptionHeader(12);
|
|
1360
1495
|
}
|
|
1361
1496
|
console.log('Verify component isolation screenshots, editor scenarios, and library tests.');
|
|
1362
1497
|
console.log();
|
|
@@ -1379,63 +1514,63 @@ function printStep10(root, feature) {
|
|
|
1379
1514
|
checkbox('Verify no broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
1380
1515
|
console.log();
|
|
1381
1516
|
console.log(chalk.bold('Library functions — test check:'));
|
|
1382
|
-
checkbox('Re-run all test files from step
|
|
1517
|
+
checkbox('Re-run all test files from step 7 to confirm they still pass');
|
|
1383
1518
|
console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
|
|
1384
1519
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1385
1520
|
console.log();
|
|
1386
1521
|
console.log(chalk.dim('Focus on fixing issues. All component screenshots, scenarios, and tests must be clean before proceeding.'));
|
|
1387
|
-
stopGate(
|
|
1522
|
+
stopGate(12);
|
|
1388
1523
|
}
|
|
1389
|
-
// ─── Step
|
|
1390
|
-
function
|
|
1524
|
+
// ─── Step 13: Journal ─────────────────────────────────────────────────
|
|
1525
|
+
function printStep13(root, feature) {
|
|
1391
1526
|
const port = getServerPort();
|
|
1392
1527
|
const prevState = readState(root);
|
|
1393
|
-
const isResuming = prevState?.step ===
|
|
1528
|
+
const isResuming = prevState?.step === 13;
|
|
1394
1529
|
const now = new Date().toISOString();
|
|
1395
1530
|
writeState(root, {
|
|
1396
1531
|
feature,
|
|
1397
|
-
step:
|
|
1398
|
-
label: STEP_LABELS[
|
|
1532
|
+
step: 13,
|
|
1533
|
+
label: STEP_LABELS[13],
|
|
1399
1534
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1400
1535
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1401
1536
|
});
|
|
1402
|
-
logEvent(root, 'step', { step:
|
|
1403
|
-
stepHeader(
|
|
1537
|
+
logEvent(root, 'step', { step: 13, label: 'Journal', feature });
|
|
1538
|
+
stepHeader(13, 'Journal', feature);
|
|
1404
1539
|
if (isResuming) {
|
|
1405
|
-
printResumptionHeader(
|
|
1540
|
+
printResumptionHeader(13);
|
|
1406
1541
|
}
|
|
1407
1542
|
console.log('Create or update the journal entry for this feature.');
|
|
1408
1543
|
console.log();
|
|
1409
1544
|
console.log(chalk.bold('Checklist:'));
|
|
1410
1545
|
checkbox('Write a concise description of what was built (2-3 sentences)');
|
|
1411
|
-
checkbox(`First time at step
|
|
1546
|
+
checkbox(`First time at step 13 — create journal entry with ALL session screenshots:`);
|
|
1412
1547
|
console.log(chalk.dim(` codeyam editor journal '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
|
|
1413
1548
|
console.log(chalk.dim(' includeSessionScenarios auto-discovers component + app + user persona screenshots'));
|
|
1414
|
-
checkbox(`Returning to step
|
|
1549
|
+
checkbox(`Returning to step 13 (before commit) — update the existing journal entry:`);
|
|
1415
1550
|
console.log(chalk.dim(` codeyam editor journal-update '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
|
|
1416
1551
|
console.log(chalk.dim(' Note: PATCH only works before the entry is committed. After commit, use POST to create a new entry.'));
|
|
1417
1552
|
console.log();
|
|
1418
|
-
console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step
|
|
1419
|
-
stopGate(
|
|
1553
|
+
console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 15.'));
|
|
1554
|
+
stopGate(13);
|
|
1420
1555
|
}
|
|
1421
|
-
// ─── Step
|
|
1422
|
-
function
|
|
1556
|
+
// ─── Step 14: Review ──────────────────────────────────────────────────
|
|
1557
|
+
function printStep14(root, feature) {
|
|
1423
1558
|
const port = getServerPort();
|
|
1424
1559
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1425
1560
|
const prevState = readState(root);
|
|
1426
|
-
const isResuming = prevState?.step ===
|
|
1561
|
+
const isResuming = prevState?.step === 14;
|
|
1427
1562
|
const now = new Date().toISOString();
|
|
1428
1563
|
writeState(root, {
|
|
1429
1564
|
feature,
|
|
1430
|
-
step:
|
|
1431
|
-
label: STEP_LABELS[
|
|
1565
|
+
step: 14,
|
|
1566
|
+
label: STEP_LABELS[14],
|
|
1432
1567
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1433
1568
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1434
1569
|
});
|
|
1435
|
-
logEvent(root, 'step', { step:
|
|
1436
|
-
stepHeader(
|
|
1570
|
+
logEvent(root, 'step', { step: 14, label: 'Review', feature });
|
|
1571
|
+
stepHeader(14, 'Review', feature);
|
|
1437
1572
|
if (isResuming) {
|
|
1438
|
-
printResumptionHeader(
|
|
1573
|
+
printResumptionHeader(14);
|
|
1439
1574
|
}
|
|
1440
1575
|
console.log('Verify all screenshots and checks pass before presenting to the user.');
|
|
1441
1576
|
console.log();
|
|
@@ -1448,33 +1583,34 @@ function printStep12(root, feature) {
|
|
|
1448
1583
|
checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
|
|
1449
1584
|
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
1450
1585
|
checkbox('Fix or remove any broken images before continuing');
|
|
1586
|
+
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
1451
1587
|
checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
|
|
1452
1588
|
checkbox('Do not proceed until all checks pass');
|
|
1453
|
-
stopGate(
|
|
1589
|
+
stopGate(14);
|
|
1454
1590
|
}
|
|
1455
|
-
// ─── Step
|
|
1456
|
-
function
|
|
1591
|
+
// ─── Step 15: Present ─────────────────────────────────────────────────
|
|
1592
|
+
function printStep15(root, feature) {
|
|
1457
1593
|
const prevState = readState(root);
|
|
1458
|
-
const isResuming = prevState?.step ===
|
|
1594
|
+
const isResuming = prevState?.step === 15;
|
|
1459
1595
|
const now = new Date().toISOString();
|
|
1460
1596
|
writeState(root, {
|
|
1461
1597
|
feature,
|
|
1462
|
-
step:
|
|
1463
|
-
label: STEP_LABELS[
|
|
1598
|
+
step: 15,
|
|
1599
|
+
label: STEP_LABELS[15],
|
|
1464
1600
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1465
1601
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1466
1602
|
});
|
|
1467
|
-
logEvent(root, 'step', { step:
|
|
1468
|
-
stepHeader(
|
|
1603
|
+
logEvent(root, 'step', { step: 15, label: 'Present', feature });
|
|
1604
|
+
stepHeader(15, 'Present', feature);
|
|
1469
1605
|
if (isResuming) {
|
|
1470
|
-
printResumptionHeader(
|
|
1606
|
+
printResumptionHeader(15);
|
|
1471
1607
|
}
|
|
1472
1608
|
console.log('Present the results to the user and get their approval.');
|
|
1473
1609
|
console.log();
|
|
1474
1610
|
console.log(chalk.bold('Checklist:'));
|
|
1475
1611
|
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1476
1612
|
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1477
|
-
console.log(chalk.dim(' The
|
|
1613
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1478
1614
|
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1479
1615
|
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1480
1616
|
checkbox('Report test count and audit status (one line)');
|
|
@@ -1486,7 +1622,7 @@ function printStep13(root, feature) {
|
|
|
1486
1622
|
chalk.dim(' — describe changes, then re-verify'));
|
|
1487
1623
|
console.log();
|
|
1488
1624
|
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1489
|
-
checkbox('Advance to the commit step: `codeyam editor
|
|
1625
|
+
checkbox('Advance to the commit step: `codeyam editor 16`');
|
|
1490
1626
|
console.log();
|
|
1491
1627
|
console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
|
|
1492
1628
|
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
@@ -1494,7 +1630,7 @@ function printStep13(root, feature) {
|
|
|
1494
1630
|
checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
|
|
1495
1631
|
checkbox('THEN make the requested changes and follow the checklist');
|
|
1496
1632
|
console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
|
|
1497
|
-
stopGate(
|
|
1633
|
+
stopGate(15, { confirm: true });
|
|
1498
1634
|
}
|
|
1499
1635
|
// ─── Migration Mode ──────────────────────────────────────────────────
|
|
1500
1636
|
/**
|
|
@@ -1714,7 +1850,7 @@ function printMigrateStep4(root) {
|
|
|
1714
1850
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1715
1851
|
checkbox('If any issues: fix the scenario data, re-register affected scenarios');
|
|
1716
1852
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1717
|
-
console.log(chalk.dim(' The
|
|
1853
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1718
1854
|
console.log();
|
|
1719
1855
|
console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
|
|
1720
1856
|
migrationStopGate(4, pageName, pageIndex, totalPages);
|
|
@@ -1881,7 +2017,7 @@ function printMigrateStep10(root) {
|
|
|
1881
2017
|
console.log();
|
|
1882
2018
|
console.log(chalk.bold('Checklist:'));
|
|
1883
2019
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1884
|
-
console.log(chalk.dim(' The
|
|
2020
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1885
2021
|
checkbox('Write a 1-2 sentence summary of what was migrated');
|
|
1886
2022
|
console.log();
|
|
1887
2023
|
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
@@ -2101,47 +2237,48 @@ function handleMigrateCommand(root, subArg) {
|
|
|
2101
2237
|
};
|
|
2102
2238
|
stepFns[step](root);
|
|
2103
2239
|
}
|
|
2104
|
-
// ─── Step
|
|
2105
|
-
function
|
|
2240
|
+
// ─── Step 16: Commit ─────────────────────────────────────────────────
|
|
2241
|
+
function printStep16(root, feature) {
|
|
2106
2242
|
const prevState = readState(root);
|
|
2107
|
-
const isResuming = prevState?.step ===
|
|
2243
|
+
const isResuming = prevState?.step === 16;
|
|
2108
2244
|
const now = new Date().toISOString();
|
|
2109
2245
|
writeState(root, {
|
|
2110
2246
|
feature,
|
|
2111
|
-
step:
|
|
2112
|
-
label: STEP_LABELS[
|
|
2247
|
+
step: 16,
|
|
2248
|
+
label: STEP_LABELS[16],
|
|
2113
2249
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2114
2250
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2115
2251
|
});
|
|
2116
|
-
logEvent(root, 'step', { step:
|
|
2117
|
-
stepHeader(
|
|
2252
|
+
logEvent(root, 'step', { step: 16, label: 'Commit', feature });
|
|
2253
|
+
stepHeader(16, 'Commit', feature);
|
|
2118
2254
|
if (isResuming) {
|
|
2119
|
-
printResumptionHeader(
|
|
2255
|
+
printResumptionHeader(16);
|
|
2120
2256
|
}
|
|
2121
2257
|
console.log('Commit all changes for this feature.');
|
|
2122
2258
|
console.log();
|
|
2123
2259
|
console.log(chalk.bold('Checklist:'));
|
|
2260
|
+
checkbox('Ensure all screenshots are fresh: `codeyam editor recapture-stale`');
|
|
2124
2261
|
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
2125
2262
|
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
2126
2263
|
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
2127
|
-
stopGate(
|
|
2264
|
+
stopGate(16);
|
|
2128
2265
|
}
|
|
2129
|
-
// ─── Step
|
|
2130
|
-
function
|
|
2266
|
+
// ─── Step 17: Finalize ───────────────────────────────────────────────
|
|
2267
|
+
function printStep17(root, feature) {
|
|
2131
2268
|
const prevState = readState(root);
|
|
2132
|
-
const isResuming = prevState?.step ===
|
|
2269
|
+
const isResuming = prevState?.step === 17;
|
|
2133
2270
|
const now = new Date().toISOString();
|
|
2134
2271
|
writeState(root, {
|
|
2135
2272
|
feature,
|
|
2136
|
-
step:
|
|
2137
|
-
label: STEP_LABELS[
|
|
2273
|
+
step: 17,
|
|
2274
|
+
label: STEP_LABELS[17],
|
|
2138
2275
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2139
2276
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2140
2277
|
});
|
|
2141
|
-
logEvent(root, 'step', { step:
|
|
2142
|
-
stepHeader(
|
|
2278
|
+
logEvent(root, 'step', { step: 17, label: 'Finalize', feature });
|
|
2279
|
+
stepHeader(17, 'Finalize', feature);
|
|
2143
2280
|
if (isResuming) {
|
|
2144
|
-
printResumptionHeader(
|
|
2281
|
+
printResumptionHeader(17);
|
|
2145
2282
|
}
|
|
2146
2283
|
console.log('Update the journal with the commit SHA and amend the commit.');
|
|
2147
2284
|
console.log();
|
|
@@ -2149,24 +2286,24 @@ function printStep15(root, feature) {
|
|
|
2149
2286
|
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
2150
2287
|
checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
|
|
2151
2288
|
console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
|
|
2152
|
-
stopGate(
|
|
2289
|
+
stopGate(17);
|
|
2153
2290
|
}
|
|
2154
|
-
// ─── Step
|
|
2155
|
-
function
|
|
2291
|
+
// ─── Step 18: Push ───────────────────────────────────────────────────
|
|
2292
|
+
function printStep18(root, feature) {
|
|
2156
2293
|
const prevState = readState(root);
|
|
2157
|
-
const isResuming = prevState?.step ===
|
|
2294
|
+
const isResuming = prevState?.step === 18;
|
|
2158
2295
|
const now = new Date().toISOString();
|
|
2159
2296
|
writeState(root, {
|
|
2160
2297
|
feature,
|
|
2161
|
-
step:
|
|
2162
|
-
label: STEP_LABELS[
|
|
2298
|
+
step: 18,
|
|
2299
|
+
label: STEP_LABELS[18],
|
|
2163
2300
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2164
2301
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2165
2302
|
});
|
|
2166
|
-
logEvent(root, 'step', { step:
|
|
2167
|
-
stepHeader(
|
|
2303
|
+
logEvent(root, 'step', { step: 18, label: 'Push', feature });
|
|
2304
|
+
stepHeader(18, 'Push', feature);
|
|
2168
2305
|
if (isResuming) {
|
|
2169
|
-
printResumptionHeader(
|
|
2306
|
+
printResumptionHeader(18);
|
|
2170
2307
|
}
|
|
2171
2308
|
console.log('Push the commit to the remote repository.');
|
|
2172
2309
|
console.log();
|
|
@@ -2177,12 +2314,15 @@ function printStep16(root, feature) {
|
|
|
2177
2314
|
console.log(chalk.dim(' If the user says yes, run: `git push`'));
|
|
2178
2315
|
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"'));
|
|
2179
2316
|
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
2180
|
-
|
|
2181
|
-
console.log();
|
|
2182
|
-
console.log(chalk.
|
|
2183
|
-
console.log(chalk.
|
|
2184
|
-
console.log(chalk.
|
|
2185
|
-
|
|
2317
|
+
console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
|
|
2318
|
+
console.log();
|
|
2319
|
+
console.log(chalk.bold.yellow('IMPORTANT: After this step, the current feature is DONE.'));
|
|
2320
|
+
console.log(chalk.yellow(' Any new user request — even if it sounds related — is a NEW feature.'));
|
|
2321
|
+
console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
|
|
2322
|
+
console.log(chalk.yellow(' Ask the user what they want to build. Once they tell you, run `codeyam editor 1` yourself.'));
|
|
2323
|
+
console.log(chalk.yellow(' NEVER tell the user to run `codeyam editor` commands — these are internal. Just ask what to build.'));
|
|
2324
|
+
console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
|
|
2325
|
+
stopGate(18, { confirm: true });
|
|
2186
2326
|
}
|
|
2187
2327
|
// ─── Command definition ───────────────────────────────────────────────
|
|
2188
2328
|
// ─── Analyze-imports subcommand ────────────────────────────────────────
|
|
@@ -2202,7 +2342,7 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2202
2342
|
return;
|
|
2203
2343
|
}
|
|
2204
2344
|
console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
|
|
2205
|
-
console.error(chalk.dim(' Run codeyam editor
|
|
2345
|
+
console.error(chalk.dim(' Run codeyam editor 8 to create the glossary first.'));
|
|
2206
2346
|
process.exit(1);
|
|
2207
2347
|
}
|
|
2208
2348
|
let glossaryEntries;
|
|
@@ -2256,25 +2396,54 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2256
2396
|
// Non-fatal — scenario file paths just won't be included
|
|
2257
2397
|
}
|
|
2258
2398
|
const progress = new ProgressReporter();
|
|
2259
|
-
//
|
|
2260
|
-
//
|
|
2261
|
-
|
|
2262
|
-
progress.start('Running import analysis for all entities...');
|
|
2399
|
+
// Filter to only files that actually need analysis — avoids re-analyzing
|
|
2400
|
+
// ~120 files when only 2 are incomplete.
|
|
2401
|
+
let targetFilePaths = filePaths;
|
|
2263
2402
|
try {
|
|
2264
|
-
await
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2403
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2404
|
+
const db = getDatabase();
|
|
2405
|
+
const { requireBranchAndProject: rbp } = await import('../utils/database.js');
|
|
2406
|
+
const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
|
|
2407
|
+
const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
|
|
2408
|
+
const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
|
|
2409
|
+
if (incomplete.length < filePaths.length && incomplete.length > 0) {
|
|
2410
|
+
if (!options.silent) {
|
|
2411
|
+
console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
|
|
2412
|
+
}
|
|
2413
|
+
targetFilePaths = incomplete;
|
|
2414
|
+
}
|
|
2415
|
+
else if (incomplete.length === 0) {
|
|
2416
|
+
if (!options.silent) {
|
|
2417
|
+
console.log(chalk.green('All entities already have analyses — nothing to do.'));
|
|
2418
|
+
}
|
|
2419
|
+
// Still need to run the import graph + backfill below
|
|
2420
|
+
targetFilePaths = [];
|
|
2421
|
+
}
|
|
2270
2422
|
}
|
|
2271
|
-
catch
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2423
|
+
catch {
|
|
2424
|
+
// Non-fatal — fall back to analyzing all files
|
|
2425
|
+
}
|
|
2426
|
+
// Run data-structure-only analysis for entities that need it.
|
|
2427
|
+
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2428
|
+
// The analyzer will discover and create them from file paths.
|
|
2429
|
+
if (targetFilePaths.length > 0) {
|
|
2430
|
+
progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
|
|
2431
|
+
try {
|
|
2432
|
+
await runAnalysisForEntities({
|
|
2433
|
+
projectRoot: root,
|
|
2434
|
+
filePaths: targetFilePaths,
|
|
2435
|
+
progress,
|
|
2436
|
+
onlyDataStructure: true,
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
catch (err) {
|
|
2440
|
+
progress.fail('Analysis failed');
|
|
2441
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2442
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
2443
|
+
process.exit(1);
|
|
2444
|
+
}
|
|
2445
|
+
progress.succeed('Analysis complete');
|
|
2276
2446
|
}
|
|
2277
|
-
progress.succeed('Analysis complete');
|
|
2278
2447
|
// Load entities WITH metadata from database
|
|
2279
2448
|
progress.start('Loading entity metadata...');
|
|
2280
2449
|
await initializeEnvironment();
|
|
@@ -2624,11 +2793,15 @@ async function handleRegister(jsonArg) {
|
|
|
2624
2793
|
}
|
|
2625
2794
|
// Normalize to array for uniform handling
|
|
2626
2795
|
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
2796
|
+
const root = getProjectRoot();
|
|
2627
2797
|
const port = getServerPort();
|
|
2628
2798
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
2629
2799
|
const isBatch = items.length > 1;
|
|
2630
2800
|
let succeeded = 0;
|
|
2631
2801
|
let failed = 0;
|
|
2802
|
+
let warnings = 0;
|
|
2803
|
+
const issues = [];
|
|
2804
|
+
const screenshotEntries = [];
|
|
2632
2805
|
for (let i = 0; i < items.length; i++) {
|
|
2633
2806
|
const body = items[i];
|
|
2634
2807
|
const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
|
|
@@ -2656,8 +2829,20 @@ async function handleRegister(jsonArg) {
|
|
|
2656
2829
|
else {
|
|
2657
2830
|
parts.push(`errors=0`);
|
|
2658
2831
|
}
|
|
2659
|
-
if (data.
|
|
2660
|
-
|
|
2832
|
+
if (data.visibleText) {
|
|
2833
|
+
const truncated = data.visibleText.length > 100
|
|
2834
|
+
? data.visibleText.substring(0, 97) + '...'
|
|
2835
|
+
: data.visibleText;
|
|
2836
|
+
parts.push(`visibleText="${truncated}"`);
|
|
2837
|
+
}
|
|
2838
|
+
if (data.seedResult) {
|
|
2839
|
+
if (data.seedResult.success) {
|
|
2840
|
+
parts.push(`seed=ok`);
|
|
2841
|
+
}
|
|
2842
|
+
else {
|
|
2843
|
+
parts.push(chalk.red(`seed=FAILED${data.seedResult.error ? ` (${data.seedResult.error})` : ''}`));
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2661
2846
|
if (data.captureError)
|
|
2662
2847
|
parts.push(chalk.yellow(`captureError="${data.captureError}"`));
|
|
2663
2848
|
console.log(prefix + parts.join(' '));
|
|
@@ -2675,14 +2860,39 @@ async function handleRegister(jsonArg) {
|
|
|
2675
2860
|
console.log(chalk.red(` → ${err}`));
|
|
2676
2861
|
}
|
|
2677
2862
|
console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
|
|
2863
|
+
// Provide actionable debugging hints for HTTP/API errors
|
|
2864
|
+
const hasApiResponseError = data.clientErrors.some((e) => e.includes('API response error:'));
|
|
2865
|
+
const hasHttpError = data.clientErrors.some((e) => e.includes('HTTP error:'));
|
|
2866
|
+
if (hasHttpError || hasApiResponseError) {
|
|
2867
|
+
console.log(chalk.yellow(' To debug: look at the "API response error" lines above — they show the exact API endpoint'));
|
|
2868
|
+
console.log(chalk.yellow(' that failed and what the server returned. Then check the scenario seed data to ensure'));
|
|
2869
|
+
console.log(chalk.yellow(' all IDs referenced in the URL exist in the seed, and that the route is correct.'));
|
|
2870
|
+
}
|
|
2678
2871
|
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2679
2872
|
}
|
|
2873
|
+
const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
|
|
2680
2874
|
if (!res.ok) {
|
|
2681
2875
|
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2682
2876
|
failed++;
|
|
2683
2877
|
}
|
|
2684
2878
|
else {
|
|
2879
|
+
if (resultIssues.length > 0)
|
|
2880
|
+
warnings++;
|
|
2685
2881
|
succeeded++;
|
|
2882
|
+
// Collect screenshot paths for duplicate detection
|
|
2883
|
+
if (data.screenshotCaptured &&
|
|
2884
|
+
data.scenario?.screenshotPath &&
|
|
2885
|
+
data.scenario?.name) {
|
|
2886
|
+
const absPath = path.join(root, '.codeyam', 'editor-scenarios', data.scenario.screenshotPath);
|
|
2887
|
+
screenshotEntries.push({
|
|
2888
|
+
scenarioName: data.scenario.name,
|
|
2889
|
+
screenshotAbsPath: absPath,
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
// Collect issues from this registration
|
|
2894
|
+
for (const issue of resultIssues) {
|
|
2895
|
+
issues.push(`"${issue.scenarioName}": ${issue.message}`);
|
|
2686
2896
|
}
|
|
2687
2897
|
}
|
|
2688
2898
|
catch (error) {
|
|
@@ -2693,13 +2903,100 @@ async function handleRegister(jsonArg) {
|
|
|
2693
2903
|
failed++;
|
|
2694
2904
|
}
|
|
2695
2905
|
}
|
|
2906
|
+
// Detect duplicate screenshots in the batch
|
|
2907
|
+
if (isBatch && screenshotEntries.length > 1) {
|
|
2908
|
+
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
|
|
2909
|
+
const hashEntries = screenshotEntries
|
|
2910
|
+
.map((e) => {
|
|
2911
|
+
const hash = computeScreenshotHash(e.screenshotAbsPath);
|
|
2912
|
+
return hash
|
|
2913
|
+
? {
|
|
2914
|
+
scenarioName: e.scenarioName,
|
|
2915
|
+
screenshotPath: e.screenshotAbsPath,
|
|
2916
|
+
hash,
|
|
2917
|
+
}
|
|
2918
|
+
: null;
|
|
2919
|
+
})
|
|
2920
|
+
.filter((e) => e !== null);
|
|
2921
|
+
const duplicates = findDuplicateScreenshots(hashEntries);
|
|
2922
|
+
if (duplicates.size > 0) {
|
|
2923
|
+
console.log('');
|
|
2924
|
+
console.log(chalk.yellow.bold('WARNING: Identical screenshots detected:'));
|
|
2925
|
+
for (const [, names] of duplicates) {
|
|
2926
|
+
const quoted = names.map((n) => `"${n}"`);
|
|
2927
|
+
console.log(chalk.yellow(` ${quoted.join(' and ')} produced the same screenshot`));
|
|
2928
|
+
}
|
|
2929
|
+
console.log(chalk.yellow(" This usually means the app's view state depends on something scenarios can't control"));
|
|
2930
|
+
console.log(chalk.yellow(' (e.g., a hardcoded function return value, or identical localStorage not differentiating views).'));
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2696
2933
|
if (isBatch) {
|
|
2697
|
-
console.log(
|
|
2934
|
+
console.log('');
|
|
2935
|
+
if (failed > 0 || warnings > 0) {
|
|
2936
|
+
console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
|
|
2937
|
+
console.log('');
|
|
2938
|
+
console.log(chalk.red.bold('Issues that MUST be fixed:'));
|
|
2939
|
+
for (const issue of issues) {
|
|
2940
|
+
console.log(chalk.red(` ✗ ${issue}`));
|
|
2941
|
+
}
|
|
2942
|
+
console.log('');
|
|
2943
|
+
console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
|
|
2944
|
+
}
|
|
2945
|
+
else {
|
|
2946
|
+
console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
|
|
2947
|
+
}
|
|
2698
2948
|
}
|
|
2699
|
-
if (failed > 0) {
|
|
2949
|
+
if (failed > 0 || warnings > 0) {
|
|
2700
2950
|
process.exit(1);
|
|
2701
2951
|
}
|
|
2702
2952
|
}
|
|
2953
|
+
// ─── Glossary-add subcommand ──────────────────────────────────────────
|
|
2954
|
+
/**
|
|
2955
|
+
* `codeyam editor glossary-add '{"name":"...", "filePath":"...", "description":"..."}'`
|
|
2956
|
+
*
|
|
2957
|
+
* Safely adds/updates entries in .codeyam/glossary.json via the CLI,
|
|
2958
|
+
* avoiding hand-editing that breaks on unicode characters.
|
|
2959
|
+
*/
|
|
2960
|
+
async function handleGlossaryAdd(jsonArg) {
|
|
2961
|
+
if (!jsonArg) {
|
|
2962
|
+
console.error(chalk.red('Error: JSON argument required.'));
|
|
2963
|
+
console.error(chalk.dim(' Usage: codeyam editor glossary-add \'{"name":"DrinkCard","filePath":"app/components/DrinkCard.tsx","description":"Displays a drink item"}\''));
|
|
2964
|
+
console.error(chalk.dim(' For large payloads: codeyam editor glossary-add @.codeyam/tmp/entry.json'));
|
|
2965
|
+
process.exit(1);
|
|
2966
|
+
}
|
|
2967
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
2968
|
+
if (parsed.error) {
|
|
2969
|
+
console.error(chalk.red(`Error: ${parsed.error}`));
|
|
2970
|
+
process.exit(1);
|
|
2971
|
+
}
|
|
2972
|
+
// Normalize to array
|
|
2973
|
+
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
2974
|
+
// Read existing glossary
|
|
2975
|
+
const root = getProjectRoot();
|
|
2976
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
2977
|
+
let existing = [];
|
|
2978
|
+
try {
|
|
2979
|
+
const raw = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
|
|
2980
|
+
existing = sanitizeGlossaryEntries(raw);
|
|
2981
|
+
}
|
|
2982
|
+
catch {
|
|
2983
|
+
// Glossary doesn't exist yet or can't be parsed — start fresh
|
|
2984
|
+
}
|
|
2985
|
+
// Merge
|
|
2986
|
+
const { validateGlossaryEntry, mergeGlossaryEntries } = await import('../utils/glossaryAdd.js');
|
|
2987
|
+
const result = mergeGlossaryEntries(existing, items);
|
|
2988
|
+
// Report validation errors
|
|
2989
|
+
for (const err of result.errors) {
|
|
2990
|
+
console.error(chalk.red(`Error at index ${err.index}: ${err.message}`));
|
|
2991
|
+
}
|
|
2992
|
+
if (result.added === 0 && result.updated === 0 && result.errors.length > 0) {
|
|
2993
|
+
process.exit(1);
|
|
2994
|
+
}
|
|
2995
|
+
// Write back with utf8 encoding to safely handle unicode
|
|
2996
|
+
fs.mkdirSync(path.dirname(glossaryPath), { recursive: true });
|
|
2997
|
+
fs.writeFileSync(glossaryPath, JSON.stringify(result.entries, null, 2), 'utf8');
|
|
2998
|
+
console.log(`added=${result.added} updated=${result.updated} total=${result.entries.length}`);
|
|
2999
|
+
}
|
|
2703
3000
|
// ─── Dependents subcommand ────────────────────────────────────────────
|
|
2704
3001
|
/**
|
|
2705
3002
|
* `codeyam editor dependents <EntityName>`
|
|
@@ -2821,7 +3118,7 @@ async function handleDependents(entityName) {
|
|
|
2821
3118
|
*
|
|
2822
3119
|
* Prints a condensed post-change checklist that guides Claude through
|
|
2823
3120
|
* re-verifying after user-requested modifications. When called from
|
|
2824
|
-
* step
|
|
3121
|
+
* step 15+, this loops back to step 15 (present). When called from an
|
|
2825
3122
|
* earlier step, it returns to that step so the normal flow continues.
|
|
2826
3123
|
*/
|
|
2827
3124
|
function handleChange(feature) {
|
|
@@ -2839,7 +3136,7 @@ function handleChange(feature) {
|
|
|
2839
3136
|
process.exit(1);
|
|
2840
3137
|
}
|
|
2841
3138
|
}
|
|
2842
|
-
const currentStep = state?.step ??
|
|
3139
|
+
const currentStep = state?.step ?? 15;
|
|
2843
3140
|
const port = getServerPort();
|
|
2844
3141
|
console.log();
|
|
2845
3142
|
console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
|
|
@@ -2861,6 +3158,8 @@ function handleChange(feature) {
|
|
|
2861
3158
|
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
2862
3159
|
printDimensionGuidance(dim, dimNames);
|
|
2863
3160
|
console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
|
|
3161
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
3162
|
+
console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
|
|
2864
3163
|
console.log();
|
|
2865
3164
|
console.log(chalk.bold('0. Close the results panel:'));
|
|
2866
3165
|
checkbox(`Hide results: \`codeyam editor hide-results\``);
|
|
@@ -2887,6 +3186,7 @@ function handleChange(feature) {
|
|
|
2887
3186
|
console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
|
|
2888
3187
|
checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
|
|
2889
3188
|
console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
|
|
3189
|
+
console.log(chalk.dim(' If the enriched data makes the scenario name too narrow, rename it to reflect its broader coverage.'));
|
|
2890
3190
|
checkbox('After each re-registration, view the screenshot to verify data is visible');
|
|
2891
3191
|
console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
|
|
2892
3192
|
console.log();
|
|
@@ -2895,6 +3195,7 @@ function handleChange(feature) {
|
|
|
2895
3195
|
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
|
|
2896
3196
|
printDimensionGuidance(dim, dimNames);
|
|
2897
3197
|
checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
|
|
3198
|
+
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
2898
3199
|
checkbox('Run `codeyam editor audit` — all checks must pass');
|
|
2899
3200
|
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
2900
3201
|
console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
|
|
@@ -2909,10 +3210,10 @@ function handleChange(feature) {
|
|
|
2909
3210
|
console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
|
|
2910
3211
|
console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
|
|
2911
3212
|
console.log();
|
|
2912
|
-
// If the change was initiated from a step before
|
|
2913
|
-
// instead of jumping to step
|
|
2914
|
-
// step
|
|
2915
|
-
if (currentStep <
|
|
3213
|
+
// If the change was initiated from a step before 15, return to that step
|
|
3214
|
+
// instead of jumping to step 15. The change workflow should only loop to
|
|
3215
|
+
// step 15 when changes are requested FROM step 15.
|
|
3216
|
+
if (currentStep < 15) {
|
|
2916
3217
|
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2917
3218
|
console.log(chalk.red.bold(' REQUIRED: Return to current step'));
|
|
2918
3219
|
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
@@ -2923,7 +3224,7 @@ function handleChange(feature) {
|
|
|
2923
3224
|
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2924
3225
|
console.log(chalk.red.bold(' REQUIRED: Show Results'));
|
|
2925
3226
|
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
2926
|
-
chalk.bold(`codeyam editor
|
|
3227
|
+
chalk.bold(`codeyam editor 15`));
|
|
2927
3228
|
console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
|
|
2928
3229
|
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2929
3230
|
}
|
|
@@ -2960,16 +3261,33 @@ async function checkAuditGate() {
|
|
|
2960
3261
|
return true; // Server unreachable — don't block
|
|
2961
3262
|
if (data.summary?.allPassing === true)
|
|
2962
3263
|
return true;
|
|
2963
|
-
//
|
|
2964
|
-
|
|
2965
|
-
|
|
3264
|
+
// Lightweight auto-fix: backfill entity_sha (DB-only, fast).
|
|
3265
|
+
// Never run handleAnalyzeImports here — it takes minutes on large projects.
|
|
3266
|
+
const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
|
|
3267
|
+
// If the only failures are pre-existing incomplete entities (not caused by
|
|
3268
|
+
// this session), don't block — the audit text already calls these "non-blocking".
|
|
3269
|
+
if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
|
|
3270
|
+
return true;
|
|
3271
|
+
}
|
|
3272
|
+
if (isOnlyIncompleteEntities(data.summary)) {
|
|
2966
3273
|
try {
|
|
2967
|
-
await
|
|
3274
|
+
const entities = await loadEntities({});
|
|
3275
|
+
if (entities && entities.length > 0) {
|
|
3276
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3277
|
+
const db = getDatabase();
|
|
3278
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3279
|
+
sha: e.sha,
|
|
3280
|
+
name: e.name,
|
|
3281
|
+
filePath: e.filePath || '',
|
|
3282
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3283
|
+
e.metadata?.namedExport === false,
|
|
3284
|
+
})));
|
|
3285
|
+
}
|
|
2968
3286
|
}
|
|
2969
3287
|
catch {
|
|
2970
|
-
|
|
3288
|
+
// Fall through
|
|
2971
3289
|
}
|
|
2972
|
-
// Re-check after
|
|
3290
|
+
// Re-check after backfill
|
|
2973
3291
|
const retry = await fetchAuditResult();
|
|
2974
3292
|
if (retry?.summary?.allPassing === true)
|
|
2975
3293
|
return true;
|
|
@@ -2988,22 +3306,79 @@ function printAuditGateFailures(data) {
|
|
|
2988
3306
|
if (!s)
|
|
2989
3307
|
return;
|
|
2990
3308
|
const issues = [];
|
|
2991
|
-
if (s.componentsMissing > 0)
|
|
3309
|
+
if (s.componentsMissing > 0) {
|
|
2992
3310
|
issues.push(`${s.componentsMissing} component(s) missing scenarios`);
|
|
2993
|
-
|
|
3311
|
+
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
3312
|
+
for (const c of missing) {
|
|
3313
|
+
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3314
|
+
if (c.hint)
|
|
3315
|
+
issues.push(` Fix: ${c.hint}`);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
if (s.componentsWithErrors > 0) {
|
|
2994
3319
|
issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
|
|
2995
|
-
|
|
3320
|
+
const withErrors = (data.components || []).filter((c) => c.status === 'has_errors');
|
|
3321
|
+
for (const c of withErrors) {
|
|
3322
|
+
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
if (s.functionsMissing > 0) {
|
|
2996
3326
|
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
2997
|
-
|
|
3327
|
+
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
3328
|
+
for (const f of missing) {
|
|
3329
|
+
if (f.testFile) {
|
|
3330
|
+
issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
|
|
3331
|
+
}
|
|
3332
|
+
else {
|
|
3333
|
+
issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
|
|
3334
|
+
if (f.suggestedTestFile) {
|
|
3335
|
+
issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
if (s.functionsFailing > 0) {
|
|
2998
3341
|
issues.push(`${s.functionsFailing} function(s) with failing tests`);
|
|
2999
|
-
|
|
3342
|
+
const failing = (data.functions || []).filter((f) => f.status === 'failing');
|
|
3343
|
+
for (const f of failing) {
|
|
3344
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
if (s.functionsRunnerError > 0) {
|
|
3000
3348
|
issues.push(`${s.functionsRunnerError} function(s) with test runner errors (the runner crashed — not a test failure)`);
|
|
3001
|
-
|
|
3349
|
+
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
3350
|
+
for (const f of runnerErrors) {
|
|
3351
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
if (s.functionsNameMismatch > 0) {
|
|
3002
3355
|
issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
|
|
3356
|
+
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
3357
|
+
for (const f of mismatch) {
|
|
3358
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3003
3361
|
if (s.missingFromGlossary > 0)
|
|
3004
3362
|
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
3005
|
-
if (s.incompleteEntities > 0)
|
|
3006
|
-
|
|
3363
|
+
if (s.incompleteEntities > 0) {
|
|
3364
|
+
const preCount = s.preExistingIncompleteEntities || 0;
|
|
3365
|
+
if (preCount > 0 && preCount === s.incompleteEntities) {
|
|
3366
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
3367
|
+
}
|
|
3368
|
+
else if (preCount > 0) {
|
|
3369
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis (${preCount} pre-existing — not from your changes)`);
|
|
3370
|
+
}
|
|
3371
|
+
else {
|
|
3372
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
if (s.unassociatedScenarios > 0) {
|
|
3376
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
3377
|
+
const unassociated = data.unassociatedScenarios || [];
|
|
3378
|
+
for (const u of unassociated) {
|
|
3379
|
+
issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3007
3382
|
if (s.miscategorizedScenarios > 0)
|
|
3008
3383
|
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3009
3384
|
if (s.scenariosNeedingRecapture > 0)
|
|
@@ -3030,8 +3405,6 @@ function printAuditGateFailures(data) {
|
|
|
3030
3405
|
}
|
|
3031
3406
|
}
|
|
3032
3407
|
console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
|
|
3033
|
-
console.error(chalk.yellow('If errors reference browser APIs (localStorage, sessionStorage, window, document),'));
|
|
3034
|
-
console.error(chalk.yellow('create a universal mock: codeyam detect-universal-mocks'));
|
|
3035
3408
|
}
|
|
3036
3409
|
}
|
|
3037
3410
|
console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
|
|
@@ -3050,30 +3423,44 @@ async function handleAudit() {
|
|
|
3050
3423
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3051
3424
|
process.exit(1);
|
|
3052
3425
|
}
|
|
3053
|
-
//
|
|
3054
|
-
//
|
|
3055
|
-
//
|
|
3056
|
-
//
|
|
3426
|
+
// Lightweight auto-fix: backfill entity_sha on scenarios that were
|
|
3427
|
+
// registered before entity records existed. This is fast (DB-only).
|
|
3428
|
+
// We do NOT run handleAnalyzeImports here — it scans all files and takes
|
|
3429
|
+
// minutes on large projects. Users should run it separately if needed.
|
|
3057
3430
|
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3431
|
+
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
3058
3432
|
let autoRemediationFailed = false;
|
|
3059
|
-
if (incompleteBeforeFix.length > 0) {
|
|
3060
|
-
|
|
3433
|
+
if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
|
|
3434
|
+
const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
|
|
3435
|
+
console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
|
|
3436
|
+
// Backfill entity_sha on scenarios that were registered before entities existed
|
|
3061
3437
|
try {
|
|
3062
|
-
await
|
|
3438
|
+
const entities = await loadEntities({});
|
|
3439
|
+
if (entities && entities.length > 0) {
|
|
3440
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3441
|
+
const db = getDatabase();
|
|
3442
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3443
|
+
sha: e.sha,
|
|
3444
|
+
name: e.name,
|
|
3445
|
+
filePath: e.filePath || '',
|
|
3446
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3447
|
+
e.metadata?.namedExport === false,
|
|
3448
|
+
})));
|
|
3449
|
+
}
|
|
3063
3450
|
}
|
|
3064
3451
|
catch {
|
|
3065
|
-
// Fall through —
|
|
3452
|
+
// Fall through — re-fetch will show remaining issues
|
|
3066
3453
|
}
|
|
3067
|
-
// Re-fetch audit results after the
|
|
3454
|
+
// Re-fetch audit results after the backfill
|
|
3068
3455
|
data = await fetchAuditResult();
|
|
3069
3456
|
if (!data) {
|
|
3070
|
-
console.error(chalk.red('Error: Could not reach the CodeYam server after
|
|
3457
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
|
|
3071
3458
|
process.exit(1);
|
|
3072
3459
|
}
|
|
3073
|
-
// If
|
|
3074
|
-
// flag it so we can show a clear message instead of looping
|
|
3460
|
+
// If issues persist after backfill, flag it so we show clear guidance
|
|
3075
3461
|
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3076
|
-
|
|
3462
|
+
const unassociatedAfterFix = data.unassociatedScenarios || [];
|
|
3463
|
+
if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
|
|
3077
3464
|
autoRemediationFailed = true;
|
|
3078
3465
|
}
|
|
3079
3466
|
}
|
|
@@ -3083,20 +3470,39 @@ async function handleAudit() {
|
|
|
3083
3470
|
console.log();
|
|
3084
3471
|
// Components
|
|
3085
3472
|
if (components.length > 0) {
|
|
3473
|
+
// Build name frequency map for disambiguation
|
|
3474
|
+
const componentNameCounts = new Map();
|
|
3475
|
+
for (const c of components) {
|
|
3476
|
+
componentNameCounts.set(c.name, (componentNameCounts.get(c.name) || 0) + 1);
|
|
3477
|
+
}
|
|
3086
3478
|
console.log(chalk.bold('Components (scenarios):'));
|
|
3087
3479
|
for (const c of components) {
|
|
3088
|
-
const icon = c.status === 'ok'
|
|
3480
|
+
const icon = c.status === 'ok'
|
|
3481
|
+
? chalk.green('✓')
|
|
3482
|
+
: c.status === 'needs_recapture'
|
|
3483
|
+
? chalk.yellow('↻')
|
|
3484
|
+
: chalk.red('✗');
|
|
3089
3485
|
let detail;
|
|
3090
3486
|
if (c.status === 'has_errors') {
|
|
3091
3487
|
detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
|
|
3092
3488
|
}
|
|
3489
|
+
else if (c.status === 'needs_recapture') {
|
|
3490
|
+
detail = chalk.yellow(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''}, needs recapture`);
|
|
3491
|
+
}
|
|
3093
3492
|
else if (c.status === 'ok') {
|
|
3094
3493
|
detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
|
|
3095
3494
|
}
|
|
3096
3495
|
else {
|
|
3097
3496
|
detail = chalk.red(' — no scenarios registered');
|
|
3497
|
+
if (c.hint) {
|
|
3498
|
+
detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
|
|
3499
|
+
}
|
|
3098
3500
|
}
|
|
3099
|
-
|
|
3501
|
+
// Show file path for failing components always, for OK only when name is ambiguous
|
|
3502
|
+
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
3503
|
+
const showPath = (c.status !== 'ok' && c.status !== 'needs_recapture') || isDuplicate;
|
|
3504
|
+
const pathSuffix = showPath && c.filePath ? chalk.dim(` (${c.filePath})`) : '';
|
|
3505
|
+
console.log(` ${icon} ${c.name}${pathSuffix}${detail}`);
|
|
3100
3506
|
if (c.clientErrors && c.clientErrors.length > 0) {
|
|
3101
3507
|
for (const err of c.clientErrors.slice(0, 3)) {
|
|
3102
3508
|
console.log(chalk.red(` → ${err}`));
|
|
@@ -3104,14 +3510,6 @@ async function handleAudit() {
|
|
|
3104
3510
|
if (c.clientErrors.length > 3) {
|
|
3105
3511
|
console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
3106
3512
|
}
|
|
3107
|
-
// Detect browser API errors and provide actionable guidance
|
|
3108
|
-
const browserApiPattern = /\b(localStorage|sessionStorage|window\.|document\.|navigator\.|indexedDB|matchMedia|ResizeObserver|IntersectionObserver|MutationObserver)\b/;
|
|
3109
|
-
const hasBrowserApiErrors = c.clientErrors.some((err) => browserApiPattern.test(err));
|
|
3110
|
-
if (hasBrowserApiErrors) {
|
|
3111
|
-
console.log(chalk.yellow(` ⚠ These errors are caused by browser APIs that don't exist during server-side analysis.`));
|
|
3112
|
-
console.log(chalk.yellow(` Fix: Create a universal mock to stub the missing API. Run: codeyam detect-universal-mocks`));
|
|
3113
|
-
console.log(chalk.yellow(` DO NOT re-run the audit or analyze-imports — the error will persist until a mock is created.`));
|
|
3114
|
-
}
|
|
3115
3513
|
}
|
|
3116
3514
|
}
|
|
3117
3515
|
console.log();
|
|
@@ -3142,9 +3540,16 @@ async function handleAudit() {
|
|
|
3142
3540
|
break;
|
|
3143
3541
|
case 'missing':
|
|
3144
3542
|
default:
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3543
|
+
if (f.testFile) {
|
|
3544
|
+
detail = chalk.red(` — test file missing: ${f.testFile}`);
|
|
3545
|
+
}
|
|
3546
|
+
else {
|
|
3547
|
+
detail = chalk.red(` — no test file specified in glossary`);
|
|
3548
|
+
detail += chalk.dim(` (source: ${f.filePath})`);
|
|
3549
|
+
if (f.suggestedTestFile) {
|
|
3550
|
+
detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3148
3553
|
break;
|
|
3149
3554
|
}
|
|
3150
3555
|
console.log(` ${icon} ${f.name}${detail}`);
|
|
@@ -3161,25 +3566,70 @@ async function handleAudit() {
|
|
|
3161
3566
|
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
3162
3567
|
console.log();
|
|
3163
3568
|
}
|
|
3164
|
-
// Incomplete entities (scenarios without analyses)
|
|
3165
|
-
|
|
3569
|
+
// Incomplete entities (scenarios without analyses) — auto-fix by running
|
|
3570
|
+
// targeted analysis on just the affected files (fast: 2 files vs 117+).
|
|
3571
|
+
let incompleteEntities = data.incompleteEntities || [];
|
|
3572
|
+
if (incompleteEntities.length > 0) {
|
|
3573
|
+
const { getIncompleteEntityFilePaths } = await import('../utils/editorAudit.js');
|
|
3574
|
+
try {
|
|
3575
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3576
|
+
const db = getDatabase();
|
|
3577
|
+
const filePaths = await getIncompleteEntityFilePaths(db, incompleteEntities);
|
|
3578
|
+
if (filePaths.length > 0) {
|
|
3579
|
+
console.log(chalk.dim(`Running targeted import analysis for ${filePaths.length} incomplete entit${filePaths.length !== 1 ? 'ies' : 'y'}...`));
|
|
3580
|
+
const root = getProjectRoot();
|
|
3581
|
+
try {
|
|
3582
|
+
const { runAnalysisForEntities } = await import('../utils/analysisRunner.js');
|
|
3583
|
+
await runAnalysisForEntities({
|
|
3584
|
+
projectRoot: root,
|
|
3585
|
+
filePaths,
|
|
3586
|
+
progress: new ProgressReporter(),
|
|
3587
|
+
onlyDataStructure: true,
|
|
3588
|
+
});
|
|
3589
|
+
// Re-fetch audit results after targeted fix
|
|
3590
|
+
const refreshed = await fetchAuditResult();
|
|
3591
|
+
if (refreshed) {
|
|
3592
|
+
data = refreshed;
|
|
3593
|
+
incompleteEntities = data.incompleteEntities || [];
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
catch {
|
|
3597
|
+
// Fall through — report remaining incomplete entities below
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
catch {
|
|
3602
|
+
// DB not available — fall through to reporting
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3166
3605
|
if (incompleteEntities.length > 0) {
|
|
3606
|
+
const { formatIncompleteEntityGuidance } = await import('../utils/editorAudit.js');
|
|
3167
3607
|
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3168
3608
|
for (const e of incompleteEntities) {
|
|
3169
|
-
|
|
3609
|
+
const guidance = formatIncompleteEntityGuidance(e);
|
|
3610
|
+
console.log(` ${chalk.red('✗')} ${guidance}`);
|
|
3611
|
+
}
|
|
3612
|
+
console.log();
|
|
3613
|
+
}
|
|
3614
|
+
// Unassociated scenarios (NULL entity_sha with file paths)
|
|
3615
|
+
const unassociatedScenarios = data.unassociatedScenarios || [];
|
|
3616
|
+
if (unassociatedScenarios.length > 0) {
|
|
3617
|
+
console.log(chalk.bold('Unassociated scenarios (missing entity link):'));
|
|
3618
|
+
for (const u of unassociatedScenarios) {
|
|
3619
|
+
console.log(` ${chalk.red('✗')} ${u.name} ${chalk.dim(`(${u.filePath})`)} — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''} with no entity_sha`);
|
|
3620
|
+
for (const sn of u.scenarioNames.slice(0, 3)) {
|
|
3621
|
+
console.log(chalk.dim(` "${sn}"`));
|
|
3622
|
+
}
|
|
3623
|
+
if (u.scenarioNames.length > 3) {
|
|
3624
|
+
console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
|
|
3625
|
+
}
|
|
3170
3626
|
}
|
|
3171
3627
|
if (autoRemediationFailed) {
|
|
3172
|
-
console.log(chalk.
|
|
3173
|
-
console.log(chalk.
|
|
3174
|
-
console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
|
|
3175
|
-
console.log(chalk.yellow(' • Entity code uses browser APIs (localStorage, window, document) — create a universal mock'));
|
|
3176
|
-
console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
|
|
3177
|
-
console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
|
|
3178
|
-
console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
|
|
3179
|
-
console.log(chalk.yellow(' To fix browser API issues: run `codeyam detect-universal-mocks`'));
|
|
3628
|
+
console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
|
|
3629
|
+
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to see the full error output.'));
|
|
3180
3630
|
}
|
|
3181
3631
|
else {
|
|
3182
|
-
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to
|
|
3632
|
+
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
|
|
3183
3633
|
}
|
|
3184
3634
|
console.log();
|
|
3185
3635
|
}
|
|
@@ -3208,7 +3658,20 @@ async function handleAudit() {
|
|
|
3208
3658
|
: `${s.status.status}`;
|
|
3209
3659
|
console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
|
|
3210
3660
|
}
|
|
3211
|
-
console.log(chalk.yellow('
|
|
3661
|
+
console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
|
|
3662
|
+
console.log();
|
|
3663
|
+
}
|
|
3664
|
+
// Duplicate glossary names (warning, not a failure)
|
|
3665
|
+
const duplicateNames = data.duplicateNames || [];
|
|
3666
|
+
if (duplicateNames.length > 0) {
|
|
3667
|
+
console.log(chalk.bold('Duplicate names in glossary (confusing for audit):'));
|
|
3668
|
+
for (const dn of duplicateNames) {
|
|
3669
|
+
console.log(` ${chalk.yellow('⚠')} "${dn.name}" appears ${dn.filePaths.length} times:`);
|
|
3670
|
+
for (const fp of dn.filePaths) {
|
|
3671
|
+
console.log(` ${fp}`);
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
console.log(chalk.yellow(' Fix: remove duplicate entries or rename them to be unique in .codeyam/glossary.json'));
|
|
3212
3675
|
console.log();
|
|
3213
3676
|
}
|
|
3214
3677
|
// Summary
|
|
@@ -3243,6 +3706,9 @@ async function handleAudit() {
|
|
|
3243
3706
|
if (summary.incompleteEntities > 0) {
|
|
3244
3707
|
parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
|
|
3245
3708
|
}
|
|
3709
|
+
if (summary.unassociatedScenarios > 0) {
|
|
3710
|
+
parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
|
|
3711
|
+
}
|
|
3246
3712
|
if (summary.miscategorizedScenarios > 0) {
|
|
3247
3713
|
parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
|
|
3248
3714
|
}
|
|
@@ -3267,6 +3733,109 @@ async function handleAudit() {
|
|
|
3267
3733
|
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
3268
3734
|
}
|
|
3269
3735
|
}
|
|
3736
|
+
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
3737
|
+
/**
|
|
3738
|
+
* `codeyam editor recapture-stale`
|
|
3739
|
+
*
|
|
3740
|
+
* Identifies all scenarios whose entity (or dependency) has changed but
|
|
3741
|
+
* whose screenshot hasn't been recaptured this session, then recaptures
|
|
3742
|
+
* them in batch.
|
|
3743
|
+
*/
|
|
3744
|
+
async function handleRecaptureStale() {
|
|
3745
|
+
const port = getServerPort();
|
|
3746
|
+
const url = `http://localhost:${port}/api/editor-recapture-stale`;
|
|
3747
|
+
console.log();
|
|
3748
|
+
console.log(chalk.bold.cyan('━━━ Recapture Stale Scenarios ━━━'));
|
|
3749
|
+
console.log();
|
|
3750
|
+
let res;
|
|
3751
|
+
try {
|
|
3752
|
+
res = await fetch(url, { method: 'POST' });
|
|
3753
|
+
}
|
|
3754
|
+
catch (err) {
|
|
3755
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3756
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
3757
|
+
process.exit(1);
|
|
3758
|
+
}
|
|
3759
|
+
if (!res.ok) {
|
|
3760
|
+
const body = await res.json().catch(() => null);
|
|
3761
|
+
console.error(chalk.red(`Error: Recapture endpoint returned ${res.status}`));
|
|
3762
|
+
if (body?.error)
|
|
3763
|
+
console.error(chalk.red(` ${body.error}`));
|
|
3764
|
+
process.exit(1);
|
|
3765
|
+
}
|
|
3766
|
+
// Handle JSON response (early returns like "no changes" / "all up to date")
|
|
3767
|
+
const contentType = res.headers.get('content-type') || '';
|
|
3768
|
+
if (contentType.includes('application/json')) {
|
|
3769
|
+
const data = await res.json();
|
|
3770
|
+
if (data.note) {
|
|
3771
|
+
console.log(chalk.dim(data.note));
|
|
3772
|
+
}
|
|
3773
|
+
else if (data.total === 0) {
|
|
3774
|
+
console.log(chalk.green('All scenarios are up to date.'));
|
|
3775
|
+
}
|
|
3776
|
+
console.log();
|
|
3777
|
+
return;
|
|
3778
|
+
}
|
|
3779
|
+
// Stream NDJSON progress events
|
|
3780
|
+
let total = 0;
|
|
3781
|
+
let recapturedCount = 0;
|
|
3782
|
+
let failedCount = 0;
|
|
3783
|
+
const reader = res.body?.getReader();
|
|
3784
|
+
if (!reader) {
|
|
3785
|
+
console.error(chalk.red('Error: No response body'));
|
|
3786
|
+
process.exit(1);
|
|
3787
|
+
}
|
|
3788
|
+
const decoder = new TextDecoder();
|
|
3789
|
+
let buffer = '';
|
|
3790
|
+
while (true) {
|
|
3791
|
+
const { done, value } = await reader.read();
|
|
3792
|
+
if (done)
|
|
3793
|
+
break;
|
|
3794
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3795
|
+
const lines = buffer.split('\n');
|
|
3796
|
+
buffer = lines.pop() || ''; // Keep incomplete last line in buffer
|
|
3797
|
+
for (const line of lines) {
|
|
3798
|
+
if (!line.trim())
|
|
3799
|
+
continue;
|
|
3800
|
+
try {
|
|
3801
|
+
const event = JSON.parse(line);
|
|
3802
|
+
switch (event.type) {
|
|
3803
|
+
case 'start':
|
|
3804
|
+
total = event.total;
|
|
3805
|
+
console.log(`Found ${total} stale scenario(s). Recapturing...\n`);
|
|
3806
|
+
break;
|
|
3807
|
+
case 'capturing':
|
|
3808
|
+
process.stdout.write(chalk.dim(` … ${event.name}`));
|
|
3809
|
+
break;
|
|
3810
|
+
case 'success':
|
|
3811
|
+
// Clear the "capturing" line and print success
|
|
3812
|
+
process.stdout.write('\r\x1b[K');
|
|
3813
|
+
console.log(` ${chalk.green('✓')} ${event.name}`);
|
|
3814
|
+
recapturedCount++;
|
|
3815
|
+
break;
|
|
3816
|
+
case 'failure':
|
|
3817
|
+
process.stdout.write('\r\x1b[K');
|
|
3818
|
+
console.log(` ${chalk.red('✗')} ${event.name} — ${chalk.dim(event.error)}`);
|
|
3819
|
+
failedCount++;
|
|
3820
|
+
break;
|
|
3821
|
+
case 'done':
|
|
3822
|
+
// Final summary
|
|
3823
|
+
console.log();
|
|
3824
|
+
const color = failedCount > 0 ? chalk.yellow : chalk.green;
|
|
3825
|
+
console.log(color(`Recaptured ${recapturedCount}/${total} scenario(s).`));
|
|
3826
|
+
console.log();
|
|
3827
|
+
break;
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
catch {
|
|
3831
|
+
// Skip unparseable lines
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
if (failedCount > 0) {
|
|
3836
|
+
process.exit(1);
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3270
3839
|
// ─── Scenarios subcommand ─────────────────────────────────────────────
|
|
3271
3840
|
async function handleScenarios() {
|
|
3272
3841
|
const port = getServerPort();
|
|
@@ -3514,7 +4083,7 @@ async function handleTemplate() {
|
|
|
3514
4083
|
console.log(chalk.green(' Git initialized.'));
|
|
3515
4084
|
}
|
|
3516
4085
|
// 4. Run codeyam init
|
|
3517
|
-
console.log(chalk.bold('
|
|
4086
|
+
console.log(chalk.bold('Initializing project...'));
|
|
3518
4087
|
await initCommand.handler({
|
|
3519
4088
|
force: true,
|
|
3520
4089
|
'keep-server': true,
|
|
@@ -3593,7 +4162,7 @@ async function handleSync() {
|
|
|
3593
4162
|
// fall through
|
|
3594
4163
|
}
|
|
3595
4164
|
if (!projectSlug) {
|
|
3596
|
-
console.error(chalk.red('Error: No project slug found. Run codeyam
|
|
4165
|
+
console.error(chalk.red('Error: No project slug found. Run `codeyam editor template` to initialize the project.'));
|
|
3597
4166
|
process.exit(1);
|
|
3598
4167
|
}
|
|
3599
4168
|
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
@@ -3777,7 +4346,7 @@ function handleEditorDebug(args) {
|
|
|
3777
4346
|
scenarios.push({
|
|
3778
4347
|
id: 'overview-with-state',
|
|
3779
4348
|
title: 'Cycle overview (project, with state)',
|
|
3780
|
-
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(
|
|
4349
|
+
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(6, feature)))),
|
|
3781
4350
|
});
|
|
3782
4351
|
}
|
|
3783
4352
|
const stepFns = {
|
|
@@ -3797,8 +4366,10 @@ function handleEditorDebug(args) {
|
|
|
3797
4366
|
14: printStep14,
|
|
3798
4367
|
15: printStep15,
|
|
3799
4368
|
16: printStep16,
|
|
4369
|
+
17: printStep17,
|
|
4370
|
+
18: printStep18,
|
|
3800
4371
|
};
|
|
3801
|
-
for (let step = 1; step <=
|
|
4372
|
+
for (let step = 1; step <= 18; step++) {
|
|
3802
4373
|
const stepId = `step-${step}`;
|
|
3803
4374
|
if (!wants(stepId))
|
|
3804
4375
|
continue;
|
|
@@ -3818,7 +4389,7 @@ function handleEditorDebug(args) {
|
|
|
3818
4389
|
if (step === 2) {
|
|
3819
4390
|
scenarios.push({
|
|
3820
4391
|
id: 'step-2-scaffold',
|
|
3821
|
-
title: 'Step 2 (
|
|
4392
|
+
title: 'Step 2 (Prepare) — scaffold flow (no project)',
|
|
3822
4393
|
render: () => withTempRoot(false, (tempRoot) => captureOutput(() => printStep2(tempRoot, feature))),
|
|
3823
4394
|
});
|
|
3824
4395
|
}
|
|
@@ -3877,8 +4448,8 @@ const editorCommand = {
|
|
|
3877
4448
|
describe: 'Editor mode guided workflow',
|
|
3878
4449
|
builder: (yargs) => {
|
|
3879
4450
|
const stepDescription = IS_INTERNAL_BUILD
|
|
3880
|
-
? 'Step number (1-
|
|
3881
|
-
: 'Step number (1-
|
|
4451
|
+
? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
|
|
4452
|
+
: 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
|
|
3882
4453
|
let builder = yargs
|
|
3883
4454
|
.positional('step', {
|
|
3884
4455
|
type: 'string',
|
|
@@ -3914,7 +4485,7 @@ const editorCommand = {
|
|
|
3914
4485
|
builder = builder
|
|
3915
4486
|
.option('target', {
|
|
3916
4487
|
type: 'string',
|
|
3917
|
-
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-
|
|
4488
|
+
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-18, or comma-separated list)',
|
|
3918
4489
|
})
|
|
3919
4490
|
.option('resume', {
|
|
3920
4491
|
type: 'boolean',
|
|
@@ -3939,6 +4510,18 @@ const editorCommand = {
|
|
|
3939
4510
|
// API subcommands: preview, show-results, hide-results, commit,
|
|
3940
4511
|
// journal, journal-update, dev-server, client-errors
|
|
3941
4512
|
if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
|
|
4513
|
+
// Guard: commit requires step 16 to have been run first.
|
|
4514
|
+
// Without this, Claude shortcuts the process by running
|
|
4515
|
+
// `codeyam editor commit` directly from step 15, skipping
|
|
4516
|
+
// steps 16-18 entirely.
|
|
4517
|
+
if (argv.step === 'commit') {
|
|
4518
|
+
const state = readState(root);
|
|
4519
|
+
if (state && state.step !== 16) {
|
|
4520
|
+
console.error(chalk.red('Error: Run `codeyam editor 16` before committing.'));
|
|
4521
|
+
console.error(chalk.dim(` Current step: ${state.step} (${state.label || 'unknown'}). Step 16 (Commit) must be run first.`));
|
|
4522
|
+
process.exit(1);
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
3942
4525
|
const port = getServerPort();
|
|
3943
4526
|
try {
|
|
3944
4527
|
const request = buildEditorApiRequest(argv.step, argv.json || undefined);
|
|
@@ -3958,6 +4541,12 @@ const editorCommand = {
|
|
|
3958
4541
|
console.log(JSON.stringify(result.data, null, 2));
|
|
3959
4542
|
}
|
|
3960
4543
|
}
|
|
4544
|
+
// After a successful commit, remind Claude to continue to step 17
|
|
4545
|
+
if (argv.step === 'commit' && result.ok) {
|
|
4546
|
+
console.log();
|
|
4547
|
+
console.log(chalk.green('Commit done. Now run: ') +
|
|
4548
|
+
chalk.bold('codeyam editor 17'));
|
|
4549
|
+
}
|
|
3961
4550
|
}
|
|
3962
4551
|
catch (err) {
|
|
3963
4552
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
@@ -3971,6 +4560,51 @@ const editorCommand = {
|
|
|
3971
4560
|
await handleRegister(argv.json || '');
|
|
3972
4561
|
return;
|
|
3973
4562
|
}
|
|
4563
|
+
// Subcommand: codeyam editor glossary-add '{"name":"...", ...}'
|
|
4564
|
+
if (argv.step === 'glossary-add') {
|
|
4565
|
+
await handleGlossaryAdd(argv.json || '');
|
|
4566
|
+
return;
|
|
4567
|
+
}
|
|
4568
|
+
// Subcommand: codeyam editor task-ontrack
|
|
4569
|
+
// Corrective command when Claude advanced without creating a task.
|
|
4570
|
+
if (argv.step === 'task-ontrack') {
|
|
4571
|
+
const state = readState(root);
|
|
4572
|
+
if (!state?.step) {
|
|
4573
|
+
console.error(chalk.red('No editor state found. Run `codeyam editor 1` to start.'));
|
|
4574
|
+
process.exit(1);
|
|
4575
|
+
}
|
|
4576
|
+
const currentLabel = STEP_LABELS[state.step] || `Step ${state.step}`;
|
|
4577
|
+
const totalSteps = Object.keys(STEP_LABELS).length;
|
|
4578
|
+
const nextLabel = state.step < totalSteps ? STEP_LABELS[state.step + 1] : undefined;
|
|
4579
|
+
console.log();
|
|
4580
|
+
console.log(chalk.bold.yellow('━━━ GETTING BACK ON TRACK ━━━'));
|
|
4581
|
+
console.log();
|
|
4582
|
+
console.log(chalk.yellow('You went off-track by not creating a task. Create the task below to continue.'));
|
|
4583
|
+
console.log();
|
|
4584
|
+
// Print the TASK directive for the current step
|
|
4585
|
+
let taskTitle;
|
|
4586
|
+
if (state.step < totalSteps) {
|
|
4587
|
+
taskTitle = `Complete codeyam editor step ${state.step}: '${currentLabel}' and move on to step ${state.step + 1}: '${nextLabel}'`;
|
|
4588
|
+
}
|
|
4589
|
+
else {
|
|
4590
|
+
taskTitle =
|
|
4591
|
+
'Ask user what to build next and restart codeyam editor workflow';
|
|
4592
|
+
}
|
|
4593
|
+
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
4594
|
+
console.log(chalk.cyan('Mark your current task (if any) as complete.'));
|
|
4595
|
+
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
4596
|
+
console.log();
|
|
4597
|
+
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
4598
|
+
console.log();
|
|
4599
|
+
console.log(chalk.green(`After creating the task, re-run: codeyam editor ${state.step + 1}`));
|
|
4600
|
+
console.log();
|
|
4601
|
+
// Mark task as expected-created so the next step can proceed.
|
|
4602
|
+
// The hook will set taskCreated=true when it sees the actual TaskCreate call.
|
|
4603
|
+
// But we also allow advancement now since task-ontrack itself is the corrective action.
|
|
4604
|
+
const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
|
|
4605
|
+
fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
|
|
4606
|
+
return;
|
|
4607
|
+
}
|
|
3974
4608
|
// Subcommand: codeyam editor analyze-imports
|
|
3975
4609
|
if (argv.step === 'analyze-imports') {
|
|
3976
4610
|
await handleAnalyzeImports();
|
|
@@ -3996,6 +4630,11 @@ const editorCommand = {
|
|
|
3996
4630
|
await handleScenarioCoverage();
|
|
3997
4631
|
return;
|
|
3998
4632
|
}
|
|
4633
|
+
// Subcommand: codeyam editor recapture-stale
|
|
4634
|
+
if (argv.step === 'recapture-stale') {
|
|
4635
|
+
await handleRecaptureStale();
|
|
4636
|
+
return;
|
|
4637
|
+
}
|
|
3999
4638
|
// Subcommand: codeyam editor change <feature>
|
|
4000
4639
|
if (argv.step === 'change') {
|
|
4001
4640
|
handleChange(argv.json || '');
|
|
@@ -4055,18 +4694,13 @@ const editorCommand = {
|
|
|
4055
4694
|
}
|
|
4056
4695
|
else {
|
|
4057
4696
|
const state = readState(root);
|
|
4058
|
-
// Clear prompt file when feature is done (step 16) so the hook
|
|
4059
|
-
// can capture the next feature request from the user.
|
|
4060
|
-
if (state?.step === 16) {
|
|
4061
|
-
clearEditorUserPrompt(root);
|
|
4062
|
-
}
|
|
4063
4697
|
printCycleOverview(root, state);
|
|
4064
4698
|
}
|
|
4065
4699
|
return;
|
|
4066
4700
|
}
|
|
4067
4701
|
const step = argv.step ? parseInt(argv.step, 10) : undefined;
|
|
4068
|
-
if (step != null && (isNaN(step) || step < 1 || step >
|
|
4069
|
-
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-
|
|
4702
|
+
if (step != null && (isNaN(step) || step < 1 || step > 18)) {
|
|
4703
|
+
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-18.`));
|
|
4070
4704
|
process.exit(1);
|
|
4071
4705
|
}
|
|
4072
4706
|
if (step == null) {
|
|
@@ -4090,7 +4724,7 @@ const editorCommand = {
|
|
|
4090
4724
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
4091
4725
|
const { projectSlug } = config;
|
|
4092
4726
|
if (!projectSlug) {
|
|
4093
|
-
errorLog('Missing project slug. Try reinitializing with: codeyam
|
|
4727
|
+
errorLog('Missing project slug. Try reinitializing with: `codeyam editor template`');
|
|
4094
4728
|
return;
|
|
4095
4729
|
}
|
|
4096
4730
|
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
@@ -4487,7 +5121,7 @@ const editorCommand = {
|
|
|
4487
5121
|
// Step 1 is planning-only and may not persist state (no --feature flag).
|
|
4488
5122
|
const skipValidation = step === 2 && argv.feature;
|
|
4489
5123
|
if (!skipValidation) {
|
|
4490
|
-
const stepError = validateStepTransition(step, state?.step ?? null);
|
|
5124
|
+
const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
|
|
4491
5125
|
if (stepError) {
|
|
4492
5126
|
console.error(chalk.red(`Error: ${stepError}`));
|
|
4493
5127
|
process.exit(1);
|
|
@@ -4527,19 +5161,20 @@ const editorCommand = {
|
|
|
4527
5161
|
case 13:
|
|
4528
5162
|
case 14:
|
|
4529
5163
|
case 15:
|
|
4530
|
-
case 16:
|
|
5164
|
+
case 16:
|
|
5165
|
+
case 17:
|
|
5166
|
+
case 18: {
|
|
4531
5167
|
const feature = argv.feature || state?.feature;
|
|
4532
5168
|
if (!feature) {
|
|
4533
5169
|
console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
|
|
4534
5170
|
process.exit(1);
|
|
4535
5171
|
}
|
|
4536
|
-
// Hard gate: steps
|
|
4537
|
-
if (step >=
|
|
5172
|
+
// Hard gate: steps 10+ require audit to have passed
|
|
5173
|
+
if (step >= 10) {
|
|
4538
5174
|
const auditOk = await checkAuditGate();
|
|
4539
5175
|
if (!auditOk) {
|
|
4540
5176
|
// checkAuditGate() already printed specific failure details above
|
|
4541
5177
|
console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
|
|
4542
|
-
console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
|
|
4543
5178
|
process.exit(1);
|
|
4544
5179
|
}
|
|
4545
5180
|
}
|
|
@@ -4558,6 +5193,8 @@ const editorCommand = {
|
|
|
4558
5193
|
14: printStep14,
|
|
4559
5194
|
15: printStep15,
|
|
4560
5195
|
16: printStep16,
|
|
5196
|
+
17: printStep17,
|
|
5197
|
+
18: printStep18,
|
|
4561
5198
|
};
|
|
4562
5199
|
stepFns[step](root, feature);
|
|
4563
5200
|
break;
|