@codeyam/codeyam-cli 0.1.21 → 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/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 +235 -58
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +170 -26
- 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 +412 -78
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +354 -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__/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 +98 -3
- 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/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/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)-aIHKLB-m.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-bcbb3d49.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-DjF-soOH.js +16 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-C91yWWCI.js → index-nAvHGWbz.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-Dkas-RUS.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 +8 -2
- 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 +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 +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/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`');
|
|
@@ -2444,6 +2449,84 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2444
2449
|
}
|
|
2445
2450
|
progress.succeed('Analysis complete');
|
|
2446
2451
|
}
|
|
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
|
+
}
|
|
2529
|
+
}
|
|
2447
2530
|
// Load entities WITH metadata from database
|
|
2448
2531
|
progress.start('Loading entity metadata...');
|
|
2449
2532
|
await initializeEnvironment();
|
|
@@ -2905,7 +2988,7 @@ async function handleRegister(jsonArg) {
|
|
|
2905
2988
|
}
|
|
2906
2989
|
// Detect duplicate screenshots in the batch
|
|
2907
2990
|
if (isBatch && screenshotEntries.length > 1) {
|
|
2908
|
-
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
|
|
2991
|
+
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
|
|
2909
2992
|
const hashEntries = screenshotEntries
|
|
2910
2993
|
.map((e) => {
|
|
2911
2994
|
const hash = computeScreenshotHash(e.screenshotAbsPath);
|
|
@@ -3235,10 +3318,11 @@ function handleChange(feature) {
|
|
|
3235
3318
|
* Fetch the audit result from the server.
|
|
3236
3319
|
* Returns null if the server is unreachable.
|
|
3237
3320
|
*/
|
|
3238
|
-
async function fetchAuditResult() {
|
|
3321
|
+
async function fetchAuditResult(options) {
|
|
3239
3322
|
const port = getServerPort();
|
|
3323
|
+
const params = options?.skipTests ? '?skipTests=true' : '';
|
|
3240
3324
|
try {
|
|
3241
|
-
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3325
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
|
|
3242
3326
|
if (!res.ok)
|
|
3243
3327
|
return null;
|
|
3244
3328
|
return await res.json();
|
|
@@ -3287,8 +3371,8 @@ async function checkAuditGate() {
|
|
|
3287
3371
|
catch {
|
|
3288
3372
|
// Fall through
|
|
3289
3373
|
}
|
|
3290
|
-
// Re-check after backfill
|
|
3291
|
-
const retry = await fetchAuditResult();
|
|
3374
|
+
// Re-check after backfill — skip re-running tests (backfill is DB-only)
|
|
3375
|
+
const retry = await fetchAuditResult({ skipTests: true });
|
|
3292
3376
|
if (retry?.summary?.allPassing === true)
|
|
3293
3377
|
return true;
|
|
3294
3378
|
}
|
|
@@ -3349,6 +3433,10 @@ function printAuditGateFailures(data) {
|
|
|
3349
3433
|
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
3350
3434
|
for (const f of runnerErrors) {
|
|
3351
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}`);
|
|
3352
3440
|
}
|
|
3353
3441
|
}
|
|
3354
3442
|
if (s.functionsNameMismatch > 0) {
|
|
@@ -3356,13 +3444,36 @@ function printAuditGateFailures(data) {
|
|
|
3356
3444
|
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
3357
3445
|
for (const f of mismatch) {
|
|
3358
3446
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3447
|
+
if (f.hint)
|
|
3448
|
+
issues.push(` Fix: ${f.hint}`);
|
|
3359
3449
|
}
|
|
3360
3450
|
}
|
|
3361
|
-
if (s.missingFromGlossary > 0)
|
|
3451
|
+
if (s.missingFromGlossary > 0) {
|
|
3362
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
|
+
}
|
|
3363
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
|
+
}
|
|
3364
3471
|
const preCount = s.preExistingIncompleteEntities || 0;
|
|
3365
|
-
|
|
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) {
|
|
3366
3477
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
3367
3478
|
}
|
|
3368
3479
|
else if (preCount > 0) {
|
|
@@ -3371,6 +3482,20 @@ function printAuditGateFailures(data) {
|
|
|
3371
3482
|
else {
|
|
3372
3483
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3373
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
|
+
}
|
|
3374
3499
|
}
|
|
3375
3500
|
if (s.unassociatedScenarios > 0) {
|
|
3376
3501
|
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
@@ -3379,10 +3504,22 @@ function printAuditGateFailures(data) {
|
|
|
3379
3504
|
issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
|
|
3380
3505
|
}
|
|
3381
3506
|
}
|
|
3382
|
-
if (s.miscategorizedScenarios > 0)
|
|
3507
|
+
if (s.miscategorizedScenarios > 0) {
|
|
3383
3508
|
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3384
|
-
|
|
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) {
|
|
3385
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
|
+
}
|
|
3386
3523
|
if (issues.length > 0) {
|
|
3387
3524
|
console.error(chalk.yellow('\nAudit failures:'));
|
|
3388
3525
|
for (const issue of issues) {
|
|
@@ -3425,7 +3562,7 @@ async function handleAudit() {
|
|
|
3425
3562
|
}
|
|
3426
3563
|
// Lightweight auto-fix: backfill entity_sha on scenarios that were
|
|
3427
3564
|
// registered before entity records existed. This is fast (DB-only).
|
|
3428
|
-
//
|
|
3565
|
+
// Import analysis is NOT triggered here — it scans all files and takes
|
|
3429
3566
|
// minutes on large projects. Users should run it separately if needed.
|
|
3430
3567
|
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3431
3568
|
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
@@ -3451,8 +3588,9 @@ async function handleAudit() {
|
|
|
3451
3588
|
catch {
|
|
3452
3589
|
// Fall through — re-fetch will show remaining issues
|
|
3453
3590
|
}
|
|
3454
|
-
// Re-fetch audit results after the backfill
|
|
3455
|
-
|
|
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 });
|
|
3456
3594
|
if (!data) {
|
|
3457
3595
|
console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
|
|
3458
3596
|
process.exit(1);
|
|
@@ -3566,48 +3704,42 @@ async function handleAudit() {
|
|
|
3566
3704
|
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
3567
3705
|
console.log();
|
|
3568
3706
|
}
|
|
3569
|
-
// Incomplete entities (scenarios without analyses) —
|
|
3570
|
-
//
|
|
3571
|
-
|
|
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.
|
|
3711
|
+
const incompleteEntities = data.incompleteEntities || [];
|
|
3572
3712
|
if (incompleteEntities.length > 0) {
|
|
3573
|
-
const {
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
incompleteEntities = data.incompleteEntities || [];
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
catch {
|
|
3597
|
-
// Fall through — report remaining incomplete entities below
|
|
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());
|
|
3717
|
+
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3718
|
+
for (const e of incompleteEntities) {
|
|
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}`);
|
|
3598
3733
|
}
|
|
3599
3734
|
}
|
|
3735
|
+
else {
|
|
3736
|
+
const guidance = formatIncompleteEntityGuidance(e);
|
|
3737
|
+
console.log(` ${chalk.red('✗')} ${guidance}`);
|
|
3738
|
+
}
|
|
3600
3739
|
}
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
}
|
|
3605
|
-
if (incompleteEntities.length > 0) {
|
|
3606
|
-
const { formatIncompleteEntityGuidance } = await import('../utils/editorAudit.js');
|
|
3607
|
-
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3608
|
-
for (const e of incompleteEntities) {
|
|
3609
|
-
const guidance = formatIncompleteEntityGuidance(e);
|
|
3610
|
-
console.log(` ${chalk.red('✗')} ${guidance}`);
|
|
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.'));
|
|
3611
3743
|
}
|
|
3612
3744
|
console.log();
|
|
3613
3745
|
}
|
|
@@ -3631,6 +3763,10 @@ async function handleAudit() {
|
|
|
3631
3763
|
else {
|
|
3632
3764
|
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to create entity records, then re-run audit to backfill.'));
|
|
3633
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
|
+
}
|
|
3634
3770
|
console.log();
|
|
3635
3771
|
}
|
|
3636
3772
|
// Miscategorized scenarios (component scenarios using real page URLs)
|
|
@@ -3721,17 +3857,6 @@ async function handleAudit() {
|
|
|
3721
3857
|
if (!allOk) {
|
|
3722
3858
|
process.exit(1);
|
|
3723
3859
|
}
|
|
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
3860
|
}
|
|
3736
3861
|
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
3737
3862
|
/**
|
|
@@ -3938,14 +4063,14 @@ async function handleScenarioCoverage() {
|
|
|
3938
4063
|
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3939
4064
|
try {
|
|
3940
4065
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3941
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4066
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
3942
4067
|
const db = getDatabase();
|
|
3943
4068
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3944
4069
|
if (backfillCount > 0) {
|
|
3945
4070
|
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3946
4071
|
await handleAnalyzeImports({ silent: true });
|
|
3947
4072
|
// Run backfill after analysis
|
|
3948
|
-
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
4073
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
|
|
3949
4074
|
const entities = await loadEntities({});
|
|
3950
4075
|
if (entities && entities.length > 0) {
|
|
3951
4076
|
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
@@ -4442,14 +4567,205 @@ function handleEditorDebug(args) {
|
|
|
4442
4567
|
console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
|
|
4443
4568
|
console.log();
|
|
4444
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
|
+
}
|
|
4445
4761
|
// ─── Command definition ───────────────────────────────────────────────
|
|
4446
4762
|
const editorCommand = {
|
|
4447
4763
|
command: 'editor [step] [json]',
|
|
4448
4764
|
describe: 'Editor mode guided workflow',
|
|
4449
4765
|
builder: (yargs) => {
|
|
4450
4766
|
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)';
|
|
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)';
|
|
4453
4769
|
let builder = yargs
|
|
4454
4770
|
.positional('step', {
|
|
4455
4771
|
type: 'string',
|
|
@@ -4591,7 +4907,9 @@ const editorCommand = {
|
|
|
4591
4907
|
'Ask user what to build next and restart codeyam editor workflow';
|
|
4592
4908
|
}
|
|
4593
4909
|
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
4594
|
-
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'));
|
|
4595
4913
|
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
4596
4914
|
console.log();
|
|
4597
4915
|
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
@@ -4610,6 +4928,11 @@ const editorCommand = {
|
|
|
4610
4928
|
await handleAnalyzeImports();
|
|
4611
4929
|
return;
|
|
4612
4930
|
}
|
|
4931
|
+
// Subcommand: codeyam editor manual-entity <JSON|@file>
|
|
4932
|
+
if (argv.step === 'manual-entity') {
|
|
4933
|
+
await handleManualEntity(argv.json || '');
|
|
4934
|
+
return;
|
|
4935
|
+
}
|
|
4613
4936
|
// Subcommand: codeyam editor dependents <EntityName>
|
|
4614
4937
|
if (argv.step === 'dependents') {
|
|
4615
4938
|
await handleDependents(argv.json || '');
|
|
@@ -4850,13 +5173,24 @@ const editorCommand = {
|
|
|
4850
5173
|
try {
|
|
4851
5174
|
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4852
5175
|
const seedDb = getDb();
|
|
4853
|
-
|
|
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
|
|
4854
5179
|
.selectFrom('editor_scenarios')
|
|
4855
5180
|
.select(['id', 'name', 'type'])
|
|
4856
5181
|
.where('project_id', '=', project.id)
|
|
4857
|
-
.where('
|
|
5182
|
+
.where('url', '=', '/')
|
|
5183
|
+
.where('component_name', 'is', null)
|
|
4858
5184
|
.orderBy('created_at', 'asc')
|
|
4859
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());
|
|
4860
5194
|
if (appScenario) {
|
|
4861
5195
|
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4862
5196
|
if (fs.existsSync(seedFile)) {
|
|
@@ -4999,7 +5333,7 @@ const editorCommand = {
|
|
|
4999
5333
|
if (!needsAnalysis) {
|
|
5000
5334
|
try {
|
|
5001
5335
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
5002
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
5336
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
5003
5337
|
const db = getDatabase();
|
|
5004
5338
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
5005
5339
|
if (backfillCount > 0) {
|
|
@@ -5038,7 +5372,7 @@ const editorCommand = {
|
|
|
5038
5372
|
.execute();
|
|
5039
5373
|
if (unresolved.length > 0) {
|
|
5040
5374
|
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
5041
|
-
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
5375
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
|
|
5042
5376
|
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
5043
5377
|
let pfpResolved = 0;
|
|
5044
5378
|
for (const row of unresolved) {
|