@codeyam/codeyam-cli 0.1.16 → 0.1.18

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.
Files changed (43) hide show
  1. package/analyzer-template/.build-info.json +6 -6
  2. package/analyzer-template/log.txt +3 -3
  3. package/codeyam-cli/src/commands/editor.js +187 -7
  4. package/codeyam-cli/src/commands/editor.js.map +1 -1
  5. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +310 -9
  6. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  7. package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js +137 -0
  8. package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js.map +1 -0
  9. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +66 -0
  10. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -1
  11. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +70 -0
  12. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
  13. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +51 -1
  14. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  15. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +233 -1
  16. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  17. package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js +177 -0
  18. package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js.map +1 -0
  19. package/codeyam-cli/src/utils/editorAudit.js +71 -25
  20. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  21. package/codeyam-cli/src/utils/editorRecapture.js +109 -0
  22. package/codeyam-cli/src/utils/editorRecapture.js.map +1 -0
  23. package/codeyam-cli/src/utils/editorScenarioSwitch.js +24 -2
  24. package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
  25. package/codeyam-cli/src/utils/editorScenarios.js +28 -1
  26. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  27. package/codeyam-cli/src/utils/entityChangeStatus.js +31 -3
  28. package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
  29. package/codeyam-cli/src/utils/glossaryAdd.js +74 -0
  30. package/codeyam-cli/src/utils/glossaryAdd.js.map +1 -0
  31. package/codeyam-cli/src/utils/scenariosManifest.js +22 -0
  32. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  33. package/codeyam-cli/src/webserver/build/client/assets/api.editor-recapture-stale-l0sNRNKZ.js +1 -0
  34. package/codeyam-cli/src/webserver/build/client/assets/{editor.entity.(_sha)-DN5ouXAl.js → editor.entity.(_sha)-Bnx7yUP0.js} +1 -1
  35. package/codeyam-cli/src/webserver/build/client/assets/manifest-b9d4d267.js +1 -0
  36. package/codeyam-cli/src/webserver/build/server/assets/{analysisRunner-D_1MSYeW.js → analysisRunner-CGwTN3V2.js} +1 -1
  37. package/codeyam-cli/src/webserver/build/server/assets/{index-ckWaCf_v.js → index-D4meMKy3.js} +1 -1
  38. package/codeyam-cli/src/webserver/build/server/assets/{init-ld124R4Z.js → init-odGJ_c2-.js} +1 -1
  39. package/codeyam-cli/src/webserver/build/server/assets/{server-build-DzzNZGv_.js → server-build-TmPfF7pT.js} +125 -124
  40. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  41. package/codeyam-cli/src/webserver/build-info.json +5 -5
  42. package/package.json +1 -1
  43. package/codeyam-cli/src/webserver/build/client/assets/manifest-389033be.js +0 -1
@@ -1,10 +1,10 @@
1
1
  {
2
- "buildTimestamp": "2026-03-19T01:18:46.592Z",
3
- "buildTime": 1773883126592,
4
- "gitCommit": "27da20c7d41586bf9567c2e2ac63312ba9fb9057",
2
+ "buildTimestamp": "2026-03-19T21:52:47.836Z",
3
+ "buildTime": 1773957167836,
4
+ "gitCommit": "40b059fe8e0f00686186b501bf7ad39e8cdbc22c",
5
5
  "nodeVersion": "v20.20.1",
6
6
  "contentHash": "b046e014847d5b02d10d6795839ccd0d5117627cbd0be413260824610596a63d",
7
- "buildNumber": 1104,
8
- "semanticVersion": "0.1.1104",
9
- "version": "0.1.1104 (2026-03-19T01:18+b046e01)"
7
+ "buildNumber": 1110,
8
+ "semanticVersion": "0.1.1110",
9
+ "version": "0.1.1110 (2026-03-19T21:52+b046e01)"
10
10
  }
@@ -1,7 +1,7 @@
1
1
 
2
- [3/19/2026, 1:18:46 AM] > codeyam-combo@1.0.0 mergeDependencies
3
- [3/19/2026, 1:18:46 AM] > node ./scripts/mergePackageJsonFiles.cjs
2
+ [3/19/2026, 9:52:47 PM] > codeyam-combo@1.0.0 mergeDependencies
3
+ [3/19/2026, 9:52:47 PM] > node ./scripts/mergePackageJsonFiles.cjs
4
4
 
