@codeyam/codeyam-cli 0.1.20 → 0.1.22
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 +1 -1
- 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 +1 -0
- 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/src/lib/loadEntity.ts +11 -4
- 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 +4 -4
- 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/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 +553 -93
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +991 -31
- 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__/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__/testRunner.test.js +217 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
- package/codeyam-cli/src/utils/analysisRunner.js +28 -1
- 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 +210 -14
- 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/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 +20 -2
- 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/testRunner.js +199 -1
- package/codeyam-cli/src/utils/testRunner.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +35 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +3 -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-CjLhfz6Z.js → index-nAvHGWbz.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-BEqlbI84.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/terminalServer.js +18 -5
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +2 -2
- 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 +1 -0
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.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/loadEntity.js +4 -4
- 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)-DII1pg_z.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-cdf2c0a7.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-B_PsTAb1.js +0 -13
- package/codeyam-cli/src/webserver/build/server/assets/server-build-YI63xTu4.js +0 -553
|
@@ -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);
|
|
@@ -210,6 +211,34 @@ function checkbox(text) {
|
|
|
210
211
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
211
212
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
212
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
|
+
}
|
|
213
242
|
/**
|
|
214
243
|
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
215
244
|
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
@@ -328,7 +357,7 @@ function printExtractionPlanInstructions() {
|
|
|
328
357
|
*/
|
|
329
358
|
function printComponentCaptureInstructions() {
|
|
330
359
|
checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
331
|
-
console.log(chalk.dim(' This creates app/
|
|
360
|
+
console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
|
|
332
361
|
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
333
362
|
checkbox('For each visual component:');
|
|
334
363
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
@@ -340,8 +369,8 @@ function printComponentCaptureInstructions() {
|
|
|
340
369
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
341
370
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
342
371
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
343
|
-
console.log(chalk.dim(' Remix: app/routes/
|
|
344
|
-
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'));
|
|
345
374
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
346
375
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
347
376
|
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
@@ -354,9 +383,9 @@ function printComponentCaptureInstructions() {
|
|
|
354
383
|
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
355
384
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
356
385
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
357
|
-
console.log(chalk.dim(` "url":"/
|
|
386
|
+
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
358
387
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
359
|
-
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/...).'));
|
|
360
389
|
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
361
390
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
362
391
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
@@ -366,7 +395,7 @@ function printComponentCaptureInstructions() {
|
|
|
366
395
|
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
367
396
|
console.log();
|
|
368
397
|
console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
|
|
369
|
-
console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/
|
|
398
|
+
console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
|
|
370
399
|
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
|
|
371
400
|
}
|
|
372
401
|
/**
|
|
@@ -419,7 +448,9 @@ function stopGate(current, opts) {
|
|
|
419
448
|
// Skip step 1 (no feature name yet — task starts at step 2)
|
|
420
449
|
if (current >= 2) {
|
|
421
450
|
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
422
|
-
console.log(chalk.cyan('
|
|
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'));
|
|
423
454
|
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
424
455
|
console.log();
|
|
425
456
|
let taskTitle;
|
|
@@ -978,6 +1009,7 @@ function printStep2(root, feature) {
|
|
|
978
1009
|
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
979
1010
|
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
980
1011
|
console.log();
|
|
1012
|
+
printDataStructureInstructions();
|
|
981
1013
|
}
|
|
982
1014
|
else {
|
|
983
1015
|
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
@@ -992,6 +1024,7 @@ function printStep2(root, feature) {
|
|
|
992
1024
|
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
993
1025
|
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
994
1026
|
console.log();
|
|
1027
|
+
printDataStructureInstructions();
|
|
995
1028
|
}
|
|
996
1029
|
stopGate(2);
|
|
997
1030
|
}
|
|
@@ -1326,7 +1359,13 @@ function printStep9(root, feature) {
|
|
|
1326
1359
|
console.log();
|
|
1327
1360
|
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1328
1361
|
console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
|
|
1329
|
-
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.'));
|
|
1330
1369
|
console.log();
|
|
1331
1370
|
stopGate(9);
|
|
1332
1371
|
}
|
|
@@ -1928,7 +1967,7 @@ function printMigrateStep8(root) {
|
|
|
1928
1967
|
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1929
1968
|
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1930
1969
|
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1931
|
-
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)');
|
|
1932
1971
|
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1933
1972
|
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1934
1973
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
@@ -2362,25 +2401,132 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2362
2401
|
// Non-fatal — scenario file paths just won't be included
|
|
2363
2402
|
}
|
|
2364
2403
|
const progress = new ProgressReporter();
|
|
2365
|
-
//
|
|
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.
|
|
2366
2432
|
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2367
2433
|
// The analyzer will discover and create them from file paths.
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
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');
|
|
2376
2451
|
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
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
|
+
}
|
|
2382
2529
|
}
|
|
2383
|
-
progress.succeed('Analysis complete');
|
|
2384
2530
|
// Load entities WITH metadata from database
|
|
2385
2531
|
progress.start('Loading entity metadata...');
|
|
2386
2532
|
await initializeEnvironment();
|
|
@@ -2736,6 +2882,8 @@ async function handleRegister(jsonArg) {
|
|
|
2736
2882
|
const isBatch = items.length > 1;
|
|
2737
2883
|
let succeeded = 0;
|
|
2738
2884
|
let failed = 0;
|
|
2885
|
+
let warnings = 0;
|
|
2886
|
+
const issues = [];
|
|
2739
2887
|
const screenshotEntries = [];
|
|
2740
2888
|
for (let i = 0; i < items.length; i++) {
|
|
2741
2889
|
const body = items[i];
|
|
@@ -2805,11 +2953,14 @@ async function handleRegister(jsonArg) {
|
|
|
2805
2953
|
}
|
|
2806
2954
|
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2807
2955
|
}
|
|
2956
|
+
const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
|
|
2808
2957
|
if (!res.ok) {
|
|
2809
2958
|
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2810
2959
|
failed++;
|
|
2811
2960
|
}
|
|
2812
2961
|
else {
|
|
2962
|
+
if (resultIssues.length > 0)
|
|
2963
|
+
warnings++;
|
|
2813
2964
|
succeeded++;
|
|
2814
2965
|
// Collect screenshot paths for duplicate detection
|
|
2815
2966
|
if (data.screenshotCaptured &&
|
|
@@ -2822,6 +2973,10 @@ async function handleRegister(jsonArg) {
|
|
|
2822
2973
|
});
|
|
2823
2974
|
}
|
|
2824
2975
|
}
|
|
2976
|
+
// Collect issues from this registration
|
|
2977
|
+
for (const issue of resultIssues) {
|
|
2978
|
+
issues.push(`"${issue.scenarioName}": ${issue.message}`);
|
|
2979
|
+
}
|
|
2825
2980
|
}
|
|
2826
2981
|
catch (error) {
|
|
2827
2982
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -2833,7 +2988,7 @@ async function handleRegister(jsonArg) {
|
|
|
2833
2988
|
}
|
|
2834
2989
|
// Detect duplicate screenshots in the batch
|
|
2835
2990
|
if (isBatch && screenshotEntries.length > 1) {
|
|
2836
|
-
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
|
|
2991
|
+
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
|
|
2837
2992
|
const hashEntries = screenshotEntries
|
|
2838
2993
|
.map((e) => {
|
|
2839
2994
|
const hash = computeScreenshotHash(e.screenshotAbsPath);
|
|
@@ -2859,9 +3014,22 @@ async function handleRegister(jsonArg) {
|
|
|
2859
3014
|
}
|
|
2860
3015
|
}
|
|
2861
3016
|
if (isBatch) {
|
|
2862
|
-
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
|
+
}
|
|
2863
3031
|
}
|
|
2864
|
-
if (failed > 0) {
|
|
3032
|
+
if (failed > 0 || warnings > 0) {
|
|
2865
3033
|
process.exit(1);
|
|
2866
3034
|
}
|
|
2867
3035
|
}
|
|
@@ -3150,10 +3318,11 @@ function handleChange(feature) {
|
|
|
3150
3318
|
* Fetch the audit result from the server.
|
|
3151
3319
|
* Returns null if the server is unreachable.
|
|
3152
3320
|
*/
|
|
3153
|
-
async function fetchAuditResult() {
|
|
3321
|
+
async function fetchAuditResult(options) {
|
|
3154
3322
|
const port = getServerPort();
|
|
3323
|
+
const params = options?.skipTests ? '?skipTests=true' : '';
|
|
3155
3324
|
try {
|
|
3156
|
-
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3325
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
|
|
3157
3326
|
if (!res.ok)
|
|
3158
3327
|
return null;
|
|
3159
3328
|
return await res.json();
|
|
@@ -3176,17 +3345,15 @@ async function checkAuditGate() {
|
|
|
3176
3345
|
return true; // Server unreachable — don't block
|
|
3177
3346
|
if (data.summary?.allPassing === true)
|
|
3178
3347
|
return true;
|
|
3179
|
-
//
|
|
3180
|
-
//
|
|
3181
|
-
const {
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
}
|
|
3189
|
-
// Backfill entity_sha on scenarios registered before entities existed
|
|
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)) {
|
|
3190
3357
|
try {
|
|
3191
3358
|
const entities = await loadEntities({});
|
|
3192
3359
|
if (entities && entities.length > 0) {
|
|
@@ -3204,8 +3371,8 @@ async function checkAuditGate() {
|
|
|
3204
3371
|
catch {
|
|
3205
3372
|
// Fall through
|
|
3206
3373
|
}
|
|
3207
|
-
// Re-check after
|
|
3208
|
-
const retry = await fetchAuditResult();
|
|
3374
|
+
// Re-check after backfill — skip re-running tests (backfill is DB-only)
|
|
3375
|
+
const retry = await fetchAuditResult({ skipTests: true });
|
|
3209
3376
|
if (retry?.summary?.allPassing === true)
|
|
3210
3377
|
return true;
|
|
3211
3378
|
}
|
|
@@ -3228,6 +3395,8 @@ function printAuditGateFailures(data) {
|
|
|
3228
3395
|
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
3229
3396
|
for (const c of missing) {
|
|
3230
3397
|
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3398
|
+
if (c.hint)
|
|
3399
|
+
issues.push(` Fix: ${c.hint}`);
|
|
3231
3400
|
}
|
|
3232
3401
|
}
|
|
3233
3402
|
if (s.componentsWithErrors > 0) {
|
|
@@ -3241,7 +3410,15 @@ function printAuditGateFailures(data) {
|
|
|
3241
3410
|
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
3242
3411
|
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
3243
3412
|
for (const f of missing) {
|
|
3244
|
-
|
|
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
|
+
}
|
|
3245
3422
|
}
|
|
3246
3423
|
}
|
|
3247
3424
|
if (s.functionsFailing > 0) {
|
|
@@ -3256,6 +3433,10 @@ function printAuditGateFailures(data) {
|
|
|
3256
3433
|
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
3257
3434
|
for (const f of runnerErrors) {
|
|
3258
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}`);
|
|
3259
3440
|
}
|
|
3260
3441
|
}
|
|
3261
3442
|
if (s.functionsNameMismatch > 0) {
|
|
@@ -3263,13 +3444,36 @@ function printAuditGateFailures(data) {
|
|
|
3263
3444
|
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
3264
3445
|
for (const f of mismatch) {
|
|
3265
3446
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3447
|
+
if (f.hint)
|
|
3448
|
+
issues.push(` Fix: ${f.hint}`);
|
|
3266
3449
|
}
|
|
3267
3450
|
}
|
|
3268
|
-
if (s.missingFromGlossary > 0)
|
|
3451
|
+
if (s.missingFromGlossary > 0) {
|
|
3269
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
|
+
}
|
|
3270
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
|
+
}
|
|
3271
3471
|
const preCount = s.preExistingIncompleteEntities || 0;
|
|
3272
|
-
|
|
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) {
|
|
3273
3477
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
3274
3478
|
}
|
|
3275
3479
|
else if (preCount > 0) {
|
|
@@ -3278,6 +3482,20 @@ function printAuditGateFailures(data) {
|
|
|
3278
3482
|
else {
|
|
3279
3483
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3280
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
|
+
}
|
|
3281
3499
|
}
|
|
3282
3500
|
if (s.unassociatedScenarios > 0) {
|
|
3283
3501
|
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
@@ -3286,10 +3504,22 @@ function printAuditGateFailures(data) {
|
|
|
3286
3504
|
issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
|
|
3287
3505
|
}
|
|
3288
3506
|
}
|
|
3289
|
-
if (s.miscategorizedScenarios > 0)
|
|
3507
|
+
if (s.miscategorizedScenarios > 0) {
|
|
3290
3508
|
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3291
|
-
|
|
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) {
|
|
3292
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
|
+
}
|
|
3293
3523
|
if (issues.length > 0) {
|
|
3294
3524
|
console.error(chalk.yellow('\nAudit failures:'));
|
|
3295
3525
|
for (const issue of issues) {
|
|
@@ -3330,22 +3560,16 @@ async function handleAudit() {
|
|
|
3330
3560
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3331
3561
|
process.exit(1);
|
|
3332
3562
|
}
|
|
3333
|
-
//
|
|
3334
|
-
//
|
|
3335
|
-
//
|
|
3336
|
-
//
|
|
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.
|
|
3337
3567
|
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3338
3568
|
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
3339
3569
|
let autoRemediationFailed = false;
|
|
3340
3570
|
if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
|
|
3341
3571
|
const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
|
|
3342
|
-
console.log(chalk.dim(`
|
|
3343
|
-
try {
|
|
3344
|
-
await handleAnalyzeImports({ silent: true });
|
|
3345
|
-
}
|
|
3346
|
-
catch {
|
|
3347
|
-
// Fall through — the audit will still report them
|
|
3348
|
-
}
|
|
3572
|
+
console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
|
|
3349
3573
|
// Backfill entity_sha on scenarios that were registered before entities existed
|
|
3350
3574
|
try {
|
|
3351
3575
|
const entities = await loadEntities({});
|
|
@@ -3364,13 +3588,14 @@ async function handleAudit() {
|
|
|
3364
3588
|
catch {
|
|
3365
3589
|
// Fall through — re-fetch will show remaining issues
|
|
3366
3590
|
}
|
|
3367
|
-
// Re-fetch audit results after the
|
|
3368
|
-
|
|
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 });
|
|
3369
3594
|
if (!data) {
|
|
3370
|
-
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.'));
|
|
3371
3596
|
process.exit(1);
|
|
3372
3597
|
}
|
|
3373
|
-
// If issues persist after
|
|
3598
|
+
// If issues persist after backfill, flag it so we show clear guidance
|
|
3374
3599
|
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3375
3600
|
const unassociatedAfterFix = data.unassociatedScenarios || [];
|
|
3376
3601
|
if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
|
|
@@ -3407,6 +3632,9 @@ async function handleAudit() {
|
|
|
3407
3632
|
}
|
|
3408
3633
|
else {
|
|
3409
3634
|
detail = chalk.red(' — no scenarios registered');
|
|
3635
|
+
if (c.hint) {
|
|
3636
|
+
detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
|
|
3637
|
+
}
|
|
3410
3638
|
}
|
|
3411
3639
|
// Show file path for failing components always, for OK only when name is ambiguous
|
|
3412
3640
|
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
@@ -3450,9 +3678,16 @@ async function handleAudit() {
|
|
|
3450
3678
|
break;
|
|
3451
3679
|
case 'missing':
|
|
3452
3680
|
default:
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
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
|
+
}
|
|
3456
3691
|
break;
|
|
3457
3692
|
}
|
|
3458
3693
|
console.log(` ${icon} ${f.name}${detail}`);
|
|
@@ -3469,19 +3704,42 @@ async function handleAudit() {
|
|
|
3469
3704
|
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
3470
3705
|
console.log();
|
|
3471
3706
|
}
|
|
3472
|
-
// 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.
|
|
3473
3711
|
const incompleteEntities = data.incompleteEntities || [];
|
|
3474
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());
|
|
3475
3717
|
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3476
3718
|
for (const e of incompleteEntities) {
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
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
|
+
}
|
|
3482
3739
|
}
|
|
3483
|
-
|
|
3484
|
-
|
|
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.'));
|
|
3485
3743
|
}
|
|
3486
3744
|
console.log();
|
|
3487
3745
|
}
|
|
@@ -3505,6 +3763,10 @@ async function handleAudit() {
|
|
|
3505
3763
|
else {
|
|
3506
3764
|
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
|
|
3507
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.'));
|
|
3769
|
+
}
|
|
3508
3770
|
console.log();
|
|
3509
3771
|
}
|
|
3510
3772
|
// Miscategorized scenarios (component scenarios using real page URLs)
|
|
@@ -3595,17 +3857,6 @@ async function handleAudit() {
|
|
|
3595
3857
|
if (!allOk) {
|
|
3596
3858
|
process.exit(1);
|
|
3597
3859
|
}
|
|
3598
|
-
// Auto-run analyze-imports when audit passes — this builds the import graph
|
|
3599
|
-
// so the App tab can show component/function dependencies for each page.
|
|
3600
|
-
// Previously this was a manual step that Claude had to remember to run.
|
|
3601
|
-
console.log(chalk.dim('Building import graph...'));
|
|
3602
|
-
try {
|
|
3603
|
-
await handleAnalyzeImports({ silent: true });
|
|
3604
|
-
}
|
|
3605
|
-
catch {
|
|
3606
|
-
// Non-fatal — audit passed, import graph is a bonus
|
|
3607
|
-
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
3608
|
-
}
|
|
3609
3860
|
}
|
|
3610
3861
|
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
3611
3862
|
/**
|
|
@@ -3812,14 +4063,14 @@ async function handleScenarioCoverage() {
|
|
|
3812
4063
|
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3813
4064
|
try {
|
|
3814
4065
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3815
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4066
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
3816
4067
|
const db = getDatabase();
|
|
3817
4068
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3818
4069
|
if (backfillCount > 0) {
|
|
3819
4070
|
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3820
4071
|
await handleAnalyzeImports({ silent: true });
|
|
3821
4072
|
// Run backfill after analysis
|
|
3822
|
-
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
4073
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
|
|
3823
4074
|
const entities = await loadEntities({});
|
|
3824
4075
|
if (entities && entities.length > 0) {
|
|
3825
4076
|
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
@@ -4316,14 +4567,205 @@ function handleEditorDebug(args) {
|
|
|
4316
4567
|
console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
|
|
4317
4568
|
console.log();
|
|
4318
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
|
+
}
|
|
4319
4761
|
// ─── Command definition ───────────────────────────────────────────────
|
|
4320
4762
|
const editorCommand = {
|
|
4321
4763
|
command: 'editor [step] [json]',
|
|
4322
4764
|
describe: 'Editor mode guided workflow',
|
|
4323
4765
|
builder: (yargs) => {
|
|
4324
4766
|
const stepDescription = IS_INTERNAL_BUILD
|
|
4325
|
-
? '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)'
|
|
4326
|
-
: '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)';
|
|
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)';
|
|
4327
4769
|
let builder = yargs
|
|
4328
4770
|
.positional('step', {
|
|
4329
4771
|
type: 'string',
|
|
@@ -4465,7 +4907,9 @@ const editorCommand = {
|
|
|
4465
4907
|
'Ask user what to build next and restart codeyam editor workflow';
|
|
4466
4908
|
}
|
|
4467
4909
|
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
4468
|
-
console.log(chalk.cyan('
|
|
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'));
|
|
4469
4913
|
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
4470
4914
|
console.log();
|
|
4471
4915
|
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
@@ -4484,6 +4928,11 @@ const editorCommand = {
|
|
|
4484
4928
|
await handleAnalyzeImports();
|
|
4485
4929
|
return;
|
|
4486
4930
|
}
|
|
4931
|
+
// Subcommand: codeyam editor manual-entity <JSON|@file>
|
|
4932
|
+
if (argv.step === 'manual-entity') {
|
|
4933
|
+
await handleManualEntity(argv.json || '');
|
|
4934
|
+
return;
|
|
4935
|
+
}
|
|
4487
4936
|
// Subcommand: codeyam editor dependents <EntityName>
|
|
4488
4937
|
if (argv.step === 'dependents') {
|
|
4489
4938
|
await handleDependents(argv.json || '');
|
|
@@ -4724,13 +5173,24 @@ const editorCommand = {
|
|
|
4724
5173
|
try {
|
|
4725
5174
|
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4726
5175
|
const seedDb = getDb();
|
|
4727
|
-
|
|
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
|
|
4728
5179
|
.selectFrom('editor_scenarios')
|
|
4729
5180
|
.select(['id', 'name', 'type'])
|
|
4730
5181
|
.where('project_id', '=', project.id)
|
|
4731
|
-
.where('
|
|
5182
|
+
.where('url', '=', '/')
|
|
5183
|
+
.where('component_name', 'is', null)
|
|
4732
5184
|
.orderBy('created_at', 'asc')
|
|
4733
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());
|
|
4734
5194
|
if (appScenario) {
|
|
4735
5195
|
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4736
5196
|
if (fs.existsSync(seedFile)) {
|
|
@@ -4873,7 +5333,7 @@ const editorCommand = {
|
|
|
4873
5333
|
if (!needsAnalysis) {
|
|
4874
5334
|
try {
|
|
4875
5335
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4876
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
5336
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
4877
5337
|
const db = getDatabase();
|
|
4878
5338
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4879
5339
|
if (backfillCount > 0) {
|
|
@@ -4912,7 +5372,7 @@ const editorCommand = {
|
|
|
4912
5372
|
.execute();
|
|
4913
5373
|
if (unresolved.length > 0) {
|
|
4914
5374
|
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
4915
|
-
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
5375
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
|
|
4916
5376
|
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
4917
5377
|
let pfpResolved = 0;
|
|
4918
5378
|
for (const row of unresolved) {
|