@codeyam/codeyam-cli 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/analyzer-template/.build-info.json +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/packages/ai/src/lib/astScopes/methodSemantics.ts +135 -0
- package/analyzer-template/packages/ai/src/lib/astScopes/nodeToSource.ts +19 -0
- package/analyzer-template/packages/ai/src/lib/astScopes/paths.ts +11 -4
- package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +31 -8
- 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/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/analyzeNextRoute.ts +8 -3
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +239 -58
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +1684 -1462
- package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js +47 -0
- package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +71 -0
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
- package/codeyam-cli/src/commands/editor.js +545 -94
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js +23 -0
- package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +456 -1
- 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 +140 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +50 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.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__/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 +4 -2
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +136 -5
- 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 +60 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +42 -2
- package/codeyam-cli/src/utils/editorSeedAdapter.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/testRunner.js +199 -1
- package/codeyam-cli/src/utils/testRunner.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +30 -11
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.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/build/client/assets/MiniClaudeChat-CQENLSrF.js +36 -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-CJzc4vOH.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DMv5ESGo.js +96 -0
- package/codeyam-cli/src/webserver/build/client/assets/{editorPreview-NTuLi4Xg.js → editorPreview-CluPkvXJ.js} +6 -6
- 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-BA5L8bU-.js → entity._sha.scenarios._scenarioId.dev-CmLO432x.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-D4dmRgvO.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-5025e428.js → manifest-1a45e154.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{root-BCx1S8Z3.js → root-D2_tktnk.js} +6 -6
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-By5slFjw.js +16 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-C91yWWCI.js → index-DXaOwBnm.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-Dkas-RUS.js → init-CLG1LjQM.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-NZmUqQv6.js +688 -0
- package/codeyam-cli/src/webserver/build/server/index.js +1 -1
- package/codeyam-cli/src/webserver/build-info.json +5 -5
- package/codeyam-cli/src/webserver/editorProxy.js +55 -3
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- 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 +8 -2
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/codeyam-editor-reference.md +8 -6
- package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +42 -34
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +2 -2
- package/package.json +1 -1
- package/packages/ai/src/lib/astScopes/methodSemantics.js +99 -0
- package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
- package/packages/ai/src/lib/astScopes/nodeToSource.js +16 -0
- package/packages/ai/src/lib/astScopes/nodeToSource.js.map +1 -1
- package/packages/ai/src/lib/astScopes/paths.js +12 -3
- package/packages/ai/src/lib/astScopes/paths.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +23 -9
- 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/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/analyzeNextRoute.js +5 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +120 -28
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +1368 -1193
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DODLxLcw.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-Dx-h1rJK.js +0 -130
- package/codeyam-cli/src/webserver/build/client/assets/globals-BrPXT1iR.css +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-C1kjC9UJ.js +0 -13
- package/codeyam-cli/src/webserver/build/server/assets/server-build-pulXLTrG.js +0 -640
|
@@ -357,7 +357,7 @@ function printExtractionPlanInstructions() {
|
|
|
357
357
|
*/
|
|
358
358
|
function printComponentCaptureInstructions() {
|
|
359
359
|
checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
360
|
-
console.log(chalk.dim(' This creates app/
|
|
360
|
+
console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
|
|
361
361
|
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
362
362
|
checkbox('For each visual component:');
|
|
363
363
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
@@ -369,8 +369,8 @@ function printComponentCaptureInstructions() {
|
|
|
369
369
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
370
370
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
371
371
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
372
|
-
console.log(chalk.dim(' Remix: app/routes/
|
|
373
|
-
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'));
|
|
374
374
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
375
375
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
376
376
|
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
@@ -383,9 +383,9 @@ function printComponentCaptureInstructions() {
|
|
|
383
383
|
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
384
384
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
385
385
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
386
|
-
console.log(chalk.dim(` "url":"/
|
|
386
|
+
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
387
387
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
388
|
-
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/...).'));
|
|
389
389
|
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
390
390
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
391
391
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
@@ -395,7 +395,7 @@ function printComponentCaptureInstructions() {
|
|
|
395
395
|
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
396
396
|
console.log();
|
|
397
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":"/
|
|
398
|
+
console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
|
|
399
399
|
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
|
|
400
400
|
}
|
|
401
401
|
/**
|
|
@@ -448,7 +448,9 @@ function stopGate(current, opts) {
|
|
|
448
448
|
// Skip step 1 (no feature name yet — task starts at step 2)
|
|
449
449
|
if (current >= 2) {
|
|
450
450
|
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
451
|
-
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'));
|
|
452
454
|
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
453
455
|
console.log();
|
|
454
456
|
let taskTitle;
|
|
@@ -1361,6 +1363,9 @@ function printStep9(root, feature) {
|
|
|
1361
1363
|
console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
|
|
1362
1364
|
console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
|
|
1363
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.'));
|
|
1364
1369
|
console.log();
|
|
1365
1370
|
stopGate(9);
|
|
1366
1371
|
}
|
|
@@ -1962,7 +1967,7 @@ function printMigrateStep8(root) {
|
|
|
1962
1967
|
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1963
1968
|
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1964
1969
|
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1965
|
-
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)');
|
|
1966
1971
|
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1967
1972
|
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1968
1973
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
@@ -1973,7 +1978,7 @@ function printMigrateStep8(root) {
|
|
|
1973
1978
|
printComponentCaptureInstructions();
|
|
1974
1979
|
console.log();
|
|
1975
1980
|
console.log(chalk.bold('Verify:'));
|
|
1976
|
-
checkbox('Run `codeyam editor analyze-imports` to populate import graph');
|
|
1981
|
+
checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
|
|
1977
1982
|
checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1978
1983
|
checkbox('Run `codeyam editor audit` to check completeness');
|
|
1979
1984
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
@@ -2351,10 +2356,14 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2351
2356
|
glossaryEntries = sanitizeGlossaryEntries(parsed);
|
|
2352
2357
|
}
|
|
2353
2358
|
catch {
|
|
2359
|
+
if (options.silent)
|
|
2360
|
+
return;
|
|
2354
2361
|
console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
|
|
2355
2362
|
process.exit(1);
|
|
2356
2363
|
}
|
|
2357
2364
|
if (glossaryEntries.length === 0) {
|
|
2365
|
+
if (options.silent)
|
|
2366
|
+
return;
|
|
2358
2367
|
console.error(chalk.red('Error: glossary.json is empty.'));
|
|
2359
2368
|
process.exit(1);
|
|
2360
2369
|
}
|
|
@@ -2423,6 +2432,15 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2423
2432
|
catch {
|
|
2424
2433
|
// Non-fatal — fall back to analyzing all files
|
|
2425
2434
|
}
|
|
2435
|
+
// If specific file paths were requested, scope analysis to only those files.
|
|
2436
|
+
// The full filePaths set is still used for the import graph below.
|
|
2437
|
+
if (options.filePaths && options.filePaths.length > 0) {
|
|
2438
|
+
const requested = new Set(options.filePaths);
|
|
2439
|
+
targetFilePaths = targetFilePaths.filter((fp) => requested.has(fp));
|
|
2440
|
+
if (targetFilePaths.length === 0 && !options.silent) {
|
|
2441
|
+
console.log(chalk.dim('Requested file(s) already analyzed or not in glossary — skipping analysis.'));
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2426
2444
|
// Run data-structure-only analysis for entities that need it.
|
|
2427
2445
|
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2428
2446
|
// The analyzer will discover and create them from file paths.
|
|
@@ -2439,11 +2457,93 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2439
2457
|
catch (err) {
|
|
2440
2458
|
progress.fail('Analysis failed');
|
|
2441
2459
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2460
|
+
if (options.silent) {
|
|
2461
|
+
// Internal caller — don't kill the process, let the caller handle it
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2442
2464
|
console.error(chalk.red(`Error: ${msg}`));
|
|
2443
2465
|
process.exit(1);
|
|
2444
2466
|
}
|
|
2445
2467
|
progress.succeed('Analysis complete');
|
|
2446
2468
|
}
|
|
2469
|
+
// Surface analysis errors if any occurred
|
|
2470
|
+
const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
|
|
2471
|
+
if (fs.existsSync(errorReportPath)) {
|
|
2472
|
+
if (!options.silent) {
|
|
2473
|
+
console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
|
|
2474
|
+
console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
|
|
2475
|
+
console.log(chalk.dim('Affected entities will be missing from the import graph.'));
|
|
2476
|
+
}
|
|
2477
|
+
// Write structured analysis-failures.json for the audit to detect.
|
|
2478
|
+
// Parse the error report to find which entities failed, and cross-reference
|
|
2479
|
+
// with the files we tried to analyze.
|
|
2480
|
+
try {
|
|
2481
|
+
const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
2482
|
+
const errorContent = fs.readFileSync(errorReportPath, 'utf8');
|
|
2483
|
+
const existingFailures = readAnalysisFailures(root);
|
|
2484
|
+
// Parse error messages to identify failed file paths.
|
|
2485
|
+
// Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
|
|
2486
|
+
// or more generic "CodeYam Error: <message>"
|
|
2487
|
+
const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
|
|
2488
|
+
const failedEntityNames = new Set();
|
|
2489
|
+
let match;
|
|
2490
|
+
while ((match = entityErrorRegex.exec(errorContent)) !== null) {
|
|
2491
|
+
const name = match[1] || match[2];
|
|
2492
|
+
if (name)
|
|
2493
|
+
failedEntityNames.add(name);
|
|
2494
|
+
}
|
|
2495
|
+
// Map failed entity names back to file paths from targetFilePaths
|
|
2496
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
2497
|
+
let glossary = [];
|
|
2498
|
+
try {
|
|
2499
|
+
glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
2500
|
+
}
|
|
2501
|
+
catch {
|
|
2502
|
+
// Non-fatal
|
|
2503
|
+
}
|
|
2504
|
+
// Also: any targetFilePaths that didn't produce entities are failures
|
|
2505
|
+
const now = new Date().toISOString();
|
|
2506
|
+
const updatedFailures = { ...existingFailures };
|
|
2507
|
+
if (failedEntityNames.size > 0) {
|
|
2508
|
+
for (const name of failedEntityNames) {
|
|
2509
|
+
const entry = glossary.find((e) => e.name === name);
|
|
2510
|
+
if (entry) {
|
|
2511
|
+
updatedFailures[entry.filePath] = {
|
|
2512
|
+
entityName: name,
|
|
2513
|
+
error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
|
|
2514
|
+
failedAt: now,
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
else if (targetFilePaths.length > 0) {
|
|
2520
|
+
// Couldn't parse specific entity names — mark all target files
|
|
2521
|
+
// that we attempted to analyze as potentially failed
|
|
2522
|
+
for (const fp of targetFilePaths) {
|
|
2523
|
+
const entry = glossary.find((e) => e.filePath === fp);
|
|
2524
|
+
updatedFailures[fp] = {
|
|
2525
|
+
entityName: entry?.name || path.basename(fp, path.extname(fp)),
|
|
2526
|
+
error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
|
|
2527
|
+
failedAt: now,
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
writeAnalysisFailures(root, updatedFailures);
|
|
2532
|
+
}
|
|
2533
|
+
catch {
|
|
2534
|
+
// Non-fatal — failure tracking is best-effort
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
else {
|
|
2538
|
+
// No errors — clear any stale failure tracking
|
|
2539
|
+
try {
|
|
2540
|
+
const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
2541
|
+
writeAnalysisFailures(root, {});
|
|
2542
|
+
}
|
|
2543
|
+
catch {
|
|
2544
|
+
// Non-fatal
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2447
2547
|
// Load entities WITH metadata from database
|
|
2448
2548
|
progress.start('Loading entity metadata...');
|
|
2449
2549
|
await initializeEnvironment();
|
|
@@ -2905,7 +3005,7 @@ async function handleRegister(jsonArg) {
|
|
|
2905
3005
|
}
|
|
2906
3006
|
// Detect duplicate screenshots in the batch
|
|
2907
3007
|
if (isBatch && screenshotEntries.length > 1) {
|
|
2908
|
-
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
|
|
3008
|
+
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
|
|
2909
3009
|
const hashEntries = screenshotEntries
|
|
2910
3010
|
.map((e) => {
|
|
2911
3011
|
const hash = computeScreenshotHash(e.screenshotAbsPath);
|
|
@@ -3235,10 +3335,11 @@ function handleChange(feature) {
|
|
|
3235
3335
|
* Fetch the audit result from the server.
|
|
3236
3336
|
* Returns null if the server is unreachable.
|
|
3237
3337
|
*/
|
|
3238
|
-
async function fetchAuditResult() {
|
|
3338
|
+
async function fetchAuditResult(options) {
|
|
3239
3339
|
const port = getServerPort();
|
|
3340
|
+
const params = options?.skipTests ? '?skipTests=true' : '';
|
|
3240
3341
|
try {
|
|
3241
|
-
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3342
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
|
|
3242
3343
|
if (!res.ok)
|
|
3243
3344
|
return null;
|
|
3244
3345
|
return await res.json();
|
|
@@ -3287,8 +3388,8 @@ async function checkAuditGate() {
|
|
|
3287
3388
|
catch {
|
|
3288
3389
|
// Fall through
|
|
3289
3390
|
}
|
|
3290
|
-
// Re-check after backfill
|
|
3291
|
-
const retry = await fetchAuditResult();
|
|
3391
|
+
// Re-check after backfill — skip re-running tests (backfill is DB-only)
|
|
3392
|
+
const retry = await fetchAuditResult({ skipTests: true });
|
|
3292
3393
|
if (retry?.summary?.allPassing === true)
|
|
3293
3394
|
return true;
|
|
3294
3395
|
}
|
|
@@ -3349,6 +3450,10 @@ function printAuditGateFailures(data) {
|
|
|
3349
3450
|
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
3350
3451
|
for (const f of runnerErrors) {
|
|
3351
3452
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3453
|
+
if (f.hint)
|
|
3454
|
+
issues.push(` ${f.hint}`);
|
|
3455
|
+
else if (f.errorMessage)
|
|
3456
|
+
issues.push(` Error: ${f.errorMessage}`);
|
|
3352
3457
|
}
|
|
3353
3458
|
}
|
|
3354
3459
|
if (s.functionsNameMismatch > 0) {
|
|
@@ -3356,13 +3461,44 @@ function printAuditGateFailures(data) {
|
|
|
3356
3461
|
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
3357
3462
|
for (const f of mismatch) {
|
|
3358
3463
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3464
|
+
if (f.hint)
|
|
3465
|
+
issues.push(` Fix: ${f.hint}`);
|
|
3359
3466
|
}
|
|
3360
3467
|
}
|
|
3361
|
-
if (s.missingFromGlossary > 0)
|
|
3468
|
+
if (s.missingFromGlossary > 0) {
|
|
3362
3469
|
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
3470
|
+
const missingGlossary = data.missingFromGlossary || [];
|
|
3471
|
+
for (const m of missingGlossary) {
|
|
3472
|
+
issues.push(` → ${m.name} (${m.filePath})`);
|
|
3473
|
+
}
|
|
3474
|
+
const missingPaths = missingGlossary
|
|
3475
|
+
.map((m) => m.filePath)
|
|
3476
|
+
.filter(Boolean);
|
|
3477
|
+
if (missingPaths.length > 0) {
|
|
3478
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports ${missingPaths.join(' ')}\``);
|
|
3479
|
+
}
|
|
3480
|
+
else {
|
|
3481
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3363
3484
|
if (s.incompleteEntities > 0) {
|
|
3485
|
+
// Check for persistent analysis failures
|
|
3486
|
+
let analysisFailures = {};
|
|
3487
|
+
try {
|
|
3488
|
+
const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
|
|
3489
|
+
if (fs.existsSync(failuresPath)) {
|
|
3490
|
+
analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
catch {
|
|
3494
|
+
// Non-fatal
|
|
3495
|
+
}
|
|
3364
3496
|
const preCount = s.preExistingIncompleteEntities || 0;
|
|
3365
|
-
|
|
3497
|
+
const hasFailures = Object.keys(analysisFailures).length > 0;
|
|
3498
|
+
if (hasFailures) {
|
|
3499
|
+
issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
|
|
3500
|
+
}
|
|
3501
|
+
else if (preCount > 0 && preCount === s.incompleteEntities) {
|
|
3366
3502
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
3367
3503
|
}
|
|
3368
3504
|
else if (preCount > 0) {
|
|
@@ -3371,18 +3507,52 @@ function printAuditGateFailures(data) {
|
|
|
3371
3507
|
else {
|
|
3372
3508
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3373
3509
|
}
|
|
3510
|
+
const incomplete = data.incompleteEntities || [];
|
|
3511
|
+
for (const e of incomplete) {
|
|
3512
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
3513
|
+
if (failureEntry) {
|
|
3514
|
+
issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
|
|
3515
|
+
issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
|
|
3516
|
+
}
|
|
3517
|
+
else {
|
|
3518
|
+
issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
if (!hasFailures) {
|
|
3522
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
|
|
3523
|
+
}
|
|
3374
3524
|
}
|
|
3375
3525
|
if (s.unassociatedScenarios > 0) {
|
|
3376
|
-
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
3377
3526
|
const unassociated = data.unassociatedScenarios || [];
|
|
3527
|
+
const unassocPaths = unassociated
|
|
3528
|
+
.map((u) => u.filePath)
|
|
3529
|
+
.filter(Boolean);
|
|
3530
|
+
if (unassocPaths.length > 0) {
|
|
3531
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports ${unassocPaths.join(' ')}\` then re-run audit`);
|
|
3532
|
+
}
|
|
3533
|
+
else {
|
|
3534
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
3535
|
+
}
|
|
3378
3536
|
for (const u of unassociated) {
|
|
3379
3537
|
issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
|
|
3380
3538
|
}
|
|
3381
3539
|
}
|
|
3382
|
-
if (s.miscategorizedScenarios > 0)
|
|
3540
|
+
if (s.miscategorizedScenarios > 0) {
|
|
3383
3541
|
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3384
|
-
|
|
3542
|
+
const miscategorized = data.miscategorizedScenarios || [];
|
|
3543
|
+
for (const m of miscategorized) {
|
|
3544
|
+
issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
|
|
3545
|
+
}
|
|
3546
|
+
issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
|
|
3547
|
+
}
|
|
3548
|
+
if (s.scenariosNeedingRecapture > 0) {
|
|
3385
3549
|
issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
|
|
3550
|
+
const recapture = data.scenariosNeedingRecapture || [];
|
|
3551
|
+
for (const r of recapture) {
|
|
3552
|
+
issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
|
|
3553
|
+
}
|
|
3554
|
+
issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
|
|
3555
|
+
}
|
|
3386
3556
|
if (issues.length > 0) {
|
|
3387
3557
|
console.error(chalk.yellow('\nAudit failures:'));
|
|
3388
3558
|
for (const issue of issues) {
|
|
@@ -3417,16 +3587,16 @@ function printAuditGateFailures(data) {
|
|
|
3417
3587
|
* which glossary components have registered scenarios and which functions
|
|
3418
3588
|
* have test files. Exits with code 1 if anything is missing.
|
|
3419
3589
|
*/
|
|
3420
|
-
async function handleAudit() {
|
|
3590
|
+
async function handleAudit(options) {
|
|
3421
3591
|
let data = await fetchAuditResult();
|
|
3422
3592
|
if (!data) {
|
|
3423
3593
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3424
3594
|
process.exit(1);
|
|
3425
3595
|
}
|
|
3426
|
-
//
|
|
3427
|
-
//
|
|
3428
|
-
//
|
|
3429
|
-
//
|
|
3596
|
+
// Two-phase auto-fix for entity associations:
|
|
3597
|
+
// Phase 1: DB-only backfill — fast, matches existing entities to scenarios.
|
|
3598
|
+
// Phase 2: If backfill fails, targeted analyze-imports for only the
|
|
3599
|
+
// specific unresolved files (1-3 files, not the full glossary scan).
|
|
3430
3600
|
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3431
3601
|
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
3432
3602
|
let autoRemediationFailed = false;
|
|
@@ -3451,20 +3621,65 @@ async function handleAudit() {
|
|
|
3451
3621
|
catch {
|
|
3452
3622
|
// Fall through — re-fetch will show remaining issues
|
|
3453
3623
|
}
|
|
3454
|
-
// Re-fetch audit results after the backfill
|
|
3455
|
-
|
|
3624
|
+
// Re-fetch audit results after the backfill — skip re-running tests
|
|
3625
|
+
// since they haven't changed (backfill is DB-only).
|
|
3626
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
3456
3627
|
if (!data) {
|
|
3457
3628
|
console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
|
|
3458
3629
|
process.exit(1);
|
|
3459
3630
|
}
|
|
3460
|
-
// If issues persist after backfill,
|
|
3631
|
+
// If issues persist after DB-only backfill, run targeted analyze-imports
|
|
3632
|
+
// for ONLY the specific files that need entities. This is fast (1-3 files)
|
|
3633
|
+
// unlike full analyze-imports which scans all glossary entries.
|
|
3634
|
+
const incompleteAfterBackfill = data.incompleteEntities || [];
|
|
3635
|
+
const unassociatedAfterBackfill = data.unassociatedScenarios || [];
|
|
3636
|
+
if (incompleteAfterBackfill.length > 0 ||
|
|
3637
|
+
unassociatedAfterBackfill.length > 0) {
|
|
3638
|
+
const { determineTargetedAnalysisPaths } = await import('../utils/editorAudit.js');
|
|
3639
|
+
const targetPaths = determineTargetedAnalysisPaths({
|
|
3640
|
+
unassociatedScenarios: unassociatedAfterBackfill,
|
|
3641
|
+
incompleteEntities: incompleteAfterBackfill,
|
|
3642
|
+
});
|
|
3643
|
+
if (targetPaths.length > 0) {
|
|
3644
|
+
console.log(chalk.dim(`Running targeted analysis for ${targetPaths.length} file${targetPaths.length !== 1 ? 's' : ''}...`));
|
|
3645
|
+
try {
|
|
3646
|
+
await handleAnalyzeImports({
|
|
3647
|
+
silent: true,
|
|
3648
|
+
filePaths: targetPaths,
|
|
3649
|
+
});
|
|
3650
|
+
// Retry backfill after analysis created entities
|
|
3651
|
+
const entities = await loadEntities({});
|
|
3652
|
+
if (entities && entities.length > 0) {
|
|
3653
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
3654
|
+
const freshDb = getDb();
|
|
3655
|
+
await backfillEntityShaOnScenarios(freshDb, entities.map((e) => ({
|
|
3656
|
+
sha: e.sha,
|
|
3657
|
+
name: e.name,
|
|
3658
|
+
filePath: e.filePath || '',
|
|
3659
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3660
|
+
e.metadata?.namedExport === false,
|
|
3661
|
+
})));
|
|
3662
|
+
}
|
|
3663
|
+
// Re-fetch audit results after targeted analysis
|
|
3664
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
3665
|
+
if (!data) {
|
|
3666
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after analysis.'));
|
|
3667
|
+
process.exit(1);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
catch {
|
|
3671
|
+
// Targeted analysis failed — fall through to show remaining issues
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
// Check if issues persist after all remediation attempts
|
|
3461
3676
|
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3462
3677
|
const unassociatedAfterFix = data.unassociatedScenarios || [];
|
|
3463
3678
|
if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
|
|
3464
3679
|
autoRemediationFailed = true;
|
|
3465
3680
|
}
|
|
3466
3681
|
}
|
|
3467
|
-
|
|
3682
|
+
let { components, functions, summary } = data;
|
|
3468
3683
|
console.log();
|
|
3469
3684
|
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
3470
3685
|
console.log();
|
|
@@ -3563,51 +3778,53 @@ async function handleAudit() {
|
|
|
3563
3778
|
for (const m of missingFromGlossary) {
|
|
3564
3779
|
console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
|
|
3565
3780
|
}
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
let incompleteEntities = data.incompleteEntities || [];
|
|
3572
|
-
if (incompleteEntities.length > 0) {
|
|
3573
|
-
const { getIncompleteEntityFilePaths } = await import('../utils/editorAudit.js');
|
|
3574
|
-
try {
|
|
3575
|
-
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3576
|
-
const db = getDatabase();
|
|
3577
|
-
const filePaths = await getIncompleteEntityFilePaths(db, incompleteEntities);
|
|
3578
|
-
if (filePaths.length > 0) {
|
|
3579
|
-
console.log(chalk.dim(`Running targeted import analysis for ${filePaths.length} incomplete entit${filePaths.length !== 1 ? 'ies' : 'y'}...`));
|
|
3580
|
-
const root = getProjectRoot();
|
|
3581
|
-
try {
|
|
3582
|
-
const { runAnalysisForEntities } = await import('../utils/analysisRunner.js');
|
|
3583
|
-
await runAnalysisForEntities({
|
|
3584
|
-
projectRoot: root,
|
|
3585
|
-
filePaths,
|
|
3586
|
-
progress: new ProgressReporter(),
|
|
3587
|
-
onlyDataStructure: true,
|
|
3588
|
-
});
|
|
3589
|
-
// Re-fetch audit results after targeted fix
|
|
3590
|
-
const refreshed = await fetchAuditResult();
|
|
3591
|
-
if (refreshed) {
|
|
3592
|
-
data = refreshed;
|
|
3593
|
-
incompleteEntities = data.incompleteEntities || [];
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
catch {
|
|
3597
|
-
// Fall through — report remaining incomplete entities below
|
|
3598
|
-
}
|
|
3599
|
-
}
|
|
3781
|
+
const mgPaths = missingFromGlossary
|
|
3782
|
+
.map((m) => m.filePath)
|
|
3783
|
+
.filter(Boolean);
|
|
3784
|
+
if (mgPaths.length > 0) {
|
|
3785
|
+
console.log(chalk.yellow(` Add these to .codeyam/glossary.json and run \`codeyam editor analyze-imports ${mgPaths.join(' ')}\``));
|
|
3600
3786
|
}
|
|
3601
|
-
|
|
3602
|
-
|
|
3787
|
+
else {
|
|
3788
|
+
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
3603
3789
|
}
|
|
3790
|
+
console.log();
|
|
3604
3791
|
}
|
|
3792
|
+
// Incomplete entities (scenarios without analyses) — report with guidance.
|
|
3793
|
+
// We intentionally do NOT run analysis here: it starts the analyzer
|
|
3794
|
+
// template which is slow even for a few files. Users should run
|
|
3795
|
+
// `codeyam editor analyze-imports` separately if needed.
|
|
3796
|
+
const incompleteEntities = data.incompleteEntities || [];
|
|
3605
3797
|
if (incompleteEntities.length > 0) {
|
|
3606
|
-
const { formatIncompleteEntityGuidance } = await import('../utils/editorAudit.js');
|
|
3798
|
+
const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
|
|
3799
|
+
const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
3800
|
+
// Check for persistent analysis failures
|
|
3801
|
+
const analysisFailures = readAnalysisFailures(getProjectRoot());
|
|
3607
3802
|
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3608
3803
|
for (const e of incompleteEntities) {
|
|
3609
|
-
|
|
3610
|
-
|
|
3804
|
+
// Check if this entity has a persistent failure
|
|
3805
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
3806
|
+
if (failureEntry) {
|
|
3807
|
+
// Show manual analysis instructions instead of "run analyze-imports"
|
|
3808
|
+
const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
|
|
3809
|
+
const guidance = formatManualAnalysisGuidance({
|
|
3810
|
+
name: e.name,
|
|
3811
|
+
filePath: filePath || '',
|
|
3812
|
+
scenarioCount: e.scenarioCount,
|
|
3813
|
+
error: failureEntry.error,
|
|
3814
|
+
});
|
|
3815
|
+
// Print each line with proper indentation
|
|
3816
|
+
for (const line of guidance.split('\n')) {
|
|
3817
|
+
console.log(` ${chalk.red('✗')} ${line}`);
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
else {
|
|
3821
|
+
const guidance = formatIncompleteEntityGuidance(e);
|
|
3822
|
+
console.log(` ${chalk.red('✗')} ${guidance}`);
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
3826
|
+
if (fs.existsSync(incompleteErrorReportPath)) {
|
|
3827
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3611
3828
|
}
|
|
3612
3829
|
console.log();
|
|
3613
3830
|
}
|
|
@@ -3624,12 +3841,22 @@ async function handleAudit() {
|
|
|
3624
3841
|
console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
|
|
3625
3842
|
}
|
|
3626
3843
|
}
|
|
3844
|
+
const uaPaths = unassociatedScenarios
|
|
3845
|
+
.map((u) => u.filePath)
|
|
3846
|
+
.filter(Boolean);
|
|
3847
|
+
const uaCmd = uaPaths.length > 0
|
|
3848
|
+
? `codeyam editor analyze-imports ${uaPaths.join(' ')}`
|
|
3849
|
+
: 'codeyam editor analyze-imports';
|
|
3627
3850
|
if (autoRemediationFailed) {
|
|
3628
3851
|
console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
|
|
3629
|
-
console.log(chalk.yellow(
|
|
3852
|
+
console.log(chalk.yellow(` Run \`${uaCmd}\` to see the full error output.`));
|
|
3630
3853
|
}
|
|
3631
3854
|
else {
|
|
3632
|
-
console.log(chalk.yellow(
|
|
3855
|
+
console.log(chalk.yellow(` Run \`${uaCmd}\` to create entity records, then re-run audit to backfill.`));
|
|
3856
|
+
}
|
|
3857
|
+
const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
3858
|
+
if (fs.existsSync(unassocErrorReportPath)) {
|
|
3859
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3633
3860
|
}
|
|
3634
3861
|
console.log();
|
|
3635
3862
|
}
|
|
@@ -3658,7 +3885,26 @@ async function handleAudit() {
|
|
|
3658
3885
|
: `${s.status.status}`;
|
|
3659
3886
|
console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
|
|
3660
3887
|
}
|
|
3661
|
-
|
|
3888
|
+
if (options?.fix) {
|
|
3889
|
+
// --fix: auto-recapture stale scenarios instead of just reporting them
|
|
3890
|
+
const { shouldAutoRecapture } = await import('../utils/editorAudit.js');
|
|
3891
|
+
if (shouldAutoRecapture({ fix: true, scenariosNeedingRecapture })) {
|
|
3892
|
+
console.log(chalk.cyan(' --fix: auto-recapturing stale scenarios...'));
|
|
3893
|
+
console.log();
|
|
3894
|
+
await handleRecaptureStale();
|
|
3895
|
+
// Re-fetch audit results so the summary and exit code reflect
|
|
3896
|
+
// the post-fix state, not the pre-fix state.
|
|
3897
|
+
const refreshed = await fetchAuditResult({ skipTests: true });
|
|
3898
|
+
if (refreshed) {
|
|
3899
|
+
data = refreshed;
|
|
3900
|
+
({ components, functions, summary } = data);
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
else {
|
|
3905
|
+
console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
|
|
3906
|
+
console.log(chalk.dim(' Or: codeyam editor audit --fix (to auto-recapture)'));
|
|
3907
|
+
}
|
|
3662
3908
|
console.log();
|
|
3663
3909
|
}
|
|
3664
3910
|
// Duplicate glossary names (warning, not a failure)
|
|
@@ -3721,17 +3967,6 @@ async function handleAudit() {
|
|
|
3721
3967
|
if (!allOk) {
|
|
3722
3968
|
process.exit(1);
|
|
3723
3969
|
}
|
|
3724
|
-
// Auto-run analyze-imports when audit passes — this builds the import graph
|
|
3725
|
-
// so the App tab can show component/function dependencies for each page.
|
|
3726
|
-
// Previously this was a manual step that Claude had to remember to run.
|
|
3727
|
-
console.log(chalk.dim('Building import graph...'));
|
|
3728
|
-
try {
|
|
3729
|
-
await handleAnalyzeImports({ silent: true });
|
|
3730
|
-
}
|
|
3731
|
-
catch {
|
|
3732
|
-
// Non-fatal — audit passed, import graph is a bonus
|
|
3733
|
-
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
3734
|
-
}
|
|
3735
3970
|
}
|
|
3736
3971
|
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
3737
3972
|
/**
|
|
@@ -3938,14 +4173,14 @@ async function handleScenarioCoverage() {
|
|
|
3938
4173
|
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3939
4174
|
try {
|
|
3940
4175
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3941
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4176
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
3942
4177
|
const db = getDatabase();
|
|
3943
4178
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3944
4179
|
if (backfillCount > 0) {
|
|
3945
4180
|
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3946
4181
|
await handleAnalyzeImports({ silent: true });
|
|
3947
4182
|
// Run backfill after analysis
|
|
3948
|
-
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
4183
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
|
|
3949
4184
|
const entities = await loadEntities({});
|
|
3950
4185
|
if (entities && entities.length > 0) {
|
|
3951
4186
|
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
@@ -4442,14 +4677,205 @@ function handleEditorDebug(args) {
|
|
|
4442
4677
|
console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
|
|
4443
4678
|
console.log();
|
|
4444
4679
|
}
|
|
4680
|
+
// ─── Manual entity analysis ───────────────────────────────────────────
|
|
4681
|
+
/**
|
|
4682
|
+
* `codeyam editor manual-entity <JSON|@file>`
|
|
4683
|
+
*
|
|
4684
|
+
* Creates entity and analysis records from Claude-provided metadata when
|
|
4685
|
+
* automated analyze-imports fails. This unblocks the audit gate without
|
|
4686
|
+
* needing the analyzer template to parse the source file.
|
|
4687
|
+
*/
|
|
4688
|
+
async function handleManualEntity(jsonArg) {
|
|
4689
|
+
const root = getProjectRoot();
|
|
4690
|
+
// Parse JSON input (supports @file.json convention)
|
|
4691
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
4692
|
+
if (parsed.error || !parsed.body) {
|
|
4693
|
+
console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
|
|
4694
|
+
console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
|
|
4695
|
+
console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
|
|
4696
|
+
process.exit(1);
|
|
4697
|
+
}
|
|
4698
|
+
const input = parsed.body;
|
|
4699
|
+
// Validate input
|
|
4700
|
+
const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
|
|
4701
|
+
// Read glossary for validation
|
|
4702
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
4703
|
+
let glossaryEntries = [];
|
|
4704
|
+
try {
|
|
4705
|
+
glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
4706
|
+
}
|
|
4707
|
+
catch {
|
|
4708
|
+
// Empty glossary — validation will still check file existence
|
|
4709
|
+
}
|
|
4710
|
+
const errors = validateManualEntityInput(input, glossaryEntries, {
|
|
4711
|
+
fileExists: (p) => fs.existsSync(path.join(root, p)),
|
|
4712
|
+
});
|
|
4713
|
+
if (errors.length > 0) {
|
|
4714
|
+
console.error(chalk.red('Validation errors:'));
|
|
4715
|
+
for (const err of errors) {
|
|
4716
|
+
console.error(chalk.red(` • ${err}`));
|
|
4717
|
+
}
|
|
4718
|
+
process.exit(1);
|
|
4719
|
+
}
|
|
4720
|
+
// Read source file and compute SHA
|
|
4721
|
+
const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
|
|
4722
|
+
const { generateSha } = await import('../../../packages/database/index.js');
|
|
4723
|
+
const entitySha = generateSha(input.filePath, input.name, sourceContent);
|
|
4724
|
+
// Get project and branch
|
|
4725
|
+
await initializeEnvironment();
|
|
4726
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
4727
|
+
let projectSlug;
|
|
4728
|
+
try {
|
|
4729
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
4730
|
+
projectSlug = config.projectSlug;
|
|
4731
|
+
}
|
|
4732
|
+
catch {
|
|
4733
|
+
console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
|
|
4734
|
+
process.exit(1);
|
|
4735
|
+
}
|
|
4736
|
+
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
4737
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4738
|
+
const db = getDatabase();
|
|
4739
|
+
// Convert type info to dataForMocks format
|
|
4740
|
+
const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
|
|
4741
|
+
// Create entity record
|
|
4742
|
+
const entityMetadata = {
|
|
4743
|
+
importedExports: (input.importedExports || []).map((ie) => ({
|
|
4744
|
+
name: ie.name,
|
|
4745
|
+
filePath: ie.filePath,
|
|
4746
|
+
})),
|
|
4747
|
+
manuallyAnalyzed: true,
|
|
4748
|
+
};
|
|
4749
|
+
// Check if entity already exists
|
|
4750
|
+
const existingEntity = await db
|
|
4751
|
+
.selectFrom('entities')
|
|
4752
|
+
.select('sha')
|
|
4753
|
+
.where('sha', '=', entitySha)
|
|
4754
|
+
.executeTakeFirst();
|
|
4755
|
+
if (!existingEntity) {
|
|
4756
|
+
await db
|
|
4757
|
+
.insertInto('entities')
|
|
4758
|
+
.values({
|
|
4759
|
+
sha: entitySha,
|
|
4760
|
+
project_id: project.id,
|
|
4761
|
+
name: input.name,
|
|
4762
|
+
entity_type: input.entityType,
|
|
4763
|
+
file_path: input.filePath,
|
|
4764
|
+
metadata: JSON.stringify(entityMetadata),
|
|
4765
|
+
})
|
|
4766
|
+
.onConflict((oc) => oc.column('sha').doNothing())
|
|
4767
|
+
.execute();
|
|
4768
|
+
console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
4769
|
+
}
|
|
4770
|
+
else {
|
|
4771
|
+
// Update metadata on existing entity
|
|
4772
|
+
await db
|
|
4773
|
+
.updateTable('entities')
|
|
4774
|
+
.set({
|
|
4775
|
+
metadata: JSON.stringify(entityMetadata),
|
|
4776
|
+
})
|
|
4777
|
+
.where('sha', '=', entitySha)
|
|
4778
|
+
.execute();
|
|
4779
|
+
console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
4780
|
+
}
|
|
4781
|
+
// Create entity_branch record
|
|
4782
|
+
await db
|
|
4783
|
+
.insertInto('entity_branches')
|
|
4784
|
+
.values({
|
|
4785
|
+
entity_sha: entitySha,
|
|
4786
|
+
branch_id: branch.id,
|
|
4787
|
+
active: true,
|
|
4788
|
+
})
|
|
4789
|
+
.onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
|
|
4790
|
+
.execute();
|
|
4791
|
+
// Create analysis record
|
|
4792
|
+
const { randomUUID } = await import('crypto');
|
|
4793
|
+
const analysisId = randomUUID();
|
|
4794
|
+
const now = new Date().toISOString();
|
|
4795
|
+
const analysisMetadata = {
|
|
4796
|
+
scenariosDataStructure: {
|
|
4797
|
+
dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
|
|
4798
|
+
},
|
|
4799
|
+
mergedDataStructure: {
|
|
4800
|
+
dependencySchemas: null,
|
|
4801
|
+
},
|
|
4802
|
+
manuallyAnalyzed: true,
|
|
4803
|
+
};
|
|
4804
|
+
const analysisStatus = {
|
|
4805
|
+
startedAt: now,
|
|
4806
|
+
finishedAt: now,
|
|
4807
|
+
steps: [],
|
|
4808
|
+
errors: [],
|
|
4809
|
+
};
|
|
4810
|
+
// Check if analysis already exists for this entity
|
|
4811
|
+
const existingAnalysis = await db
|
|
4812
|
+
.selectFrom('analyses')
|
|
4813
|
+
.select('id')
|
|
4814
|
+
.where('entity_sha', '=', entitySha)
|
|
4815
|
+
.executeTakeFirst();
|
|
4816
|
+
if (!existingAnalysis) {
|
|
4817
|
+
await db
|
|
4818
|
+
.insertInto('analyses')
|
|
4819
|
+
.values({
|
|
4820
|
+
id: analysisId,
|
|
4821
|
+
project_id: project.id,
|
|
4822
|
+
entity_sha: entitySha,
|
|
4823
|
+
entity_name: input.name,
|
|
4824
|
+
entity_type: input.entityType,
|
|
4825
|
+
file_path: input.filePath,
|
|
4826
|
+
status: JSON.stringify(analysisStatus),
|
|
4827
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
4828
|
+
})
|
|
4829
|
+
.execute();
|
|
4830
|
+
console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
|
|
4831
|
+
}
|
|
4832
|
+
else {
|
|
4833
|
+
// Update existing analysis with manual metadata
|
|
4834
|
+
await db
|
|
4835
|
+
.updateTable('analyses')
|
|
4836
|
+
.set({
|
|
4837
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
4838
|
+
status: JSON.stringify(analysisStatus),
|
|
4839
|
+
})
|
|
4840
|
+
.where('entity_sha', '=', entitySha)
|
|
4841
|
+
.execute();
|
|
4842
|
+
console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
|
|
4843
|
+
}
|
|
4844
|
+
// Backfill entity_sha on scenarios
|
|
4845
|
+
const backfillResult = await backfillEntityShaOnScenarios(db, [
|
|
4846
|
+
{
|
|
4847
|
+
sha: entitySha,
|
|
4848
|
+
name: input.name,
|
|
4849
|
+
filePath: input.filePath,
|
|
4850
|
+
isDefaultExport: false,
|
|
4851
|
+
},
|
|
4852
|
+
]);
|
|
4853
|
+
if (backfillResult.updated > 0) {
|
|
4854
|
+
console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
|
|
4855
|
+
}
|
|
4856
|
+
// Clear from analysis failures tracker
|
|
4857
|
+
const failures = readAnalysisFailures(root);
|
|
4858
|
+
if (failures[input.filePath]) {
|
|
4859
|
+
const updated = clearFailureForPath(failures, input.filePath);
|
|
4860
|
+
writeAnalysisFailures(root, updated);
|
|
4861
|
+
console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
|
|
4862
|
+
}
|
|
4863
|
+
console.log();
|
|
4864
|
+
console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
|
|
4865
|
+
console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
|
|
4866
|
+
console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
|
|
4867
|
+
console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
|
|
4868
|
+
console.log();
|
|
4869
|
+
console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
|
|
4870
|
+
}
|
|
4445
4871
|
// ─── Command definition ───────────────────────────────────────────────
|
|
4446
4872
|
const editorCommand = {
|
|
4447
4873
|
command: 'editor [step] [json]',
|
|
4448
4874
|
describe: 'Editor mode guided workflow',
|
|
4449
4875
|
builder: (yargs) => {
|
|
4450
4876
|
const stepDescription = IS_INTERNAL_BUILD
|
|
4451
|
-
? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
|
|
4452
|
-
: 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
|
|
4877
|
+
? '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)'
|
|
4878
|
+
: '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)';
|
|
4453
4879
|
let builder = yargs
|
|
4454
4880
|
.positional('step', {
|
|
4455
4881
|
type: 'string',
|
|
@@ -4480,6 +4906,11 @@ const editorCommand = {
|
|
|
4480
4906
|
alias: 'p',
|
|
4481
4907
|
describe: 'Port to run the web server on',
|
|
4482
4908
|
default: 3111,
|
|
4909
|
+
})
|
|
4910
|
+
.option('fix', {
|
|
4911
|
+
type: 'boolean',
|
|
4912
|
+
describe: 'For audit: also recapture stale scenarios after displaying results',
|
|
4913
|
+
default: false,
|
|
4483
4914
|
});
|
|
4484
4915
|
if (IS_INTERNAL_BUILD) {
|
|
4485
4916
|
builder = builder
|
|
@@ -4591,7 +5022,9 @@ const editorCommand = {
|
|
|
4591
5022
|
'Ask user what to build next and restart codeyam editor workflow';
|
|
4592
5023
|
}
|
|
4593
5024
|
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
4594
|
-
console.log(chalk.cyan('
|
|
5025
|
+
console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
|
|
5026
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
5027
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
4595
5028
|
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
4596
5029
|
console.log();
|
|
4597
5030
|
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
@@ -4605,9 +5038,16 @@ const editorCommand = {
|
|
|
4605
5038
|
fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
|
|
4606
5039
|
return;
|
|
4607
5040
|
}
|
|
4608
|
-
// Subcommand: codeyam editor analyze-imports
|
|
5041
|
+
// Subcommand: codeyam editor analyze-imports [file1.tsx file2.tsx ...]
|
|
4609
5042
|
if (argv.step === 'analyze-imports') {
|
|
4610
|
-
await
|
|
5043
|
+
const { parseAnalyzeImportsArgs } = await import('./editorAnalyzeImportsArgs.js');
|
|
5044
|
+
const requestedPaths = parseAnalyzeImportsArgs(argv.json, argv._);
|
|
5045
|
+
await handleAnalyzeImports(requestedPaths.length > 0 ? { filePaths: requestedPaths } : {});
|
|
5046
|
+
return;
|
|
5047
|
+
}
|
|
5048
|
+
// Subcommand: codeyam editor manual-entity <JSON|@file>
|
|
5049
|
+
if (argv.step === 'manual-entity') {
|
|
5050
|
+
await handleManualEntity(argv.json || '');
|
|
4611
5051
|
return;
|
|
4612
5052
|
}
|
|
4613
5053
|
// Subcommand: codeyam editor dependents <EntityName>
|
|
@@ -4615,9 +5055,9 @@ const editorCommand = {
|
|
|
4615
5055
|
await handleDependents(argv.json || '');
|
|
4616
5056
|
return;
|
|
4617
5057
|
}
|
|
4618
|
-
// Subcommand: codeyam editor audit
|
|
5058
|
+
// Subcommand: codeyam editor audit [--fix]
|
|
4619
5059
|
if (argv.step === 'audit') {
|
|
4620
|
-
await handleAudit();
|
|
5060
|
+
await handleAudit({ fix: argv.fix || false });
|
|
4621
5061
|
return;
|
|
4622
5062
|
}
|
|
4623
5063
|
// Subcommand: codeyam editor scenarios
|
|
@@ -4850,13 +5290,24 @@ const editorCommand = {
|
|
|
4850
5290
|
try {
|
|
4851
5291
|
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4852
5292
|
const seedDb = getDb();
|
|
4853
|
-
|
|
5293
|
+
// Prefer the home page scenario (url "/") since the App tab
|
|
5294
|
+
// sorts "Home" first — fall back to any application scenario.
|
|
5295
|
+
const homeScenario = await seedDb
|
|
4854
5296
|
.selectFrom('editor_scenarios')
|
|
4855
5297
|
.select(['id', 'name', 'type'])
|
|
4856
5298
|
.where('project_id', '=', project.id)
|
|
4857
|
-
.where('
|
|
5299
|
+
.where('url', '=', '/')
|
|
5300
|
+
.where('component_name', 'is', null)
|
|
4858
5301
|
.orderBy('created_at', 'asc')
|
|
4859
5302
|
.executeTakeFirst();
|
|
5303
|
+
const appScenario = homeScenario ||
|
|
5304
|
+
(await seedDb
|
|
5305
|
+
.selectFrom('editor_scenarios')
|
|
5306
|
+
.select(['id', 'name', 'type'])
|
|
5307
|
+
.where('project_id', '=', project.id)
|
|
5308
|
+
.where('type', '=', 'application')
|
|
5309
|
+
.orderBy('created_at', 'asc')
|
|
5310
|
+
.executeTakeFirst());
|
|
4860
5311
|
if (appScenario) {
|
|
4861
5312
|
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4862
5313
|
if (fs.existsSync(seedFile)) {
|
|
@@ -4999,7 +5450,7 @@ const editorCommand = {
|
|
|
4999
5450
|
if (!needsAnalysis) {
|
|
5000
5451
|
try {
|
|
5001
5452
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
5002
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
5453
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
5003
5454
|
const db = getDatabase();
|
|
5004
5455
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
5005
5456
|
if (backfillCount > 0) {
|
|
@@ -5038,7 +5489,7 @@ const editorCommand = {
|
|
|
5038
5489
|
.execute();
|
|
5039
5490
|
if (unresolved.length > 0) {
|
|
5040
5491
|
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
5041
|
-
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
5492
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
|
|
5042
5493
|
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
5043
5494
|
let pfpResolved = 0;
|
|
5044
5495
|
for (const row of unresolved) {
|