5
5
 
6
- [3/19/2026, 1:18:46 AM] Merged dependencies into root package.json
6
+ [3/19/2026, 9:52:47 PM] Merged dependencies into root package.json
7
7
 
@@ -1449,6 +1449,7 @@ function printStep12(root, feature) {
1449
1449
  checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
1450
1450
  checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
1451
1451
  checkbox('Fix or remove any broken images before continuing');
1452
+ checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
1452
1453
  checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
1453
1454
  checkbox('Do not proceed until all checks pass');
1454
1455
  stopGate(12);
@@ -2122,6 +2123,7 @@ function printStep14(root, feature) {
2122
2123
  console.log('Commit all changes for this feature.');
2123
2124
  console.log();
2124
2125
  console.log(chalk.bold('Checklist:'));
2126
+ checkbox('Ensure all screenshots are fresh: `codeyam editor recapture-stale`');
2125
2127
  checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
2126
2128
  checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
2127
2129
  console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
@@ -2709,6 +2711,53 @@ async function handleRegister(jsonArg) {
2709
2711
  process.exit(1);
2710
2712
  }
2711
2713
  }
2714
+ // ─── Glossary-add subcommand ──────────────────────────────────────────
2715
+ /**
2716
+ * `codeyam editor glossary-add '{"name":"...", "filePath":"...", "description":"..."}'`
2717
+ *
2718
+ * Safely adds/updates entries in .codeyam/glossary.json via the CLI,
2719
+ * avoiding hand-editing that breaks on unicode characters.
2720
+ */
2721
+ async function handleGlossaryAdd(jsonArg) {
2722
+ if (!jsonArg) {
2723
+ console.error(chalk.red('Error: JSON argument required.'));
2724
+ console.error(chalk.dim(' Usage: codeyam editor glossary-add \'{"name":"DrinkCard","filePath":"app/components/DrinkCard.tsx","description":"Displays a drink item"}\''));
2725
+ console.error(chalk.dim(' For large payloads: codeyam editor glossary-add @.codeyam/tmp/entry.json'));
2726
+ process.exit(1);
2727
+ }
2728
+ const parsed = parseRegisterArg(jsonArg);
2729
+ if (parsed.error) {
2730
+ console.error(chalk.red(`Error: ${parsed.error}`));
2731
+ process.exit(1);
2732
+ }
2733
+ // Normalize to array
2734
+ const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
2735
+ // Read existing glossary
2736
+ const root = getProjectRoot();
2737
+ const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
2738
+ let existing = [];
2739
+ try {
2740
+ const raw = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
2741
+ existing = sanitizeGlossaryEntries(raw);
2742
+ }
2743
+ catch {
2744
+ // Glossary doesn't exist yet or can't be parsed — start fresh
2745
+ }
2746
+ // Merge
2747
+ const { validateGlossaryEntry, mergeGlossaryEntries } = await import('../utils/glossaryAdd.js');
2748
+ const result = mergeGlossaryEntries(existing, items);
2749
+ // Report validation errors
2750
+ for (const err of result.errors) {
2751
+ console.error(chalk.red(`Error at index ${err.index}: ${err.message}`));
2752
+ }
2753
+ if (result.added === 0 && result.updated === 0 && result.errors.length > 0) {
2754
+ process.exit(1);
2755
+ }
2756
+ // Write back with utf8 encoding to safely handle unicode
2757
+ fs.mkdirSync(path.dirname(glossaryPath), { recursive: true });
2758
+ fs.writeFileSync(glossaryPath, JSON.stringify(result.entries, null, 2), 'utf8');
2759
+ console.log(`added=${result.added} updated=${result.updated} total=${result.entries.length}`);
2760
+ }
2712
2761
  // ─── Dependents subcommand ────────────────────────────────────────────
2713
2762
  /**
2714
2763
  * `codeyam editor dependents <EntityName>`
@@ -2904,6 +2953,7 @@ function handleChange(feature) {
2904
2953
  checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
2905
2954
  printDimensionGuidance(dim, dimNames);
2906
2955
  checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
2956
+ checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
2907
2957
  checkbox('Run `codeyam editor audit` — all checks must pass');
2908
2958
  checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
2909
2959
  console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
@@ -3041,8 +3091,18 @@ function printAuditGateFailures(data) {
3041
3091
  }
3042
3092
  if (s.missingFromGlossary > 0)
3043
3093
  issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
3044
- if (s.incompleteEntities > 0)
3045
- issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3094
+ if (s.incompleteEntities > 0) {
3095
+ const preCount = s.preExistingIncompleteEntities || 0;
3096
+ if (preCount > 0 && preCount === s.incompleteEntities) {
3097
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
3098
+ }
3099
+ else if (preCount > 0) {
3100
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis (${preCount} pre-existing — not from your changes)`);
3101
+ }
3102
+ else {
3103
+ issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
3104
+ }
3105
+ }
3046
3106
  if (s.miscategorizedScenarios > 0)
3047
3107
  issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
3048
3108
  if (s.scenariosNeedingRecapture > 0)
@@ -3129,11 +3189,18 @@ async function handleAudit() {
3129
3189
  }
3130
3190
  console.log(chalk.bold('Components (scenarios):'));
3131
3191
  for (const c of components) {
3132
- const icon = c.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
3192
+ const icon = c.status === 'ok'
3193
+ ? chalk.green('✓')
3194
+ : c.status === 'needs_recapture'
3195
+ ? chalk.yellow('↻')
3196
+ : chalk.red('✗');
3133
3197
  let detail;
3134
3198
  if (c.status === 'has_errors') {
3135
3199
  detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
3136
3200
  }
3201
+ else if (c.status === 'needs_recapture') {
3202
+ detail = chalk.yellow(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''}, needs recapture`);
3203
+ }
3137
3204
  else if (c.status === 'ok') {
3138
3205
  detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
3139
3206
  }
@@ -3142,7 +3209,7 @@ async function handleAudit() {
3142
3209
  }
3143
3210
  // Show file path for failing components always, for OK only when name is ambiguous
3144
3211
  const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
3145
- const showPath = c.status !== 'ok' || isDuplicate;
3212
+ const showPath = (c.status !== 'ok' && c.status !== 'needs_recapture') || isDuplicate;
3146
3213
  const pathSuffix = showPath && c.filePath ? chalk.dim(` (${c.filePath})`) : '';
3147
3214
  console.log(` ${icon} ${c.name}${pathSuffix}${detail}`);
3148
3215
  if (c.clientErrors && c.clientErrors.length > 0) {
@@ -3256,7 +3323,7 @@ async function handleAudit() {
3256
3323
  : `${s.status.status}`;
3257
3324
  console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
3258
3325
  }
3259
- console.log(chalk.yellow(' Re-register these scenarios to capture updated screenshots.'));
3326
+ console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
3260
3327
  console.log();
3261
3328
  }
3262
3329
  // Duplicate glossary names (warning, not a failure)
@@ -3328,6 +3395,109 @@ async function handleAudit() {
3328
3395
  console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
3329
3396
  }
3330
3397
  }
3398
+ // ─── Recapture-stale subcommand ────────────────────────────────────────
3399
+ /**
3400
+ * `codeyam editor recapture-stale`
3401
+ *
3402
+ * Identifies all scenarios whose entity (or dependency) has changed but
3403
+ * whose screenshot hasn't been recaptured this session, then recaptures
3404
+ * them in batch.
3405
+ */
3406
+ async function handleRecaptureStale() {
3407
+ const port = getServerPort();
3408
+ const url = `http://localhost:${port}/api/editor-recapture-stale`;
3409
+ console.log();
3410
+ console.log(chalk.bold.cyan('━━━ Recapture Stale Scenarios ━━━'));
3411
+ console.log();
3412
+ let res;
3413
+ try {
3414
+ res = await fetch(url, { method: 'POST' });
3415
+ }
3416
+ catch (err) {
3417
+ console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
3418
+ console.error(chalk.dim(` ${err.message}`));
3419
+ process.exit(1);
3420
+ }
3421
+ if (!res.ok) {
3422
+ const body = await res.json().catch(() => null);
3423
+ console.error(chalk.red(`Error: Recapture endpoint returned ${res.status}`));
3424
+ if (body?.error)
3425
+ console.error(chalk.red(` ${body.error}`));
3426
+ process.exit(1);
3427
+ }
3428
+ // Handle JSON response (early returns like "no changes" / "all up to date")
3429
+ const contentType = res.headers.get('content-type') || '';
3430
+ if (contentType.includes('application/json')) {
3431
+ const data = await res.json();
3432
+ if (data.note) {
3433
+ console.log(chalk.dim(data.note));
3434
+ }
3435
+ else if (data.total === 0) {
3436
+ console.log(chalk.green('All scenarios are up to date.'));
3437
+ }
3438
+ console.log();
3439
+ return;
3440
+ }
3441
+ // Stream NDJSON progress events
3442
+ let total = 0;
3443
+ let recapturedCount = 0;
3444
+ let failedCount = 0;
3445
+ const reader = res.body?.getReader();
3446
+ if (!reader) {
3447
+ console.error(chalk.red('Error: No response body'));
3448
+ process.exit(1);
3449
+ }
3450
+ const decoder = new TextDecoder();
3451
+ let buffer = '';
3452
+ while (true) {
3453
+ const { done, value } = await reader.read();
3454
+ if (done)
3455
+ break;
3456
+ buffer += decoder.decode(value, { stream: true });
3457
+ const lines = buffer.split('\n');
3458
+ buffer = lines.pop() || ''; // Keep incomplete last line in buffer
3459
+ for (const line of lines) {
3460
+ if (!line.trim())
3461
+ continue;
3462
+ try {
3463
+ const event = JSON.parse(line);
3464
+ switch (event.type) {
3465
+ case 'start':
3466
+ total = event.total;
3467
+ console.log(`Found ${total} stale scenario(s). Recapturing...\n`);
3468
+ break;
3469
+ case 'capturing':
3470
+ process.stdout.write(chalk.dim(` … ${event.name}`));
3471
+ break;
3472
+ case 'success':
3473
+ // Clear the "capturing" line and print success
3474
+ process.stdout.write('\r\x1b[K');
3475
+ console.log(` ${chalk.green('✓')} ${event.name}`);
3476
+ recapturedCount++;
3477
+ break;
3478
+ case 'failure':
3479
+ process.stdout.write('\r\x1b[K');
3480
+ console.log(` ${chalk.red('✗')} ${event.name} — ${chalk.dim(event.error)}`);
3481
+ failedCount++;
3482
+ break;
3483
+ case 'done':
3484
+ // Final summary
3485
+ console.log();
3486
+ const color = failedCount > 0 ? chalk.yellow : chalk.green;
3487
+ console.log(color(`Recaptured ${recapturedCount}/${total} scenario(s).`));
3488
+ console.log();
3489
+ break;
3490
+ }
3491
+ }
3492
+ catch {
3493
+ // Skip unparseable lines
3494
+ }
3495
+ }
3496
+ }
3497
+ if (failedCount > 0) {
3498
+ process.exit(1);
3499
+ }
3500
+ }
3331
3501
  // ─── Scenarios subcommand ─────────────────────────────────────────────
3332
3502
  async function handleScenarios() {
3333
3503
  const port = getServerPort();
@@ -3938,8 +4108,8 @@ const editorCommand = {
3938
4108
  describe: 'Editor mode guided workflow',
3939
4109
  builder: (yargs) => {
3940
4110
  const stepDescription = IS_INTERNAL_BUILD
3941
- ? 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)'
3942
- : 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)';
4111
+ ? 'Step number (1-16) 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)'
4112
+ : 'Step number (1-16) 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)';
3943
4113
  let builder = yargs
3944
4114
  .positional('step', {
3945
4115
  type: 'string',
@@ -4032,6 +4202,11 @@ const editorCommand = {
4032
4202
  await handleRegister(argv.json || '');
4033
4203
  return;
4034
4204
  }
4205
+ // Subcommand: codeyam editor glossary-add '{"name":"...", ...}'
4206
+ if (argv.step === 'glossary-add') {
4207
+ await handleGlossaryAdd(argv.json || '');
4208
+ return;
4209
+ }
4035
4210
  // Subcommand: codeyam editor analyze-imports
4036
4211
  if (argv.step === 'analyze-imports') {
4037
4212
  await handleAnalyzeImports();
@@ -4057,6 +4232,11 @@ const editorCommand = {
4057
4232
  await handleScenarioCoverage();
4058
4233
  return;
4059
4234
  }
4235
+ // Subcommand: codeyam editor recapture-stale
4236
+ if (argv.step === 'recapture-stale') {
4237
+ await handleRecaptureStale();
4238
+ return;
4239
+ }
4060
4240
  // Subcommand: codeyam editor change <feature>
4061
4241
  if (argv.step === 'change') {
4062
4242
  handleChange(argv.json || '');