@codeyam/codeyam-cli 0.1.0-staging.30dc541 → 0.1.0-staging.39719f5
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 +36 -9
- package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.ts +10 -3
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +16 -6
- package/analyzer-template/packages/analyze/index.ts +4 -1
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +28 -2
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -36
- 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/analyze/trackEntityCircularDependencies.ts +21 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
- 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/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +235 -58
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +170 -26
- 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/__tests__/editor.auditNoAutoAnalysis.test.js +63 -0
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
- package/codeyam-cli/src/commands/editor.js +775 -125
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +1248 -16
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
- package/codeyam-cli/src/utils/__tests__/editorPreview.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__/entityChangeStatus.test.js +33 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
- package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
- 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/__tests__/testRunner.test.js +217 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
- package/codeyam-cli/src/utils/analysisRunner.js +36 -7
- package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +11 -1
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +280 -15
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorPreview.js +5 -3
- package/codeyam-cli/src/utils/editorPreview.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/entityChangeStatus.server.js +16 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
- package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
- package/codeyam-cli/src/utils/queue/job.js +26 -5
- 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/utils/testRunner.js +199 -1
- package/codeyam-cli/src/utils/testRunner.js.map +1 -1
- 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 +57 -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/MiniClaudeChat-CQENLSrF.js +36 -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-Coe5NhbS.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-aIHKLB-m.js +96 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-CluPkvXJ.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Blfy9UlN.js → entity._sha._-ByHz6rAQ.js} +13 -12
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js → entity._sha.scenarios._scenarioId.dev-CmLO432x.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js → entity._sha.scenarios._scenarioId.fullscreen-Bz9sCUF_.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-oyPmV37k.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/manifest-bcbb3d49.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{root-BxUQigda.js → root-D2_tktnk.js} +26 -13
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-DjF-soOH.js +16 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-Cd-ufawF.js → index-nAvHGWbz.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-CzeBGOto.js → init-XhpIt-OT.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-DVwiibFu.js +644 -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 +15 -0
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
- 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 +69 -15
- 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 +27 -10
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
- package/packages/analyze/index.js +1 -1
- package/packages/analyze/index.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.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/analyze/trackEntityCircularDependencies.js +14 -0
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.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/analyze/src/lib/files/analyzeNextRoute.js +5 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +116 -28
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +139 -24
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.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
|
}
|
|
@@ -317,7 +357,7 @@ function printExtractionPlanInstructions() {
|
|
|
317
357
|
*/
|
|
318
358
|
function printComponentCaptureInstructions() {
|
|
319
359
|
checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
320
|
-
console.log(chalk.dim(' This creates app/
|
|
360
|
+
console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
|
|
321
361
|
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
322
362
|
checkbox('For each visual component:');
|
|
323
363
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
@@ -329,8 +369,8 @@ function printComponentCaptureInstructions() {
|
|
|
329
369
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
330
370
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
331
371
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
332
|
-
console.log(chalk.dim(' Remix: app/routes/
|
|
333
|
-
console.log(chalk.dim(' Next.js: app/
|
|
372
|
+
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
373
|
+
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
334
374
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
335
375
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
336
376
|
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
@@ -343,9 +383,9 @@ function printComponentCaptureInstructions() {
|
|
|
343
383
|
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
344
384
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
345
385
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
346
|
-
console.log(chalk.dim(` "url":"/
|
|
386
|
+
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
347
387
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
348
|
-
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/
|
|
388
|
+
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
|
|
349
389
|
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
350
390
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
351
391
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
@@ -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":"/isolated-components/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,33 @@ 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('Before creating the task below, delete ALL existing tasks:'));
|
|
452
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
453
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
454
|
+
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
455
|
+
console.log();
|
|
456
|
+
let taskTitle;
|
|
457
|
+
if (current < totalSteps) {
|
|
458
|
+
taskTitle = `Complete codeyam editor step ${current}: '${currentLabel}' and move on to step ${current + 1}: '${nextLabel}'`;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
taskTitle =
|
|
462
|
+
'Ask user what to build next and restart codeyam editor workflow';
|
|
463
|
+
}
|
|
464
|
+
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
465
|
+
console.log();
|
|
466
|
+
}
|
|
399
467
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
400
468
|
console.log();
|
|
401
469
|
console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
|
|
@@ -404,20 +472,12 @@ function stopGate(current, opts) {
|
|
|
404
472
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
405
473
|
}
|
|
406
474
|
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) {
|
|
475
|
+
if (current < totalSteps) {
|
|
414
476
|
console.log(chalk.green('When done, run: ') +
|
|
415
477
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
416
478
|
}
|
|
417
479
|
else {
|
|
418
|
-
console.log(chalk.green('Feature complete!
|
|
419
|
-
chalk.bold('codeyam editor steps') +
|
|
420
|
-
chalk.green(' to start the next feature'));
|
|
480
|
+
console.log(chalk.green('Feature complete! Ask the user what to build next, then run: ') + chalk.bold('codeyam editor 1'));
|
|
421
481
|
}
|
|
422
482
|
console.log();
|
|
423
483
|
}
|
|
@@ -785,9 +845,7 @@ function printCycleOverview(root, state) {
|
|
|
785
845
|
console.log();
|
|
786
846
|
console.log(chalk.green('Continue with: ') +
|
|
787
847
|
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'));
|
|
848
|
+
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
849
|
console.log();
|
|
792
850
|
console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
|
|
793
851
|
chalk.bold('codeyam editor change'));
|
|
@@ -815,7 +873,8 @@ function printCycleOverview(root, state) {
|
|
|
815
873
|
console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
|
|
816
874
|
console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
|
|
817
875
|
console.log();
|
|
818
|
-
console.log(chalk.green('Start now: ') +
|
|
876
|
+
console.log(chalk.green('Start now: ') +
|
|
877
|
+
'Ask the user what they want to build, then run `codeyam editor 1` (never expose this command to the user)');
|
|
819
878
|
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
820
879
|
}
|
|
821
880
|
console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
|
|
@@ -894,12 +953,6 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
894
953
|
console.log();
|
|
895
954
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
896
955
|
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
956
|
console.log(chalk.green('When done, run: ') +
|
|
904
957
|
chalk.bold('codeyam editor 2 --feature "Feature Name"'));
|
|
905
958
|
console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
|
|
@@ -927,13 +980,6 @@ function printStep2(root, feature) {
|
|
|
927
980
|
if (isResuming) {
|
|
928
981
|
printResumptionHeader(2);
|
|
929
982
|
}
|
|
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
983
|
console.log('Get the project ready to build.');
|
|
938
984
|
console.log();
|
|
939
985
|
// If no project exists yet, include scaffolding instructions first
|
|
@@ -963,6 +1009,7 @@ function printStep2(root, feature) {
|
|
|
963
1009
|
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
964
1010
|
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
965
1011
|
console.log();
|
|
1012
|
+
printDataStructureInstructions();
|
|
966
1013
|
}
|
|
967
1014
|
else {
|
|
968
1015
|
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
@@ -977,6 +1024,7 @@ function printStep2(root, feature) {
|
|
|
977
1024
|
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
978
1025
|
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
979
1026
|
console.log();
|
|
1027
|
+
printDataStructureInstructions();
|
|
980
1028
|
}
|
|
981
1029
|
stopGate(2);
|
|
982
1030
|
}
|
|
@@ -1012,6 +1060,13 @@ function printStep3(root, feature) {
|
|
|
1012
1060
|
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
1013
1061
|
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
1014
1062
|
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
1063
|
+
console.log();
|
|
1064
|
+
console.log(chalk.bold.cyan('Make seed data visually rich:'));
|
|
1065
|
+
console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
|
|
1066
|
+
console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
|
|
1067
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1068
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1069
|
+
console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
|
|
1015
1070
|
}
|
|
1016
1071
|
checkbox('Verify the dev server shows the changes');
|
|
1017
1072
|
checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
|
|
@@ -1051,6 +1106,8 @@ function printStep3(root, feature) {
|
|
|
1051
1106
|
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
1052
1107
|
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
|
|
1053
1108
|
printDimensionGuidance(dim, dimNames);
|
|
1109
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
1110
|
+
console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
|
|
1054
1111
|
console.log();
|
|
1055
1112
|
stopGate(3);
|
|
1056
1113
|
}
|
|
@@ -1147,6 +1204,8 @@ function printStep5(root, feature) {
|
|
|
1147
1204
|
printDimensionGuidance(dim, dimNames);
|
|
1148
1205
|
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
1149
1206
|
console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
|
|
1207
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
1208
|
+
console.log(chalk.red(' The preview only updates when you explicitly call `codeyam editor preview`. Verify, then describe.'));
|
|
1150
1209
|
console.log();
|
|
1151
1210
|
console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
|
|
1152
1211
|
checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
|
|
@@ -1300,7 +1359,13 @@ function printStep9(root, feature) {
|
|
|
1300
1359
|
console.log();
|
|
1301
1360
|
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1302
1361
|
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('
|
|
1362
|
+
console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
|
|
1363
|
+
console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
|
|
1364
|
+
console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
|
|
1365
|
+
console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
|
|
1366
|
+
console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
|
|
1367
|
+
console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
|
|
1368
|
+
console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
|
|
1304
1369
|
console.log();
|
|
1305
1370
|
stopGate(9);
|
|
1306
1371
|
}
|
|
@@ -1335,6 +1400,7 @@ function printStep10(root, feature) {
|
|
|
1335
1400
|
console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
|
|
1336
1401
|
console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
|
|
1337
1402
|
console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
|
|
1403
|
+
console.log(chalk.cyan(' • Use real image URLs (Unsplash, Pravatar) — not broken placeholders or empty strings'));
|
|
1338
1404
|
console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
|
|
1339
1405
|
console.log();
|
|
1340
1406
|
console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
|
|
@@ -1549,7 +1615,7 @@ function printStep15(root, feature) {
|
|
|
1549
1615
|
console.log(chalk.bold('Checklist:'));
|
|
1550
1616
|
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1551
1617
|
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1552
|
-
console.log(chalk.dim(' The
|
|
1618
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1553
1619
|
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1554
1620
|
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1555
1621
|
checkbox('Report test count and audit status (one line)');
|
|
@@ -1789,7 +1855,7 @@ function printMigrateStep4(root) {
|
|
|
1789
1855
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1790
1856
|
checkbox('If any issues: fix the scenario data, re-register affected scenarios');
|
|
1791
1857
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1792
|
-
console.log(chalk.dim(' The
|
|
1858
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1793
1859
|
console.log();
|
|
1794
1860
|
console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
|
|
1795
1861
|
migrationStopGate(4, pageName, pageIndex, totalPages);
|
|
@@ -1901,7 +1967,7 @@ function printMigrateStep8(root) {
|
|
|
1901
1967
|
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1902
1968
|
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1903
1969
|
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1904
|
-
checkbox('Find all scenarios that use the real page route (not /
|
|
1970
|
+
checkbox('Find all scenarios that use the real page route (not /isolated-components/ paths)');
|
|
1905
1971
|
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1906
1972
|
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1907
1973
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
@@ -1956,7 +2022,7 @@ function printMigrateStep10(root) {
|
|
|
1956
2022
|
console.log();
|
|
1957
2023
|
console.log(chalk.bold('Checklist:'));
|
|
1958
2024
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1959
|
-
console.log(chalk.dim(' The
|
|
2025
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1960
2026
|
checkbox('Write a 1-2 sentence summary of what was migrated');
|
|
1961
2027
|
console.log();
|
|
1962
2028
|
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
@@ -2253,15 +2319,13 @@ function printStep18(root, feature) {
|
|
|
2253
2319
|
console.log(chalk.dim(' If the user says yes, run: `git push`'));
|
|
2254
2320
|
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
2321
|
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.'));
|
|
2322
|
+
console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
|
|
2260
2323
|
console.log();
|
|
2261
2324
|
console.log(chalk.bold.yellow('IMPORTANT: After this step, the current feature is DONE.'));
|
|
2262
2325
|
console.log(chalk.yellow(' Any new user request — even if it sounds related — is a NEW feature.'));
|
|
2263
2326
|
console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
|
|
2264
|
-
console.log(chalk.yellow('
|
|
2327
|
+
console.log(chalk.yellow(' Ask the user what they want to build. Once they tell you, run `codeyam editor 1` yourself.'));
|
|
2328
|
+
console.log(chalk.yellow(' NEVER tell the user to run `codeyam editor` commands — these are internal. Just ask what to build.'));
|
|
2265
2329
|
console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
|
|
2266
2330
|
stopGate(18, { confirm: true });
|
|
2267
2331
|
}
|
|
@@ -2337,25 +2401,132 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2337
2401
|
// Non-fatal — scenario file paths just won't be included
|
|
2338
2402
|
}
|
|
2339
2403
|
const progress = new ProgressReporter();
|
|
2340
|
-
//
|
|
2404
|
+
// Filter to only files that actually need analysis — avoids re-analyzing
|
|
2405
|
+
// ~120 files when only 2 are incomplete.
|
|
2406
|
+
let targetFilePaths = filePaths;
|
|
2407
|
+
try {
|
|
2408
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2409
|
+
const db = getDatabase();
|
|
2410
|
+
const { requireBranchAndProject: rbp } = await import('../utils/database.js');
|
|
2411
|
+
const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
|
|
2412
|
+
const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
|
|
2413
|
+
const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
|
|
2414
|
+
if (incomplete.length < filePaths.length && incomplete.length > 0) {
|
|
2415
|
+
if (!options.silent) {
|
|
2416
|
+
console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
|
|
2417
|
+
}
|
|
2418
|
+
targetFilePaths = incomplete;
|
|
2419
|
+
}
|
|
2420
|
+
else if (incomplete.length === 0) {
|
|
2421
|
+
if (!options.silent) {
|
|
2422
|
+
console.log(chalk.green('All entities already have analyses — nothing to do.'));
|
|
2423
|
+
}
|
|
2424
|
+
// Still need to run the import graph + backfill below
|
|
2425
|
+
targetFilePaths = [];
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
catch {
|
|
2429
|
+
// Non-fatal — fall back to analyzing all files
|
|
2430
|
+
}
|
|
2431
|
+
// Run data-structure-only analysis for entities that need it.
|
|
2341
2432
|
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2342
2433
|
// The analyzer will discover and create them from file paths.
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2434
|
+
if (targetFilePaths.length > 0) {
|
|
2435
|
+
progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
|
|
2436
|
+
try {
|
|
2437
|
+
await runAnalysisForEntities({
|
|
2438
|
+
projectRoot: root,
|
|
2439
|
+
filePaths: targetFilePaths,
|
|
2440
|
+
progress,
|
|
2441
|
+
onlyDataStructure: true,
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
catch (err) {
|
|
2445
|
+
progress.fail('Analysis failed');
|
|
2446
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2447
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
2448
|
+
process.exit(1);
|
|
2449
|
+
}
|
|
2450
|
+
progress.succeed('Analysis complete');
|
|
2351
2451
|
}
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2452
|
+
// Surface analysis errors if any occurred
|
|
2453
|
+
const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
|
|
2454
|
+
if (fs.existsSync(errorReportPath)) {
|
|
2455
|
+
if (!options.silent) {
|
|
2456
|
+
console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
|
|
2457
|
+
console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
|
|
2458
|
+
console.log(chalk.dim('Affected entities will be missing from the import graph.'));
|
|
2459
|
+
}
|
|
2460
|
+
// Write structured analysis-failures.json for the audit to detect.
|
|
2461
|
+
// Parse the error report to find which entities failed, and cross-reference
|
|
2462
|
+
// with the files we tried to analyze.
|
|
2463
|
+
try {
|
|
2464
|
+
const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
2465
|
+
const errorContent = fs.readFileSync(errorReportPath, 'utf8');
|
|
2466
|
+
const existingFailures = readAnalysisFailures(root);
|
|
2467
|
+
// Parse error messages to identify failed file paths.
|
|
2468
|
+
// Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
|
|
2469
|
+
// or more generic "CodeYam Error: <message>"
|
|
2470
|
+
const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
|
|
2471
|
+
const failedEntityNames = new Set();
|
|
2472
|
+
let match;
|
|
2473
|
+
while ((match = entityErrorRegex.exec(errorContent)) !== null) {
|
|
2474
|
+
const name = match[1] || match[2];
|
|
2475
|
+
if (name)
|
|
2476
|
+
failedEntityNames.add(name);
|
|
2477
|
+
}
|
|
2478
|
+
// Map failed entity names back to file paths from targetFilePaths
|
|
2479
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
2480
|
+
let glossary = [];
|
|
2481
|
+
try {
|
|
2482
|
+
glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
2483
|
+
}
|
|
2484
|
+
catch {
|
|
2485
|
+
// Non-fatal
|
|
2486
|
+
}
|
|
2487
|
+
// Also: any targetFilePaths that didn't produce entities are failures
|
|
2488
|
+
const now = new Date().toISOString();
|
|
2489
|
+
const updatedFailures = { ...existingFailures };
|
|
2490
|
+
if (failedEntityNames.size > 0) {
|
|
2491
|
+
for (const name of failedEntityNames) {
|
|
2492
|
+
const entry = glossary.find((e) => e.name === name);
|
|
2493
|
+
if (entry) {
|
|
2494
|
+
updatedFailures[entry.filePath] = {
|
|
2495
|
+
entityName: name,
|
|
2496
|
+
error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
|
|
2497
|
+
failedAt: now,
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
else if (targetFilePaths.length > 0) {
|
|
2503
|
+
// Couldn't parse specific entity names — mark all target files
|
|
2504
|
+
// that we attempted to analyze as potentially failed
|
|
2505
|
+
for (const fp of targetFilePaths) {
|
|
2506
|
+
const entry = glossary.find((e) => e.filePath === fp);
|
|
2507
|
+
updatedFailures[fp] = {
|
|
2508
|
+
entityName: entry?.name || path.basename(fp, path.extname(fp)),
|
|
2509
|
+
error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
|
|
2510
|
+
failedAt: now,
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
writeAnalysisFailures(root, updatedFailures);
|
|
2515
|
+
}
|
|
2516
|
+
catch {
|
|
2517
|
+
// Non-fatal — failure tracking is best-effort
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
else {
|
|
2521
|
+
// No errors — clear any stale failure tracking
|
|
2522
|
+
try {
|
|
2523
|
+
const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
2524
|
+
writeAnalysisFailures(root, {});
|
|
2525
|
+
}
|
|
2526
|
+
catch {
|
|
2527
|
+
// Non-fatal
|
|
2528
|
+
}
|
|
2357
2529
|
}
|
|
2358
|
-
progress.succeed('Analysis complete');
|
|
2359
2530
|
// Load entities WITH metadata from database
|
|
2360
2531
|
progress.start('Loading entity metadata...');
|
|
2361
2532
|
await initializeEnvironment();
|
|
@@ -2705,11 +2876,15 @@ async function handleRegister(jsonArg) {
|
|
|
2705
2876
|
}
|
|
2706
2877
|
// Normalize to array for uniform handling
|
|
2707
2878
|
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
2879
|
+
const root = getProjectRoot();
|
|
2708
2880
|
const port = getServerPort();
|
|
2709
2881
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
2710
2882
|
const isBatch = items.length > 1;
|
|
2711
2883
|
let succeeded = 0;
|
|
2712
2884
|
let failed = 0;
|
|
2885
|
+
let warnings = 0;
|
|
2886
|
+
const issues = [];
|
|
2887
|
+
const screenshotEntries = [];
|
|
2713
2888
|
for (let i = 0; i < items.length; i++) {
|
|
2714
2889
|
const body = items[i];
|
|
2715
2890
|
const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
|
|
@@ -2737,6 +2912,12 @@ async function handleRegister(jsonArg) {
|
|
|
2737
2912
|
else {
|
|
2738
2913
|
parts.push(`errors=0`);
|
|
2739
2914
|
}
|
|
2915
|
+
if (data.visibleText) {
|
|
2916
|
+
const truncated = data.visibleText.length > 100
|
|
2917
|
+
? data.visibleText.substring(0, 97) + '...'
|
|
2918
|
+
: data.visibleText;
|
|
2919
|
+
parts.push(`visibleText="${truncated}"`);
|
|
2920
|
+
}
|
|
2740
2921
|
if (data.seedResult) {
|
|
2741
2922
|
if (data.seedResult.success) {
|
|
2742
2923
|
parts.push(`seed=ok`);
|
|
@@ -2772,12 +2953,29 @@ async function handleRegister(jsonArg) {
|
|
|
2772
2953
|
}
|
|
2773
2954
|
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2774
2955
|
}
|
|
2956
|
+
const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
|
|
2775
2957
|
if (!res.ok) {
|
|
2776
2958
|
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2777
2959
|
failed++;
|
|
2778
2960
|
}
|
|
2779
2961
|
else {
|
|
2962
|
+
if (resultIssues.length > 0)
|
|
2963
|
+
warnings++;
|
|
2780
2964
|
succeeded++;
|
|
2965
|
+
// Collect screenshot paths for duplicate detection
|
|
2966
|
+
if (data.screenshotCaptured &&
|
|
2967
|
+
data.scenario?.screenshotPath &&
|
|
2968
|
+
data.scenario?.name) {
|
|
2969
|
+
const absPath = path.join(root, '.codeyam', 'editor-scenarios', data.scenario.screenshotPath);
|
|
2970
|
+
screenshotEntries.push({
|
|
2971
|
+
scenarioName: data.scenario.name,
|
|
2972
|
+
screenshotAbsPath: absPath,
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
// Collect issues from this registration
|
|
2977
|
+
for (const issue of resultIssues) {
|
|
2978
|
+
issues.push(`"${issue.scenarioName}": ${issue.message}`);
|
|
2781
2979
|
}
|
|
2782
2980
|
}
|
|
2783
2981
|
catch (error) {
|
|
@@ -2788,10 +2986,50 @@ async function handleRegister(jsonArg) {
|
|
|
2788
2986
|
failed++;
|
|
2789
2987
|
}
|
|
2790
2988
|
}
|
|
2989
|
+
// Detect duplicate screenshots in the batch
|
|
2990
|
+
if (isBatch && screenshotEntries.length > 1) {
|
|
2991
|
+
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
|
|
2992
|
+
const hashEntries = screenshotEntries
|
|
2993
|
+
.map((e) => {
|
|
2994
|
+
const hash = computeScreenshotHash(e.screenshotAbsPath);
|
|
2995
|
+
return hash
|
|
2996
|
+
? {
|
|
2997
|
+
scenarioName: e.scenarioName,
|
|
2998
|
+
screenshotPath: e.screenshotAbsPath,
|
|
2999
|
+
hash,
|
|
3000
|
+
}
|
|
3001
|
+
: null;
|
|
3002
|
+
})
|
|
3003
|
+
.filter((e) => e !== null);
|
|
3004
|
+
const duplicates = findDuplicateScreenshots(hashEntries);
|
|
3005
|
+
if (duplicates.size > 0) {
|
|
3006
|
+
console.log('');
|
|
3007
|
+
console.log(chalk.yellow.bold('WARNING: Identical screenshots detected:'));
|
|
3008
|
+
for (const [, names] of duplicates) {
|
|
3009
|
+
const quoted = names.map((n) => `"${n}"`);
|
|
3010
|
+
console.log(chalk.yellow(` ${quoted.join(' and ')} produced the same screenshot`));
|
|
3011
|
+
}
|
|
3012
|
+
console.log(chalk.yellow(" This usually means the app's view state depends on something scenarios can't control"));
|
|
3013
|
+
console.log(chalk.yellow(' (e.g., a hardcoded function return value, or identical localStorage not differentiating views).'));
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
2791
3016
|
if (isBatch) {
|
|
2792
|
-
console.log(
|
|
3017
|
+
console.log('');
|
|
3018
|
+
if (failed > 0 || warnings > 0) {
|
|
3019
|
+
console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
|
|
3020
|
+
console.log('');
|
|
3021
|
+
console.log(chalk.red.bold('Issues that MUST be fixed:'));
|
|
3022
|
+
for (const issue of issues) {
|
|
3023
|
+
console.log(chalk.red(` ✗ ${issue}`));
|
|
3024
|
+
}
|
|
3025
|
+
console.log('');
|
|
3026
|
+
console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
|
|
3027
|
+
}
|
|
3028
|
+
else {
|
|
3029
|
+
console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
|
|
3030
|
+
}
|
|
2793
3031
|
}
|
|
2794
|
-
if (failed > 0) {
|
|
3032
|
+
if (failed > 0 || warnings > 0) {
|
|
2795
3033
|
process.exit(1);
|
|
2796
3034
|
}
|
|
2797
3035
|
}
|
|
@@ -3003,6 +3241,8 @@ function handleChange(feature) {
|
|
|
3003
3241
|
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
3004
3242
|
printDimensionGuidance(dim, dimNames);
|
|
3005
3243
|
console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
|
|
3244
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
3245
|
+
console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
|
|
3006
3246
|
console.log();
|
|
3007
3247
|
console.log(chalk.bold('0. Close the results panel:'));
|
|
3008
3248
|
checkbox(`Hide results: \`codeyam editor hide-results\``);
|
|
@@ -3078,10 +3318,11 @@ function handleChange(feature) {
|
|
|
3078
3318
|
* Fetch the audit result from the server.
|
|
3079
3319
|
* Returns null if the server is unreachable.
|
|
3080
3320
|
*/
|
|
3081
|
-
async function fetchAuditResult() {
|
|
3321
|
+
async function fetchAuditResult(options) {
|
|
3082
3322
|
const port = getServerPort();
|
|
3323
|
+
const params = options?.skipTests ? '?skipTests=true' : '';
|
|
3083
3324
|
try {
|
|
3084
|
-
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3325
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
|
|
3085
3326
|
if (!res.ok)
|
|
3086
3327
|
return null;
|
|
3087
3328
|
return await res.json();
|
|
@@ -3104,17 +3345,34 @@ async function checkAuditGate() {
|
|
|
3104
3345
|
return true; // Server unreachable — don't block
|
|
3105
3346
|
if (data.summary?.allPassing === true)
|
|
3106
3347
|
return true;
|
|
3107
|
-
//
|
|
3108
|
-
|
|
3109
|
-
|
|
3348
|
+
// Lightweight auto-fix: backfill entity_sha (DB-only, fast).
|
|
3349
|
+
// Never run handleAnalyzeImports here — it takes minutes on large projects.
|
|
3350
|
+
const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
|
|
3351
|
+
// If the only failures are pre-existing incomplete entities (not caused by
|
|
3352
|
+
// this session), don't block — the audit text already calls these "non-blocking".
|
|
3353
|
+
if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
|
|
3354
|
+
return true;
|
|
3355
|
+
}
|
|
3356
|
+
if (isOnlyIncompleteEntities(data.summary)) {
|
|
3110
3357
|
try {
|
|
3111
|
-
await
|
|
3358
|
+
const entities = await loadEntities({});
|
|
3359
|
+
if (entities && entities.length > 0) {
|
|
3360
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3361
|
+
const db = getDatabase();
|
|
3362
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3363
|
+
sha: e.sha,
|
|
3364
|
+
name: e.name,
|
|
3365
|
+
filePath: e.filePath || '',
|
|
3366
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3367
|
+
e.metadata?.namedExport === false,
|
|
3368
|
+
})));
|
|
3369
|
+
}
|
|
3112
3370
|
}
|
|
3113
3371
|
catch {
|
|
3114
|
-
|
|
3372
|
+
// Fall through
|
|
3115
3373
|
}
|
|
3116
|
-
// Re-check after
|
|
3117
|
-
const retry = await fetchAuditResult();
|
|
3374
|
+
// Re-check after backfill — skip re-running tests (backfill is DB-only)
|
|
3375
|
+
const retry = await fetchAuditResult({ skipTests: true });
|
|
3118
3376
|
if (retry?.summary?.allPassing === true)
|
|
3119
3377
|
return true;
|
|
3120
3378
|
}
|
|
@@ -3137,6 +3395,8 @@ function printAuditGateFailures(data) {
|
|
|
3137
3395
|
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
3138
3396
|
for (const c of missing) {
|
|
3139
3397
|
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3398
|
+
if (c.hint)
|
|
3399
|
+
issues.push(` Fix: ${c.hint}`);
|
|
3140
3400
|
}
|
|
3141
3401
|
}
|
|
3142
3402
|
if (s.componentsWithErrors > 0) {
|
|
@@ -3150,7 +3410,15 @@ function printAuditGateFailures(data) {
|
|
|
3150
3410
|
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
3151
3411
|
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
3152
3412
|
for (const f of missing) {
|
|
3153
|
-
|
|
3413
|
+
if (f.testFile) {
|
|
3414
|
+
issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
|
|
3415
|
+
}
|
|
3416
|
+
else {
|
|
3417
|
+
issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
|
|
3418
|
+
if (f.suggestedTestFile) {
|
|
3419
|
+
issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3154
3422
|
}
|
|
3155
3423
|
}
|
|
3156
3424
|
if (s.functionsFailing > 0) {
|
|
@@ -3165,6 +3433,10 @@ function printAuditGateFailures(data) {
|
|
|
3165
3433
|
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
3166
3434
|
for (const f of runnerErrors) {
|
|
3167
3435
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3436
|
+
if (f.hint)
|
|
3437
|
+
issues.push(` ${f.hint}`);
|
|
3438
|
+
else if (f.errorMessage)
|
|
3439
|
+
issues.push(` Error: ${f.errorMessage}`);
|
|
3168
3440
|
}
|
|
3169
3441
|
}
|
|
3170
3442
|
if (s.functionsNameMismatch > 0) {
|
|
@@ -3172,13 +3444,36 @@ function printAuditGateFailures(data) {
|
|
|
3172
3444
|
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
3173
3445
|
for (const f of mismatch) {
|
|
3174
3446
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3447
|
+
if (f.hint)
|
|
3448
|
+
issues.push(` Fix: ${f.hint}`);
|
|
3175
3449
|
}
|
|
3176
3450
|
}
|
|
3177
|
-
if (s.missingFromGlossary > 0)
|
|
3451
|
+
if (s.missingFromGlossary > 0) {
|
|
3178
3452
|
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
3453
|
+
const missingGlossary = data.missingFromGlossary || [];
|
|
3454
|
+
for (const m of missingGlossary) {
|
|
3455
|
+
issues.push(` → ${m.name} (${m.filePath})`);
|
|
3456
|
+
}
|
|
3457
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
|
|
3458
|
+
}
|
|
3179
3459
|
if (s.incompleteEntities > 0) {
|
|
3460
|
+
// Check for persistent analysis failures
|
|
3461
|
+
let analysisFailures = {};
|
|
3462
|
+
try {
|
|
3463
|
+
const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
|
|
3464
|
+
if (fs.existsSync(failuresPath)) {
|
|
3465
|
+
analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
catch {
|
|
3469
|
+
// Non-fatal
|
|
3470
|
+
}
|
|
3180
3471
|
const preCount = s.preExistingIncompleteEntities || 0;
|
|
3181
|
-
|
|
3472
|
+
const hasFailures = Object.keys(analysisFailures).length > 0;
|
|
3473
|
+
if (hasFailures) {
|
|
3474
|
+
issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
|
|
3475
|
+
}
|
|
3476
|
+
else if (preCount > 0 && preCount === s.incompleteEntities) {
|
|
3182
3477
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
3183
3478
|
}
|
|
3184
3479
|
else if (preCount > 0) {
|
|
@@ -3187,11 +3482,44 @@ function printAuditGateFailures(data) {
|
|
|
3187
3482
|
else {
|
|
3188
3483
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3189
3484
|
}
|
|
3485
|
+
const incomplete = data.incompleteEntities || [];
|
|
3486
|
+
for (const e of incomplete) {
|
|
3487
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
3488
|
+
if (failureEntry) {
|
|
3489
|
+
issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
|
|
3490
|
+
issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
|
|
3491
|
+
}
|
|
3492
|
+
else {
|
|
3493
|
+
issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
if (!hasFailures) {
|
|
3497
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
if (s.unassociatedScenarios > 0) {
|
|
3501
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
3502
|
+
const unassociated = data.unassociatedScenarios || [];
|
|
3503
|
+
for (const u of unassociated) {
|
|
3504
|
+
issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
|
|
3505
|
+
}
|
|
3190
3506
|
}
|
|
3191
|
-
if (s.miscategorizedScenarios > 0)
|
|
3507
|
+
if (s.miscategorizedScenarios > 0) {
|
|
3192
3508
|
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3193
|
-
|
|
3509
|
+
const miscategorized = data.miscategorizedScenarios || [];
|
|
3510
|
+
for (const m of miscategorized) {
|
|
3511
|
+
issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
|
|
3512
|
+
}
|
|
3513
|
+
issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
|
|
3514
|
+
}
|
|
3515
|
+
if (s.scenariosNeedingRecapture > 0) {
|
|
3194
3516
|
issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
|
|
3517
|
+
const recapture = data.scenariosNeedingRecapture || [];
|
|
3518
|
+
for (const r of recapture) {
|
|
3519
|
+
issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
|
|
3520
|
+
}
|
|
3521
|
+
issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
|
|
3522
|
+
}
|
|
3195
3523
|
if (issues.length > 0) {
|
|
3196
3524
|
console.error(chalk.yellow('\nAudit failures:'));
|
|
3197
3525
|
for (const issue of issues) {
|
|
@@ -3232,30 +3560,45 @@ async function handleAudit() {
|
|
|
3232
3560
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3233
3561
|
process.exit(1);
|
|
3234
3562
|
}
|
|
3235
|
-
//
|
|
3236
|
-
//
|
|
3237
|
-
//
|
|
3238
|
-
//
|
|
3563
|
+
// Lightweight auto-fix: backfill entity_sha on scenarios that were
|
|
3564
|
+
// registered before entity records existed. This is fast (DB-only).
|
|
3565
|
+
// Import analysis is NOT triggered here — it scans all files and takes
|
|
3566
|
+
// minutes on large projects. Users should run it separately if needed.
|
|
3239
3567
|
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3568
|
+
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
3240
3569
|
let autoRemediationFailed = false;
|
|
3241
|
-
if (incompleteBeforeFix.length > 0) {
|
|
3242
|
-
|
|
3570
|
+
if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
|
|
3571
|
+
const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
|
|
3572
|
+
console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
|
|
3573
|
+
// Backfill entity_sha on scenarios that were registered before entities existed
|
|
3243
3574
|
try {
|
|
3244
|
-
await
|
|
3575
|
+
const entities = await loadEntities({});
|
|
3576
|
+
if (entities && entities.length > 0) {
|
|
3577
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3578
|
+
const db = getDatabase();
|
|
3579
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3580
|
+
sha: e.sha,
|
|
3581
|
+
name: e.name,
|
|
3582
|
+
filePath: e.filePath || '',
|
|
3583
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3584
|
+
e.metadata?.namedExport === false,
|
|
3585
|
+
})));
|
|
3586
|
+
}
|
|
3245
3587
|
}
|
|
3246
3588
|
catch {
|
|
3247
|
-
// Fall through —
|
|
3589
|
+
// Fall through — re-fetch will show remaining issues
|
|
3248
3590
|
}
|
|
3249
|
-
// Re-fetch audit results after the
|
|
3250
|
-
|
|
3591
|
+
// Re-fetch audit results after the backfill — skip re-running tests
|
|
3592
|
+
// since they haven't changed (backfill is DB-only).
|
|
3593
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
3251
3594
|
if (!data) {
|
|
3252
|
-
console.error(chalk.red('Error: Could not reach the CodeYam server after
|
|
3595
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
|
|
3253
3596
|
process.exit(1);
|
|
3254
3597
|
}
|
|
3255
|
-
// If
|
|
3256
|
-
// flag it so we can show a clear message instead of looping
|
|
3598
|
+
// If issues persist after backfill, flag it so we show clear guidance
|
|
3257
3599
|
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3258
|
-
|
|
3600
|
+
const unassociatedAfterFix = data.unassociatedScenarios || [];
|
|
3601
|
+
if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
|
|
3259
3602
|
autoRemediationFailed = true;
|
|
3260
3603
|
}
|
|
3261
3604
|
}
|
|
@@ -3289,6 +3632,9 @@ async function handleAudit() {
|
|
|
3289
3632
|
}
|
|
3290
3633
|
else {
|
|
3291
3634
|
detail = chalk.red(' — no scenarios registered');
|
|
3635
|
+
if (c.hint) {
|
|
3636
|
+
detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
|
|
3637
|
+
}
|
|
3292
3638
|
}
|
|
3293
3639
|
// Show file path for failing components always, for OK only when name is ambiguous
|
|
3294
3640
|
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
@@ -3332,9 +3678,16 @@ async function handleAudit() {
|
|
|
3332
3678
|
break;
|
|
3333
3679
|
case 'missing':
|
|
3334
3680
|
default:
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3681
|
+
if (f.testFile) {
|
|
3682
|
+
detail = chalk.red(` — test file missing: ${f.testFile}`);
|
|
3683
|
+
}
|
|
3684
|
+
else {
|
|
3685
|
+
detail = chalk.red(` — no test file specified in glossary`);
|
|
3686
|
+
detail += chalk.dim(` (source: ${f.filePath})`);
|
|
3687
|
+
if (f.suggestedTestFile) {
|
|
3688
|
+
detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3338
3691
|
break;
|
|
3339
3692
|
}
|
|
3340
3693
|
console.log(` ${icon} ${f.name}${detail}`);
|
|
@@ -3351,23 +3704,68 @@ async function handleAudit() {
|
|
|
3351
3704
|
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
3352
3705
|
console.log();
|
|
3353
3706
|
}
|
|
3354
|
-
// Incomplete entities (scenarios without analyses)
|
|
3707
|
+
// Incomplete entities (scenarios without analyses) — report with guidance.
|
|
3708
|
+
// We intentionally do NOT run analysis here: it starts the analyzer
|
|
3709
|
+
// template which is slow even for a few files. Users should run
|
|
3710
|
+
// `codeyam editor analyze-imports` separately if needed.
|
|
3355
3711
|
const incompleteEntities = data.incompleteEntities || [];
|
|
3356
3712
|
if (incompleteEntities.length > 0) {
|
|
3713
|
+
const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
|
|
3714
|
+
const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
3715
|
+
// Check for persistent analysis failures
|
|
3716
|
+
const analysisFailures = readAnalysisFailures(getProjectRoot());
|
|
3357
3717
|
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3358
3718
|
for (const e of incompleteEntities) {
|
|
3359
|
-
|
|
3719
|
+
// Check if this entity has a persistent failure
|
|
3720
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
3721
|
+
if (failureEntry) {
|
|
3722
|
+
// Show manual analysis instructions instead of "run analyze-imports"
|
|
3723
|
+
const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
|
|
3724
|
+
const guidance = formatManualAnalysisGuidance({
|
|
3725
|
+
name: e.name,
|
|
3726
|
+
filePath: filePath || '',
|
|
3727
|
+
scenarioCount: e.scenarioCount,
|
|
3728
|
+
error: failureEntry.error,
|
|
3729
|
+
});
|
|
3730
|
+
// Print each line with proper indentation
|
|
3731
|
+
for (const line of guidance.split('\n')) {
|
|
3732
|
+
console.log(` ${chalk.red('✗')} ${line}`);
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
else {
|
|
3736
|
+
const guidance = formatIncompleteEntityGuidance(e);
|
|
3737
|
+
console.log(` ${chalk.red('✗')} ${guidance}`);
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
3741
|
+
if (fs.existsSync(incompleteErrorReportPath)) {
|
|
3742
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3743
|
+
}
|
|
3744
|
+
console.log();
|
|
3745
|
+
}
|
|
3746
|
+
// Unassociated scenarios (NULL entity_sha with file paths)
|
|
3747
|
+
const unassociatedScenarios = data.unassociatedScenarios || [];
|
|
3748
|
+
if (unassociatedScenarios.length > 0) {
|
|
3749
|
+
console.log(chalk.bold('Unassociated scenarios (missing entity link):'));
|
|
3750
|
+
for (const u of unassociatedScenarios) {
|
|
3751
|
+
console.log(` ${chalk.red('✗')} ${u.name} ${chalk.dim(`(${u.filePath})`)} — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''} with no entity_sha`);
|
|
3752
|
+
for (const sn of u.scenarioNames.slice(0, 3)) {
|
|
3753
|
+
console.log(chalk.dim(` "${sn}"`));
|
|
3754
|
+
}
|
|
3755
|
+
if (u.scenarioNames.length > 3) {
|
|
3756
|
+
console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
|
|
3757
|
+
}
|
|
3360
3758
|
}
|
|
3361
3759
|
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.'));
|
|
3760
|
+
console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
|
|
3761
|
+
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to see the full error output.'));
|
|
3368
3762
|
}
|
|
3369
3763
|
else {
|
|
3370
|
-
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to
|
|
3764
|
+
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
|
|
3765
|
+
}
|
|
3766
|
+
const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
3767
|
+
if (fs.existsSync(unassocErrorReportPath)) {
|
|
3768
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3371
3769
|
}
|
|
3372
3770
|
console.log();
|
|
3373
3771
|
}
|
|
@@ -3444,6 +3842,9 @@ async function handleAudit() {
|
|
|
3444
3842
|
if (summary.incompleteEntities > 0) {
|
|
3445
3843
|
parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
|
|
3446
3844
|
}
|
|
3845
|
+
if (summary.unassociatedScenarios > 0) {
|
|
3846
|
+
parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
|
|
3847
|
+
}
|
|
3447
3848
|
if (summary.miscategorizedScenarios > 0) {
|
|
3448
3849
|
parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
|
|
3449
3850
|
}
|
|
@@ -3456,17 +3857,6 @@ async function handleAudit() {
|
|
|
3456
3857
|
if (!allOk) {
|
|
3457
3858
|
process.exit(1);
|
|
3458
3859
|
}
|
|
3459
|
-
// Auto-run analyze-imports when audit passes — this builds the import graph
|
|
3460
|
-
// so the App tab can show component/function dependencies for each page.
|
|
3461
|
-
// Previously this was a manual step that Claude had to remember to run.
|
|
3462
|
-
console.log(chalk.dim('Building import graph...'));
|
|
3463
|
-
try {
|
|
3464
|
-
await handleAnalyzeImports({ silent: true });
|
|
3465
|
-
}
|
|
3466
|
-
catch {
|
|
3467
|
-
// Non-fatal — audit passed, import graph is a bonus
|
|
3468
|
-
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
3469
|
-
}
|
|
3470
3860
|
}
|
|
3471
3861
|
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
3472
3862
|
/**
|
|
@@ -3673,14 +4063,14 @@ async function handleScenarioCoverage() {
|
|
|
3673
4063
|
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3674
4064
|
try {
|
|
3675
4065
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3676
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4066
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
3677
4067
|
const db = getDatabase();
|
|
3678
4068
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3679
4069
|
if (backfillCount > 0) {
|
|
3680
4070
|
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3681
4071
|
await handleAnalyzeImports({ silent: true });
|
|
3682
4072
|
// Run backfill after analysis
|
|
3683
|
-
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
4073
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
|
|
3684
4074
|
const entities = await loadEntities({});
|
|
3685
4075
|
if (entities && entities.length > 0) {
|
|
3686
4076
|
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
@@ -4177,14 +4567,205 @@ function handleEditorDebug(args) {
|
|
|
4177
4567
|
console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
|
|
4178
4568
|
console.log();
|
|
4179
4569
|
}
|
|
4570
|
+
// ─── Manual entity analysis ───────────────────────────────────────────
|
|
4571
|
+
/**
|
|
4572
|
+
* `codeyam editor manual-entity <JSON|@file>`
|
|
4573
|
+
*
|
|
4574
|
+
* Creates entity and analysis records from Claude-provided metadata when
|
|
4575
|
+
* automated analyze-imports fails. This unblocks the audit gate without
|
|
4576
|
+
* needing the analyzer template to parse the source file.
|
|
4577
|
+
*/
|
|
4578
|
+
async function handleManualEntity(jsonArg) {
|
|
4579
|
+
const root = getProjectRoot();
|
|
4580
|
+
// Parse JSON input (supports @file.json convention)
|
|
4581
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
4582
|
+
if (parsed.error || !parsed.body) {
|
|
4583
|
+
console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
|
|
4584
|
+
console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
|
|
4585
|
+
console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
|
|
4586
|
+
process.exit(1);
|
|
4587
|
+
}
|
|
4588
|
+
const input = parsed.body;
|
|
4589
|
+
// Validate input
|
|
4590
|
+
const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
|
|
4591
|
+
// Read glossary for validation
|
|
4592
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
4593
|
+
let glossaryEntries = [];
|
|
4594
|
+
try {
|
|
4595
|
+
glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
4596
|
+
}
|
|
4597
|
+
catch {
|
|
4598
|
+
// Empty glossary — validation will still check file existence
|
|
4599
|
+
}
|
|
4600
|
+
const errors = validateManualEntityInput(input, glossaryEntries, {
|
|
4601
|
+
fileExists: (p) => fs.existsSync(path.join(root, p)),
|
|
4602
|
+
});
|
|
4603
|
+
if (errors.length > 0) {
|
|
4604
|
+
console.error(chalk.red('Validation errors:'));
|
|
4605
|
+
for (const err of errors) {
|
|
4606
|
+
console.error(chalk.red(` • ${err}`));
|
|
4607
|
+
}
|
|
4608
|
+
process.exit(1);
|
|
4609
|
+
}
|
|
4610
|
+
// Read source file and compute SHA
|
|
4611
|
+
const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
|
|
4612
|
+
const { generateSha } = await import('../../../packages/database/index.js');
|
|
4613
|
+
const entitySha = generateSha(input.filePath, input.name, sourceContent);
|
|
4614
|
+
// Get project and branch
|
|
4615
|
+
await initializeEnvironment();
|
|
4616
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
4617
|
+
let projectSlug;
|
|
4618
|
+
try {
|
|
4619
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
4620
|
+
projectSlug = config.projectSlug;
|
|
4621
|
+
}
|
|
4622
|
+
catch {
|
|
4623
|
+
console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
|
|
4624
|
+
process.exit(1);
|
|
4625
|
+
}
|
|
4626
|
+
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
4627
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4628
|
+
const db = getDatabase();
|
|
4629
|
+
// Convert type info to dataForMocks format
|
|
4630
|
+
const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
|
|
4631
|
+
// Create entity record
|
|
4632
|
+
const entityMetadata = {
|
|
4633
|
+
importedExports: (input.importedExports || []).map((ie) => ({
|
|
4634
|
+
name: ie.name,
|
|
4635
|
+
filePath: ie.filePath,
|
|
4636
|
+
})),
|
|
4637
|
+
manuallyAnalyzed: true,
|
|
4638
|
+
};
|
|
4639
|
+
// Check if entity already exists
|
|
4640
|
+
const existingEntity = await db
|
|
4641
|
+
.selectFrom('entities')
|
|
4642
|
+
.select('sha')
|
|
4643
|
+
.where('sha', '=', entitySha)
|
|
4644
|
+
.executeTakeFirst();
|
|
4645
|
+
if (!existingEntity) {
|
|
4646
|
+
await db
|
|
4647
|
+
.insertInto('entities')
|
|
4648
|
+
.values({
|
|
4649
|
+
sha: entitySha,
|
|
4650
|
+
project_id: project.id,
|
|
4651
|
+
name: input.name,
|
|
4652
|
+
entity_type: input.entityType,
|
|
4653
|
+
file_path: input.filePath,
|
|
4654
|
+
metadata: JSON.stringify(entityMetadata),
|
|
4655
|
+
})
|
|
4656
|
+
.onConflict((oc) => oc.column('sha').doNothing())
|
|
4657
|
+
.execute();
|
|
4658
|
+
console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
4659
|
+
}
|
|
4660
|
+
else {
|
|
4661
|
+
// Update metadata on existing entity
|
|
4662
|
+
await db
|
|
4663
|
+
.updateTable('entities')
|
|
4664
|
+
.set({
|
|
4665
|
+
metadata: JSON.stringify(entityMetadata),
|
|
4666
|
+
})
|
|
4667
|
+
.where('sha', '=', entitySha)
|
|
4668
|
+
.execute();
|
|
4669
|
+
console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
4670
|
+
}
|
|
4671
|
+
// Create entity_branch record
|
|
4672
|
+
await db
|
|
4673
|
+
.insertInto('entity_branches')
|
|
4674
|
+
.values({
|
|
4675
|
+
entity_sha: entitySha,
|
|
4676
|
+
branch_id: branch.id,
|
|
4677
|
+
active: true,
|
|
4678
|
+
})
|
|
4679
|
+
.onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
|
|
4680
|
+
.execute();
|
|
4681
|
+
// Create analysis record
|
|
4682
|
+
const { randomUUID } = await import('crypto');
|
|
4683
|
+
const analysisId = randomUUID();
|
|
4684
|
+
const now = new Date().toISOString();
|
|
4685
|
+
const analysisMetadata = {
|
|
4686
|
+
scenariosDataStructure: {
|
|
4687
|
+
dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
|
|
4688
|
+
},
|
|
4689
|
+
mergedDataStructure: {
|
|
4690
|
+
dependencySchemas: null,
|
|
4691
|
+
},
|
|
4692
|
+
manuallyAnalyzed: true,
|
|
4693
|
+
};
|
|
4694
|
+
const analysisStatus = {
|
|
4695
|
+
startedAt: now,
|
|
4696
|
+
finishedAt: now,
|
|
4697
|
+
steps: [],
|
|
4698
|
+
errors: [],
|
|
4699
|
+
};
|
|
4700
|
+
// Check if analysis already exists for this entity
|
|
4701
|
+
const existingAnalysis = await db
|
|
4702
|
+
.selectFrom('analyses')
|
|
4703
|
+
.select('id')
|
|
4704
|
+
.where('entity_sha', '=', entitySha)
|
|
4705
|
+
.executeTakeFirst();
|
|
4706
|
+
if (!existingAnalysis) {
|
|
4707
|
+
await db
|
|
4708
|
+
.insertInto('analyses')
|
|
4709
|
+
.values({
|
|
4710
|
+
id: analysisId,
|
|
4711
|
+
project_id: project.id,
|
|
4712
|
+
entity_sha: entitySha,
|
|
4713
|
+
entity_name: input.name,
|
|
4714
|
+
entity_type: input.entityType,
|
|
4715
|
+
file_path: input.filePath,
|
|
4716
|
+
status: JSON.stringify(analysisStatus),
|
|
4717
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
4718
|
+
})
|
|
4719
|
+
.execute();
|
|
4720
|
+
console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
|
|
4721
|
+
}
|
|
4722
|
+
else {
|
|
4723
|
+
// Update existing analysis with manual metadata
|
|
4724
|
+
await db
|
|
4725
|
+
.updateTable('analyses')
|
|
4726
|
+
.set({
|
|
4727
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
4728
|
+
status: JSON.stringify(analysisStatus),
|
|
4729
|
+
})
|
|
4730
|
+
.where('entity_sha', '=', entitySha)
|
|
4731
|
+
.execute();
|
|
4732
|
+
console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
|
|
4733
|
+
}
|
|
4734
|
+
// Backfill entity_sha on scenarios
|
|
4735
|
+
const backfillResult = await backfillEntityShaOnScenarios(db, [
|
|
4736
|
+
{
|
|
4737
|
+
sha: entitySha,
|
|
4738
|
+
name: input.name,
|
|
4739
|
+
filePath: input.filePath,
|
|
4740
|
+
isDefaultExport: false,
|
|
4741
|
+
},
|
|
4742
|
+
]);
|
|
4743
|
+
if (backfillResult.updated > 0) {
|
|
4744
|
+
console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
|
|
4745
|
+
}
|
|
4746
|
+
// Clear from analysis failures tracker
|
|
4747
|
+
const failures = readAnalysisFailures(root);
|
|
4748
|
+
if (failures[input.filePath]) {
|
|
4749
|
+
const updated = clearFailureForPath(failures, input.filePath);
|
|
4750
|
+
writeAnalysisFailures(root, updated);
|
|
4751
|
+
console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
|
|
4752
|
+
}
|
|
4753
|
+
console.log();
|
|
4754
|
+
console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
|
|
4755
|
+
console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
|
|
4756
|
+
console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
|
|
4757
|
+
console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
|
|
4758
|
+
console.log();
|
|
4759
|
+
console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
|
|
4760
|
+
}
|
|
4180
4761
|
// ─── Command definition ───────────────────────────────────────────────
|
|
4181
4762
|
const editorCommand = {
|
|
4182
4763
|
command: 'editor [step] [json]',
|
|
4183
4764
|
describe: 'Editor mode guided workflow',
|
|
4184
4765
|
builder: (yargs) => {
|
|
4185
4766
|
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)';
|
|
4767
|
+
? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, 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)'
|
|
4768
|
+
: 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, 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
4769
|
let builder = yargs
|
|
4189
4770
|
.positional('step', {
|
|
4190
4771
|
type: 'string',
|
|
@@ -4245,6 +4826,18 @@ const editorCommand = {
|
|
|
4245
4826
|
// API subcommands: preview, show-results, hide-results, commit,
|
|
4246
4827
|
// journal, journal-update, dev-server, client-errors
|
|
4247
4828
|
if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
|
|
4829
|
+
// Guard: commit requires step 16 to have been run first.
|
|
4830
|
+
// Without this, Claude shortcuts the process by running
|
|
4831
|
+
// `codeyam editor commit` directly from step 15, skipping
|
|
4832
|
+
// steps 16-18 entirely.
|
|
4833
|
+
if (argv.step === 'commit') {
|
|
4834
|
+
const state = readState(root);
|
|
4835
|
+
if (state && state.step !== 16) {
|
|
4836
|
+
console.error(chalk.red('Error: Run `codeyam editor 16` before committing.'));
|
|
4837
|
+
console.error(chalk.dim(` Current step: ${state.step} (${state.label || 'unknown'}). Step 16 (Commit) must be run first.`));
|
|
4838
|
+
process.exit(1);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4248
4841
|
const port = getServerPort();
|
|
4249
4842
|
try {
|
|
4250
4843
|
const request = buildEditorApiRequest(argv.step, argv.json || undefined);
|
|
@@ -4288,11 +4881,58 @@ const editorCommand = {
|
|
|
4288
4881
|
await handleGlossaryAdd(argv.json || '');
|
|
4289
4882
|
return;
|
|
4290
4883
|
}
|
|
4884
|
+
// Subcommand: codeyam editor task-ontrack
|
|
4885
|
+
// Corrective command when Claude advanced without creating a task.
|
|
4886
|
+
if (argv.step === 'task-ontrack') {
|
|
4887
|
+
const state = readState(root);
|
|
4888
|
+
if (!state?.step) {
|
|
4889
|
+
console.error(chalk.red('No editor state found. Run `codeyam editor 1` to start.'));
|
|
4890
|
+
process.exit(1);
|
|
4891
|
+
}
|
|
4892
|
+
const currentLabel = STEP_LABELS[state.step] || `Step ${state.step}`;
|
|
4893
|
+
const totalSteps = Object.keys(STEP_LABELS).length;
|
|
4894
|
+
const nextLabel = state.step < totalSteps ? STEP_LABELS[state.step + 1] : undefined;
|
|
4895
|
+
console.log();
|
|
4896
|
+
console.log(chalk.bold.yellow('━━━ GETTING BACK ON TRACK ━━━'));
|
|
4897
|
+
console.log();
|
|
4898
|
+
console.log(chalk.yellow('You went off-track by not creating a task. Create the task below to continue.'));
|
|
4899
|
+
console.log();
|
|
4900
|
+
// Print the TASK directive for the current step
|
|
4901
|
+
let taskTitle;
|
|
4902
|
+
if (state.step < totalSteps) {
|
|
4903
|
+
taskTitle = `Complete codeyam editor step ${state.step}: '${currentLabel}' and move on to step ${state.step + 1}: '${nextLabel}'`;
|
|
4904
|
+
}
|
|
4905
|
+
else {
|
|
4906
|
+
taskTitle =
|
|
4907
|
+
'Ask user what to build next and restart codeyam editor workflow';
|
|
4908
|
+
}
|
|
4909
|
+
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
4910
|
+
console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
|
|
4911
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
4912
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
4913
|
+
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
4914
|
+
console.log();
|
|
4915
|
+
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
4916
|
+
console.log();
|
|
4917
|
+
console.log(chalk.green(`After creating the task, re-run: codeyam editor ${state.step + 1}`));
|
|
4918
|
+
console.log();
|
|
4919
|
+
// Mark task as expected-created so the next step can proceed.
|
|
4920
|
+
// The hook will set taskCreated=true when it sees the actual TaskCreate call.
|
|
4921
|
+
// But we also allow advancement now since task-ontrack itself is the corrective action.
|
|
4922
|
+
const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
|
|
4923
|
+
fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
|
|
4924
|
+
return;
|
|
4925
|
+
}
|
|
4291
4926
|
// Subcommand: codeyam editor analyze-imports
|
|
4292
4927
|
if (argv.step === 'analyze-imports') {
|
|
4293
4928
|
await handleAnalyzeImports();
|
|
4294
4929
|
return;
|
|
4295
4930
|
}
|
|
4931
|
+
// Subcommand: codeyam editor manual-entity <JSON|@file>
|
|
4932
|
+
if (argv.step === 'manual-entity') {
|
|
4933
|
+
await handleManualEntity(argv.json || '');
|
|
4934
|
+
return;
|
|
4935
|
+
}
|
|
4296
4936
|
// Subcommand: codeyam editor dependents <EntityName>
|
|
4297
4937
|
if (argv.step === 'dependents') {
|
|
4298
4938
|
await handleDependents(argv.json || '');
|
|
@@ -4533,13 +5173,24 @@ const editorCommand = {
|
|
|
4533
5173
|
try {
|
|
4534
5174
|
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4535
5175
|
const seedDb = getDb();
|
|
4536
|
-
|
|
5176
|
+
// Prefer the home page scenario (url "/") since the App tab
|
|
5177
|
+
// sorts "Home" first — fall back to any application scenario.
|
|
5178
|
+
const homeScenario = await seedDb
|
|
4537
5179
|
.selectFrom('editor_scenarios')
|
|
4538
5180
|
.select(['id', 'name', 'type'])
|
|
4539
5181
|
.where('project_id', '=', project.id)
|
|
4540
|
-
.where('
|
|
5182
|
+
.where('url', '=', '/')
|
|
5183
|
+
.where('component_name', 'is', null)
|
|
4541
5184
|
.orderBy('created_at', 'asc')
|
|
4542
5185
|
.executeTakeFirst();
|
|
5186
|
+
const appScenario = homeScenario ||
|
|
5187
|
+
(await seedDb
|
|
5188
|
+
.selectFrom('editor_scenarios')
|
|
5189
|
+
.select(['id', 'name', 'type'])
|
|
5190
|
+
.where('project_id', '=', project.id)
|
|
5191
|
+
.where('type', '=', 'application')
|
|
5192
|
+
.orderBy('created_at', 'asc')
|
|
5193
|
+
.executeTakeFirst());
|
|
4543
5194
|
if (appScenario) {
|
|
4544
5195
|
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4545
5196
|
if (fs.existsSync(seedFile)) {
|
|
@@ -4682,7 +5333,7 @@ const editorCommand = {
|
|
|
4682
5333
|
if (!needsAnalysis) {
|
|
4683
5334
|
try {
|
|
4684
5335
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4685
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
5336
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
4686
5337
|
const db = getDatabase();
|
|
4687
5338
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4688
5339
|
if (backfillCount > 0) {
|
|
@@ -4721,7 +5372,7 @@ const editorCommand = {
|
|
|
4721
5372
|
.execute();
|
|
4722
5373
|
if (unresolved.length > 0) {
|
|
4723
5374
|
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
4724
|
-
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
5375
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
|
|
4725
5376
|
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
4726
5377
|
let pfpResolved = 0;
|
|
4727
5378
|
for (const row of unresolved) {
|
|
@@ -4804,7 +5455,7 @@ const editorCommand = {
|
|
|
4804
5455
|
// Step 1 is planning-only and may not persist state (no --feature flag).
|
|
4805
5456
|
const skipValidation = step === 2 && argv.feature;
|
|
4806
5457
|
if (!skipValidation) {
|
|
4807
|
-
const stepError = validateStepTransition(step, state?.step ?? null);
|
|
5458
|
+
const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
|
|
4808
5459
|
if (stepError) {
|
|
4809
5460
|
console.error(chalk.red(`Error: ${stepError}`));
|
|
4810
5461
|
process.exit(1);
|
|
@@ -4858,7 +5509,6 @@ const editorCommand = {
|
|
|
4858
5509
|
if (!auditOk) {
|
|
4859
5510
|
// checkAuditGate() already printed specific failure details above
|
|
4860
5511
|
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
5512
|
process.exit(1);
|
|
4863
5513
|
}
|
|
4864
5514
|
}
|