@codeyam/codeyam-cli 0.1.19 → 0.1.21
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/commands/editor.js +412 -96
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +895 -16
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +20 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarios.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__/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 +183 -13
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +36 -4
- package/codeyam-cli/src/utils/editorScenarios.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/screenshotHash.js +26 -0
- package/codeyam-cli/src/utils/screenshotHash.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +28 -1
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +22 -2
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +57 -1
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -1
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +15 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
- 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-KTQuL0aj.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-BxUQigda.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-Cd-ufawF.js → index-C91yWWCI.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-CzeBGOto.js → init-Dkas-RUS.js} +1 -1
- 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/scripts/journalCapture.ts +17 -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 +61 -13
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/editor-step-hook.py +21 -0
- 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)-CGzKlIHg.js +0 -58
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/globals-Yn9W3zp3.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-2ef99f38.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-BPmOG9bE.js +0 -13
- package/codeyam-cli/src/webserver/build/server/assets/server-build-Dht7CKXY.js +0 -552
|
@@ -21,6 +21,7 @@ import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } f
|
|
|
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);
|
|
@@ -121,6 +122,13 @@ function writeState(root, state) {
|
|
|
121
122
|
const dir = path.dirname(statePath);
|
|
122
123
|
fs.mkdirSync(dir, { recursive: true });
|
|
123
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
|
+
}
|
|
124
132
|
}
|
|
125
133
|
/**
|
|
126
134
|
* Clear the editor state (for starting a new feature).
|
|
@@ -203,6 +211,34 @@ function checkbox(text) {
|
|
|
203
211
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
204
212
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
205
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
|
+
}
|
|
206
242
|
/**
|
|
207
243
|
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
208
244
|
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
@@ -248,11 +284,15 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
248
284
|
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
249
285
|
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
250
286
|
}
|
|
251
|
-
|
|
252
|
-
console.log(chalk.
|
|
253
|
-
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();
|
|
254
294
|
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
255
|
-
checkbox('After
|
|
295
|
+
checkbox('After registration, check the response for `clientErrors`');
|
|
256
296
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
257
297
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
258
298
|
}
|
|
@@ -353,6 +393,10 @@ function printComponentCaptureInstructions() {
|
|
|
353
393
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
354
394
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
355
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'));
|
|
356
400
|
}
|
|
357
401
|
/**
|
|
358
402
|
* Print a section header.
|
|
@@ -393,9 +437,31 @@ function printProgressTracker(current) {
|
|
|
393
437
|
*
|
|
394
438
|
* Options:
|
|
395
439
|
* - confirm: true → step requires user confirmation before proceeding (steps 1, 3, 11)
|
|
440
|
+
* - feature: string → feature name for task directive
|
|
396
441
|
*/
|
|
397
442
|
function stopGate(current, opts) {
|
|
398
|
-
|
|
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
|
+
}
|
|
399
465
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
400
466
|
console.log();
|
|
401
467
|
console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
|
|
@@ -404,20 +470,12 @@ function stopGate(current, opts) {
|
|
|
404
470
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
405
471
|
}
|
|
406
472
|
console.log();
|
|
407
|
-
|
|
408
|
-
printProgressTracker(current);
|
|
409
|
-
console.log();
|
|
410
|
-
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
411
|
-
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
412
|
-
console.log();
|
|
413
|
-
if (current < 18) {
|
|
473
|
+
if (current < totalSteps) {
|
|
414
474
|
console.log(chalk.green('When done, run: ') +
|
|
415
475
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
416
476
|
}
|
|
417
477
|
else {
|
|
418
|
-
console.log(chalk.green('Feature complete!
|
|
419
|
-
chalk.bold('codeyam editor steps') +
|
|
420
|
-
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'));
|
|
421
479
|
}
|
|
422
480
|
console.log();
|
|
423
481
|
}
|
|
@@ -785,9 +843,7 @@ function printCycleOverview(root, state) {
|
|
|
785
843
|
console.log();
|
|
786
844
|
console.log(chalk.green('Continue with: ') +
|
|
787
845
|
chalk.bold(`codeyam editor ${state.step}`));
|
|
788
|
-
console.log(chalk.dim('Or run ')
|
|
789
|
-
chalk.bold('codeyam editor 1') +
|
|
790
|
-
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)'));
|
|
791
847
|
console.log();
|
|
792
848
|
console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
|
|
793
849
|
chalk.bold('codeyam editor change'));
|
|
@@ -815,7 +871,8 @@ function printCycleOverview(root, state) {
|
|
|
815
871
|
console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
|
|
816
872
|
console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
|
|
817
873
|
console.log();
|
|
818
|
-
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)');
|
|
819
876
|
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
820
877
|
}
|
|
821
878
|
console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
|
|
@@ -894,12 +951,6 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
894
951
|
console.log();
|
|
895
952
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
896
953
|
console.log();
|
|
897
|
-
console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
|
|
898
|
-
printProgressTracker(1);
|
|
899
|
-
console.log();
|
|
900
|
-
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
901
|
-
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
902
|
-
console.log();
|
|
903
954
|
console.log(chalk.green('When done, run: ') +
|
|
904
955
|
chalk.bold('codeyam editor 2 --feature "Feature Name"'));
|
|
905
956
|
console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
|
|
@@ -927,13 +978,6 @@ function printStep2(root, feature) {
|
|
|
927
978
|
if (isResuming) {
|
|
928
979
|
printResumptionHeader(2);
|
|
929
980
|
}
|
|
930
|
-
console.log(chalk.bold.red('━━━ MANDATORY: CREATE TASK NOW ━━━'));
|
|
931
|
-
console.log();
|
|
932
|
-
console.log(chalk.red(`Run TaskCreate with title: "Use \`codeyam editor\` to work on ${feature}"`));
|
|
933
|
-
console.log(chalk.red('DO NOT read files, scaffold, write code, or do ANYTHING else until this task exists.'));
|
|
934
|
-
console.log(chalk.red('This is not optional. Create the task, then continue below.'));
|
|
935
|
-
console.log(chalk.bold.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
936
|
-
console.log();
|
|
937
981
|
console.log('Get the project ready to build.');
|
|
938
982
|
console.log();
|
|
939
983
|
// If no project exists yet, include scaffolding instructions first
|
|
@@ -963,6 +1007,7 @@ function printStep2(root, feature) {
|
|
|
963
1007
|
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
964
1008
|
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
965
1009
|
console.log();
|
|
1010
|
+
printDataStructureInstructions();
|
|
966
1011
|
}
|
|
967
1012
|
else {
|
|
968
1013
|
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
@@ -977,6 +1022,7 @@ function printStep2(root, feature) {
|
|
|
977
1022
|
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
978
1023
|
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
979
1024
|
console.log();
|
|
1025
|
+
printDataStructureInstructions();
|
|
980
1026
|
}
|
|
981
1027
|
stopGate(2);
|
|
982
1028
|
}
|
|
@@ -1012,6 +1058,13 @@ function printStep3(root, feature) {
|
|
|
1012
1058
|
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
1013
1059
|
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
1014
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'));
|
|
1015
1068
|
}
|
|
1016
1069
|
checkbox('Verify the dev server shows the changes');
|
|
1017
1070
|
checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
|
|
@@ -1051,6 +1104,8 @@ function printStep3(root, feature) {
|
|
|
1051
1104
|
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
1052
1105
|
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
|
|
1053
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.'));
|
|
1054
1109
|
console.log();
|
|
1055
1110
|
stopGate(3);
|
|
1056
1111
|
}
|
|
@@ -1147,6 +1202,8 @@ function printStep5(root, feature) {
|
|
|
1147
1202
|
printDimensionGuidance(dim, dimNames);
|
|
1148
1203
|
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
1149
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.'));
|
|
1150
1207
|
console.log();
|
|
1151
1208
|
console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
|
|
1152
1209
|
checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
|
|
@@ -1300,7 +1357,10 @@ function printStep9(root, feature) {
|
|
|
1300
1357
|
console.log();
|
|
1301
1358
|
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1302
1359
|
console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
|
|
1303
|
-
console.log(chalk.dim('
|
|
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.'));
|
|
1304
1364
|
console.log();
|
|
1305
1365
|
stopGate(9);
|
|
1306
1366
|
}
|
|
@@ -1335,6 +1395,7 @@ function printStep10(root, feature) {
|
|
|
1335
1395
|
console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
|
|
1336
1396
|
console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
|
|
1337
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'));
|
|
1338
1399
|
console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
|
|
1339
1400
|
console.log();
|
|
1340
1401
|
console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
|
|
@@ -1549,7 +1610,7 @@ function printStep15(root, feature) {
|
|
|
1549
1610
|
console.log(chalk.bold('Checklist:'));
|
|
1550
1611
|
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1551
1612
|
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1552
|
-
console.log(chalk.dim(' The
|
|
1613
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1553
1614
|
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1554
1615
|
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1555
1616
|
checkbox('Report test count and audit status (one line)');
|
|
@@ -1789,7 +1850,7 @@ function printMigrateStep4(root) {
|
|
|
1789
1850
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1790
1851
|
checkbox('If any issues: fix the scenario data, re-register affected scenarios');
|
|
1791
1852
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1792
|
-
console.log(chalk.dim(' The
|
|
1853
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1793
1854
|
console.log();
|
|
1794
1855
|
console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
|
|
1795
1856
|
migrationStopGate(4, pageName, pageIndex, totalPages);
|
|
@@ -1956,7 +2017,7 @@ function printMigrateStep10(root) {
|
|
|
1956
2017
|
console.log();
|
|
1957
2018
|
console.log(chalk.bold('Checklist:'));
|
|
1958
2019
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1959
|
-
console.log(chalk.dim(' The
|
|
2020
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1960
2021
|
checkbox('Write a 1-2 sentence summary of what was migrated');
|
|
1961
2022
|
console.log();
|
|
1962
2023
|
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
@@ -2253,15 +2314,13 @@ function printStep18(root, feature) {
|
|
|
2253
2314
|
console.log(chalk.dim(' If the user says yes, run: `git push`'));
|
|
2254
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"'));
|
|
2255
2316
|
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
2256
|
-
|
|
2257
|
-
checkbox('Create a new task with EXACTLY this title: "Ask the user what to build next, then run `codeyam editor 1` to start the next feature"');
|
|
2258
|
-
console.log(chalk.dim(' There must ALWAYS be an active task. Never leave the task list empty.'));
|
|
2259
|
-
console.log(chalk.dim(' This task reminds you to use the editor workflow for the next feature.'));
|
|
2317
|
+
console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
|
|
2260
2318
|
console.log();
|
|
2261
2319
|
console.log(chalk.bold.yellow('IMPORTANT: After this step, the current feature is DONE.'));
|
|
2262
2320
|
console.log(chalk.yellow(' Any new user request — even if it sounds related — is a NEW feature.'));
|
|
2263
2321
|
console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
|
|
2264
|
-
console.log(chalk.yellow('
|
|
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.'));
|
|
2265
2324
|
console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
|
|
2266
2325
|
stopGate(18, { confirm: true });
|
|
2267
2326
|
}
|
|
@@ -2337,25 +2396,54 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2337
2396
|
// Non-fatal — scenario file paths just won't be included
|
|
2338
2397
|
}
|
|
2339
2398
|
const progress = new ProgressReporter();
|
|
2340
|
-
//
|
|
2341
|
-
//
|
|
2342
|
-
|
|
2343
|
-
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;
|
|
2344
2402
|
try {
|
|
2345
|
-
await
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
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
|
+
}
|
|
2351
2422
|
}
|
|
2352
|
-
catch
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
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');
|
|
2357
2446
|
}
|
|
2358
|
-
progress.succeed('Analysis complete');
|
|
2359
2447
|
// Load entities WITH metadata from database
|
|
2360
2448
|
progress.start('Loading entity metadata...');
|
|
2361
2449
|
await initializeEnvironment();
|
|
@@ -2705,11 +2793,15 @@ async function handleRegister(jsonArg) {
|
|
|
2705
2793
|
}
|
|
2706
2794
|
// Normalize to array for uniform handling
|
|
2707
2795
|
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
2796
|
+
const root = getProjectRoot();
|
|
2708
2797
|
const port = getServerPort();
|
|
2709
2798
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
2710
2799
|
const isBatch = items.length > 1;
|
|
2711
2800
|
let succeeded = 0;
|
|
2712
2801
|
let failed = 0;
|
|
2802
|
+
let warnings = 0;
|
|
2803
|
+
const issues = [];
|
|
2804
|
+
const screenshotEntries = [];
|
|
2713
2805
|
for (let i = 0; i < items.length; i++) {
|
|
2714
2806
|
const body = items[i];
|
|
2715
2807
|
const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
|
|
@@ -2737,6 +2829,12 @@ async function handleRegister(jsonArg) {
|
|
|
2737
2829
|
else {
|
|
2738
2830
|
parts.push(`errors=0`);
|
|
2739
2831
|
}
|
|
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
|
+
}
|
|
2740
2838
|
if (data.seedResult) {
|
|
2741
2839
|
if (data.seedResult.success) {
|
|
2742
2840
|
parts.push(`seed=ok`);
|
|
@@ -2772,12 +2870,29 @@ async function handleRegister(jsonArg) {
|
|
|
2772
2870
|
}
|
|
2773
2871
|
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2774
2872
|
}
|
|
2873
|
+
const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
|
|
2775
2874
|
if (!res.ok) {
|
|
2776
2875
|
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2777
2876
|
failed++;
|
|
2778
2877
|
}
|
|
2779
2878
|
else {
|
|
2879
|
+
if (resultIssues.length > 0)
|
|
2880
|
+
warnings++;
|
|
2780
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}`);
|
|
2781
2896
|
}
|
|
2782
2897
|
}
|
|
2783
2898
|
catch (error) {
|
|
@@ -2788,10 +2903,50 @@ async function handleRegister(jsonArg) {
|
|
|
2788
2903
|
failed++;
|
|
2789
2904
|
}
|
|
2790
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
|
+
}
|
|
2791
2933
|
if (isBatch) {
|
|
2792
|
-
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
|
+
}
|
|
2793
2948
|
}
|
|
2794
|
-
if (failed > 0) {
|
|
2949
|
+
if (failed > 0 || warnings > 0) {
|
|
2795
2950
|
process.exit(1);
|
|
2796
2951
|
}
|
|
2797
2952
|
}
|
|
@@ -3003,6 +3158,8 @@ function handleChange(feature) {
|
|
|
3003
3158
|
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
3004
3159
|
printDimensionGuidance(dim, dimNames);
|
|
3005
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.'));
|
|
3006
3163
|
console.log();
|
|
3007
3164
|
console.log(chalk.bold('0. Close the results panel:'));
|
|
3008
3165
|
checkbox(`Hide results: \`codeyam editor hide-results\``);
|
|
@@ -3104,16 +3261,33 @@ async function checkAuditGate() {
|
|
|
3104
3261
|
return true; // Server unreachable — don't block
|
|
3105
3262
|
if (data.summary?.allPassing === true)
|
|
3106
3263
|
return true;
|
|
3107
|
-
//
|
|
3108
|
-
|
|
3109
|
-
|
|
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)) {
|
|
3110
3273
|
try {
|
|
3111
|
-
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
|
+
}
|
|
3112
3286
|
}
|
|
3113
3287
|
catch {
|
|
3114
|
-
|
|
3288
|
+
// Fall through
|
|
3115
3289
|
}
|
|
3116
|
-
// Re-check after
|
|
3290
|
+
// Re-check after backfill
|
|
3117
3291
|
const retry = await fetchAuditResult();
|
|
3118
3292
|
if (retry?.summary?.allPassing === true)
|
|
3119
3293
|
return true;
|
|
@@ -3137,6 +3311,8 @@ function printAuditGateFailures(data) {
|
|
|
3137
3311
|
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
3138
3312
|
for (const c of missing) {
|
|
3139
3313
|
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3314
|
+
if (c.hint)
|
|
3315
|
+
issues.push(` Fix: ${c.hint}`);
|
|
3140
3316
|
}
|
|
3141
3317
|
}
|
|
3142
3318
|
if (s.componentsWithErrors > 0) {
|
|
@@ -3150,7 +3326,15 @@ function printAuditGateFailures(data) {
|
|
|
3150
3326
|
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
3151
3327
|
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
3152
3328
|
for (const f of missing) {
|
|
3153
|
-
|
|
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
|
+
}
|
|
3154
3338
|
}
|
|
3155
3339
|
}
|
|
3156
3340
|
if (s.functionsFailing > 0) {
|
|
@@ -3188,6 +3372,13 @@ function printAuditGateFailures(data) {
|
|
|
3188
3372
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3189
3373
|
}
|
|
3190
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
|
+
}
|
|
3191
3382
|
if (s.miscategorizedScenarios > 0)
|
|
3192
3383
|
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3193
3384
|
if (s.scenariosNeedingRecapture > 0)
|
|
@@ -3232,30 +3423,44 @@ async function handleAudit() {
|
|
|
3232
3423
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3233
3424
|
process.exit(1);
|
|
3234
3425
|
}
|
|
3235
|
-
//
|
|
3236
|
-
//
|
|
3237
|
-
//
|
|
3238
|
-
//
|
|
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.
|
|
3239
3430
|
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3431
|
+
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
3240
3432
|
let autoRemediationFailed = false;
|
|
3241
|
-
if (incompleteBeforeFix.length > 0) {
|
|
3242
|
-
|
|
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
|
|
3243
3437
|
try {
|
|
3244
|
-
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
|
+
}
|
|
3245
3450
|
}
|
|
3246
3451
|
catch {
|
|
3247
|
-
// Fall through —
|
|
3452
|
+
// Fall through — re-fetch will show remaining issues
|
|
3248
3453
|
}
|
|
3249
|
-
// Re-fetch audit results after the
|
|
3454
|
+
// Re-fetch audit results after the backfill
|
|
3250
3455
|
data = await fetchAuditResult();
|
|
3251
3456
|
if (!data) {
|
|
3252
|
-
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.'));
|
|
3253
3458
|
process.exit(1);
|
|
3254
3459
|
}
|
|
3255
|
-
// If
|
|
3256
|
-
// 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
|
|
3257
3461
|
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3258
|
-
|
|
3462
|
+
const unassociatedAfterFix = data.unassociatedScenarios || [];
|
|
3463
|
+
if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
|
|
3259
3464
|
autoRemediationFailed = true;
|
|
3260
3465
|
}
|
|
3261
3466
|
}
|
|
@@ -3289,6 +3494,9 @@ async function handleAudit() {
|
|
|
3289
3494
|
}
|
|
3290
3495
|
else {
|
|
3291
3496
|
detail = chalk.red(' — no scenarios registered');
|
|
3497
|
+
if (c.hint) {
|
|
3498
|
+
detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
|
|
3499
|
+
}
|
|
3292
3500
|
}
|
|
3293
3501
|
// Show file path for failing components always, for OK only when name is ambiguous
|
|
3294
3502
|
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
@@ -3332,9 +3540,16 @@ async function handleAudit() {
|
|
|
3332
3540
|
break;
|
|
3333
3541
|
case 'missing':
|
|
3334
3542
|
default:
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
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
|
+
}
|
|
3338
3553
|
break;
|
|
3339
3554
|
}
|
|
3340
3555
|
console.log(` ${icon} ${f.name}${detail}`);
|
|
@@ -3351,23 +3566,70 @@ async function handleAudit() {
|
|
|
3351
3566
|
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
3352
3567
|
console.log();
|
|
3353
3568
|
}
|
|
3354
|
-
// Incomplete entities (scenarios without analyses)
|
|
3355
|
-
|
|
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 || [];
|
|
3356
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
|
+
}
|
|
3605
|
+
if (incompleteEntities.length > 0) {
|
|
3606
|
+
const { formatIncompleteEntityGuidance } = await import('../utils/editorAudit.js');
|
|
3357
3607
|
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3358
3608
|
for (const e of incompleteEntities) {
|
|
3359
|
-
|
|
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
|
+
}
|
|
3360
3626
|
}
|
|
3361
3627
|
if (autoRemediationFailed) {
|
|
3362
|
-
console.log(chalk.
|
|
3363
|
-
console.log(chalk.
|
|
3364
|
-
console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
|
|
3365
|
-
console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
|
|
3366
|
-
console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
|
|
3367
|
-
console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
|
|
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.'));
|
|
3368
3630
|
}
|
|
3369
3631
|
else {
|
|
3370
|
-
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.'));
|
|
3371
3633
|
}
|
|
3372
3634
|
console.log();
|
|
3373
3635
|
}
|
|
@@ -3444,6 +3706,9 @@ async function handleAudit() {
|
|
|
3444
3706
|
if (summary.incompleteEntities > 0) {
|
|
3445
3707
|
parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
|
|
3446
3708
|
}
|
|
3709
|
+
if (summary.unassociatedScenarios > 0) {
|
|
3710
|
+
parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
|
|
3711
|
+
}
|
|
3447
3712
|
if (summary.miscategorizedScenarios > 0) {
|
|
3448
3713
|
parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
|
|
3449
3714
|
}
|
|
@@ -4183,8 +4448,8 @@ const editorCommand = {
|
|
|
4183
4448
|
describe: 'Editor mode guided workflow',
|
|
4184
4449
|
builder: (yargs) => {
|
|
4185
4450
|
const stepDescription = IS_INTERNAL_BUILD
|
|
4186
|
-
? '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)'
|
|
4187
|
-
: '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)';
|
|
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)';
|
|
4188
4453
|
let builder = yargs
|
|
4189
4454
|
.positional('step', {
|
|
4190
4455
|
type: 'string',
|
|
@@ -4245,6 +4510,18 @@ const editorCommand = {
|
|
|
4245
4510
|
// API subcommands: preview, show-results, hide-results, commit,
|
|
4246
4511
|
// journal, journal-update, dev-server, client-errors
|
|
4247
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
|
+
}
|
|
4248
4525
|
const port = getServerPort();
|
|
4249
4526
|
try {
|
|
4250
4527
|
const request = buildEditorApiRequest(argv.step, argv.json || undefined);
|
|
@@ -4288,6 +4565,46 @@ const editorCommand = {
|
|
|
4288
4565
|
await handleGlossaryAdd(argv.json || '');
|
|
4289
4566
|
return;
|
|
4290
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
|
+
}
|
|
4291
4608
|
// Subcommand: codeyam editor analyze-imports
|
|
4292
4609
|
if (argv.step === 'analyze-imports') {
|
|
4293
4610
|
await handleAnalyzeImports();
|
|
@@ -4804,7 +5121,7 @@ const editorCommand = {
|
|
|
4804
5121
|
// Step 1 is planning-only and may not persist state (no --feature flag).
|
|
4805
5122
|
const skipValidation = step === 2 && argv.feature;
|
|
4806
5123
|
if (!skipValidation) {
|
|
4807
|
-
const stepError = validateStepTransition(step, state?.step ?? null);
|
|
5124
|
+
const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
|
|
4808
5125
|
if (stepError) {
|
|
4809
5126
|
console.error(chalk.red(`Error: ${stepError}`));
|
|
4810
5127
|
process.exit(1);
|
|
@@ -4858,7 +5175,6 @@ const editorCommand = {
|
|
|
4858
5175
|
if (!auditOk) {
|
|
4859
5176
|
// checkAuditGate() already printed specific failure details above
|
|
4860
5177
|
console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
|
|
4861
|
-
console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
|
|
4862
5178
|
process.exit(1);
|
|
4863
5179
|
}
|
|
4864
5180
|
}
|