lean-spec 0.2.5 → 0.2.6-dev.20251125010539

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 (85) hide show
  1. package/dist/{chunk-7WXYOHZU.js → chunk-RTEGSMVL.js} +1253 -678
  2. package/dist/chunk-RTEGSMVL.js.map +1 -0
  3. package/dist/cli.js +7 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/mcp-server.js +1 -1
  6. package/package.json +2 -3
  7. package/templates/detailed/AGENTS.md +113 -0
  8. package/templates/detailed/README.md +28 -0
  9. package/templates/detailed/files/DESIGN.md +43 -0
  10. package/templates/detailed/files/PLAN.md +59 -0
  11. package/templates/detailed/files/README.md +30 -0
  12. package/templates/detailed/files/TEST.md +71 -0
  13. package/templates/examples/api-refactor/README.md +81 -0
  14. package/templates/examples/api-refactor/package.json +16 -0
  15. package/templates/examples/api-refactor/src/app.js +40 -0
  16. package/templates/examples/api-refactor/src/services/currencyService.js +43 -0
  17. package/templates/examples/api-refactor/src/services/timezoneService.js +41 -0
  18. package/templates/examples/api-refactor/src/services/weatherService.js +42 -0
  19. package/templates/examples/dark-theme/README.md +66 -0
  20. package/templates/examples/dark-theme/package.json +16 -0
  21. package/templates/examples/dark-theme/src/public/app.js +277 -0
  22. package/templates/examples/dark-theme/src/public/index.html +225 -0
  23. package/templates/examples/dark-theme/src/public/style.css +625 -0
  24. package/templates/examples/dark-theme/src/server.js +18 -0
  25. package/templates/examples/dashboard-widgets/README.md +70 -0
  26. package/templates/examples/dashboard-widgets/index.html +12 -0
  27. package/templates/examples/dashboard-widgets/package.json +22 -0
  28. package/templates/examples/dashboard-widgets/src/App.css +20 -0
  29. package/templates/examples/dashboard-widgets/src/App.jsx +16 -0
  30. package/templates/examples/dashboard-widgets/src/components/Dashboard.css +17 -0
  31. package/templates/examples/dashboard-widgets/src/components/Dashboard.jsx +15 -0
  32. package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.css +23 -0
  33. package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.jsx +16 -0
  34. package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.css +33 -0
  35. package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.jsx +28 -0
  36. package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.css +24 -0
  37. package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.jsx +22 -0
  38. package/templates/examples/dashboard-widgets/src/index.css +13 -0
  39. package/templates/examples/dashboard-widgets/src/main.jsx +10 -0
  40. package/templates/examples/dashboard-widgets/src/utils/mockData.js +30 -0
  41. package/templates/examples/dashboard-widgets/vite.config.js +6 -0
  42. package/templates/standard/AGENTS.md +113 -0
  43. package/templates/standard/README.md +4 -2
  44. package/dist/chunk-7WXYOHZU.js.map +0 -1
  45. package/templates/_shared/agents-components/core-rules-base-additions.md +0 -4
  46. package/templates/_shared/agents-components/core-rules-enterprise-additions.md +0 -4
  47. package/templates/_shared/agents-components/core-rules-shared.md +0 -1
  48. package/templates/_shared/agents-components/discovery-commands-enterprise-additions.md +0 -6
  49. package/templates/_shared/agents-components/discovery-commands-minimal-additions.md +0 -0
  50. package/templates/_shared/agents-components/discovery-commands-shared.md +0 -8
  51. package/templates/_shared/agents-components/discovery-commands-standard-additions.md +0 -3
  52. package/templates/_shared/agents-components/enterprise-approval.md +0 -10
  53. package/templates/_shared/agents-components/enterprise-compliance.md +0 -12
  54. package/templates/_shared/agents-components/enterprise-when-required.md +0 -13
  55. package/templates/_shared/agents-components/essential-commands-enterprise-additions.md +0 -29
  56. package/templates/_shared/agents-components/essential-commands-minimal-additions.md +0 -1
  57. package/templates/_shared/agents-components/essential-commands-shared.md +0 -15
  58. package/templates/_shared/agents-components/essential-commands-standard-additions.md +0 -18
  59. package/templates/_shared/agents-components/frontmatter-enterprise.md +0 -33
  60. package/templates/_shared/agents-components/frontmatter-minimal.md +0 -18
  61. package/templates/_shared/agents-components/frontmatter-standard.md +0 -23
  62. package/templates/_shared/agents-components/quality-standards-enterprise-additions.md +0 -4
  63. package/templates/_shared/agents-components/quality-standards-minimal-additions.md +0 -3
  64. package/templates/_shared/agents-components/quality-standards-shared.md +0 -6
  65. package/templates/_shared/agents-components/status-update-triggers.md +0 -14
  66. package/templates/_shared/agents-components/when-to-use-enterprise.md +0 -11
  67. package/templates/_shared/agents-components/when-to-use-minimal.md +0 -9
  68. package/templates/_shared/agents-components/when-to-use-standard.md +0 -9
  69. package/templates/_shared/agents-components/workflow-enterprise.md +0 -11
  70. package/templates/_shared/agents-components/workflow-standard-detailed.md +0 -10
  71. package/templates/_shared/agents-components/workflow-standard.md +0 -8
  72. package/templates/_shared/agents-template.hbs +0 -43
  73. package/templates/enterprise/README.md +0 -25
  74. package/templates/enterprise/agents-config.json +0 -16
  75. package/templates/enterprise/files/AGENTS.md +0 -194
  76. package/templates/enterprise/spec-template.md +0 -80
  77. package/templates/minimal/README.md +0 -18
  78. package/templates/minimal/agents-config.json +0 -13
  79. package/templates/minimal/config.json +0 -15
  80. package/templates/minimal/files/AGENTS.md +0 -116
  81. package/templates/minimal/spec-template.md +0 -25
  82. package/templates/standard/agents-config.json +0 -13
  83. package/templates/standard/files/AGENTS.md +0 -142
  84. /package/templates/{enterprise → detailed}/config.json +0 -0
  85. /package/templates/standard/{spec-template.md → files/README.md} +0 -0
@@ -3,12 +3,12 @@ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mc
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { readFileSync, existsSync } from 'fs';
5
5
  import { fileURLToPath } from 'url';
6
- import * as path4 from 'path';
6
+ import * as path15 from 'path';
7
7
  import { dirname, join, resolve } from 'path';
8
8
  import { z } from 'zod';
9
9
  import * as fs9 from 'fs/promises';
10
10
  import { readFile, writeFile } from 'fs/promises';
11
- import chalk18 from 'chalk';
11
+ import chalk19 from 'chalk';
12
12
  import matter2 from 'gray-matter';
13
13
  import yaml2 from 'js-yaml';
14
14
  import { Command } from 'commander';
@@ -16,7 +16,6 @@ import { execSync, spawn } from 'child_process';
16
16
  import ora from 'ora';
17
17
  import stripAnsi from 'strip-ansi';
18
18
  import { select } from '@inquirer/prompts';
19
- import { encoding_for_model } from 'tiktoken';
20
19
  import dayjs3 from 'dayjs';
21
20
  import { marked } from 'marked';
22
21
  import { markedTerminal } from 'marked-terminal';
@@ -42,7 +41,7 @@ var DEFAULT_CONFIG = {
42
41
  }
43
42
  };
44
43
  async function loadConfig(cwd = process.cwd()) {
45
- const configPath = path4.join(cwd, ".lean-spec", "config.json");
44
+ const configPath = path15.join(cwd, ".lean-spec", "config.json");
46
45
  try {
47
46
  const content = await fs9.readFile(configPath, "utf-8");
48
47
  const userConfig = JSON.parse(content);
@@ -54,8 +53,8 @@ async function loadConfig(cwd = process.cwd()) {
54
53
  }
55
54
  }
56
55
  async function saveConfig(config, cwd = process.cwd()) {
57
- const configDir = path4.join(cwd, ".lean-spec");
58
- const configPath = path4.join(configDir, "config.json");
56
+ const configDir = path15.join(cwd, ".lean-spec");
57
+ const configPath = path15.join(configDir, "config.json");
59
58
  await fs9.mkdir(configDir, { recursive: true });
60
59
  await fs9.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
61
60
  }
@@ -154,7 +153,7 @@ async function getGlobalNextSeq(specsDir, digits) {
154
153
  }
155
154
  }
156
155
  if (entry.name === "archived") continue;
157
- const subDir = path4.join(dir, entry.name);
156
+ const subDir = path15.join(dir, entry.name);
158
157
  await scanDirectory(subDir);
159
158
  }
160
159
  } catch {
@@ -171,7 +170,7 @@ async function getGlobalNextSeq(specsDir, digits) {
171
170
  }
172
171
  }
173
172
  async function resolveSpecPath(specPath, cwd, specsDir) {
174
- if (path4.isAbsolute(specPath)) {
173
+ if (path15.isAbsolute(specPath)) {
175
174
  try {
176
175
  await fs9.access(specPath);
177
176
  return specPath;
@@ -179,13 +178,13 @@ async function resolveSpecPath(specPath, cwd, specsDir) {
179
178
  return null;
180
179
  }
181
180
  }
182
- const cwdPath = path4.resolve(cwd, specPath);
181
+ const cwdPath = path15.resolve(cwd, specPath);
183
182
  try {
184
183
  await fs9.access(cwdPath);
185
184
  return cwdPath;
186
185
  } catch {
187
186
  }
188
- const specsPath = path4.join(specsDir, specPath);
187
+ const specsPath = path15.join(specsDir, specPath);
189
188
  try {
190
189
  await fs9.access(specsPath);
191
190
  return specsPath;
@@ -212,10 +211,10 @@ async function searchBySequence(specsDir, seqNum) {
212
211
  if (match) {
213
212
  const entrySeq = parseInt(match[1], 10);
214
213
  if (entrySeq === seqNum) {
215
- return path4.join(dir, entry.name);
214
+ return path15.join(dir, entry.name);
216
215
  }
217
216
  }
218
- const subDir = path4.join(dir, entry.name);
217
+ const subDir = path15.join(dir, entry.name);
219
218
  const result = await scanDirectory(subDir);
220
219
  if (result) return result;
221
220
  }
@@ -232,9 +231,9 @@ async function searchInAllDirectories(specsDir, specName) {
232
231
  for (const entry of entries) {
233
232
  if (!entry.isDirectory()) continue;
234
233
  if (entry.name === specName) {
235
- return path4.join(dir, entry.name);
234
+ return path15.join(dir, entry.name);
236
235
  }
237
- const subDir = path4.join(dir, entry.name);
236
+ const subDir = path15.join(dir, entry.name);
238
237
  const result = await scanDirectory(subDir);
239
238
  if (result) return result;
240
239
  }
@@ -265,7 +264,7 @@ async function getGitInfo() {
265
264
  }
266
265
  async function getProjectName(cwd = process.cwd()) {
267
266
  try {
268
- const packageJsonPath = path4.join(cwd, "package.json");
267
+ const packageJsonPath = path15.join(cwd, "package.json");
269
268
  const content = await fs9.readFile(packageJsonPath, "utf-8");
270
269
  const packageJson2 = JSON.parse(content);
271
270
  return packageJson2.name || null;
@@ -354,9 +353,9 @@ async function loadSubFiles(specDir, options = {}) {
354
353
  for (const entry of entries) {
355
354
  if (entry.name === "README.md") continue;
356
355
  if (entry.isDirectory()) continue;
357
- const filePath = path4.join(specDir, entry.name);
356
+ const filePath = path15.join(specDir, entry.name);
358
357
  const stat6 = await fs9.stat(filePath);
359
- const ext = path4.extname(entry.name).toLowerCase();
358
+ const ext = path15.extname(entry.name).toLowerCase();
360
359
  const isDocument = ext === ".md";
361
360
  const subFile = {
362
361
  name: entry.name,
@@ -382,7 +381,7 @@ async function loadSubFiles(specDir, options = {}) {
382
381
  async function loadAllSpecs(options = {}) {
383
382
  const config = await loadConfig();
384
383
  const cwd = process.cwd();
385
- const specsDir = path4.join(cwd, config.specsDir);
384
+ const specsDir = path15.join(cwd, config.specsDir);
386
385
  const specs = [];
387
386
  try {
388
387
  await fs9.access(specsDir);
@@ -396,7 +395,7 @@ async function loadAllSpecs(options = {}) {
396
395
  for (const entry of entries) {
397
396
  if (!entry.isDirectory()) continue;
398
397
  if (entry.name === "archived" && relativePath === "") continue;
399
- const entryPath = path4.join(dir, entry.name);
398
+ const entryPath = path15.join(dir, entry.name);
400
399
  const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
401
400
  if (specPattern.test(entry.name)) {
402
401
  const specFile = await getSpecFile(entryPath, config.structure.defaultFile);
@@ -445,7 +444,7 @@ async function loadAllSpecs(options = {}) {
445
444
  }
446
445
  await loadSpecsFromDir(specsDir);
447
446
  if (options.includeArchived) {
448
- const archivedPath = path4.join(specsDir, "archived");
447
+ const archivedPath = path15.join(specsDir, "archived");
449
448
  await loadSpecsFromDir(archivedPath, "archived");
450
449
  }
451
450
  const sortBy = options.sortBy || "id";
@@ -493,12 +492,12 @@ async function loadAllSpecs(options = {}) {
493
492
  async function getSpec(specPath) {
494
493
  const config = await loadConfig();
495
494
  const cwd = process.cwd();
496
- const specsDir = path4.join(cwd, config.specsDir);
495
+ const specsDir = path15.join(cwd, config.specsDir);
497
496
  let fullPath;
498
- if (path4.isAbsolute(specPath)) {
497
+ if (path15.isAbsolute(specPath)) {
499
498
  fullPath = specPath;
500
499
  } else {
501
- fullPath = path4.join(specsDir, specPath);
500
+ fullPath = path15.join(specsDir, specPath);
502
501
  }
503
502
  try {
504
503
  await fs9.access(fullPath);
@@ -510,8 +509,8 @@ async function getSpec(specPath) {
510
509
  const frontmatter = await parseFrontmatter(specFile, config);
511
510
  if (!frontmatter) return null;
512
511
  const content = await fs9.readFile(specFile, "utf-8");
513
- const relativePath = path4.relative(specsDir, fullPath);
514
- const parts = relativePath.split(path4.sep);
512
+ const relativePath = path15.relative(specsDir, fullPath);
513
+ const parts = relativePath.split(path15.sep);
515
514
  const date = parts[0] === "archived" ? parts[1] : parts[0];
516
515
  const name = parts[parts.length - 1];
517
516
  return {
@@ -551,7 +550,7 @@ async function withSpinner(text, fn, options) {
551
550
 
552
551
  // src/commands/check.ts
553
552
  function checkCommand() {
554
- return new Command("check").description("Check for sequence conflicts").option("-q, --quiet", "Brief output").action(async (options) => {
553
+ return new Command("check").description("Check for sequence conflicts").option("-q, --quiet", "Brief output").option("--json", "Output as JSON").action(async (options) => {
555
554
  const hasNoConflicts = await checkSpecs(options);
556
555
  process.exit(hasNoConflicts ? 0 : 1);
557
556
  });
@@ -559,12 +558,12 @@ function checkCommand() {
559
558
  async function checkSpecs(options = {}) {
560
559
  const config = await loadConfig();
561
560
  const cwd = process.cwd();
562
- path4.join(cwd, config.specsDir);
561
+ path15.join(cwd, config.specsDir);
563
562
  const specs = await loadAllSpecs();
564
563
  const sequenceMap = /* @__PURE__ */ new Map();
565
564
  const specPattern = createSpecDirPattern();
566
565
  for (const spec of specs) {
567
- const specName = path4.basename(spec.path);
566
+ const specName = path15.basename(spec.path);
568
567
  const match = specName.match(specPattern);
569
568
  if (match) {
570
569
  const seq = parseInt(match[1], 10);
@@ -579,30 +578,45 @@ async function checkSpecs(options = {}) {
579
578
  const conflicts = Array.from(sequenceMap.entries()).filter(([_, paths]) => paths.length > 1).sort(([a], [b]) => a - b);
580
579
  if (conflicts.length === 0) {
581
580
  if (!options.quiet && !options.silent) {
582
- console.log(chalk18.green("\u2713 No sequence conflicts detected"));
581
+ if (options.json) {
582
+ console.log(JSON.stringify({ conflicts: [], hasConflicts: false }, null, 2));
583
+ } else {
584
+ console.log(chalk19.green("\u2713 No sequence conflicts detected"));
585
+ }
583
586
  }
584
587
  return true;
585
588
  }
589
+ if (options.json) {
590
+ const jsonOutput = {
591
+ hasConflicts: true,
592
+ conflicts: conflicts.map(([seq, paths]) => ({
593
+ sequence: seq,
594
+ specs: paths
595
+ }))
596
+ };
597
+ console.log(JSON.stringify(jsonOutput, null, 2));
598
+ return false;
599
+ }
586
600
  if (!options.silent) {
587
601
  if (!options.quiet) {
588
602
  console.log("");
589
- console.log(chalk18.yellow("\u26A0\uFE0F Sequence conflicts detected:\n"));
603
+ console.log(chalk19.yellow("\u26A0\uFE0F Sequence conflicts detected:\n"));
590
604
  for (const [seq, paths] of conflicts) {
591
- console.log(chalk18.red(` Sequence ${String(seq).padStart(config.structure.sequenceDigits, "0")}:`));
605
+ console.log(chalk19.red(` Sequence ${String(seq).padStart(config.structure.sequenceDigits, "0")}:`));
592
606
  for (const p of paths) {
593
- console.log(chalk18.gray(` - ${sanitizeUserInput(p)}`));
607
+ console.log(chalk19.gray(` - ${sanitizeUserInput(p)}`));
594
608
  }
595
609
  console.log("");
596
610
  }
597
- console.log(chalk18.cyan("Tip: Use date prefix to prevent conflicts:"));
598
- console.log(chalk18.gray(' Edit .lean-spec/config.json \u2192 structure.prefix: "{YYYYMMDD}-"'));
611
+ console.log(chalk19.cyan("Tip: Use date prefix to prevent conflicts:"));
612
+ console.log(chalk19.gray(' Edit .lean-spec/config.json \u2192 structure.prefix: "{YYYYMMDD}-"'));
599
613
  console.log("");
600
- console.log(chalk18.cyan("Or rename folders manually to resolve."));
614
+ console.log(chalk19.cyan("Or rename folders manually to resolve."));
601
615
  console.log("");
602
616
  } else {
603
617
  console.log("");
604
- console.log(chalk18.yellow(`\u26A0\uFE0F Conflict warning: ${conflicts.length} sequence conflict(s) detected`));
605
- console.log(chalk18.gray("Run: lean-spec check"));
618
+ console.log(chalk19.yellow(`\u26A0\uFE0F Conflict warning: ${conflicts.length} sequence conflict(s) detected`));
619
+ console.log(chalk19.gray("Run: lean-spec check"));
606
620
  console.log("");
607
621
  }
608
622
  }
@@ -655,7 +669,7 @@ function createCommand() {
655
669
  async function createSpec(name, options = {}) {
656
670
  const config = await loadConfig();
657
671
  const cwd = process.cwd();
658
- const specsDir = path4.join(cwd, config.specsDir);
672
+ const specsDir = path15.join(cwd, config.specsDir);
659
673
  await fs9.mkdir(specsDir, { recursive: true });
660
674
  const seq = await getGlobalNextSeq(specsDir, config.structure.sequenceDigits);
661
675
  let specRelativePath;
@@ -676,8 +690,8 @@ async function createSpec(name, options = {}) {
676
690
  } else {
677
691
  throw new Error(`Unknown pattern: ${config.structure.pattern}`);
678
692
  }
679
- const specDir = path4.join(specsDir, specRelativePath);
680
- const specFile = path4.join(specDir, config.structure.defaultFile);
693
+ const specDir = path15.join(specsDir, specRelativePath);
694
+ const specFile = path15.join(specDir, config.structure.defaultFile);
681
695
  try {
682
696
  await fs9.access(specDir);
683
697
  throw new Error(`Spec already exists: ${sanitizeUserInput(specDir)}`);
@@ -687,7 +701,7 @@ async function createSpec(name, options = {}) {
687
701
  }
688
702
  }
689
703
  await fs9.mkdir(specDir, { recursive: true });
690
- const templatesDir = path4.join(cwd, ".lean-spec", "templates");
704
+ const templatesDir = path15.join(cwd, ".lean-spec", "templates");
691
705
  let templateName;
692
706
  if (options.template) {
693
707
  if (config.templates?.[options.template]) {
@@ -699,13 +713,33 @@ async function createSpec(name, options = {}) {
699
713
  } else {
700
714
  templateName = config.template || "spec-template.md";
701
715
  }
702
- const templatePath = path4.join(templatesDir, templateName);
716
+ let templatePath = path15.join(templatesDir, templateName);
717
+ try {
718
+ await fs9.access(templatePath);
719
+ } catch {
720
+ const legacyPath = path15.join(templatesDir, "spec-template.md");
721
+ try {
722
+ await fs9.access(legacyPath);
723
+ templatePath = legacyPath;
724
+ templateName = "spec-template.md";
725
+ } catch {
726
+ const readmePath = path15.join(templatesDir, "README.md");
727
+ try {
728
+ await fs9.access(readmePath);
729
+ templatePath = readmePath;
730
+ templateName = "README.md";
731
+ } catch {
732
+ throw new Error(`Template not found: ${templatePath}. Run: lean-spec init`);
733
+ }
734
+ }
735
+ }
703
736
  let content;
737
+ let varContext;
704
738
  try {
705
739
  const template = await fs9.readFile(templatePath, "utf-8");
706
740
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
707
741
  const title = options.title || name;
708
- const varContext = await buildVariableContext(config, { name: title, date });
742
+ varContext = await buildVariableContext(config, { name: title, date });
709
743
  content = resolveVariables(template, varContext);
710
744
  const parsed = matter2(content, {
711
745
  engines: {
@@ -747,8 +781,29 @@ ${options.description}`
747
781
  throw new Error(`Template not found: ${templatePath}. Run: lean-spec init`);
748
782
  }
749
783
  await fs9.writeFile(specFile, content, "utf-8");
750
- console.log(chalk18.green(`\u2713 Created: ${sanitizeUserInput(specDir)}/`));
751
- console.log(chalk18.gray(` Edit: ${sanitizeUserInput(specFile)}`));
784
+ try {
785
+ const templateFiles = await fs9.readdir(templatesDir);
786
+ const additionalFiles = templateFiles.filter(
787
+ (f) => f.endsWith(".md") && f !== templateName && f !== "spec-template.md" && f !== config.structure.defaultFile
788
+ );
789
+ if (additionalFiles.length > 0) {
790
+ for (const file of additionalFiles) {
791
+ const srcPath = path15.join(templatesDir, file);
792
+ const destPath = path15.join(specDir, file);
793
+ let fileContent = await fs9.readFile(srcPath, "utf-8");
794
+ fileContent = resolveVariables(fileContent, varContext);
795
+ await fs9.writeFile(destPath, fileContent, "utf-8");
796
+ }
797
+ console.log(chalk19.green(`\u2713 Created: ${sanitizeUserInput(specDir)}/`));
798
+ console.log(chalk19.gray(` Files: ${config.structure.defaultFile}, ${additionalFiles.join(", ")}`));
799
+ } else {
800
+ console.log(chalk19.green(`\u2713 Created: ${sanitizeUserInput(specDir)}/`));
801
+ console.log(chalk19.gray(` Edit: ${sanitizeUserInput(specFile)}`));
802
+ }
803
+ } catch (error) {
804
+ console.log(chalk19.green(`\u2713 Created: ${sanitizeUserInput(specDir)}/`));
805
+ console.log(chalk19.gray(` Edit: ${sanitizeUserInput(specFile)}`));
806
+ }
752
807
  await autoCheckIfEnabled();
753
808
  }
754
809
  function archiveCommand() {
@@ -760,7 +815,7 @@ async function archiveSpec(specPath) {
760
815
  await autoCheckIfEnabled();
761
816
  const config = await loadConfig();
762
817
  const cwd = process.cwd();
763
- const specsDir = path4.join(cwd, config.specsDir);
818
+ const specsDir = path15.join(cwd, config.specsDir);
764
819
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
765
820
  if (!resolvedPath) {
766
821
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
@@ -769,12 +824,12 @@ async function archiveSpec(specPath) {
769
824
  if (specFile) {
770
825
  await updateFrontmatter(specFile, { status: "archived" });
771
826
  }
772
- const archiveDir = path4.join(specsDir, "archived");
827
+ const archiveDir = path15.join(specsDir, "archived");
773
828
  await fs9.mkdir(archiveDir, { recursive: true });
774
- const specName = path4.basename(resolvedPath);
775
- const archivePath = path4.join(archiveDir, specName);
829
+ const specName = path15.basename(resolvedPath);
830
+ const archivePath = path15.join(archiveDir, specName);
776
831
  await fs9.rename(resolvedPath, archivePath);
777
- console.log(chalk18.green(`\u2713 Archived: ${sanitizeUserInput(archivePath)}`));
832
+ console.log(chalk19.green(`\u2713 Archived: ${sanitizeUserInput(archivePath)}`));
778
833
  }
779
834
 
780
835
  // src/utils/pattern-detection.ts
@@ -804,59 +859,59 @@ var STATUS_CONFIG = {
804
859
  planned: {
805
860
  emoji: "\u{1F4C5}",
806
861
  label: "Planned",
807
- colorFn: chalk18.blue,
808
- badge: (s = "planned") => chalk18.blue(`[${s}]`)
862
+ colorFn: chalk19.blue,
863
+ badge: (s = "planned") => chalk19.blue(`[${s}]`)
809
864
  },
810
865
  "in-progress": {
811
866
  emoji: "\u23F3",
812
867
  label: "In Progress",
813
- colorFn: chalk18.yellow,
814
- badge: (s = "in-progress") => chalk18.yellow(`[${s}]`)
868
+ colorFn: chalk19.yellow,
869
+ badge: (s = "in-progress") => chalk19.yellow(`[${s}]`)
815
870
  },
816
871
  complete: {
817
872
  emoji: "\u2705",
818
873
  label: "Complete",
819
- colorFn: chalk18.green,
820
- badge: (s = "complete") => chalk18.green(`[${s}]`)
874
+ colorFn: chalk19.green,
875
+ badge: (s = "complete") => chalk19.green(`[${s}]`)
821
876
  },
822
877
  archived: {
823
878
  emoji: "\u{1F4E6}",
824
879
  label: "Archived",
825
- colorFn: chalk18.gray,
826
- badge: (s = "archived") => chalk18.gray(`[${s}]`)
880
+ colorFn: chalk19.gray,
881
+ badge: (s = "archived") => chalk19.gray(`[${s}]`)
827
882
  }
828
883
  };
829
884
  var PRIORITY_CONFIG = {
830
885
  critical: {
831
886
  emoji: "\u{1F534}",
832
- colorFn: chalk18.red.bold,
833
- badge: (s = "critical") => chalk18.red.bold(`[${s}]`)
887
+ colorFn: chalk19.red.bold,
888
+ badge: (s = "critical") => chalk19.red.bold(`[${s}]`)
834
889
  },
835
890
  high: {
836
891
  emoji: "\u{1F7E0}",
837
- colorFn: chalk18.hex("#FFA500"),
838
- badge: (s = "high") => chalk18.hex("#FFA500")(`[${s}]`)
892
+ colorFn: chalk19.hex("#FFA500"),
893
+ badge: (s = "high") => chalk19.hex("#FFA500")(`[${s}]`)
839
894
  },
840
895
  medium: {
841
896
  emoji: "\u{1F7E1}",
842
- colorFn: chalk18.yellow,
843
- badge: (s = "medium") => chalk18.yellow(`[${s}]`)
897
+ colorFn: chalk19.yellow,
898
+ badge: (s = "medium") => chalk19.yellow(`[${s}]`)
844
899
  },
845
900
  low: {
846
901
  emoji: "\u{1F7E2}",
847
- colorFn: chalk18.gray,
848
- badge: (s = "low") => chalk18.gray(`[${s}]`)
902
+ colorFn: chalk19.gray,
903
+ badge: (s = "low") => chalk19.gray(`[${s}]`)
849
904
  }
850
905
  };
851
906
  function formatStatusBadge(status) {
852
- return STATUS_CONFIG[status]?.badge() || chalk18.white(`[${status}]`);
907
+ return STATUS_CONFIG[status]?.badge() || chalk19.white(`[${status}]`);
853
908
  }
854
909
  function formatPriorityBadge(priority) {
855
- return PRIORITY_CONFIG[priority]?.badge() || chalk18.white(`[${priority}]`);
910
+ return PRIORITY_CONFIG[priority]?.badge() || chalk19.white(`[${priority}]`);
856
911
  }
857
912
  function getStatusIndicator(status) {
858
913
  const config = STATUS_CONFIG[status];
859
- if (!config) return chalk18.gray("[unknown]");
914
+ if (!config) return chalk19.gray("[unknown]");
860
915
  return config.colorFn(`[${status}]`);
861
916
  }
862
917
  function getStatusEmoji(status) {
@@ -868,7 +923,7 @@ function getPriorityEmoji(priority) {
868
923
 
869
924
  // src/commands/list.ts
870
925
  function listCommand() {
871
- return new Command("list").description("List all specs").option("--archived", "Include archived specs").option("--status <status>", "Filter by status (planned, in-progress, complete, archived)").option("--tag <tag...>", "Filter by tag (can specify multiple)").option("--priority <priority>", "Filter by priority (low, medium, high, critical)").option("--assignee <name>", "Filter by assignee").option("--field <name=value...>", "Filter by custom field (can specify multiple)").option("--sort <field>", "Sort by field (id, created, name, status, priority)", "id").option("--order <order>", "Sort order (asc, desc)", "desc").action(async (options) => {
926
+ return new Command("list").description("List all specs").option("--archived", "Include archived specs").option("--status <status>", "Filter by status (planned, in-progress, complete, archived)").option("--tag <tag...>", "Filter by tag (can specify multiple)").option("--priority <priority>", "Filter by priority (low, medium, high, critical)").option("--assignee <name>", "Filter by assignee").option("--field <name=value...>", "Filter by custom field (can specify multiple)").option("--sort <field>", "Sort by field (id, created, name, status, priority)", "id").option("--order <order>", "Sort order (asc, desc)", "desc").option("--json", "Output as JSON").action(async (options) => {
872
927
  const customFields = parseCustomFieldOptions(options.field);
873
928
  const listOptions = {
874
929
  showArchived: options.archived,
@@ -878,7 +933,8 @@ function listCommand() {
878
933
  assignee: options.assignee,
879
934
  customFields: Object.keys(customFields).length > 0 ? customFields : void 0,
880
935
  sortBy: options.sort || "id",
881
- sortOrder: options.order || "desc"
936
+ sortOrder: options.order || "desc",
937
+ json: options.json
882
938
  };
883
939
  await listSpecs(listOptions);
884
940
  });
@@ -887,7 +943,7 @@ async function listSpecs(options = {}) {
887
943
  await autoCheckIfEnabled();
888
944
  const config = await loadConfig();
889
945
  const cwd = process.cwd();
890
- const specsDir = path4.join(cwd, config.specsDir);
946
+ const specsDir = path15.join(cwd, config.specsDir);
891
947
  try {
892
948
  await fs9.access(specsDir);
893
949
  } catch {
@@ -914,10 +970,33 @@ async function listSpecs(options = {}) {
914
970
  })
915
971
  );
916
972
  if (specs.length === 0) {
917
- console.log(chalk18.dim("No specs found."));
973
+ if (options.json) {
974
+ console.log(JSON.stringify({ specs: [], total: 0 }, null, 2));
975
+ } else {
976
+ console.log(chalk19.dim("No specs found."));
977
+ }
918
978
  return;
919
979
  }
920
- console.log(chalk18.bold.cyan("\u{1F4C4} Spec List"));
980
+ if (options.json) {
981
+ const jsonOutput = {
982
+ specs: specs.map((spec) => ({
983
+ path: spec.path,
984
+ name: spec.name,
985
+ status: spec.frontmatter.status,
986
+ priority: spec.frontmatter.priority,
987
+ tags: spec.frontmatter.tags,
988
+ assignee: spec.frontmatter.assignee,
989
+ created: spec.frontmatter.created,
990
+ completed: spec.frontmatter.completed,
991
+ subFiles: spec.subFiles?.length || 0
992
+ })),
993
+ total: specs.length,
994
+ filter: options
995
+ };
996
+ console.log(JSON.stringify(jsonOutput, null, 2));
997
+ return;
998
+ }
999
+ console.log(chalk19.bold.cyan("\u{1F4C4} Spec List"));
921
1000
  const filterParts = [];
922
1001
  if (options.status) {
923
1002
  const statusStr = Array.isArray(options.status) ? options.status.join(",") : options.status;
@@ -930,7 +1009,7 @@ async function listSpecs(options = {}) {
930
1009
  }
931
1010
  if (options.assignee) filterParts.push(`assignee=${options.assignee}`);
932
1011
  if (filterParts.length > 0) {
933
- console.log(chalk18.dim(`Filtered by: ${filterParts.join(", ")}`));
1012
+ console.log(chalk19.dim(`Filtered by: ${filterParts.join(", ")}`));
934
1013
  }
935
1014
  console.log("");
936
1015
  const patternInfo = detectPatternType(config);
@@ -940,7 +1019,7 @@ async function listSpecs(options = {}) {
940
1019
  renderFlatList(specs);
941
1020
  }
942
1021
  console.log("");
943
- console.log(chalk18.bold(`Total: ${chalk18.green(specs.length)} spec${specs.length !== 1 ? "s" : ""}`));
1022
+ console.log(chalk19.bold(`Total: ${chalk19.green(specs.length)} spec${specs.length !== 1 ? "s" : ""}`));
944
1023
  }
945
1024
  function renderFlatList(specs) {
946
1025
  for (const spec of specs) {
@@ -948,25 +1027,25 @@ function renderFlatList(specs) {
948
1027
  const priorityEmoji = getPriorityEmoji(spec.frontmatter.priority);
949
1028
  let assigneeStr = "";
950
1029
  if (spec.frontmatter.assignee) {
951
- assigneeStr = " " + chalk18.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
1030
+ assigneeStr = " " + chalk19.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
952
1031
  }
953
1032
  let tagsStr = "";
954
1033
  if (spec.frontmatter.tags?.length) {
955
1034
  const tags = Array.isArray(spec.frontmatter.tags) ? spec.frontmatter.tags : [];
956
1035
  if (tags.length > 0) {
957
1036
  const tagStr = tags.map((tag) => `#${sanitizeUserInput(tag)}`).join(" ");
958
- tagsStr = " " + chalk18.dim(chalk18.magenta(tagStr));
1037
+ tagsStr = " " + chalk19.dim(chalk19.magenta(tagStr));
959
1038
  }
960
1039
  }
961
1040
  let subSpecStr = "";
962
1041
  if (spec.subFiles) {
963
1042
  const docCount = spec.subFiles.filter((f) => f.type === "document").length;
964
1043
  if (docCount > 0) {
965
- subSpecStr = " " + chalk18.dim(chalk18.yellow(`(+${docCount} sub-spec${docCount > 1 ? "s" : ""})`));
1044
+ subSpecStr = " " + chalk19.dim(chalk19.yellow(`(+${docCount} sub-spec${docCount > 1 ? "s" : ""})`));
966
1045
  }
967
1046
  }
968
1047
  const priorityPrefix = priorityEmoji ? `${priorityEmoji} ` : "";
969
- console.log(`${priorityPrefix}${statusEmoji} ${chalk18.cyan(sanitizeUserInput(spec.path))}${assigneeStr}${tagsStr}${subSpecStr}`);
1048
+ console.log(`${priorityPrefix}${statusEmoji} ${chalk19.cyan(sanitizeUserInput(spec.path))}${assigneeStr}${tagsStr}${subSpecStr}`);
970
1049
  }
971
1050
  }
972
1051
  function renderGroupedList(specs, groupExtractor) {
@@ -995,7 +1074,7 @@ function renderGroupedList(specs, groupExtractor) {
995
1074
  const groupName = sortedGroups[i];
996
1075
  const groupSpecs = groups.get(groupName);
997
1076
  const groupEmoji = /^\d{8}$/.test(groupName) ? "\u{1F4C5}" : groupName.startsWith("milestone") ? "\u{1F3AF}" : "\u{1F4C1}";
998
- console.log(`${chalk18.bold.cyan(`${groupEmoji} ${groupName}/`)} ${chalk18.dim(`(${groupSpecs.length})`)}`);
1077
+ console.log(`${chalk19.bold.cyan(`${groupEmoji} ${groupName}/`)} ${chalk19.dim(`(${groupSpecs.length})`)}`);
999
1078
  console.log("");
1000
1079
  for (const spec of groupSpecs) {
1001
1080
  const statusEmoji = getStatusEmoji(spec.frontmatter.status);
@@ -1003,25 +1082,25 @@ function renderGroupedList(specs, groupExtractor) {
1003
1082
  const displayPath = spec.path.includes("/") ? spec.path.split("/").slice(1).join("/") : spec.path;
1004
1083
  let assigneeStr = "";
1005
1084
  if (spec.frontmatter.assignee) {
1006
- assigneeStr = " " + chalk18.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
1085
+ assigneeStr = " " + chalk19.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
1007
1086
  }
1008
1087
  let tagsStr = "";
1009
1088
  if (spec.frontmatter.tags?.length) {
1010
1089
  const tags = Array.isArray(spec.frontmatter.tags) ? spec.frontmatter.tags : [];
1011
1090
  if (tags.length > 0) {
1012
1091
  const tagStr = tags.map((tag) => `#${sanitizeUserInput(tag)}`).join(" ");
1013
- tagsStr = " " + chalk18.dim(chalk18.magenta(tagStr));
1092
+ tagsStr = " " + chalk19.dim(chalk19.magenta(tagStr));
1014
1093
  }
1015
1094
  }
1016
1095
  let subSpecStr = "";
1017
1096
  if (spec.subFiles) {
1018
1097
  const docCount = spec.subFiles.filter((f) => f.type === "document").length;
1019
1098
  if (docCount > 0) {
1020
- subSpecStr = " " + chalk18.dim(chalk18.yellow(`(+${docCount} sub-spec${docCount > 1 ? "s" : ""})`));
1099
+ subSpecStr = " " + chalk19.dim(chalk19.yellow(`(+${docCount} sub-spec${docCount > 1 ? "s" : ""})`));
1021
1100
  }
1022
1101
  }
1023
1102
  const priorityPrefix = priorityEmoji ? `${priorityEmoji} ` : "";
1024
- console.log(` ${priorityPrefix}${statusEmoji} ${chalk18.cyan(sanitizeUserInput(displayPath))}${assigneeStr}${tagsStr}${subSpecStr}`);
1103
+ console.log(` ${priorityPrefix}${statusEmoji} ${chalk19.cyan(sanitizeUserInput(displayPath))}${assigneeStr}${tagsStr}${subSpecStr}`);
1025
1104
  }
1026
1105
  if (i < sortedGroups.length - 1) {
1027
1106
  console.log("");
@@ -1050,11 +1129,11 @@ function updateCommand() {
1050
1129
  await updateSpec(specPath, updates);
1051
1130
  });
1052
1131
  }
1053
- async function updateSpec(specPath, updates) {
1132
+ async function updateSpec(specPath, updates, options = {}) {
1054
1133
  await autoCheckIfEnabled();
1055
- const config = await loadConfig();
1056
- const cwd = process.cwd();
1057
- const specsDir = path4.join(cwd, config.specsDir);
1134
+ const cwd = options.cwd ?? process.cwd();
1135
+ const config = await loadConfig(cwd);
1136
+ const specsDir = path15.join(cwd, config.specsDir);
1058
1137
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
1059
1138
  if (!resolvedPath) {
1060
1139
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}. Tried: ${sanitizeUserInput(specPath)}, specs/${sanitizeUserInput(specPath)}, and searching in date directories`);
@@ -1076,12 +1155,12 @@ async function updateSpec(specPath, updates) {
1076
1155
  });
1077
1156
  }
1078
1157
  await updateFrontmatter(specFile, allUpdates);
1079
- console.log(chalk18.green(`\u2713 Updated: ${sanitizeUserInput(path4.relative(cwd, resolvedPath))}`));
1158
+ console.log(chalk19.green(`\u2713 Updated: ${sanitizeUserInput(path15.relative(cwd, resolvedPath))}`));
1080
1159
  const updatedFields = Object.keys(updates).filter((k) => k !== "customFields");
1081
1160
  if (updates.customFields) {
1082
1161
  updatedFields.push(...Object.keys(updates.customFields));
1083
1162
  }
1084
- console.log(chalk18.gray(` Fields: ${updatedFields.join(", ")}`));
1163
+ console.log(chalk19.gray(` Fields: ${updatedFields.join(", ")}`));
1085
1164
  }
1086
1165
  function linkCommand() {
1087
1166
  return new Command("link").description("Add relationships between specs (depends_on, related)").argument("<spec>", "Spec to update").option("--depends-on <specs>", "Add dependencies (comma-separated spec numbers or names)").option("--related <specs>", "Add related specs (comma-separated spec numbers or names)").action(async (specPath, options) => {
@@ -1096,7 +1175,7 @@ async function linkSpec(specPath, options) {
1096
1175
  await autoCheckIfEnabled();
1097
1176
  const config = await loadConfig();
1098
1177
  const cwd = process.cwd();
1099
- const specsDir = path4.join(cwd, config.specsDir);
1178
+ const specsDir = path15.join(cwd, config.specsDir);
1100
1179
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
1101
1180
  if (!resolvedPath) {
1102
1181
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
@@ -1109,7 +1188,7 @@ async function linkSpec(specPath, options) {
1109
1188
  const specMap = new Map(allSpecs.map((s) => [s.path, s]));
1110
1189
  const dependsOnSpecs = options.dependsOn ? options.dependsOn.split(",").map((s) => s.trim()) : [];
1111
1190
  const relatedSpecs = options.related ? options.related.split(",").map((s) => s.trim()) : [];
1112
- const targetSpecName = path4.basename(resolvedPath);
1191
+ const targetSpecName = path15.basename(resolvedPath);
1113
1192
  const allRelationshipSpecs = [...dependsOnSpecs, ...relatedSpecs];
1114
1193
  const resolvedRelationships = /* @__PURE__ */ new Map();
1115
1194
  for (const relSpec of allRelationshipSpecs) {
@@ -1123,7 +1202,7 @@ async function linkSpec(specPath, options) {
1123
1202
  if (relResolvedPath === resolvedPath) {
1124
1203
  throw new Error(`Cannot link spec to itself: ${sanitizeUserInput(relSpec)}`);
1125
1204
  }
1126
- const relSpecName = path4.basename(relResolvedPath);
1205
+ const relSpecName = path15.basename(relResolvedPath);
1127
1206
  resolvedRelationships.set(relSpec, relSpecName);
1128
1207
  }
1129
1208
  const { parseFrontmatter: parseFrontmatter2 } = await import('./frontmatter-R2DANL5X.js');
@@ -1143,7 +1222,7 @@ async function linkSpec(specPath, options) {
1143
1222
  }
1144
1223
  updates.depends_on = newDependsOn;
1145
1224
  if (added === 0) {
1146
- console.log(chalk18.gray(`\u2139 Dependencies already exist, no changes made`));
1225
+ console.log(chalk19.gray(`\u2139 Dependencies already exist, no changes made`));
1147
1226
  }
1148
1227
  }
1149
1228
  if (relatedSpecs.length > 0) {
@@ -1170,19 +1249,19 @@ async function linkSpec(specPath, options) {
1170
1249
  await updateFrontmatter(relSpecFile, {
1171
1250
  related: [...relCurrentRelated, targetSpecName]
1172
1251
  });
1173
- console.log(chalk18.gray(` Updated: ${sanitizeUserInput(relSpecName)} (bidirectional)`));
1252
+ console.log(chalk19.gray(` Updated: ${sanitizeUserInput(relSpecName)} (bidirectional)`));
1174
1253
  }
1175
1254
  }
1176
1255
  }
1177
1256
  }
1178
1257
  if (added === 0) {
1179
- console.log(chalk18.gray(`\u2139 Related specs already exist, no changes made`));
1258
+ console.log(chalk19.gray(`\u2139 Related specs already exist, no changes made`));
1180
1259
  }
1181
1260
  }
1182
1261
  if (updates.depends_on && updates.depends_on.length > 0) {
1183
1262
  const cycles = detectCycles(targetSpecName, updates.depends_on, specMap);
1184
1263
  if (cycles.length > 0) {
1185
- console.log(chalk18.yellow(`\u26A0\uFE0F Dependency cycle detected: ${cycles.join(" \u2192 ")}`));
1264
+ console.log(chalk19.yellow(`\u26A0\uFE0F Dependency cycle detected: ${cycles.join(" \u2192 ")}`));
1186
1265
  }
1187
1266
  }
1188
1267
  await updateFrontmatter(specFile, updates);
@@ -1193,8 +1272,8 @@ async function linkSpec(specPath, options) {
1193
1272
  if (relatedSpecs.length > 0) {
1194
1273
  updatedFields.push(`related: ${relatedSpecs.join(", ")}`);
1195
1274
  }
1196
- console.log(chalk18.green(`\u2713 Added relationships: ${updatedFields.join(", ")}`));
1197
- console.log(chalk18.gray(` Updated: ${sanitizeUserInput(path4.relative(cwd, resolvedPath))}`));
1275
+ console.log(chalk19.green(`\u2713 Added relationships: ${updatedFields.join(", ")}`));
1276
+ console.log(chalk19.gray(` Updated: ${sanitizeUserInput(path15.relative(cwd, resolvedPath))}`));
1198
1277
  }
1199
1278
  function detectCycles(startSpec, dependsOn, specMap, visited = /* @__PURE__ */ new Set(), path31 = []) {
1200
1279
  if (visited.has(startSpec)) {
@@ -1230,7 +1309,7 @@ async function unlinkSpec(specPath, options) {
1230
1309
  await autoCheckIfEnabled();
1231
1310
  const config = await loadConfig();
1232
1311
  const cwd = process.cwd();
1233
- const specsDir = path4.join(cwd, config.specsDir);
1312
+ const specsDir = path15.join(cwd, config.specsDir);
1234
1313
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
1235
1314
  if (!resolvedPath) {
1236
1315
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
@@ -1239,7 +1318,7 @@ async function unlinkSpec(specPath, options) {
1239
1318
  if (!specFile) {
1240
1319
  throw new Error(`No spec file found in: ${sanitizeUserInput(specPath)}`);
1241
1320
  }
1242
- const targetSpecName = path4.basename(resolvedPath);
1321
+ const targetSpecName = path15.basename(resolvedPath);
1243
1322
  const { parseFrontmatter: parseFrontmatter2 } = await import('./frontmatter-R2DANL5X.js');
1244
1323
  const currentFrontmatter = await parseFrontmatter2(specFile);
1245
1324
  const currentDependsOn = currentFrontmatter?.depends_on || [];
@@ -1256,7 +1335,7 @@ async function unlinkSpec(specPath, options) {
1256
1335
  for (const spec of toRemove) {
1257
1336
  const resolvedSpecPath = await resolveSpecPath(spec, cwd, specsDir);
1258
1337
  if (resolvedSpecPath) {
1259
- resolvedToRemove.add(path4.basename(resolvedSpecPath));
1338
+ resolvedToRemove.add(path15.basename(resolvedSpecPath));
1260
1339
  } else {
1261
1340
  resolvedToRemove.add(spec);
1262
1341
  }
@@ -1280,7 +1359,7 @@ async function unlinkSpec(specPath, options) {
1280
1359
  await updateFrontmatter(relSpecFile, {
1281
1360
  related: relNewRelated
1282
1361
  });
1283
- console.log(chalk18.gray(` Updated: ${sanitizeUserInput(relSpec)} (bidirectional)`));
1362
+ console.log(chalk19.gray(` Updated: ${sanitizeUserInput(relSpec)} (bidirectional)`));
1284
1363
  }
1285
1364
  }
1286
1365
  }
@@ -1293,7 +1372,7 @@ async function unlinkSpec(specPath, options) {
1293
1372
  for (const spec of toRemove) {
1294
1373
  const resolvedSpecPath = await resolveSpecPath(spec, cwd, specsDir);
1295
1374
  if (resolvedSpecPath) {
1296
- const specName = path4.basename(resolvedSpecPath);
1375
+ const specName = path15.basename(resolvedSpecPath);
1297
1376
  resolvedToRemove.add(specName);
1298
1377
  const relSpecFile = await getSpecFile(resolvedSpecPath, config.structure.defaultFile);
1299
1378
  if (relSpecFile) {
@@ -1304,7 +1383,7 @@ async function unlinkSpec(specPath, options) {
1304
1383
  await updateFrontmatter(relSpecFile, {
1305
1384
  related: relNewRelated
1306
1385
  });
1307
- console.log(chalk18.gray(` Updated: ${sanitizeUserInput(specName)} (bidirectional)`));
1386
+ console.log(chalk19.gray(` Updated: ${sanitizeUserInput(specName)} (bidirectional)`));
1308
1387
  }
1309
1388
  }
1310
1389
  } else {
@@ -1318,7 +1397,7 @@ async function unlinkSpec(specPath, options) {
1318
1397
  }
1319
1398
  await updateFrontmatter(specFile, updates);
1320
1399
  if (removedCount === 0) {
1321
- console.log(chalk18.gray(`\u2139 No matching relationships found to remove`));
1400
+ console.log(chalk19.gray(`\u2139 No matching relationships found to remove`));
1322
1401
  } else {
1323
1402
  const updatedFields = [];
1324
1403
  if (options.dependsOn !== void 0) {
@@ -1327,8 +1406,8 @@ async function unlinkSpec(specPath, options) {
1327
1406
  if (options.related !== void 0) {
1328
1407
  updatedFields.push(`related`);
1329
1408
  }
1330
- console.log(chalk18.green(`\u2713 Removed relationships: ${updatedFields.join(", ")} (${removedCount} total)`));
1331
- console.log(chalk18.gray(` Updated: ${sanitizeUserInput(path4.relative(cwd, resolvedPath))}`));
1409
+ console.log(chalk19.green(`\u2713 Removed relationships: ${updatedFields.join(", ")} (${removedCount} total)`));
1410
+ console.log(chalk19.gray(` Updated: ${sanitizeUserInput(path15.relative(cwd, resolvedPath))}`));
1332
1411
  }
1333
1412
  }
1334
1413
  function isGitRepository() {
@@ -1459,13 +1538,14 @@ function fileExistsInGit(filePath) {
1459
1538
 
1460
1539
  // src/commands/backfill.ts
1461
1540
  function backfillCommand() {
1462
- return new Command("backfill").description("Backfill timestamps from git history").argument("[specs...]", "Specific specs to backfill (optional)").option("--dry-run", "Show what would be updated without making changes").option("--force", "Overwrite existing timestamp values").option("--assignee", "Include assignee from first commit author").option("--transitions", "Include full status transition history").option("--all", "Include all optional fields (assignee + transitions)").action(async (specs, options) => {
1541
+ return new Command("backfill").description("Backfill timestamps from git history").argument("[specs...]", "Specific specs to backfill (optional)").option("--dry-run", "Show what would be updated without making changes").option("--force", "Overwrite existing timestamp values").option("--assignee", "Include assignee from first commit author").option("--transitions", "Include full status transition history").option("--all", "Include all optional fields (assignee + transitions)").option("--json", "Output as JSON").action(async (specs, options) => {
1463
1542
  await backfillTimestamps({
1464
1543
  dryRun: options.dryRun,
1465
1544
  force: options.force,
1466
1545
  includeAssignee: options.assignee || options.all,
1467
1546
  includeTransitions: options.transitions || options.all,
1468
- specs: specs && specs.length > 0 ? specs : void 0
1547
+ specs: specs && specs.length > 0 ? specs : void 0,
1548
+ json: options.json
1469
1549
  });
1470
1550
  });
1471
1551
  }
@@ -1481,7 +1561,7 @@ async function backfillTimestamps(options = {}) {
1481
1561
  specs = [];
1482
1562
  const config = await loadConfig();
1483
1563
  const cwd = process.cwd();
1484
- const specsDir = path4.join(cwd, config.specsDir);
1564
+ const specsDir = path15.join(cwd, config.specsDir);
1485
1565
  for (const specPath of options.specs) {
1486
1566
  const resolved = await resolveSpecPath(specPath, cwd, specsDir);
1487
1567
  if (!resolved) {
@@ -1677,79 +1757,79 @@ function templatesCommand() {
1677
1757
  }
1678
1758
  async function listTemplates(cwd = process.cwd()) {
1679
1759
  const config = await loadConfig(cwd);
1680
- const templatesDir = path4.join(cwd, ".lean-spec", "templates");
1760
+ const templatesDir = path15.join(cwd, ".lean-spec", "templates");
1681
1761
  console.log("");
1682
- console.log(chalk18.green("=== Project Templates ==="));
1762
+ console.log(chalk19.green("=== Project Templates ==="));
1683
1763
  console.log("");
1684
1764
  try {
1685
1765
  await fs9.access(templatesDir);
1686
1766
  } catch {
1687
- console.log(chalk18.yellow("No templates directory found."));
1688
- console.log(chalk18.gray("Run: lean-spec init"));
1767
+ console.log(chalk19.yellow("No templates directory found."));
1768
+ console.log(chalk19.gray("Run: lean-spec init"));
1689
1769
  console.log("");
1690
1770
  return;
1691
1771
  }
1692
1772
  const files = await fs9.readdir(templatesDir);
1693
1773
  const templateFiles = files.filter((f) => f.endsWith(".md"));
1694
1774
  if (templateFiles.length === 0) {
1695
- console.log(chalk18.yellow("No templates found."));
1775
+ console.log(chalk19.yellow("No templates found."));
1696
1776
  console.log("");
1697
1777
  return;
1698
1778
  }
1699
1779
  if (config.templates && Object.keys(config.templates).length > 0) {
1700
- console.log(chalk18.cyan("Registered:"));
1780
+ console.log(chalk19.cyan("Registered:"));
1701
1781
  for (const [name, file] of Object.entries(config.templates)) {
1702
1782
  const isDefault = config.template === file;
1703
- const marker = isDefault ? chalk18.green("\u2713 (default)") : "";
1704
- console.log(` ${chalk18.bold(name)}: ${file} ${marker}`);
1783
+ const marker = isDefault ? chalk19.green("\u2713 (default)") : "";
1784
+ console.log(` ${chalk19.bold(name)}: ${file} ${marker}`);
1705
1785
  }
1706
1786
  console.log("");
1707
1787
  }
1708
- console.log(chalk18.cyan("Available files:"));
1788
+ console.log(chalk19.cyan("Available files:"));
1709
1789
  for (const file of templateFiles) {
1710
- const filePath = path4.join(templatesDir, file);
1790
+ const filePath = path15.join(templatesDir, file);
1711
1791
  const stat6 = await fs9.stat(filePath);
1712
1792
  const sizeKB = (stat6.size / 1024).toFixed(1);
1713
1793
  console.log(` ${file} (${sizeKB} KB)`);
1714
1794
  }
1715
1795
  console.log("");
1716
- console.log(chalk18.gray("Use templates with: lean-spec create <name> --template=<template-name>"));
1796
+ console.log(chalk19.gray("Use templates with: lean-spec create <name> --template=<template-name>"));
1717
1797
  console.log("");
1718
1798
  }
1719
1799
  async function showTemplate(templateName, cwd = process.cwd()) {
1720
1800
  const config = await loadConfig(cwd);
1721
1801
  if (!config.templates?.[templateName]) {
1722
- console.error(chalk18.red(`Template not found: ${templateName}`));
1723
- console.error(chalk18.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
1802
+ console.error(chalk19.red(`Template not found: ${templateName}`));
1803
+ console.error(chalk19.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
1724
1804
  process.exit(1);
1725
1805
  }
1726
- const templatesDir = path4.join(cwd, ".lean-spec", "templates");
1806
+ const templatesDir = path15.join(cwd, ".lean-spec", "templates");
1727
1807
  const templateFile = config.templates[templateName];
1728
- const templatePath = path4.join(templatesDir, templateFile);
1808
+ const templatePath = path15.join(templatesDir, templateFile);
1729
1809
  try {
1730
1810
  const content = await fs9.readFile(templatePath, "utf-8");
1731
1811
  console.log("");
1732
- console.log(chalk18.cyan(`=== Template: ${templateName} (${templateFile}) ===`));
1812
+ console.log(chalk19.cyan(`=== Template: ${templateName} (${templateFile}) ===`));
1733
1813
  console.log("");
1734
1814
  console.log(content);
1735
1815
  console.log("");
1736
1816
  } catch (error) {
1737
- console.error(chalk18.red(`Error reading template: ${templateFile}`));
1817
+ console.error(chalk19.red(`Error reading template: ${templateFile}`));
1738
1818
  console.error(error);
1739
1819
  process.exit(1);
1740
1820
  }
1741
1821
  }
1742
1822
  async function addTemplate(name, file, cwd = process.cwd()) {
1743
1823
  const config = await loadConfig(cwd);
1744
- const templatesDir = path4.join(cwd, ".lean-spec", "templates");
1745
- const templatePath = path4.join(templatesDir, file);
1824
+ const templatesDir = path15.join(cwd, ".lean-spec", "templates");
1825
+ const templatePath = path15.join(templatesDir, file);
1746
1826
  try {
1747
1827
  await fs9.access(templatePath);
1748
1828
  } catch {
1749
- console.error(chalk18.red(`Template file not found: ${file}`));
1750
- console.error(chalk18.gray(`Expected at: ${templatePath}`));
1829
+ console.error(chalk19.red(`Template file not found: ${file}`));
1830
+ console.error(chalk19.gray(`Expected at: ${templatePath}`));
1751
1831
  console.error(
1752
- chalk18.yellow("Create the file first or use: lean-spec templates copy <source> <target>")
1832
+ chalk19.yellow("Create the file first or use: lean-spec templates copy <source> <target>")
1753
1833
  );
1754
1834
  process.exit(1);
1755
1835
  }
@@ -1757,60 +1837,60 @@ async function addTemplate(name, file, cwd = process.cwd()) {
1757
1837
  config.templates = {};
1758
1838
  }
1759
1839
  if (config.templates[name]) {
1760
- console.log(chalk18.yellow(`Warning: Template '${name}' already exists, updating...`));
1840
+ console.log(chalk19.yellow(`Warning: Template '${name}' already exists, updating...`));
1761
1841
  }
1762
1842
  config.templates[name] = file;
1763
1843
  await saveConfig(config, cwd);
1764
- console.log(chalk18.green(`\u2713 Added template: ${name} \u2192 ${file}`));
1765
- console.log(chalk18.gray(` Use with: lean-spec create <spec-name> --template=${name}`));
1844
+ console.log(chalk19.green(`\u2713 Added template: ${name} \u2192 ${file}`));
1845
+ console.log(chalk19.gray(` Use with: lean-spec create <spec-name> --template=${name}`));
1766
1846
  }
1767
1847
  async function removeTemplate(name, cwd = process.cwd()) {
1768
1848
  const config = await loadConfig(cwd);
1769
1849
  if (!config.templates?.[name]) {
1770
- console.error(chalk18.red(`Template not found: ${name}`));
1771
- console.error(chalk18.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
1850
+ console.error(chalk19.red(`Template not found: ${name}`));
1851
+ console.error(chalk19.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
1772
1852
  process.exit(1);
1773
1853
  }
1774
1854
  if (name === "default") {
1775
- console.error(chalk18.red("Cannot remove default template"));
1855
+ console.error(chalk19.red("Cannot remove default template"));
1776
1856
  process.exit(1);
1777
1857
  }
1778
1858
  const file = config.templates[name];
1779
1859
  delete config.templates[name];
1780
1860
  await saveConfig(config, cwd);
1781
- console.log(chalk18.green(`\u2713 Removed template: ${name}`));
1782
- console.log(chalk18.gray(` Note: Template file ${file} still exists in .lean-spec/templates/`));
1861
+ console.log(chalk19.green(`\u2713 Removed template: ${name}`));
1862
+ console.log(chalk19.gray(` Note: Template file ${file} still exists in .lean-spec/templates/`));
1783
1863
  }
1784
1864
  async function copyTemplate(source, target, cwd = process.cwd()) {
1785
1865
  const config = await loadConfig(cwd);
1786
- const templatesDir = path4.join(cwd, ".lean-spec", "templates");
1866
+ const templatesDir = path15.join(cwd, ".lean-spec", "templates");
1787
1867
  let sourceFile;
1788
1868
  if (config.templates?.[source]) {
1789
1869
  sourceFile = config.templates[source];
1790
1870
  } else {
1791
1871
  sourceFile = source;
1792
1872
  }
1793
- const sourcePath = path4.join(templatesDir, sourceFile);
1873
+ const sourcePath = path15.join(templatesDir, sourceFile);
1794
1874
  try {
1795
1875
  await fs9.access(sourcePath);
1796
1876
  } catch {
1797
- console.error(chalk18.red(`Source template not found: ${source}`));
1798
- console.error(chalk18.gray(`Expected at: ${sourcePath}`));
1877
+ console.error(chalk19.red(`Source template not found: ${source}`));
1878
+ console.error(chalk19.gray(`Expected at: ${sourcePath}`));
1799
1879
  process.exit(1);
1800
1880
  }
1801
1881
  const targetFile = target.endsWith(".md") ? target : `${target}.md`;
1802
- const targetPath = path4.join(templatesDir, targetFile);
1882
+ const targetPath = path15.join(templatesDir, targetFile);
1803
1883
  await fs9.copyFile(sourcePath, targetPath);
1804
- console.log(chalk18.green(`\u2713 Copied: ${sourceFile} \u2192 ${targetFile}`));
1884
+ console.log(chalk19.green(`\u2713 Copied: ${sourceFile} \u2192 ${targetFile}`));
1805
1885
  if (!config.templates) {
1806
1886
  config.templates = {};
1807
1887
  }
1808
1888
  const templateName = target.replace(/\.md$/, "");
1809
1889
  config.templates[templateName] = targetFile;
1810
1890
  await saveConfig(config, cwd);
1811
- console.log(chalk18.green(`\u2713 Registered template: ${templateName}`));
1812
- console.log(chalk18.gray(` Edit: ${targetPath}`));
1813
- console.log(chalk18.gray(` Use with: lean-spec create <spec-name> --template=${templateName}`));
1891
+ console.log(chalk19.green(`\u2713 Registered template: ${templateName}`));
1892
+ console.log(chalk19.gray(` Edit: ${targetPath}`));
1893
+ console.log(chalk19.gray(` Use with: lean-spec create <spec-name> --template=${templateName}`));
1814
1894
  }
1815
1895
  async function detectExistingSystemPrompts(cwd) {
1816
1896
  const commonFiles = [
@@ -1821,7 +1901,7 @@ async function detectExistingSystemPrompts(cwd) {
1821
1901
  const found = [];
1822
1902
  for (const file of commonFiles) {
1823
1903
  try {
1824
- await fs9.access(path4.join(cwd, file));
1904
+ await fs9.access(path15.join(cwd, file));
1825
1905
  found.push(file);
1826
1906
  } catch {
1827
1907
  }
@@ -1830,8 +1910,8 @@ async function detectExistingSystemPrompts(cwd) {
1830
1910
  }
1831
1911
  async function handleExistingFiles(action, existingFiles, templateDir, cwd, variables = {}) {
1832
1912
  for (const file of existingFiles) {
1833
- const filePath = path4.join(cwd, file);
1834
- const templateFilePath = path4.join(templateDir, "files", file);
1913
+ const filePath = path15.join(cwd, file);
1914
+ const templateFilePath = path15.join(templateDir, file);
1835
1915
  try {
1836
1916
  await fs9.access(templateFilePath);
1837
1917
  } catch {
@@ -1843,7 +1923,7 @@ async function handleExistingFiles(action, existingFiles, templateDir, cwd, vari
1843
1923
  for (const [key, value] of Object.entries(variables)) {
1844
1924
  template = template.replace(new RegExp(`\\{${key}\\}`, "g"), value);
1845
1925
  }
1846
- const promptPath = path4.join(cwd, ".lean-spec", "MERGE-AGENTS-PROMPT.md");
1926
+ const promptPath = path15.join(cwd, ".lean-spec", "MERGE-AGENTS-PROMPT.md");
1847
1927
  const aiPrompt = `# AI Prompt: Consolidate AGENTS.md
1848
1928
 
1849
1929
  ## Task
@@ -1875,16 +1955,16 @@ Create a single consolidated AGENTS.md that:
1875
1955
  - Maintains clear structure and readability
1876
1956
  - Removes any duplicate or conflicting guidance
1877
1957
  `;
1878
- await fs9.mkdir(path4.dirname(promptPath), { recursive: true });
1958
+ await fs9.mkdir(path15.dirname(promptPath), { recursive: true });
1879
1959
  await fs9.writeFile(promptPath, aiPrompt, "utf-8");
1880
- console.log(chalk18.green(`\u2713 Created AI consolidation prompt`));
1881
- console.log(chalk18.cyan(` \u2192 ${promptPath}`));
1960
+ console.log(chalk19.green(`\u2713 Created AI consolidation prompt`));
1961
+ console.log(chalk19.cyan(` \u2192 ${promptPath}`));
1882
1962
  console.log("");
1883
- console.log(chalk18.yellow("\u{1F4DD} Next steps:"));
1884
- console.log(chalk18.gray(" 1. Open .lean-spec/MERGE-AGENTS-PROMPT.md"));
1885
- console.log(chalk18.gray(" 2. Send it to your AI coding assistant (GitHub Copilot, Cursor, etc.)"));
1886
- console.log(chalk18.gray(" 3. Let AI create the consolidated AGENTS.md"));
1887
- console.log(chalk18.gray(" 4. Review and commit the result"));
1963
+ console.log(chalk19.yellow("\u{1F4DD} Next steps:"));
1964
+ console.log(chalk19.gray(" 1. Open .lean-spec/MERGE-AGENTS-PROMPT.md"));
1965
+ console.log(chalk19.gray(" 2. Send it to your AI coding assistant (GitHub Copilot, Cursor, etc.)"));
1966
+ console.log(chalk19.gray(" 3. Let AI create the consolidated AGENTS.md"));
1967
+ console.log(chalk19.gray(" 4. Review and commit the result"));
1888
1968
  console.log("");
1889
1969
  } else if (action === "merge-append" && file === "AGENTS.md") {
1890
1970
  const existing = await fs9.readFile(filePath, "utf-8");
@@ -1900,19 +1980,19 @@ Create a single consolidated AGENTS.md that:
1900
1980
 
1901
1981
  ${template.split("\n").slice(1).join("\n")}`;
1902
1982
  await fs9.writeFile(filePath, merged, "utf-8");
1903
- console.log(chalk18.green(`\u2713 Appended LeanSpec section to ${file}`));
1904
- console.log(chalk18.yellow(" \u26A0 Note: May be verbose. Consider consolidating later."));
1983
+ console.log(chalk19.green(`\u2713 Appended LeanSpec section to ${file}`));
1984
+ console.log(chalk19.yellow(" \u26A0 Note: May be verbose. Consider consolidating later."));
1905
1985
  } else if (action === "overwrite") {
1906
1986
  const backupPath = `${filePath}.backup`;
1907
1987
  await fs9.rename(filePath, backupPath);
1908
- console.log(chalk18.yellow(`\u2713 Backed up ${file} \u2192 ${file}.backup`));
1988
+ console.log(chalk19.yellow(`\u2713 Backed up ${file} \u2192 ${file}.backup`));
1909
1989
  let content = await fs9.readFile(templateFilePath, "utf-8");
1910
1990
  for (const [key, value] of Object.entries(variables)) {
1911
1991
  content = content.replace(new RegExp(`\\{${key}\\}`, "g"), value);
1912
1992
  }
1913
1993
  await fs9.writeFile(filePath, content, "utf-8");
1914
- console.log(chalk18.green(`\u2713 Created new ${file}`));
1915
- console.log(chalk18.gray(` \u{1F4A1} Your original content is preserved in ${file}.backup`));
1994
+ console.log(chalk19.green(`\u2713 Created new ${file}`));
1995
+ console.log(chalk19.gray(` \u{1F4A1} Your original content is preserved in ${file}.backup`));
1916
1996
  }
1917
1997
  }
1918
1998
  }
@@ -1920,8 +2000,8 @@ async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
1920
2000
  await fs9.mkdir(dest, { recursive: true });
1921
2001
  const entries = await fs9.readdir(src, { withFileTypes: true });
1922
2002
  for (const entry of entries) {
1923
- const srcPath = path4.join(src, entry.name);
1924
- const destPath = path4.join(dest, entry.name);
2003
+ const srcPath = path15.join(src, entry.name);
2004
+ const destPath = path15.join(dest, entry.name);
1925
2005
  if (skipFiles.includes(entry.name)) {
1926
2006
  continue;
1927
2007
  }
@@ -1942,7 +2022,7 @@ async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
1942
2022
  }
1943
2023
  async function getProjectName2(cwd) {
1944
2024
  try {
1945
- const packageJsonPath = path4.join(cwd, "package.json");
2025
+ const packageJsonPath = path15.join(cwd, "package.json");
1946
2026
  const content = await fs9.readFile(packageJsonPath, "utf-8");
1947
2027
  const pkg = JSON.parse(content);
1948
2028
  if (pkg.name) {
@@ -1950,77 +2030,148 @@ async function getProjectName2(cwd) {
1950
2030
  }
1951
2031
  } catch {
1952
2032
  }
1953
- return path4.basename(cwd);
2033
+ return path15.basename(cwd);
2034
+ }
2035
+
2036
+ // src/utils/examples.ts
2037
+ var EXAMPLES = {
2038
+ "dark-theme": {
2039
+ name: "dark-theme",
2040
+ title: "Dark Theme Support",
2041
+ description: "Add dark theme support to a professional admin dashboard with charts",
2042
+ difficulty: "beginner",
2043
+ tutorial: "Your First Spec with AI",
2044
+ tutorialUrl: "https://leanspec.dev/docs/tutorials/first-spec-with-ai",
2045
+ tech: ["HTML", "CSS", "JavaScript", "Chart.js", "Express.js"],
2046
+ files: 6,
2047
+ lines: 420
2048
+ },
2049
+ "dashboard-widgets": {
2050
+ name: "dashboard-widgets",
2051
+ title: "Dashboard Widgets",
2052
+ description: "Add three new widgets to an analytics dashboard",
2053
+ difficulty: "intermediate",
2054
+ tutorial: "Managing Multiple Features",
2055
+ tutorialUrl: "https://leanspec.dev/docs/tutorials/multiple-features",
2056
+ tech: ["React", "Vite"],
2057
+ files: 17,
2058
+ lines: 300
2059
+ },
2060
+ "api-refactor": {
2061
+ name: "api-refactor",
2062
+ title: "API Client Refactor",
2063
+ description: "Extract reusable API client from monolithic code",
2064
+ difficulty: "intermediate",
2065
+ tutorial: "Refactoring with Specs",
2066
+ tutorialUrl: "https://leanspec.dev/docs/tutorials/refactoring-specs",
2067
+ tech: ["Node.js"],
2068
+ files: 7,
2069
+ lines: 250
2070
+ }
2071
+ };
2072
+ function getExamplesList() {
2073
+ return Object.values(EXAMPLES);
2074
+ }
2075
+ function getExample(name) {
2076
+ return EXAMPLES[name];
2077
+ }
2078
+ function exampleExists(name) {
2079
+ return name in EXAMPLES;
1954
2080
  }
1955
2081
 
1956
2082
  // src/commands/init.ts
1957
- var __dirname = path4.dirname(fileURLToPath(import.meta.url));
1958
- var TEMPLATES_DIR = path4.join(__dirname, "..", "templates");
2083
+ var __dirname = path15.dirname(fileURLToPath(import.meta.url));
2084
+ var TEMPLATES_DIR = path15.join(__dirname, "..", "templates");
2085
+ var EXAMPLES_DIR = path15.join(TEMPLATES_DIR, "examples");
1959
2086
  function initCommand() {
1960
- return new Command("init").description("Initialize LeanSpec in current directory").action(async () => {
1961
- await initProject();
2087
+ return new Command("init").description("Initialize LeanSpec in current directory").option("-y, --yes", "Skip prompts and use defaults (quick start with standard template)").option("--template <name>", "Use specific template (standard or detailed)").option("--example [name]", "Scaffold an example project for tutorials (interactive if no name provided)").option("--name <dirname>", "Custom directory name for example project").option("--list", "List available example projects").action(async (options) => {
2088
+ if (options.list) {
2089
+ await listExamples();
2090
+ return;
2091
+ }
2092
+ if (options.example !== void 0) {
2093
+ await scaffoldExample(options.example, options.name);
2094
+ return;
2095
+ }
2096
+ await initProject(options.yes, options.template);
1962
2097
  });
1963
2098
  }
1964
- async function initProject() {
2099
+ async function initProject(skipPrompts = false, templateOption) {
1965
2100
  const cwd = process.cwd();
1966
2101
  try {
1967
- await fs9.access(path4.join(cwd, ".lean-spec", "config.json"));
1968
- console.log(chalk18.yellow("\u26A0 LeanSpec already initialized in this directory."));
1969
- console.log(chalk18.gray("To reinitialize, delete .lean-spec/ directory first."));
2102
+ await fs9.access(path15.join(cwd, ".lean-spec", "config.json"));
2103
+ console.log(chalk19.yellow("\u26A0 LeanSpec already initialized in this directory."));
2104
+ console.log(chalk19.gray("To reinitialize, delete .lean-spec/ directory first."));
1970
2105
  return;
1971
2106
  } catch {
1972
2107
  }
1973
2108
  console.log("");
1974
- console.log(chalk18.green("Welcome to LeanSpec!"));
2109
+ console.log(chalk19.green("Welcome to LeanSpec!"));
1975
2110
  console.log("");
1976
- const setupMode = await select({
1977
- message: "How would you like to set up?",
1978
- choices: [
1979
- {
1980
- name: "Quick start (recommended)",
1981
- value: "quick",
1982
- description: "Use standard template, start immediately"
1983
- },
1984
- {
1985
- name: "Choose template",
1986
- value: "template",
1987
- description: "Pick from: minimal, standard, enterprise"
1988
- }
1989
- // TODO: Re-enable when custom setup mode is implemented
1990
- // {
1991
- // name: 'Customize everything',
1992
- // value: 'custom',
1993
- // description: 'Full control over structure and settings',
1994
- // },
1995
- ]
1996
- });
1997
- let templateName = "standard";
1998
- if (setupMode === "template") {
1999
- templateName = await select({
2000
- message: "Select template:",
2111
+ let setupMode = "quick";
2112
+ let templateName = templateOption || "standard";
2113
+ if (skipPrompts) {
2114
+ console.log(chalk19.gray("Using defaults: quick start with standard template"));
2115
+ console.log("");
2116
+ } else if (!templateOption) {
2117
+ setupMode = await select({
2118
+ message: "How would you like to set up?",
2001
2119
  choices: [
2002
- { name: "minimal", value: "minimal", description: "Just folder structure, no extras" },
2003
- { name: "standard", value: "standard", description: "Recommended - includes AGENTS.md" },
2004
2120
  {
2005
- name: "enterprise",
2006
- value: "enterprise",
2007
- description: "Governance with approvals and compliance"
2121
+ name: "Quick start (recommended)",
2122
+ value: "quick",
2123
+ description: "Use standard template, start immediately"
2124
+ },
2125
+ {
2126
+ name: "Choose template",
2127
+ value: "template",
2128
+ description: "Pick from: standard, detailed"
2008
2129
  }
2130
+ // TODO: Re-enable when custom setup mode is implemented
2131
+ // {
2132
+ // name: 'Customize everything',
2133
+ // value: 'custom',
2134
+ // description: 'Full control over structure and settings',
2135
+ // },
2009
2136
  ]
2010
2137
  });
2138
+ if (setupMode === "template") {
2139
+ templateName = await select({
2140
+ message: "Select template:",
2141
+ choices: [
2142
+ { name: "standard", value: "standard", description: "Recommended - single-file specs with AGENTS.md" },
2143
+ {
2144
+ name: "detailed",
2145
+ value: "detailed",
2146
+ description: "Complex specs with sub-spec structure (DESIGN, PLAN, TEST)"
2147
+ }
2148
+ ]
2149
+ });
2150
+ }
2011
2151
  }
2012
- const templateDir = path4.join(TEMPLATES_DIR, templateName);
2013
- const templateConfigPath = path4.join(templateDir, "config.json");
2152
+ if (templateName === "minimal") {
2153
+ console.log(chalk19.yellow('\u26A0 The "minimal" template has been removed.'));
2154
+ console.log(chalk19.gray(' Using "standard" template instead (same lightweight approach).'));
2155
+ console.log("");
2156
+ templateName = "standard";
2157
+ } else if (templateName === "enterprise") {
2158
+ console.log(chalk19.yellow('\u26A0 The "enterprise" template has been renamed to "detailed".'));
2159
+ console.log(chalk19.gray(' Using "detailed" template (sub-spec structure for complex specs).'));
2160
+ console.log("");
2161
+ templateName = "detailed";
2162
+ }
2163
+ const templateDir = path15.join(TEMPLATES_DIR, templateName);
2164
+ const templateConfigPath = path15.join(templateDir, "config.json");
2014
2165
  let templateConfig;
2015
2166
  try {
2016
2167
  const content = await fs9.readFile(templateConfigPath, "utf-8");
2017
2168
  templateConfig = JSON.parse(content).config;
2018
2169
  } catch {
2019
- console.error(chalk18.red(`Error: Template not found: ${templateName}`));
2170
+ console.error(chalk19.red(`Error: Template not found: ${templateName}`));
2020
2171
  process.exit(1);
2021
2172
  }
2022
2173
  let patternChoice = "simple";
2023
- if (setupMode !== "quick") {
2174
+ if (setupMode !== "quick" && !skipPrompts) {
2024
2175
  patternChoice = await select({
2025
2176
  message: "Select folder pattern:",
2026
2177
  choices: [
@@ -2059,89 +2210,272 @@ async function initProject() {
2059
2210
  templateConfig.structure.prefix = "{YYYYMMDD}-";
2060
2211
  } else if (patternChoice === "custom") {
2061
2212
  console.log("");
2062
- console.log(chalk18.yellow("\u26A0 Custom pattern input is not yet implemented."));
2063
- console.log(chalk18.gray(" You can manually edit .lean-spec/config.json after initialization."));
2064
- console.log(chalk18.gray(" Using simple pattern for now."));
2213
+ console.log(chalk19.yellow("\u26A0 Custom pattern input is not yet implemented."));
2214
+ console.log(chalk19.gray(" You can manually edit .lean-spec/config.json after initialization."));
2215
+ console.log(chalk19.gray(" Using simple pattern for now."));
2065
2216
  console.log("");
2066
2217
  templateConfig.structure.pattern = "flat";
2067
2218
  templateConfig.structure.prefix = "";
2068
2219
  }
2069
- const templatesDir = path4.join(cwd, ".lean-spec", "templates");
2220
+ const templatesDir = path15.join(cwd, ".lean-spec", "templates");
2070
2221
  try {
2071
2222
  await fs9.mkdir(templatesDir, { recursive: true });
2072
2223
  } catch (error) {
2073
- console.error(chalk18.red("Error creating templates directory:"), error);
2224
+ console.error(chalk19.red("Error creating templates directory:"), error);
2074
2225
  process.exit(1);
2075
2226
  }
2076
- const templateSpecPath = path4.join(templateDir, "spec-template.md");
2077
- const targetSpecPath = path4.join(templatesDir, "spec-template.md");
2227
+ const templateFilesDir = path15.join(templateDir, "files");
2078
2228
  try {
2079
- await fs9.copyFile(templateSpecPath, targetSpecPath);
2080
- console.log(chalk18.green("\u2713 Created .lean-spec/templates/spec-template.md"));
2229
+ const files = await fs9.readdir(templateFilesDir);
2230
+ if (templateName === "standard") {
2231
+ const readmePath = path15.join(templateFilesDir, "README.md");
2232
+ const targetSpecPath = path15.join(templatesDir, "spec-template.md");
2233
+ await fs9.copyFile(readmePath, targetSpecPath);
2234
+ console.log(chalk19.green("\u2713 Created .lean-spec/templates/spec-template.md"));
2235
+ templateConfig.template = "spec-template.md";
2236
+ templateConfig.templates = {
2237
+ default: "spec-template.md"
2238
+ };
2239
+ } else if (templateName === "detailed") {
2240
+ for (const file of files) {
2241
+ const srcPath = path15.join(templateFilesDir, file);
2242
+ const destPath = path15.join(templatesDir, file);
2243
+ await fs9.copyFile(srcPath, destPath);
2244
+ }
2245
+ console.log(chalk19.green(`\u2713 Created .lean-spec/templates/ with ${files.length} files`));
2246
+ console.log(chalk19.gray(` Files: ${files.join(", ")}`));
2247
+ templateConfig.template = "README.md";
2248
+ templateConfig.templates = {
2249
+ default: "README.md"
2250
+ };
2251
+ }
2081
2252
  } catch (error) {
2082
- console.error(chalk18.red("Error copying template:"), error);
2253
+ console.error(chalk19.red("Error copying template files:"), error);
2083
2254
  process.exit(1);
2084
2255
  }
2085
- templateConfig.template = "spec-template.md";
2086
- templateConfig.templates = {
2087
- default: "spec-template.md"
2088
- };
2089
2256
  await saveConfig(templateConfig, cwd);
2090
- console.log(chalk18.green("\u2713 Created .lean-spec/config.json"));
2257
+ console.log(chalk19.green("\u2713 Created .lean-spec/config.json"));
2091
2258
  const existingFiles = await detectExistingSystemPrompts(cwd);
2092
2259
  let skipFiles = [];
2093
2260
  if (existingFiles.length > 0) {
2094
2261
  console.log("");
2095
- console.log(chalk18.yellow(`Found existing: ${existingFiles.join(", ")}`));
2096
- const action = await select({
2097
- message: "How would you like to handle existing AGENTS.md?",
2098
- choices: [
2099
- {
2100
- name: "AI-Assisted Merge (recommended)",
2101
- value: "merge-ai",
2102
- description: "Creates prompt for AI to intelligently consolidate both files"
2103
- },
2104
- {
2105
- name: "Simple Append",
2106
- value: "merge-append",
2107
- description: "Quickly appends LeanSpec section (may be verbose)"
2108
- },
2109
- {
2110
- name: "Replace with LeanSpec",
2111
- value: "overwrite",
2112
- description: "Backs up existing, creates fresh AGENTS.md from template"
2113
- },
2114
- {
2115
- name: "Keep Existing Only",
2116
- value: "skip",
2117
- description: "Skips AGENTS.md, only adds .lean-spec config and specs/"
2118
- }
2119
- ]
2120
- });
2121
- const projectName2 = await getProjectName2(cwd);
2122
- await handleExistingFiles(action, existingFiles, templateDir, cwd, { project_name: projectName2 });
2123
- if (action === "skip") {
2124
- skipFiles = existingFiles;
2262
+ console.log(chalk19.yellow(`Found existing: ${existingFiles.join(", ")}`));
2263
+ if (skipPrompts) {
2264
+ console.log(chalk19.gray("Using AI-Assisted Merge for existing AGENTS.md"));
2265
+ const projectName2 = await getProjectName2(cwd);
2266
+ await handleExistingFiles("merge-ai", existingFiles, templateDir, cwd, { project_name: projectName2 });
2267
+ } else {
2268
+ const action = await select({
2269
+ message: "How would you like to handle existing AGENTS.md?",
2270
+ choices: [
2271
+ {
2272
+ name: "AI-Assisted Merge (recommended)",
2273
+ value: "merge-ai",
2274
+ description: "Creates prompt for AI to intelligently consolidate both files"
2275
+ },
2276
+ {
2277
+ name: "Simple Append",
2278
+ value: "merge-append",
2279
+ description: "Quickly appends LeanSpec section (may be verbose)"
2280
+ },
2281
+ {
2282
+ name: "Replace with LeanSpec",
2283
+ value: "overwrite",
2284
+ description: "Backs up existing, creates fresh AGENTS.md from template"
2285
+ },
2286
+ {
2287
+ name: "Keep Existing Only",
2288
+ value: "skip",
2289
+ description: "Skips AGENTS.md, only adds .lean-spec config and specs/"
2290
+ }
2291
+ ]
2292
+ });
2293
+ const projectName2 = await getProjectName2(cwd);
2294
+ await handleExistingFiles(action, existingFiles, templateDir, cwd, { project_name: projectName2 });
2295
+ if (action === "skip") {
2296
+ skipFiles = existingFiles;
2297
+ }
2125
2298
  }
2126
2299
  }
2127
2300
  const projectName = await getProjectName2(cwd);
2128
- const filesDir = path4.join(templateDir, "files");
2301
+ if (!skipFiles.includes("AGENTS.md")) {
2302
+ const agentsSourcePath = path15.join(templateDir, "AGENTS.md");
2303
+ const agentsTargetPath = path15.join(cwd, "AGENTS.md");
2304
+ try {
2305
+ let agentsContent = await fs9.readFile(agentsSourcePath, "utf-8");
2306
+ agentsContent = agentsContent.replace(/\{project_name\}/g, projectName);
2307
+ await fs9.writeFile(agentsTargetPath, agentsContent, "utf-8");
2308
+ console.log(chalk19.green("\u2713 Created AGENTS.md"));
2309
+ } catch (error) {
2310
+ console.error(chalk19.red("Error copying AGENTS.md:"), error);
2311
+ process.exit(1);
2312
+ }
2313
+ }
2314
+ const filesDir = path15.join(templateDir, "files");
2315
+ try {
2316
+ const filesToCopy = await fs9.readdir(filesDir);
2317
+ const hasOtherFiles = filesToCopy.some((f) => !f.match(/\.(md)$/i) || !["README.md", "DESIGN.md", "PLAN.md", "TEST.md"].includes(f));
2318
+ if (hasOtherFiles) {
2319
+ await copyDirectory(filesDir, cwd, [...skipFiles, "README.md", "DESIGN.md", "PLAN.md", "TEST.md"], { project_name: projectName });
2320
+ }
2321
+ console.log(chalk19.green("\u2713 Initialized project structure"));
2322
+ } catch (error) {
2323
+ console.error(chalk19.red("Error copying template files:"), error);
2324
+ process.exit(1);
2325
+ }
2326
+ const specsDir = path15.join(cwd, "specs");
2129
2327
  try {
2130
- await copyDirectory(filesDir, cwd, skipFiles, { project_name: projectName });
2131
- console.log(chalk18.green("\u2713 Initialized project structure"));
2328
+ await fs9.mkdir(specsDir, { recursive: true });
2329
+ console.log(chalk19.green("\u2713 Created specs/ directory"));
2132
2330
  } catch (error) {
2133
- console.error(chalk18.red("Error copying template files:"), error);
2331
+ console.error(chalk19.red("Error creating specs directory:"), error);
2134
2332
  process.exit(1);
2135
2333
  }
2136
2334
  console.log("");
2137
- console.log(chalk18.green("\u2713 LeanSpec initialized!"));
2335
+ console.log(chalk19.green("\u2713 LeanSpec initialized!"));
2138
2336
  console.log("");
2139
2337
  console.log("Next steps:");
2140
- console.log(chalk18.gray(" - Review and customize AGENTS.md"));
2141
- console.log(chalk18.gray(" - Check out example spec in specs/"));
2142
- console.log(chalk18.gray(" - Create your first spec: lean-spec create my-feature"));
2338
+ console.log(chalk19.gray(" - Review and customize AGENTS.md"));
2339
+ console.log(chalk19.gray(" - Check out example spec in specs/"));
2340
+ console.log(chalk19.gray(" - Create your first spec: lean-spec create my-feature"));
2341
+ console.log("");
2342
+ }
2343
+ async function listExamples() {
2344
+ const examples = getExamplesList();
2345
+ console.log("");
2346
+ console.log(chalk19.bold("Available Examples:"));
2347
+ console.log("");
2348
+ for (const example of examples) {
2349
+ const difficultyColor = example.difficulty === "beginner" ? chalk19.green : example.difficulty === "intermediate" ? chalk19.yellow : chalk19.red;
2350
+ console.log(chalk19.cyan(` ${example.name}`));
2351
+ console.log(` ${example.description}`);
2352
+ console.log(` ${difficultyColor(example.difficulty)} \u2022 ${example.tech.join(", ")} \u2022 ~${example.lines} lines`);
2353
+ console.log(` Tutorial: ${chalk19.gray(example.tutorialUrl)}`);
2354
+ console.log("");
2355
+ }
2356
+ console.log("Usage:");
2357
+ console.log(chalk19.gray(" lean-spec init --example <name>"));
2358
+ console.log(chalk19.gray(" lean-spec init --example dark-theme"));
2143
2359
  console.log("");
2144
2360
  }
2361
+ async function scaffoldExample(exampleName, customName) {
2362
+ if (!exampleName) {
2363
+ exampleName = await selectExample();
2364
+ }
2365
+ if (!exampleExists(exampleName)) {
2366
+ console.error(chalk19.red(`Error: Example "${exampleName}" not found.`));
2367
+ console.log("");
2368
+ console.log("Available examples:");
2369
+ getExamplesList().forEach((ex) => {
2370
+ console.log(` - ${ex.name}`);
2371
+ });
2372
+ console.log("");
2373
+ console.log("Use: lean-spec init --list");
2374
+ process.exit(1);
2375
+ }
2376
+ const example = getExample(exampleName);
2377
+ const targetDirName = customName || exampleName;
2378
+ const targetPath = path15.join(process.cwd(), targetDirName);
2379
+ try {
2380
+ const files = await fs9.readdir(targetPath);
2381
+ const nonGitFiles = files.filter((f) => f !== ".git");
2382
+ if (nonGitFiles.length > 0) {
2383
+ console.error(chalk19.red(`Error: Directory "${targetDirName}" already exists and is not empty.`));
2384
+ console.log(chalk19.gray("Choose a different name with --name option."));
2385
+ process.exit(1);
2386
+ }
2387
+ } catch {
2388
+ }
2389
+ console.log("");
2390
+ console.log(chalk19.green(`Setting up example: ${example.title}`));
2391
+ console.log(chalk19.gray(example.description));
2392
+ console.log("");
2393
+ await fs9.mkdir(targetPath, { recursive: true });
2394
+ console.log(chalk19.green(`\u2713 Created directory: ${targetDirName}/`));
2395
+ const examplePath = path15.join(EXAMPLES_DIR, exampleName);
2396
+ await copyDirectoryRecursive(examplePath, targetPath);
2397
+ console.log(chalk19.green("\u2713 Copied example project"));
2398
+ const originalCwd = process.cwd();
2399
+ try {
2400
+ process.chdir(targetPath);
2401
+ console.log(chalk19.gray("Initializing LeanSpec..."));
2402
+ await initProject(true);
2403
+ console.log(chalk19.green("\u2713 Initialized LeanSpec"));
2404
+ } catch (error) {
2405
+ console.error(chalk19.red("Error initializing LeanSpec:"), error);
2406
+ process.exit(1);
2407
+ } finally {
2408
+ process.chdir(originalCwd);
2409
+ }
2410
+ const packageManager = await detectPackageManager();
2411
+ console.log(chalk19.gray(`Installing dependencies with ${packageManager}...`));
2412
+ try {
2413
+ const { execSync: execSync3 } = await import('child_process');
2414
+ execSync3(`${packageManager} install`, {
2415
+ cwd: targetPath,
2416
+ stdio: "inherit"
2417
+ });
2418
+ console.log(chalk19.green("\u2713 Installed dependencies"));
2419
+ } catch (error) {
2420
+ console.log(chalk19.yellow("\u26A0 Failed to install dependencies automatically"));
2421
+ console.log(chalk19.gray(` Run: cd ${targetDirName} && ${packageManager} install`));
2422
+ }
2423
+ console.log("");
2424
+ console.log(chalk19.green("\u2713 Example project ready!"));
2425
+ console.log("");
2426
+ console.log(chalk19.gray("Created:"));
2427
+ console.log(chalk19.gray(` - Application code (${example.tech.join(", ")})`));
2428
+ console.log(chalk19.gray(" - LeanSpec files (AGENTS.md, .lean-spec/, specs/)"));
2429
+ console.log("");
2430
+ console.log("Next steps:");
2431
+ console.log(chalk19.cyan(` 1. cd ${targetDirName}`));
2432
+ console.log(chalk19.cyan(" 2. Open this project in your editor"));
2433
+ console.log(chalk19.cyan(` 3. Follow the tutorial: ${example.tutorialUrl}`));
2434
+ console.log(chalk19.cyan(` 4. Ask your AI: "Help me with this tutorial using LeanSpec"`));
2435
+ console.log("");
2436
+ }
2437
+ async function selectExample() {
2438
+ const examples = getExamplesList();
2439
+ const choice = await select({
2440
+ message: "Select an example project:",
2441
+ choices: examples.map((ex) => {
2442
+ const difficultyLabel = ex.difficulty === "beginner" ? "\u2605\u2606\u2606" : ex.difficulty === "intermediate" ? "\u2605\u2605\u2606" : "\u2605\u2605\u2605";
2443
+ return {
2444
+ name: `${ex.title} (${difficultyLabel})`,
2445
+ value: ex.name,
2446
+ description: `${ex.description} \u2022 ${ex.tech.join(", ")}`
2447
+ };
2448
+ })
2449
+ });
2450
+ return choice;
2451
+ }
2452
+ async function copyDirectoryRecursive(src, dest) {
2453
+ const entries = await fs9.readdir(src, { withFileTypes: true });
2454
+ for (const entry of entries) {
2455
+ const srcPath = path15.join(src, entry.name);
2456
+ const destPath = path15.join(dest, entry.name);
2457
+ if (entry.isDirectory()) {
2458
+ await fs9.mkdir(destPath, { recursive: true });
2459
+ await copyDirectoryRecursive(srcPath, destPath);
2460
+ } else {
2461
+ await fs9.copyFile(srcPath, destPath);
2462
+ }
2463
+ }
2464
+ }
2465
+ async function detectPackageManager() {
2466
+ const cwd = process.cwd();
2467
+ try {
2468
+ await fs9.access(path15.join(cwd, "..", "pnpm-lock.yaml"));
2469
+ return "pnpm";
2470
+ } catch {
2471
+ }
2472
+ try {
2473
+ await fs9.access(path15.join(cwd, "..", "yarn.lock"));
2474
+ return "yarn";
2475
+ } catch {
2476
+ }
2477
+ return "npm";
2478
+ }
2145
2479
  function normalizeDateFields2(data) {
2146
2480
  const dateFields = ["created", "completed", "updated", "due"];
2147
2481
  for (const field of dateFields) {
@@ -2449,21 +2783,28 @@ function analyzeMarkdownStructure(content) {
2449
2783
  };
2450
2784
  }
2451
2785
  var TokenCounter = class {
2452
- encoding;
2453
- constructor() {
2454
- this.encoding = encoding_for_model("gpt-4");
2786
+ encoding = null;
2787
+ async getEncoding() {
2788
+ if (!this.encoding) {
2789
+ const { encoding_for_model } = await import('tiktoken');
2790
+ this.encoding = encoding_for_model("gpt-4");
2791
+ }
2792
+ return this.encoding;
2455
2793
  }
2456
2794
  /**
2457
2795
  * Clean up resources (important to prevent memory leaks)
2458
2796
  */
2459
2797
  dispose() {
2460
- this.encoding.free();
2798
+ if (this.encoding) {
2799
+ this.encoding.free();
2800
+ }
2461
2801
  }
2462
2802
  /**
2463
2803
  * Count tokens in a string
2464
2804
  */
2465
- countString(text) {
2466
- const tokens = this.encoding.encode(text);
2805
+ async countString(text) {
2806
+ const encoding = await this.getEncoding();
2807
+ const tokens = encoding.encode(text);
2467
2808
  return tokens.length;
2468
2809
  }
2469
2810
  /**
@@ -2478,7 +2819,7 @@ var TokenCounter = class {
2478
2819
  */
2479
2820
  async countFile(filePath, options = {}) {
2480
2821
  const content = await fs9.readFile(filePath, "utf-8");
2481
- const tokens = this.countString(content);
2822
+ const tokens = await this.countString(content);
2482
2823
  const lines = content.split("\n").length;
2483
2824
  const result = {
2484
2825
  total: tokens,
@@ -2526,9 +2867,9 @@ var TokenCounter = class {
2526
2867
  };
2527
2868
  }
2528
2869
  for (const file of filesToCount) {
2529
- const filePath = path4.join(specPath, file);
2870
+ const filePath = path15.join(specPath, file);
2530
2871
  const content = await fs9.readFile(filePath, "utf-8");
2531
- const tokens = this.countString(content);
2872
+ const tokens = await this.countString(content);
2532
2873
  const lines = content.split("\n").length;
2533
2874
  fileCounts.push({
2534
2875
  path: file,
@@ -2566,7 +2907,7 @@ var TokenCounter = class {
2566
2907
  const parsed = matter2(content);
2567
2908
  body = parsed.content;
2568
2909
  frontmatterContent = parsed.matter;
2569
- breakdown.frontmatter = this.countString(frontmatterContent);
2910
+ breakdown.frontmatter = await this.countString(frontmatterContent);
2570
2911
  } catch {
2571
2912
  }
2572
2913
  let inCodeBlock = false;
@@ -2577,23 +2918,23 @@ var TokenCounter = class {
2577
2918
  const trimmed = line.trim();
2578
2919
  if (trimmed.startsWith("```")) {
2579
2920
  inCodeBlock = !inCodeBlock;
2580
- breakdown.code += this.countString(line + "\n");
2921
+ breakdown.code += await this.countString(line + "\n");
2581
2922
  continue;
2582
2923
  }
2583
2924
  if (inCodeBlock) {
2584
- breakdown.code += this.countString(line + "\n");
2925
+ breakdown.code += await this.countString(line + "\n");
2585
2926
  continue;
2586
2927
  }
2587
2928
  const isTableSeparator = trimmed.includes("|") && /[-:]{3,}/.test(trimmed);
2588
2929
  const isTableRow = trimmed.includes("|") && trimmed.startsWith("|");
2589
2930
  if (isTableSeparator || inTable && isTableRow) {
2590
2931
  inTable = true;
2591
- breakdown.tables += this.countString(line + "\n");
2932
+ breakdown.tables += await this.countString(line + "\n");
2592
2933
  continue;
2593
2934
  } else if (inTable && !isTableRow) {
2594
2935
  inTable = false;
2595
2936
  }
2596
- breakdown.prose += this.countString(line + "\n");
2937
+ breakdown.prose += await this.countString(line + "\n");
2597
2938
  }
2598
2939
  return breakdown;
2599
2940
  }
@@ -2674,12 +3015,12 @@ async function countTokens(input, options) {
2674
3015
  try {
2675
3016
  if (typeof input === "string") {
2676
3017
  return {
2677
- total: counter.countString(input),
3018
+ total: await counter.countString(input),
2678
3019
  files: []
2679
3020
  };
2680
3021
  } else if ("content" in input) {
2681
3022
  return {
2682
- total: counter.countString(input.content),
3023
+ total: await counter.countString(input.content),
2683
3024
  files: []
2684
3025
  };
2685
3026
  } else if ("filePath" in input) {
@@ -2857,12 +3198,12 @@ var ComplexityValidator = class {
2857
3198
  const listItemCount = lines.filter((line) => line.match(/^[\s]*[-*]\s/) || line.match(/^[\s]*\d+\.\s/)).length;
2858
3199
  const tableCount = lines.filter((line) => line.includes("|") && line.match(/[-:]{3,}/)).length;
2859
3200
  const counter = new TokenCounter();
2860
- const tokenCount = counter.countString(content);
3201
+ const tokenCount = await counter.countString(content);
2861
3202
  counter.dispose();
2862
3203
  let hasSubSpecs = false;
2863
3204
  let subSpecCount = 0;
2864
3205
  try {
2865
- const specDir = path4.dirname(spec.filePath);
3206
+ const specDir = path15.dirname(spec.filePath);
2866
3207
  const files = await fs9.readdir(specDir);
2867
3208
  const mdFiles = files.filter(
2868
3209
  (f) => f.endsWith(".md") && f !== "README.md"
@@ -3431,7 +3772,7 @@ function filesCommand(specPath, options = {}) {
3431
3772
  if (typeof specPath === "string") {
3432
3773
  return showFiles(specPath, options);
3433
3774
  }
3434
- return new Command("files").description("List files in a spec").argument("<spec>", "Spec to list files for").option("--type <type>", "Filter by type: docs, assets").option("--tree", "Show tree structure").action(async (target, opts) => {
3775
+ return new Command("files").description("List files in a spec").argument("<spec>", "Spec to list files for").option("--type <type>", "Filter by type: docs, assets").option("--tree", "Show tree structure").option("--json", "Output as JSON").action(async (target, opts) => {
3435
3776
  await showFiles(target, opts);
3436
3777
  });
3437
3778
  }
@@ -3439,7 +3780,7 @@ async function showFiles(specPath, options = {}) {
3439
3780
  await autoCheckIfEnabled();
3440
3781
  const config = await loadConfig();
3441
3782
  const cwd = process.cwd();
3442
- const specsDir = path4.join(cwd, config.specsDir);
3783
+ const specsDir = path15.join(cwd, config.specsDir);
3443
3784
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
3444
3785
  if (!resolvedPath) {
3445
3786
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}. Try using the full path or spec name (e.g., 001-my-spec)`);
@@ -3449,15 +3790,40 @@ async function showFiles(specPath, options = {}) {
3449
3790
  throw new Error(`Could not load spec: ${sanitizeUserInput(specPath)}`);
3450
3791
  }
3451
3792
  const subFiles = await loadSubFiles(spec.fullPath);
3793
+ if (options.json) {
3794
+ const readmeStat2 = await fs9.stat(spec.filePath);
3795
+ const readmeContent2 = await fs9.readFile(spec.filePath, "utf-8");
3796
+ const readmeTokens2 = await countTokens({ content: readmeContent2 });
3797
+ const jsonOutput = {
3798
+ spec: spec.name,
3799
+ path: spec.fullPath,
3800
+ files: [
3801
+ {
3802
+ name: "README.md",
3803
+ type: "required",
3804
+ size: readmeStat2.size,
3805
+ tokens: readmeTokens2.total
3806
+ },
3807
+ ...subFiles.map((f) => ({
3808
+ name: f.name,
3809
+ type: f.type,
3810
+ size: f.size
3811
+ }))
3812
+ ],
3813
+ total: subFiles.length + 1
3814
+ };
3815
+ console.log(JSON.stringify(jsonOutput, null, 2));
3816
+ return;
3817
+ }
3452
3818
  console.log("");
3453
- console.log(chalk18.cyan(`\u{1F4C4} Files in ${sanitizeUserInput(spec.name)}`));
3819
+ console.log(chalk19.cyan(`\u{1F4C4} Files in ${sanitizeUserInput(spec.name)}`));
3454
3820
  console.log("");
3455
- console.log(chalk18.green("Required:"));
3821
+ console.log(chalk19.green("Required:"));
3456
3822
  const readmeStat = await fs9.stat(spec.filePath);
3457
3823
  const readmeSize = formatSize(readmeStat.size);
3458
3824
  const readmeContent = await fs9.readFile(spec.filePath, "utf-8");
3459
3825
  const readmeTokens = await countTokens({ content: readmeContent });
3460
- console.log(chalk18.green(` \u2713 README.md (${readmeSize}, ~${readmeTokens.total.toLocaleString()} tokens) Main spec`));
3826
+ console.log(chalk19.green(` \u2713 README.md (${readmeSize}, ~${readmeTokens.total.toLocaleString()} tokens) Main spec`));
3461
3827
  console.log("");
3462
3828
  let filteredFiles = subFiles;
3463
3829
  if (options.type === "docs") {
@@ -3466,27 +3832,27 @@ async function showFiles(specPath, options = {}) {
3466
3832
  filteredFiles = subFiles.filter((f) => f.type === "asset");
3467
3833
  }
3468
3834
  if (filteredFiles.length === 0) {
3469
- console.log(chalk18.gray("No additional files"));
3835
+ console.log(chalk19.gray("No additional files"));
3470
3836
  console.log("");
3471
3837
  return;
3472
3838
  }
3473
3839
  const documents = filteredFiles.filter((f) => f.type === "document");
3474
3840
  const assets = filteredFiles.filter((f) => f.type === "asset");
3475
3841
  if (documents.length > 0 && (!options.type || options.type === "docs")) {
3476
- console.log(chalk18.cyan("Documents:"));
3842
+ console.log(chalk19.cyan("Documents:"));
3477
3843
  for (const file of documents) {
3478
3844
  const size = formatSize(file.size);
3479
3845
  const content = await fs9.readFile(file.path, "utf-8");
3480
3846
  const tokenCount = await countTokens({ content });
3481
- console.log(chalk18.cyan(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size}, ~${tokenCount.total.toLocaleString()} tokens)`));
3847
+ console.log(chalk19.cyan(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size}, ~${tokenCount.total.toLocaleString()} tokens)`));
3482
3848
  }
3483
3849
  console.log("");
3484
3850
  }
3485
3851
  if (assets.length > 0 && (!options.type || options.type === "assets")) {
3486
- console.log(chalk18.yellow("Assets:"));
3852
+ console.log(chalk19.yellow("Assets:"));
3487
3853
  for (const file of assets) {
3488
3854
  const size = formatSize(file.size);
3489
- console.log(chalk18.yellow(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size})`));
3855
+ console.log(chalk19.yellow(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size})`));
3490
3856
  }
3491
3857
  console.log("");
3492
3858
  }
@@ -3494,7 +3860,7 @@ async function showFiles(specPath, options = {}) {
3494
3860
  const totalSize = formatSize(
3495
3861
  readmeStat.size + filteredFiles.reduce((sum, f) => sum + f.size, 0)
3496
3862
  );
3497
- console.log(chalk18.gray(`Total: ${totalFiles} files, ${totalSize}`));
3863
+ console.log(chalk19.gray(`Total: ${totalFiles} files, ${totalSize}`));
3498
3864
  console.log("");
3499
3865
  }
3500
3866
  function formatSize(bytes) {
@@ -3506,6 +3872,41 @@ function formatSize(bytes) {
3506
3872
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3507
3873
  }
3508
3874
  }
3875
+ function examplesCommand() {
3876
+ return new Command("examples").description("List available example projects for tutorials").action(async () => {
3877
+ await listExamples2();
3878
+ });
3879
+ }
3880
+ async function listExamples2() {
3881
+ const examples = getExamplesList();
3882
+ console.log("");
3883
+ console.log(chalk19.bold("LeanSpec Example Projects"));
3884
+ console.log("");
3885
+ console.log("Scaffold complete example projects to follow tutorials:");
3886
+ console.log("");
3887
+ for (const example of examples) {
3888
+ const difficultyColor = example.difficulty === "beginner" ? chalk19.green : example.difficulty === "intermediate" ? chalk19.yellow : chalk19.red;
3889
+ const difficultyStars = example.difficulty === "beginner" ? "\u2605\u2606\u2606" : example.difficulty === "intermediate" ? "\u2605\u2605\u2606" : "\u2605\u2605\u2605";
3890
+ console.log(chalk19.cyan.bold(` ${example.title}`));
3891
+ console.log(` ${chalk19.gray(example.name)}`);
3892
+ console.log(` ${example.description}`);
3893
+ console.log(` ${difficultyColor(difficultyStars + " " + example.difficulty)} \u2022 ${example.tech.join(", ")} \u2022 ~${example.lines} lines`);
3894
+ console.log(` ${chalk19.gray("Tutorial:")} ${example.tutorial}`);
3895
+ console.log(` ${chalk19.gray(example.tutorialUrl)}`);
3896
+ console.log("");
3897
+ }
3898
+ console.log(chalk19.bold("Usage:"));
3899
+ console.log("");
3900
+ console.log(" # Scaffold an example");
3901
+ console.log(chalk19.cyan(" lean-spec init --example <name>"));
3902
+ console.log("");
3903
+ console.log(" # Interactive selection");
3904
+ console.log(chalk19.cyan(" lean-spec init --example"));
3905
+ console.log("");
3906
+ console.log(" # Custom directory name");
3907
+ console.log(chalk19.cyan(" lean-spec init --example dark-theme --name my-demo"));
3908
+ console.log("");
3909
+ }
3509
3910
  var FrontmatterValidator = class {
3510
3911
  name = "frontmatter";
3511
3912
  description = "Validate spec frontmatter for required fields and valid values";
@@ -3993,7 +4394,7 @@ var SubSpecValidator = class {
3993
4394
  */
3994
4395
  validateNamingConventions(subSpecs, warnings) {
3995
4396
  for (const subSpec of subSpecs) {
3996
- const baseName = path4.basename(subSpec.name, ".md");
4397
+ const baseName = path15.basename(subSpec.name, ".md");
3997
4398
  if (baseName !== baseName.toUpperCase()) {
3998
4399
  warnings.push({
3999
4400
  message: `Sub-spec filename should be uppercase: ${subSpec.name}`,
@@ -4157,17 +4558,17 @@ function formatFileIssues(fileResult, specsDir) {
4157
4558
  const priority = fileResult.spec.frontmatter.priority || "medium";
4158
4559
  const statusBadge = formatStatusBadge(status);
4159
4560
  const priorityBadge = formatPriorityBadge(priority);
4160
- lines.push(chalk18.bold.cyan(`${specName} ${statusBadge} ${priorityBadge}`));
4561
+ lines.push(chalk19.bold.cyan(`${specName} ${statusBadge} ${priorityBadge}`));
4161
4562
  } else {
4162
- lines.push(chalk18.cyan.underline(relativePath));
4563
+ lines.push(chalk19.cyan.underline(relativePath));
4163
4564
  }
4164
4565
  for (const issue of fileResult.issues) {
4165
- const severityColor = issue.severity === "error" ? chalk18.red : chalk18.yellow;
4566
+ const severityColor = issue.severity === "error" ? chalk19.red : chalk19.yellow;
4166
4567
  const severityText = severityColor(issue.severity.padEnd(9));
4167
- const ruleText = chalk18.gray(issue.ruleName);
4568
+ const ruleText = chalk19.gray(issue.ruleName);
4168
4569
  lines.push(` ${severityText}${issue.message.padEnd(60)} ${ruleText}`);
4169
4570
  if (issue.suggestion) {
4170
- lines.push(chalk18.gray(` \u2192 ${issue.suggestion}`));
4571
+ lines.push(chalk19.gray(` \u2192 ${issue.suggestion}`));
4171
4572
  }
4172
4573
  }
4173
4574
  lines.push("");
@@ -4177,25 +4578,25 @@ function formatSummary(totalSpecs, errorCount, warningCount, cleanCount) {
4177
4578
  if (errorCount > 0) {
4178
4579
  const errorText = errorCount === 1 ? "error" : "errors";
4179
4580
  const warningText = warningCount === 1 ? "warning" : "warnings";
4180
- return chalk18.red.bold(
4581
+ return chalk19.red.bold(
4181
4582
  `\u2716 ${errorCount} ${errorText}, ${warningCount} ${warningText} (${totalSpecs} specs checked, ${cleanCount} clean)`
4182
4583
  );
4183
4584
  } else if (warningCount > 0) {
4184
4585
  const warningText = warningCount === 1 ? "warning" : "warnings";
4185
- return chalk18.yellow.bold(
4586
+ return chalk19.yellow.bold(
4186
4587
  `\u26A0 ${warningCount} ${warningText} (${totalSpecs} specs checked, ${cleanCount} clean)`
4187
4588
  );
4188
4589
  } else {
4189
- return chalk18.green.bold(`\u2713 All ${totalSpecs} specs passed`);
4590
+ return chalk19.green.bold(`\u2713 All ${totalSpecs} specs passed`);
4190
4591
  }
4191
4592
  }
4192
4593
  function formatPassingSpecs(specs, specsDir) {
4193
4594
  const lines = [];
4194
- lines.push(chalk18.green.bold(`
4595
+ lines.push(chalk19.green.bold(`
4195
4596
  \u2713 ${specs.length} specs passed:`));
4196
4597
  for (const spec of specs) {
4197
4598
  const relativePath = normalizeFilePath(spec.filePath);
4198
- lines.push(chalk18.gray(` ${relativePath}`));
4599
+ lines.push(chalk19.gray(` ${relativePath}`));
4199
4600
  }
4200
4601
  return lines.join("\n");
4201
4602
  }
@@ -4241,13 +4642,13 @@ function formatValidationResults(results, specs, specsDir, options = {}) {
4241
4642
  return formatJson(displayResults, specs.length, errorCount2, warningCount2);
4242
4643
  }
4243
4644
  const lines = [];
4244
- lines.push(chalk18.bold(`
4645
+ lines.push(chalk19.bold(`
4245
4646
  Validating ${specs.length} specs...
4246
4647
  `));
4247
4648
  let previousSpecName;
4248
4649
  for (const fileResult of displayResults) {
4249
4650
  if (fileResult.spec && previousSpecName && fileResult.spec.name !== previousSpecName) {
4250
- lines.push(chalk18.gray("\u2500".repeat(80)));
4651
+ lines.push(chalk19.gray("\u2500".repeat(80)));
4251
4652
  lines.push("");
4252
4653
  }
4253
4654
  lines.push(formatFileIssues(fileResult));
@@ -4271,20 +4672,20 @@ Validating ${specs.length} specs...
4271
4672
  lines.push(formatPassingSpecs(passingSpecs));
4272
4673
  }
4273
4674
  if (!options.verbose && cleanCount > 0 && displayResults.length > 0) {
4274
- lines.push(chalk18.gray("\nRun with --verbose to see passing specs."));
4675
+ lines.push(chalk19.gray("\nRun with --verbose to see passing specs."));
4275
4676
  }
4276
4677
  return lines.join("\n");
4277
4678
  }
4278
4679
 
4279
4680
  // src/commands/validate.ts
4280
4681
  function validateCommand() {
4281
- return new Command("validate").description("Validate specs for quality issues").argument("[specs...]", "Specific specs to validate (optional)").option("--max-lines <number>", "Custom line limit (default: 400)", parseInt).option("--verbose", "Show passing specs").option("--quiet", "Suppress warnings, only show errors").option("--format <format>", "Output format: default, json, compact", "default").option("--rule <rule>", "Filter by specific rule name (e.g., max-lines, frontmatter)").option("--warnings-only", "Treat all issues as warnings, never fail (useful for CI pre-release checks)").action(async (specs, options) => {
4682
+ return new Command("validate").description("Validate specs for quality issues").argument("[specs...]", "Specific specs to validate (optional)").option("--max-lines <number>", "Custom line limit (default: 400)", parseInt).option("--verbose", "Show passing specs").option("--quiet", "Suppress warnings, only show errors").option("--format <format>", "Output format: default, json, compact", "default").option("--json", "Output as JSON (shorthand for --format json)").option("--rule <rule>", "Filter by specific rule name (e.g., max-lines, frontmatter)").option("--warnings-only", "Treat all issues as warnings, never fail (useful for CI pre-release checks)").action(async (specs, options) => {
4282
4683
  const passed = await validateSpecs({
4283
4684
  maxLines: options.maxLines,
4284
4685
  specs: specs && specs.length > 0 ? specs : void 0,
4285
4686
  verbose: options.verbose,
4286
4687
  quiet: options.quiet,
4287
- format: options.format,
4688
+ format: options.json ? "json" : options.format,
4288
4689
  rule: options.rule,
4289
4690
  warningsOnly: options.warningsOnly
4290
4691
  });
@@ -4299,12 +4700,12 @@ async function validateSpecs(options = {}) {
4299
4700
  specs = [];
4300
4701
  for (const specPath of options.specs) {
4301
4702
  const spec = allSpecs.find(
4302
- (s) => s.path.includes(specPath) || path4.basename(s.path).includes(specPath)
4703
+ (s) => s.path.includes(specPath) || path15.basename(s.path).includes(specPath)
4303
4704
  );
4304
4705
  if (spec) {
4305
4706
  specs.push(spec);
4306
4707
  } else {
4307
- console.error(chalk18.red(`Error: Spec not found: ${specPath}`));
4708
+ console.error(chalk19.red(`Error: Spec not found: ${specPath}`));
4308
4709
  return false;
4309
4710
  }
4310
4711
  }
@@ -4332,7 +4733,7 @@ async function validateSpecs(options = {}) {
4332
4733
  try {
4333
4734
  content = await fs9.readFile(spec.filePath, "utf-8");
4334
4735
  } catch (error) {
4335
- console.error(chalk18.red(`Error reading ${spec.filePath}:`), error);
4736
+ console.error(chalk19.red(`Error reading ${spec.filePath}:`), error);
4336
4737
  continue;
4337
4738
  }
4338
4739
  for (const validator of validators) {
@@ -4345,7 +4746,7 @@ async function validateSpecs(options = {}) {
4345
4746
  content
4346
4747
  });
4347
4748
  } catch (error) {
4348
- console.error(chalk18.yellow(`Warning: Validator ${validator.name} failed:`), error instanceof Error ? error.message : error);
4749
+ console.error(chalk19.yellow(`Warning: Validator ${validator.name} failed:`), error instanceof Error ? error.message : error);
4349
4750
  }
4350
4751
  }
4351
4752
  }
@@ -4414,7 +4815,7 @@ async function scanDocuments(dirPath) {
4414
4815
  async function scanRecursive(currentPath) {
4415
4816
  const entries = await fs9.readdir(currentPath, { withFileTypes: true });
4416
4817
  for (const entry of entries) {
4417
- const fullPath = path4.join(currentPath, entry.name);
4818
+ const fullPath = path15.join(currentPath, entry.name);
4418
4819
  if (entry.isDirectory()) {
4419
4820
  if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
4420
4821
  await scanRecursive(fullPath);
@@ -4759,7 +5160,7 @@ function calculateVelocityMetrics(specs) {
4759
5160
 
4760
5161
  // src/commands/board.ts
4761
5162
  function boardCommand() {
4762
- return new Command("board").description("Show Kanban-style board view with project completion summary").option("--complete", "Include complete specs (default: hidden)").option("--simple", "Hide completion summary (kanban only)").option("--completion-only", "Show only completion summary (no kanban)").option("--tag <tag>", "Filter by tag").option("--assignee <name>", "Filter by assignee").action(async (options) => {
5163
+ return new Command("board").description("Show Kanban-style board view with project completion summary").option("--complete", "Include complete specs (default: hidden)").option("--simple", "Hide completion summary (kanban only)").option("--completion-only", "Show only completion summary (no kanban)").option("--tag <tag>", "Filter by tag").option("--assignee <name>", "Filter by assignee").option("--json", "Output as JSON").action(async (options) => {
4763
5164
  await showBoard(options);
4764
5165
  });
4765
5166
  }
@@ -4780,7 +5181,11 @@ async function showBoard(options) {
4780
5181
  })
4781
5182
  );
4782
5183
  if (specs.length === 0) {
4783
- console.log(chalk18.dim("No specs found."));
5184
+ if (options.json) {
5185
+ console.log(JSON.stringify({ columns: {}, total: 0 }, null, 2));
5186
+ } else {
5187
+ console.log(chalk19.dim("No specs found."));
5188
+ }
4784
5189
  return;
4785
5190
  }
4786
5191
  const columns = {
@@ -4795,12 +5200,35 @@ async function showBoard(options) {
4795
5200
  columns[status].push(spec);
4796
5201
  }
4797
5202
  }
4798
- console.log(chalk18.bold.cyan("\u{1F4CB} Spec Kanban Board"));
5203
+ if (options.json) {
5204
+ const completionMetrics = calculateCompletion(specs);
5205
+ const velocityMetrics = calculateVelocityMetrics(specs);
5206
+ const jsonOutput = {
5207
+ columns: {
5208
+ planned: columns.planned.map((s) => ({ path: s.path, priority: s.frontmatter.priority, assignee: s.frontmatter.assignee, tags: s.frontmatter.tags })),
5209
+ "in-progress": columns["in-progress"].map((s) => ({ path: s.path, priority: s.frontmatter.priority, assignee: s.frontmatter.assignee, tags: s.frontmatter.tags })),
5210
+ complete: columns.complete.map((s) => ({ path: s.path, priority: s.frontmatter.priority, assignee: s.frontmatter.assignee, tags: s.frontmatter.tags }))
5211
+ },
5212
+ summary: {
5213
+ total: completionMetrics.totalSpecs,
5214
+ active: completionMetrics.activeSpecs,
5215
+ complete: completionMetrics.completeSpecs,
5216
+ completionRate: completionMetrics.score,
5217
+ velocity: {
5218
+ avgCycleTime: velocityMetrics.cycleTime.average,
5219
+ throughputPerWeek: velocityMetrics.throughput.perWeek / 7 * 7
5220
+ }
5221
+ }
5222
+ };
5223
+ console.log(JSON.stringify(jsonOutput, null, 2));
5224
+ return;
5225
+ }
5226
+ console.log(chalk19.bold.cyan("\u{1F4CB} Spec Kanban Board"));
4799
5227
  if (options.tag || options.assignee) {
4800
5228
  const filterParts = [];
4801
5229
  if (options.tag) filterParts.push(`tag=${options.tag}`);
4802
5230
  if (options.assignee) filterParts.push(`assignee=${options.assignee}`);
4803
- console.log(chalk18.dim(`Filtered by: ${filterParts.join(", ")}`));
5231
+ console.log(chalk19.dim(`Filtered by: ${filterParts.join(", ")}`));
4804
5232
  }
4805
5233
  console.log("");
4806
5234
  if (!options.simple) {
@@ -4815,12 +5243,12 @@ async function showBoard(options) {
4815
5243
  const padding = boxWidth - 2 - visibleLength;
4816
5244
  return content + " ".repeat(Math.max(0, padding));
4817
5245
  };
4818
- console.log(chalk18.dim(topBorder));
4819
- const headerLine = chalk18.bold(" Project Overview");
4820
- console.log(chalk18.dim("\u2551") + padLine(headerLine) + chalk18.dim("\u2551"));
4821
- const percentageColor = completionMetrics.score >= 70 ? chalk18.green : completionMetrics.score >= 40 ? chalk18.yellow : chalk18.red;
5246
+ console.log(chalk19.dim(topBorder));
5247
+ const headerLine = chalk19.bold(" Project Overview");
5248
+ console.log(chalk19.dim("\u2551") + padLine(headerLine) + chalk19.dim("\u2551"));
5249
+ const percentageColor = completionMetrics.score >= 70 ? chalk19.green : completionMetrics.score >= 40 ? chalk19.yellow : chalk19.red;
4822
5250
  const line1 = ` ${completionMetrics.totalSpecs} total \xB7 ${completionMetrics.activeSpecs} active \xB7 ${completionMetrics.completeSpecs} complete ${percentageColor("(" + completionMetrics.score + "%)")}`;
4823
- console.log(chalk18.dim("\u2551") + padLine(line1) + chalk18.dim("\u2551"));
5251
+ console.log(chalk19.dim("\u2551") + padLine(line1) + chalk19.dim("\u2551"));
4824
5252
  if (completionMetrics.criticalIssues.length > 0 || completionMetrics.warnings.length > 0) {
4825
5253
  const alerts = [];
4826
5254
  if (completionMetrics.criticalIssues.length > 0) {
@@ -4829,27 +5257,27 @@ async function showBoard(options) {
4829
5257
  if (completionMetrics.warnings.length > 0) {
4830
5258
  alerts.push(`${completionMetrics.warnings.length} specs WIP > 7 days`);
4831
5259
  }
4832
- const alertLine = ` ${chalk18.yellow("\u26A0\uFE0F " + alerts.join(" \xB7 "))}`;
4833
- console.log(chalk18.dim("\u2551") + padLine(alertLine) + chalk18.dim("\u2551"));
5260
+ const alertLine = ` ${chalk19.yellow("\u26A0\uFE0F " + alerts.join(" \xB7 "))}`;
5261
+ console.log(chalk19.dim("\u2551") + padLine(alertLine) + chalk19.dim("\u2551"));
4834
5262
  }
4835
- const velocityLine = ` ${chalk18.cyan("\u{1F680} Velocity:")} ${velocityMetrics.cycleTime.average.toFixed(1)}d avg cycle \xB7 ${(velocityMetrics.throughput.perWeek / 7 * 7).toFixed(1)}/wk throughput`;
4836
- console.log(chalk18.dim("\u2551") + padLine(velocityLine) + chalk18.dim("\u2551"));
4837
- console.log(chalk18.dim(bottomBorder));
5263
+ const velocityLine = ` ${chalk19.cyan("\u{1F680} Velocity:")} ${velocityMetrics.cycleTime.average.toFixed(1)}d avg cycle \xB7 ${(velocityMetrics.throughput.perWeek / 7 * 7).toFixed(1)}/wk throughput`;
5264
+ console.log(chalk19.dim("\u2551") + padLine(velocityLine) + chalk19.dim("\u2551"));
5265
+ console.log(chalk19.dim(bottomBorder));
4838
5266
  console.log("");
4839
5267
  if (options.completionOnly) {
4840
5268
  return;
4841
5269
  }
4842
5270
  }
4843
5271
  renderColumn(STATUS_CONFIG.planned.label, STATUS_CONFIG.planned.emoji, columns.planned, true, STATUS_CONFIG.planned.colorFn);
4844
- console.log(chalk18.dim("\u2501".repeat(70)));
5272
+ console.log(chalk19.dim("\u2501".repeat(70)));
4845
5273
  console.log("");
4846
5274
  renderColumn(STATUS_CONFIG["in-progress"].label, STATUS_CONFIG["in-progress"].emoji, columns["in-progress"], true, STATUS_CONFIG["in-progress"].colorFn);
4847
- console.log(chalk18.dim("\u2501".repeat(70)));
5275
+ console.log(chalk19.dim("\u2501".repeat(70)));
4848
5276
  console.log("");
4849
5277
  renderColumn(STATUS_CONFIG.complete.label, STATUS_CONFIG.complete.emoji, columns.complete, options.showComplete || false, STATUS_CONFIG.complete.colorFn);
4850
5278
  }
4851
5279
  function renderColumn(title, emoji, specs, expanded, colorFn) {
4852
- console.log(`${emoji} ${colorFn(chalk18.bold(`${title} (${specs.length})`))}`);
5280
+ console.log(`${emoji} ${colorFn(chalk19.bold(`${title} (${specs.length})`))}`);
4853
5281
  console.log("");
4854
5282
  if (expanded && specs.length > 0) {
4855
5283
  const priorityGroups = {
@@ -4874,30 +5302,30 @@ function renderColumn(title, emoji, specs, expanded, colorFn) {
4874
5302
  firstGroup = false;
4875
5303
  const priorityLabel = priority === "none" ? "No Priority" : priority.charAt(0).toUpperCase() + priority.slice(1);
4876
5304
  const priorityEmoji = priority === "none" ? "\u26AA" : PRIORITY_CONFIG[priority].emoji;
4877
- const priorityColor = priority === "none" ? chalk18.dim : PRIORITY_CONFIG[priority].colorFn;
4878
- console.log(` ${priorityColor(`${priorityEmoji} ${chalk18.bold(priorityLabel)} ${chalk18.dim(`(${groupSpecs.length})`)}`)}`);
5305
+ const priorityColor = priority === "none" ? chalk19.dim : PRIORITY_CONFIG[priority].colorFn;
5306
+ console.log(` ${priorityColor(`${priorityEmoji} ${chalk19.bold(priorityLabel)} ${chalk19.dim(`(${groupSpecs.length})`)}`)}`);
4879
5307
  for (const spec of groupSpecs) {
4880
5308
  let assigneeStr = "";
4881
5309
  if (spec.frontmatter.assignee) {
4882
- assigneeStr = " " + chalk18.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
5310
+ assigneeStr = " " + chalk19.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
4883
5311
  }
4884
5312
  let tagsStr = "";
4885
5313
  if (spec.frontmatter.tags?.length) {
4886
5314
  const tags = Array.isArray(spec.frontmatter.tags) ? spec.frontmatter.tags : [];
4887
5315
  if (tags.length > 0) {
4888
5316
  const tagStr = tags.map((tag) => `#${sanitizeUserInput(tag)}`).join(" ");
4889
- tagsStr = " " + chalk18.dim(chalk18.magenta(tagStr));
5317
+ tagsStr = " " + chalk19.dim(chalk19.magenta(tagStr));
4890
5318
  }
4891
5319
  }
4892
- console.log(` ${chalk18.cyan(sanitizeUserInput(spec.path))}${assigneeStr}${tagsStr}`);
5320
+ console.log(` ${chalk19.cyan(sanitizeUserInput(spec.path))}${assigneeStr}${tagsStr}`);
4893
5321
  }
4894
5322
  }
4895
5323
  console.log("");
4896
5324
  } else if (!expanded && specs.length > 0) {
4897
- console.log(` ${chalk18.dim("(collapsed, use --complete to expand)")}`);
5325
+ console.log(` ${chalk19.dim("(collapsed, use --complete to expand)")}`);
4898
5326
  console.log("");
4899
5327
  } else {
4900
- console.log(` ${chalk18.dim("(empty)")}`);
5328
+ console.log(` ${chalk19.dim("(empty)")}`);
4901
5329
  console.log("");
4902
5330
  }
4903
5331
  }
@@ -5066,26 +5494,26 @@ async function showStats(options) {
5066
5494
  console.log(JSON.stringify(data, null, 2));
5067
5495
  return;
5068
5496
  }
5069
- console.log(chalk18.bold.cyan("\u{1F4CA} Spec Stats"));
5497
+ console.log(chalk19.bold.cyan("\u{1F4CA} Spec Stats"));
5070
5498
  console.log("");
5071
5499
  if (options.tag || options.assignee) {
5072
5500
  const filterParts = [];
5073
5501
  if (options.tag) filterParts.push(`tag=${options.tag}`);
5074
5502
  if (options.assignee) filterParts.push(`assignee=${options.assignee}`);
5075
- console.log(chalk18.dim(`Filtered by: ${filterParts.join(", ")}`));
5503
+ console.log(chalk19.dim(`Filtered by: ${filterParts.join(", ")}`));
5076
5504
  console.log("");
5077
5505
  }
5078
5506
  if (showSimplified) {
5079
- console.log(chalk18.bold("\u{1F4C8} Overview"));
5507
+ console.log(chalk19.bold("\u{1F4C8} Overview"));
5080
5508
  console.log("");
5081
5509
  const completionStatus = getCompletionStatus(completionMetrics.score);
5082
- const completionColor = completionStatus.color === "green" ? chalk18.green : completionStatus.color === "yellow" ? chalk18.yellow : chalk18.red;
5083
- console.log(` Total Specs ${chalk18.cyan(completionMetrics.totalSpecs)}`);
5084
- console.log(` Active (Planned+WIP) ${chalk18.yellow(completionMetrics.activeSpecs)}`);
5085
- console.log(` Complete ${chalk18.green(completionMetrics.completeSpecs)}`);
5510
+ const completionColor = completionStatus.color === "green" ? chalk19.green : completionStatus.color === "yellow" ? chalk19.yellow : chalk19.red;
5511
+ console.log(` Total Specs ${chalk19.cyan(completionMetrics.totalSpecs)}`);
5512
+ console.log(` Active (Planned+WIP) ${chalk19.yellow(completionMetrics.activeSpecs)}`);
5513
+ console.log(` Complete ${chalk19.green(completionMetrics.completeSpecs)}`);
5086
5514
  console.log(` Completion Rate ${completionColor(`${completionMetrics.score}% ${completionStatus.emoji}`)}`);
5087
5515
  console.log("");
5088
- console.log(chalk18.bold("\u{1F4CA} Status"));
5516
+ console.log(chalk19.bold("\u{1F4CA} Status"));
5089
5517
  console.log("");
5090
5518
  const labelWidth2 = 15;
5091
5519
  const barWidth2 = 20;
@@ -5094,19 +5522,19 @@ async function showStats(options) {
5094
5522
  const width = Math.round(count / maxCount * barWidth2);
5095
5523
  const filledWidth = Math.min(width, barWidth2);
5096
5524
  const emptyWidth = barWidth2 - filledWidth;
5097
- return char.repeat(filledWidth) + chalk18.dim("\u2591").repeat(emptyWidth);
5525
+ return char.repeat(filledWidth) + chalk19.dim("\u2591").repeat(emptyWidth);
5098
5526
  };
5099
- console.log(` \u{1F4C5} ${"Planned".padEnd(labelWidth2)} ${chalk18.cyan(createBar2(statusCounts.planned, maxStatusCount))} ${chalk18.cyan(statusCounts.planned)}`);
5100
- console.log(` \u23F3 ${"In Progress".padEnd(labelWidth2)} ${chalk18.yellow(createBar2(statusCounts["in-progress"], maxStatusCount))} ${chalk18.yellow(statusCounts["in-progress"])}`);
5101
- console.log(` \u2705 ${"Complete".padEnd(labelWidth2)} ${chalk18.green(createBar2(statusCounts.complete, maxStatusCount))} ${chalk18.green(statusCounts.complete)}`);
5527
+ console.log(` \u{1F4C5} ${"Planned".padEnd(labelWidth2)} ${chalk19.cyan(createBar2(statusCounts.planned, maxStatusCount))} ${chalk19.cyan(statusCounts.planned)}`);
5528
+ console.log(` \u23F3 ${"In Progress".padEnd(labelWidth2)} ${chalk19.yellow(createBar2(statusCounts["in-progress"], maxStatusCount))} ${chalk19.yellow(statusCounts["in-progress"])}`);
5529
+ console.log(` \u2705 ${"Complete".padEnd(labelWidth2)} ${chalk19.green(createBar2(statusCounts.complete, maxStatusCount))} ${chalk19.green(statusCounts.complete)}`);
5102
5530
  if (statusCounts.archived > 0) {
5103
- console.log(` \u{1F4E6} ${"Archived".padEnd(labelWidth2)} ${chalk18.dim(createBar2(statusCounts.archived, maxStatusCount))} ${chalk18.dim(statusCounts.archived)}`);
5531
+ console.log(` \u{1F4E6} ${"Archived".padEnd(labelWidth2)} ${chalk19.dim(createBar2(statusCounts.archived, maxStatusCount))} ${chalk19.dim(statusCounts.archived)}`);
5104
5532
  }
5105
5533
  console.log("");
5106
5534
  const criticalCount = priorityCounts.critical || 0;
5107
5535
  const highCount = priorityCounts.high || 0;
5108
5536
  if (criticalCount > 0 || highCount > 0) {
5109
- console.log(chalk18.bold("\u{1F3AF} Priority Focus"));
5537
+ console.log(chalk19.bold("\u{1F3AF} Priority Focus"));
5110
5538
  console.log("");
5111
5539
  if (criticalCount > 0) {
5112
5540
  const criticalPlanned = specs.filter((s) => s.frontmatter.priority === "critical" && s.frontmatter.status === "planned").length;
@@ -5116,11 +5544,11 @@ async function showStats(options) {
5116
5544
  (s) => s.frontmatter.priority === "critical" && s.frontmatter.due && dayjs3(s.frontmatter.due).isBefore(dayjs3(), "day") && s.frontmatter.status !== "complete"
5117
5545
  ).length;
5118
5546
  const parts = [];
5119
- if (criticalPlanned > 0) parts.push(chalk18.dim(`${criticalPlanned} planned`));
5547
+ if (criticalPlanned > 0) parts.push(chalk19.dim(`${criticalPlanned} planned`));
5120
5548
  if (criticalInProgress > 0) parts.push(`${criticalInProgress} in-progress`);
5121
- if (criticalComplete > 0) parts.push(chalk18.green(`${criticalComplete} complete`));
5122
- if (criticalOverdue > 0) parts.push(chalk18.red(`${criticalOverdue} overdue!`));
5123
- console.log(` \u{1F534} Critical ${chalk18.red(criticalCount)} specs${parts.length > 0 ? ` (${parts.join(", ")})` : ""}`);
5549
+ if (criticalComplete > 0) parts.push(chalk19.green(`${criticalComplete} complete`));
5550
+ if (criticalOverdue > 0) parts.push(chalk19.red(`${criticalOverdue} overdue!`));
5551
+ console.log(` \u{1F534} Critical ${chalk19.red(criticalCount)} specs${parts.length > 0 ? ` (${parts.join(", ")})` : ""}`);
5124
5552
  }
5125
5553
  if (highCount > 0) {
5126
5554
  const highPlanned = specs.filter((s) => s.frontmatter.priority === "high" && s.frontmatter.status === "planned").length;
@@ -5130,52 +5558,52 @@ async function showStats(options) {
5130
5558
  (s) => s.frontmatter.priority === "high" && s.frontmatter.due && dayjs3(s.frontmatter.due).isBefore(dayjs3(), "day") && s.frontmatter.status !== "complete"
5131
5559
  ).length;
5132
5560
  const parts = [];
5133
- if (highPlanned > 0) parts.push(chalk18.dim(`${highPlanned} planned`));
5561
+ if (highPlanned > 0) parts.push(chalk19.dim(`${highPlanned} planned`));
5134
5562
  if (highInProgress > 0) parts.push(`${highInProgress} in-progress`);
5135
- if (highComplete > 0) parts.push(chalk18.green(`${highComplete} complete`));
5136
- if (highOverdue > 0) parts.push(chalk18.yellow(`${highOverdue} overdue`));
5137
- console.log(` \u{1F7E0} High ${chalk18.hex("#FFA500")(highCount)} specs${parts.length > 0 ? ` (${parts.join(", ")})` : ""}`);
5563
+ if (highComplete > 0) parts.push(chalk19.green(`${highComplete} complete`));
5564
+ if (highOverdue > 0) parts.push(chalk19.yellow(`${highOverdue} overdue`));
5565
+ console.log(` \u{1F7E0} High ${chalk19.hex("#FFA500")(highCount)} specs${parts.length > 0 ? ` (${parts.join(", ")})` : ""}`);
5138
5566
  }
5139
5567
  console.log("");
5140
5568
  }
5141
5569
  if (insights.length > 0) {
5142
- console.log(chalk18.bold.yellow("\u26A0\uFE0F Needs Attention"));
5570
+ console.log(chalk19.bold.yellow("\u26A0\uFE0F Needs Attention"));
5143
5571
  console.log("");
5144
5572
  for (const insight of insights) {
5145
- const color = insight.severity === "critical" ? chalk18.red : insight.severity === "warning" ? chalk18.yellow : chalk18.cyan;
5573
+ const color = insight.severity === "critical" ? chalk19.red : insight.severity === "warning" ? chalk19.yellow : chalk19.cyan;
5146
5574
  console.log(` ${color("\u2022")} ${insight.message}`);
5147
5575
  for (const specPath of insight.specs.slice(0, 3)) {
5148
5576
  const spec = specs.find((s) => s.path === specPath);
5149
5577
  const details = spec ? getSpecInsightDetails(spec) : null;
5150
- console.log(` ${chalk18.dim(specPath)}${details ? chalk18.dim(` (${details})`) : ""}`);
5578
+ console.log(` ${chalk19.dim(specPath)}${details ? chalk19.dim(` (${details})`) : ""}`);
5151
5579
  }
5152
5580
  if (insight.specs.length > 3) {
5153
- console.log(` ${chalk18.dim(`...and ${insight.specs.length - 3} more`)}`);
5581
+ console.log(` ${chalk19.dim(`...and ${insight.specs.length - 3} more`)}`);
5154
5582
  }
5155
5583
  }
5156
5584
  console.log("");
5157
5585
  } else if (completionMetrics.activeSpecs === 0 && completionMetrics.completeSpecs > 0) {
5158
- console.log(chalk18.bold.green("\u{1F389} All Specs Complete!"));
5586
+ console.log(chalk19.bold.green("\u{1F389} All Specs Complete!"));
5159
5587
  console.log("");
5160
- console.log(` ${chalk18.dim("Great work! All active specs are complete.")}`);
5588
+ console.log(` ${chalk19.dim("Great work! All active specs are complete.")}`);
5161
5589
  console.log("");
5162
5590
  } else if (completionMetrics.activeSpecs > 0) {
5163
- console.log(chalk18.bold.green("\u2728 All Clear!"));
5591
+ console.log(chalk19.bold.green("\u2728 All Clear!"));
5164
5592
  console.log("");
5165
- console.log(` ${chalk18.dim("No critical issues detected. Keep up the good work!")}`);
5593
+ console.log(` ${chalk19.dim("No critical issues detected. Keep up the good work!")}`);
5166
5594
  console.log("");
5167
5595
  }
5168
- console.log(chalk18.bold("\u{1F680} Velocity Summary"));
5596
+ console.log(chalk19.bold("\u{1F680} Velocity Summary"));
5169
5597
  console.log("");
5170
- const cycleTimeStatus = velocityMetrics.cycleTime.average <= 7 ? chalk18.green("\u2713") : chalk18.yellow("\u26A0");
5171
- const throughputTrend = velocityMetrics.throughput.trend === "up" ? chalk18.green("\u2191") : velocityMetrics.throughput.trend === "down" ? chalk18.red("\u2193") : chalk18.yellow("\u2192");
5172
- console.log(` Avg Cycle Time ${chalk18.cyan(velocityMetrics.cycleTime.average.toFixed(1))} days ${cycleTimeStatus}${velocityMetrics.cycleTime.average <= 7 ? chalk18.dim(" (target: 7d)") : ""}`);
5173
- console.log(` Throughput ${chalk18.cyan((velocityMetrics.throughput.perWeek / 7 * 7).toFixed(1))}/week ${throughputTrend}`);
5174
- console.log(` WIP ${chalk18.yellow(velocityMetrics.wip.current)} specs`);
5598
+ const cycleTimeStatus = velocityMetrics.cycleTime.average <= 7 ? chalk19.green("\u2713") : chalk19.yellow("\u26A0");
5599
+ const throughputTrend = velocityMetrics.throughput.trend === "up" ? chalk19.green("\u2191") : velocityMetrics.throughput.trend === "down" ? chalk19.red("\u2193") : chalk19.yellow("\u2192");
5600
+ console.log(` Avg Cycle Time ${chalk19.cyan(velocityMetrics.cycleTime.average.toFixed(1))} days ${cycleTimeStatus}${velocityMetrics.cycleTime.average <= 7 ? chalk19.dim(" (target: 7d)") : ""}`);
5601
+ console.log(` Throughput ${chalk19.cyan((velocityMetrics.throughput.perWeek / 7 * 7).toFixed(1))}/week ${throughputTrend}`);
5602
+ console.log(` WIP ${chalk19.yellow(velocityMetrics.wip.current)} specs`);
5175
5603
  console.log("");
5176
- console.log(chalk18.dim("\u{1F4A1} Use `lean-spec stats --full` for detailed analytics"));
5177
- console.log(chalk18.dim(" Use `lean-spec stats --velocity` for velocity breakdown"));
5178
- console.log(chalk18.dim(" Use `lean-spec stats --timeline` for activity timeline"));
5604
+ console.log(chalk19.dim("\u{1F4A1} Use `lean-spec stats --full` for detailed analytics"));
5605
+ console.log(chalk19.dim(" Use `lean-spec stats --velocity` for velocity breakdown"));
5606
+ console.log(chalk19.dim(" Use `lean-spec stats --timeline` for activity timeline"));
5179
5607
  console.log("");
5180
5608
  return;
5181
5609
  }
@@ -5191,97 +5619,97 @@ async function showStats(options) {
5191
5619
  (sum, count) => sum + count,
5192
5620
  0
5193
5621
  );
5194
- console.log(chalk18.bold("\u{1F4C8} Overview"));
5622
+ console.log(chalk19.bold("\u{1F4C8} Overview"));
5195
5623
  console.log("");
5196
5624
  console.log(
5197
5625
  ` ${"Metric".padEnd(labelWidth)} ${"Value".padStart(valueWidth)}`
5198
5626
  );
5199
5627
  console.log(
5200
- ` ${chalk18.dim("\u2500".repeat(labelWidth))} ${chalk18.dim("\u2500".repeat(valueWidth))}`
5628
+ ` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`
5201
5629
  );
5202
5630
  console.log(
5203
- ` ${"Total Specs".padEnd(labelWidth)} ${chalk18.green(specs.length.toString().padStart(valueWidth))}`
5631
+ ` ${"Total Specs".padEnd(labelWidth)} ${chalk19.green(specs.length.toString().padStart(valueWidth))}`
5204
5632
  );
5205
5633
  console.log(
5206
- ` ${"With Priority".padEnd(labelWidth)} ${chalk18.cyan(totalWithPriority.toString().padStart(valueWidth))}`
5634
+ ` ${"With Priority".padEnd(labelWidth)} ${chalk19.cyan(totalWithPriority.toString().padStart(valueWidth))}`
5207
5635
  );
5208
5636
  console.log(
5209
- ` ${"Unique Tags".padEnd(labelWidth)} ${chalk18.magenta(Object.keys(tagCounts).length.toString().padStart(valueWidth))}`
5637
+ ` ${"Unique Tags".padEnd(labelWidth)} ${chalk19.magenta(Object.keys(tagCounts).length.toString().padStart(valueWidth))}`
5210
5638
  );
5211
5639
  console.log("");
5212
- console.log(chalk18.bold("\u{1F4CA} Status Distribution"));
5640
+ console.log(chalk19.bold("\u{1F4CA} Status Distribution"));
5213
5641
  console.log("");
5214
5642
  const maxStatusCount = Math.max(...Object.values(statusCounts));
5215
5643
  const colWidth = barWidth + 3;
5216
5644
  console.log(
5217
- ` ${"Status".padEnd(labelWidth)} ${chalk18.cyan("Count".padEnd(colWidth))}`
5645
+ ` ${"Status".padEnd(labelWidth)} ${chalk19.cyan("Count".padEnd(colWidth))}`
5218
5646
  );
5219
5647
  console.log(
5220
- ` ${chalk18.dim("\u2500".repeat(labelWidth))} ${chalk18.dim("\u2500".repeat(colWidth))}`
5648
+ ` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`
5221
5649
  );
5222
5650
  console.log(
5223
- ` \u{1F4CB} ${"Planned".padEnd(labelWidth - 3)} ${chalk18.cyan(createBar(statusCounts.planned, maxStatusCount).padEnd(barWidth))}${chalk18.cyan(statusCounts.planned.toString().padStart(3))}`
5651
+ ` \u{1F4CB} ${"Planned".padEnd(labelWidth - 3)} ${chalk19.cyan(createBar(statusCounts.planned, maxStatusCount).padEnd(barWidth))}${chalk19.cyan(statusCounts.planned.toString().padStart(3))}`
5224
5652
  );
5225
5653
  console.log(
5226
- ` \u23F3 ${"In Progress".padEnd(labelWidth - 3)} ${chalk18.yellow(createBar(statusCounts["in-progress"], maxStatusCount).padEnd(barWidth))}${chalk18.yellow(statusCounts["in-progress"].toString().padStart(3))}`
5654
+ ` \u23F3 ${"In Progress".padEnd(labelWidth - 3)} ${chalk19.yellow(createBar(statusCounts["in-progress"], maxStatusCount).padEnd(barWidth))}${chalk19.yellow(statusCounts["in-progress"].toString().padStart(3))}`
5227
5655
  );
5228
5656
  console.log(
5229
- ` \u2705 ${"Complete".padEnd(labelWidth - 3)} ${chalk18.green(createBar(statusCounts.complete, maxStatusCount).padEnd(barWidth))}${chalk18.green(statusCounts.complete.toString().padStart(3))}`
5657
+ ` \u2705 ${"Complete".padEnd(labelWidth - 3)} ${chalk19.green(createBar(statusCounts.complete, maxStatusCount).padEnd(barWidth))}${chalk19.green(statusCounts.complete.toString().padStart(3))}`
5230
5658
  );
5231
5659
  console.log(
5232
- ` \u{1F4E6} ${"Archived".padEnd(labelWidth - 3)} ${chalk18.dim(createBar(statusCounts.archived, maxStatusCount).padEnd(barWidth))}${chalk18.dim(statusCounts.archived.toString().padStart(3))}`
5660
+ ` \u{1F4E6} ${"Archived".padEnd(labelWidth - 3)} ${chalk19.dim(createBar(statusCounts.archived, maxStatusCount).padEnd(barWidth))}${chalk19.dim(statusCounts.archived.toString().padStart(3))}`
5233
5661
  );
5234
5662
  console.log("");
5235
5663
  if (totalWithPriority > 0) {
5236
- console.log(chalk18.bold("\u{1F3AF} Priority Breakdown"));
5664
+ console.log(chalk19.bold("\u{1F3AF} Priority Breakdown"));
5237
5665
  console.log("");
5238
5666
  const maxPriorityCount = Math.max(
5239
5667
  ...Object.values(priorityCounts).filter((c) => c > 0)
5240
5668
  );
5241
5669
  console.log(
5242
- ` ${"Priority".padEnd(labelWidth)} ${chalk18.cyan("Count".padEnd(colWidth))}`
5670
+ ` ${"Priority".padEnd(labelWidth)} ${chalk19.cyan("Count".padEnd(colWidth))}`
5243
5671
  );
5244
5672
  console.log(
5245
- ` ${chalk18.dim("\u2500".repeat(labelWidth))} ${chalk18.dim("\u2500".repeat(colWidth))}`
5673
+ ` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`
5246
5674
  );
5247
5675
  if (priorityCounts.critical > 0) {
5248
5676
  console.log(
5249
- ` \u{1F534} ${"Critical".padEnd(labelWidth - 3)} ${chalk18.red(createBar(priorityCounts.critical, maxPriorityCount).padEnd(barWidth))}${chalk18.red(priorityCounts.critical.toString().padStart(3))}`
5677
+ ` \u{1F534} ${"Critical".padEnd(labelWidth - 3)} ${chalk19.red(createBar(priorityCounts.critical, maxPriorityCount).padEnd(barWidth))}${chalk19.red(priorityCounts.critical.toString().padStart(3))}`
5250
5678
  );
5251
5679
  }
5252
5680
  if (priorityCounts.high > 0) {
5253
5681
  console.log(
5254
- ` \u{1F7E0} ${"High".padEnd(labelWidth - 3)} ${chalk18.hex("#FFA500")(createBar(priorityCounts.high, maxPriorityCount).padEnd(barWidth))}${chalk18.hex("#FFA500")(priorityCounts.high.toString().padStart(3))}`
5682
+ ` \u{1F7E0} ${"High".padEnd(labelWidth - 3)} ${chalk19.hex("#FFA500")(createBar(priorityCounts.high, maxPriorityCount).padEnd(barWidth))}${chalk19.hex("#FFA500")(priorityCounts.high.toString().padStart(3))}`
5255
5683
  );
5256
5684
  }
5257
5685
  if (priorityCounts.medium > 0) {
5258
5686
  console.log(
5259
- ` \u{1F7E1} ${"Medium".padEnd(labelWidth - 3)} ${chalk18.yellow(createBar(priorityCounts.medium, maxPriorityCount).padEnd(barWidth))}${chalk18.yellow(priorityCounts.medium.toString().padStart(3))}`
5687
+ ` \u{1F7E1} ${"Medium".padEnd(labelWidth - 3)} ${chalk19.yellow(createBar(priorityCounts.medium, maxPriorityCount).padEnd(barWidth))}${chalk19.yellow(priorityCounts.medium.toString().padStart(3))}`
5260
5688
  );
5261
5689
  }
5262
5690
  if (priorityCounts.low > 0) {
5263
5691
  console.log(
5264
- ` \u{1F7E2} ${"Low".padEnd(labelWidth - 3)} ${chalk18.green(createBar(priorityCounts.low, maxPriorityCount).padEnd(barWidth))}${chalk18.green(priorityCounts.low.toString().padStart(3))}`
5692
+ ` \u{1F7E2} ${"Low".padEnd(labelWidth - 3)} ${chalk19.green(createBar(priorityCounts.low, maxPriorityCount).padEnd(barWidth))}${chalk19.green(priorityCounts.low.toString().padStart(3))}`
5265
5693
  );
5266
5694
  }
5267
5695
  console.log("");
5268
5696
  }
5269
5697
  const topTags = Object.entries(tagCounts).sort((a, b) => b[1] - a[1]).slice(0, 5);
5270
5698
  if (topTags.length > 0) {
5271
- console.log(chalk18.bold("\u{1F3F7}\uFE0F Popular Tags"));
5699
+ console.log(chalk19.bold("\u{1F3F7}\uFE0F Popular Tags"));
5272
5700
  console.log("");
5273
5701
  const maxTagCount = Math.max(...topTags.map(([, count]) => count));
5274
5702
  console.log(
5275
- ` ${"Tag".padEnd(labelWidth)} ${chalk18.magenta("Count".padEnd(colWidth))}`
5703
+ ` ${"Tag".padEnd(labelWidth)} ${chalk19.magenta("Count".padEnd(colWidth))}`
5276
5704
  );
5277
5705
  console.log(
5278
- ` ${chalk18.dim("\u2500".repeat(labelWidth))} ${chalk18.dim("\u2500".repeat(colWidth))}`
5706
+ ` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`
5279
5707
  );
5280
5708
  for (const [tag, count] of topTags) {
5281
5709
  const truncatedTag = tag.length > labelWidth ? tag.substring(0, labelWidth - 1) + "\u2026" : tag;
5282
5710
  const bar = createBar(count, maxTagCount);
5283
5711
  console.log(
5284
- ` ${truncatedTag.padEnd(labelWidth)} ${chalk18.magenta(bar.padEnd(barWidth))}${chalk18.magenta(count.toString().padStart(3))}`
5712
+ ` ${truncatedTag.padEnd(labelWidth)} ${chalk19.magenta(bar.padEnd(barWidth))}${chalk19.magenta(count.toString().padStart(3))}`
5285
5713
  );
5286
5714
  }
5287
5715
  console.log("");
@@ -5313,14 +5741,14 @@ async function showStats(options) {
5313
5741
  ]);
5314
5742
  const sortedDates = Array.from(allDates).sort();
5315
5743
  if (sortedDates.length > 0) {
5316
- console.log(chalk18.bold(`\u{1F4C5} Activity (Last ${days} Days)`));
5744
+ console.log(chalk19.bold(`\u{1F4C5} Activity (Last ${days} Days)`));
5317
5745
  console.log("");
5318
5746
  const colWidth = barWidth + 3;
5319
5747
  console.log(
5320
- ` ${"Date".padEnd(15)} ${chalk18.cyan("Created".padEnd(colWidth))} ${chalk18.green("Completed".padEnd(colWidth))}`
5748
+ ` ${"Date".padEnd(15)} ${chalk19.cyan("Created".padEnd(colWidth))} ${chalk19.green("Completed".padEnd(colWidth))}`
5321
5749
  );
5322
5750
  console.log(
5323
- ` ${chalk18.dim("\u2500".repeat(15))} ${chalk18.dim("\u2500".repeat(colWidth))} ${chalk18.dim("\u2500".repeat(colWidth))}`
5751
+ ` ${chalk19.dim("\u2500".repeat(15))} ${chalk19.dim("\u2500".repeat(colWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`
5324
5752
  );
5325
5753
  const maxCount = Math.max(
5326
5754
  ...Object.values(createdByDate),
@@ -5334,85 +5762,85 @@ async function showStats(options) {
5334
5762
  const createdCol = `${createdBar.padEnd(barWidth)}${created.toString().padStart(3)}`;
5335
5763
  const completedCol = `${completedBar.padEnd(barWidth)}${completed.toString().padStart(3)}`;
5336
5764
  console.log(
5337
- ` ${chalk18.dim(date.padEnd(15))} ${chalk18.cyan(createdCol)} ${chalk18.green(completedCol)}`
5765
+ ` ${chalk19.dim(date.padEnd(15))} ${chalk19.cyan(createdCol)} ${chalk19.green(completedCol)}`
5338
5766
  );
5339
5767
  }
5340
5768
  console.log("");
5341
5769
  }
5342
5770
  }
5343
5771
  if (showVelocity) {
5344
- console.log(chalk18.bold("\u{1F680} Velocity Metrics"));
5772
+ console.log(chalk19.bold("\u{1F680} Velocity Metrics"));
5345
5773
  console.log("");
5346
- console.log(chalk18.bold("\u23F1\uFE0F Cycle Time (Created \u2192 Completed)"));
5774
+ console.log(chalk19.bold("\u23F1\uFE0F Cycle Time (Created \u2192 Completed)"));
5347
5775
  console.log("");
5348
5776
  console.log(
5349
5777
  ` ${"Metric".padEnd(labelWidth)} ${"Days".padStart(valueWidth)}`
5350
5778
  );
5351
5779
  console.log(
5352
- ` ${chalk18.dim("\u2500".repeat(labelWidth))} ${chalk18.dim("\u2500".repeat(valueWidth))}`
5780
+ ` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`
5353
5781
  );
5354
5782
  console.log(
5355
- ` ${"Average".padEnd(labelWidth)} ${chalk18.cyan(velocityMetrics.cycleTime.average.toFixed(1).padStart(valueWidth))}`
5783
+ ` ${"Average".padEnd(labelWidth)} ${chalk19.cyan(velocityMetrics.cycleTime.average.toFixed(1).padStart(valueWidth))}`
5356
5784
  );
5357
5785
  console.log(
5358
- ` ${"Median".padEnd(labelWidth)} ${chalk18.cyan(velocityMetrics.cycleTime.median.toFixed(1).padStart(valueWidth))}`
5786
+ ` ${"Median".padEnd(labelWidth)} ${chalk19.cyan(velocityMetrics.cycleTime.median.toFixed(1).padStart(valueWidth))}`
5359
5787
  );
5360
5788
  console.log(
5361
- ` ${"90th Percentile".padEnd(labelWidth)} ${chalk18.yellow(velocityMetrics.cycleTime.p90.toFixed(1).padStart(valueWidth))}`
5789
+ ` ${"90th Percentile".padEnd(labelWidth)} ${chalk19.yellow(velocityMetrics.cycleTime.p90.toFixed(1).padStart(valueWidth))}`
5362
5790
  );
5363
5791
  console.log("");
5364
- console.log(chalk18.bold("\u{1F4E6} Throughput"));
5792
+ console.log(chalk19.bold("\u{1F4E6} Throughput"));
5365
5793
  console.log("");
5366
5794
  console.log(
5367
5795
  ` ${"Period".padEnd(labelWidth)} ${"Specs".padStart(valueWidth)}`
5368
5796
  );
5369
5797
  console.log(
5370
- ` ${chalk18.dim("\u2500".repeat(labelWidth))} ${chalk18.dim("\u2500".repeat(valueWidth))}`
5798
+ ` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`
5371
5799
  );
5372
5800
  console.log(
5373
- ` ${"Last 7 days".padEnd(labelWidth)} ${chalk18.green(velocityMetrics.throughput.perWeek.toString().padStart(valueWidth))}`
5801
+ ` ${"Last 7 days".padEnd(labelWidth)} ${chalk19.green(velocityMetrics.throughput.perWeek.toString().padStart(valueWidth))}`
5374
5802
  );
5375
5803
  console.log(
5376
- ` ${"Last 30 days".padEnd(labelWidth)} ${chalk18.green(velocityMetrics.throughput.perMonth.toString().padStart(valueWidth))}`
5804
+ ` ${"Last 30 days".padEnd(labelWidth)} ${chalk19.green(velocityMetrics.throughput.perMonth.toString().padStart(valueWidth))}`
5377
5805
  );
5378
- const trendColor = velocityMetrics.throughput.trend === "up" ? chalk18.green : velocityMetrics.throughput.trend === "down" ? chalk18.red : chalk18.yellow;
5806
+ const trendColor = velocityMetrics.throughput.trend === "up" ? chalk19.green : velocityMetrics.throughput.trend === "down" ? chalk19.red : chalk19.yellow;
5379
5807
  const trendSymbol = velocityMetrics.throughput.trend === "up" ? "\u2191" : velocityMetrics.throughput.trend === "down" ? "\u2193" : "\u2192";
5380
5808
  console.log(
5381
5809
  ` ${"Trend".padEnd(labelWidth)} ${trendColor(trendSymbol + " " + velocityMetrics.throughput.trend.padStart(valueWidth - 2))}`
5382
5810
  );
5383
5811
  console.log("");
5384
- console.log(chalk18.bold("\u{1F504} Work In Progress"));
5812
+ console.log(chalk19.bold("\u{1F504} Work In Progress"));
5385
5813
  console.log("");
5386
5814
  console.log(
5387
5815
  ` ${"Metric".padEnd(labelWidth)} ${"Specs".padStart(valueWidth)}`
5388
5816
  );
5389
5817
  console.log(
5390
- ` ${chalk18.dim("\u2500".repeat(labelWidth))} ${chalk18.dim("\u2500".repeat(valueWidth))}`
5818
+ ` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`
5391
5819
  );
5392
5820
  console.log(
5393
- ` ${"Current".padEnd(labelWidth)} ${chalk18.yellow(velocityMetrics.wip.current.toString().padStart(valueWidth))}`
5821
+ ` ${"Current".padEnd(labelWidth)} ${chalk19.yellow(velocityMetrics.wip.current.toString().padStart(valueWidth))}`
5394
5822
  );
5395
5823
  console.log(
5396
- ` ${"30-day Average".padEnd(labelWidth)} ${chalk18.cyan(velocityMetrics.wip.average.toFixed(1).padStart(valueWidth))}`
5824
+ ` ${"30-day Average".padEnd(labelWidth)} ${chalk19.cyan(velocityMetrics.wip.average.toFixed(1).padStart(valueWidth))}`
5397
5825
  );
5398
5826
  console.log("");
5399
5827
  if (velocityMetrics.leadTime.plannedToInProgress > 0 || velocityMetrics.leadTime.inProgressToComplete > 0) {
5400
- console.log(chalk18.bold("\u{1F500} Lead Time by Stage"));
5828
+ console.log(chalk19.bold("\u{1F500} Lead Time by Stage"));
5401
5829
  console.log("");
5402
5830
  console.log(
5403
5831
  ` ${"Stage".padEnd(labelWidth)} ${"Days".padStart(valueWidth)}`
5404
5832
  );
5405
5833
  console.log(
5406
- ` ${chalk18.dim("\u2500".repeat(labelWidth))} ${chalk18.dim("\u2500".repeat(valueWidth))}`
5834
+ ` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`
5407
5835
  );
5408
5836
  if (velocityMetrics.leadTime.plannedToInProgress > 0) {
5409
5837
  console.log(
5410
- ` ${"Planned \u2192 In Progress".padEnd(labelWidth)} ${chalk18.cyan(velocityMetrics.leadTime.plannedToInProgress.toFixed(1).padStart(valueWidth))}`
5838
+ ` ${"Planned \u2192 In Progress".padEnd(labelWidth)} ${chalk19.cyan(velocityMetrics.leadTime.plannedToInProgress.toFixed(1).padStart(valueWidth))}`
5411
5839
  );
5412
5840
  }
5413
5841
  if (velocityMetrics.leadTime.inProgressToComplete > 0) {
5414
5842
  console.log(
5415
- ` ${"In Progress \u2192 Complete".padEnd(labelWidth)} ${chalk18.green(velocityMetrics.leadTime.inProgressToComplete.toFixed(1).padStart(valueWidth))}`
5843
+ ` ${"In Progress \u2192 Complete".padEnd(labelWidth)} ${chalk19.green(velocityMetrics.leadTime.inProgressToComplete.toFixed(1).padStart(valueWidth))}`
5416
5844
  );
5417
5845
  }
5418
5846
  console.log("");
@@ -5420,14 +5848,15 @@ async function showStats(options) {
5420
5848
  }
5421
5849
  }
5422
5850
  function searchCommand() {
5423
- return new Command("search").description("Full-text search with metadata filters").argument("<query>", "Search query").option("--status <status>", "Filter by status").option("--tag <tag>", "Filter by tag").option("--priority <priority>", "Filter by priority").option("--assignee <name>", "Filter by assignee").option("--field <name=value...>", "Filter by custom field (can specify multiple)").action(async (query, options) => {
5851
+ return new Command("search").description("Full-text search with metadata filters").argument("<query>", "Search query").option("--status <status>", "Filter by status").option("--tag <tag>", "Filter by tag").option("--priority <priority>", "Filter by priority").option("--assignee <name>", "Filter by assignee").option("--field <name=value...>", "Filter by custom field (can specify multiple)").option("--json", "Output as JSON").action(async (query, options) => {
5424
5852
  const customFields = parseCustomFieldOptions(options.field);
5425
5853
  await performSearch(query, {
5426
5854
  status: options.status,
5427
5855
  tag: options.tag,
5428
5856
  priority: options.priority,
5429
5857
  assignee: options.assignee,
5430
- customFields: Object.keys(customFields).length > 0 ? customFields : void 0
5858
+ customFields: Object.keys(customFields).length > 0 ? customFields : void 0,
5859
+ json: options.json
5431
5860
  });
5432
5861
  });
5433
5862
  }
@@ -5466,36 +5895,55 @@ async function performSearch(query, options) {
5466
5895
  contextLength: 80
5467
5896
  });
5468
5897
  const { results, metadata } = searchResult;
5898
+ if (options.json) {
5899
+ const jsonOutput = {
5900
+ query,
5901
+ results: results.map((r) => ({
5902
+ spec: r.spec.path,
5903
+ score: r.score,
5904
+ totalMatches: r.totalMatches,
5905
+ matches: r.matches.map((m) => ({
5906
+ field: m.field,
5907
+ text: m.text,
5908
+ lineNumber: m.lineNumber
5909
+ }))
5910
+ })),
5911
+ metadata,
5912
+ filters: filter
5913
+ };
5914
+ console.log(JSON.stringify(jsonOutput, null, 2));
5915
+ return;
5916
+ }
5469
5917
  if (results.length === 0) {
5470
5918
  console.log("");
5471
- console.log(chalk18.yellow(`\u{1F50D} No specs found matching "${sanitizeUserInput(query)}"`));
5919
+ console.log(chalk19.yellow(`\u{1F50D} No specs found matching "${sanitizeUserInput(query)}"`));
5472
5920
  if (Object.keys(filter).length > 0) {
5473
5921
  const filters = [];
5474
5922
  if (options.status) filters.push(`status=${sanitizeUserInput(options.status)}`);
5475
5923
  if (options.tag) filters.push(`tag=${sanitizeUserInput(options.tag)}`);
5476
5924
  if (options.priority) filters.push(`priority=${sanitizeUserInput(options.priority)}`);
5477
5925
  if (options.assignee) filters.push(`assignee=${sanitizeUserInput(options.assignee)}`);
5478
- console.log(chalk18.gray(`With filters: ${filters.join(", ")}`));
5926
+ console.log(chalk19.gray(`With filters: ${filters.join(", ")}`));
5479
5927
  }
5480
5928
  console.log("");
5481
5929
  return;
5482
5930
  }
5483
5931
  console.log("");
5484
- console.log(chalk18.green(`\u{1F50D} Found ${results.length} spec${results.length === 1 ? "" : "s"} matching "${sanitizeUserInput(query)}"`));
5485
- console.log(chalk18.gray(` Searched ${metadata.specsSearched} specs in ${metadata.searchTime}ms`));
5932
+ console.log(chalk19.green(`\u{1F50D} Found ${results.length} spec${results.length === 1 ? "" : "s"} matching "${sanitizeUserInput(query)}"`));
5933
+ console.log(chalk19.gray(` Searched ${metadata.specsSearched} specs in ${metadata.searchTime}ms`));
5486
5934
  if (Object.keys(filter).length > 0) {
5487
5935
  const filters = [];
5488
5936
  if (options.status) filters.push(`status=${sanitizeUserInput(options.status)}`);
5489
5937
  if (options.tag) filters.push(`tag=${sanitizeUserInput(options.tag)}`);
5490
5938
  if (options.priority) filters.push(`priority=${sanitizeUserInput(options.priority)}`);
5491
5939
  if (options.assignee) filters.push(`assignee=${sanitizeUserInput(options.assignee)}`);
5492
- console.log(chalk18.gray(` With filters: ${filters.join(", ")}`));
5940
+ console.log(chalk19.gray(` With filters: ${filters.join(", ")}`));
5493
5941
  }
5494
5942
  console.log("");
5495
5943
  for (const result of results) {
5496
5944
  const { spec, matches, score, totalMatches } = result;
5497
5945
  const statusEmoji = spec.status === "in-progress" ? "\u{1F528}" : spec.status === "complete" ? "\u2705" : "\u{1F4C5}";
5498
- console.log(chalk18.cyan(`${statusEmoji} ${sanitizeUserInput(spec.path)} ${chalk18.gray(`(${score}% match)`)}`));
5946
+ console.log(chalk19.cyan(`${statusEmoji} ${sanitizeUserInput(spec.path)} ${chalk19.gray(`(${score}% match)`)}`));
5499
5947
  const meta = [];
5500
5948
  if (spec.priority) {
5501
5949
  const priorityEmoji = spec.priority === "critical" ? "\u{1F534}" : spec.priority === "high" ? "\u{1F7E1}" : spec.priority === "medium" ? "\u{1F7E0}" : "\u{1F7E2}";
@@ -5505,30 +5953,30 @@ async function performSearch(query, options) {
5505
5953
  meta.push(`[${spec.tags.map((tag) => sanitizeUserInput(tag)).join(", ")}]`);
5506
5954
  }
5507
5955
  if (meta.length > 0) {
5508
- console.log(chalk18.gray(` ${meta.join(" \u2022 ")}`));
5956
+ console.log(chalk19.gray(` ${meta.join(" \u2022 ")}`));
5509
5957
  }
5510
5958
  const titleMatch = matches.find((m) => m.field === "title");
5511
5959
  if (titleMatch) {
5512
- console.log(` ${chalk18.bold("Title:")} ${highlightMatches(titleMatch.text, titleMatch.highlights)}`);
5960
+ console.log(` ${chalk19.bold("Title:")} ${highlightMatches(titleMatch.text, titleMatch.highlights)}`);
5513
5961
  }
5514
5962
  const descMatch = matches.find((m) => m.field === "description");
5515
5963
  if (descMatch) {
5516
- console.log(` ${chalk18.bold("Description:")} ${highlightMatches(descMatch.text, descMatch.highlights)}`);
5964
+ console.log(` ${chalk19.bold("Description:")} ${highlightMatches(descMatch.text, descMatch.highlights)}`);
5517
5965
  }
5518
5966
  const tagMatches = matches.filter((m) => m.field === "tags");
5519
5967
  if (tagMatches.length > 0) {
5520
- console.log(` ${chalk18.bold("Tags:")} ${tagMatches.map((m) => highlightMatches(m.text, m.highlights)).join(", ")}`);
5968
+ console.log(` ${chalk19.bold("Tags:")} ${tagMatches.map((m) => highlightMatches(m.text, m.highlights)).join(", ")}`);
5521
5969
  }
5522
5970
  const contentMatches = matches.filter((m) => m.field === "content");
5523
5971
  if (contentMatches.length > 0) {
5524
- console.log(` ${chalk18.bold("Content matches:")}`);
5972
+ console.log(` ${chalk19.bold("Content matches:")}`);
5525
5973
  for (const match of contentMatches) {
5526
- const lineInfo = match.lineNumber ? chalk18.gray(`[L${match.lineNumber}]`) : "";
5974
+ const lineInfo = match.lineNumber ? chalk19.gray(`[L${match.lineNumber}]`) : "";
5527
5975
  console.log(` ${lineInfo} ${highlightMatches(match.text, match.highlights)}`);
5528
5976
  }
5529
5977
  }
5530
5978
  if (totalMatches > matches.length) {
5531
- console.log(chalk18.gray(` ... and ${totalMatches - matches.length} more match${totalMatches - matches.length === 1 ? "" : "es"}`));
5979
+ console.log(chalk19.gray(` ... and ${totalMatches - matches.length} more match${totalMatches - matches.length === 1 ? "" : "es"}`));
5532
5980
  }
5533
5981
  console.log("");
5534
5982
  }
@@ -5539,7 +5987,7 @@ function highlightMatches(text, highlights) {
5539
5987
  let lastEnd = 0;
5540
5988
  for (const [start, end] of highlights) {
5541
5989
  result += text.substring(lastEnd, start);
5542
- result += chalk18.yellow(text.substring(start, end));
5990
+ result += chalk19.yellow(text.substring(start, end));
5543
5991
  lastEnd = end;
5544
5992
  }
5545
5993
  result += text.substring(lastEnd);
@@ -5557,7 +6005,7 @@ async function showDeps(specPath, options = {}) {
5557
6005
  await autoCheckIfEnabled();
5558
6006
  const config = await loadConfig();
5559
6007
  const cwd = process.cwd();
5560
- const specsDir = path4.join(cwd, config.specsDir);
6008
+ const specsDir = path15.join(cwd, config.specsDir);
5561
6009
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
5562
6010
  if (!resolvedPath) {
5563
6011
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
@@ -5621,17 +6069,17 @@ async function showDeps(specPath, options = {}) {
5621
6069
  return;
5622
6070
  }
5623
6071
  console.log("");
5624
- console.log(chalk18.green(`\u{1F4E6} Dependencies for ${chalk18.cyan(sanitizeUserInput(spec.path))}`));
6072
+ console.log(chalk19.green(`\u{1F4E6} Dependencies for ${chalk19.cyan(sanitizeUserInput(spec.path))}`));
5625
6073
  console.log("");
5626
6074
  const hasAnyRelationships = dependsOn.length > 0 || requiredBy.length > 0 || related.length > 0;
5627
6075
  if (!hasAnyRelationships) {
5628
- console.log(chalk18.gray(" No dependencies or relationships"));
6076
+ console.log(chalk19.gray(" No dependencies or relationships"));
5629
6077
  console.log("");
5630
6078
  return;
5631
6079
  }
5632
6080
  if ((mode === "complete" || mode === "upstream" || mode === "impact") && dependsOn.length > 0) {
5633
6081
  const label = mode === "complete" ? "Depends On" : mode === "upstream" ? "Upstream Dependencies" : "Upstream (Impact)";
5634
- console.log(chalk18.bold(`${label}:`));
6082
+ console.log(chalk19.bold(`${label}:`));
5635
6083
  for (const dep of dependsOn) {
5636
6084
  const status = getStatusIndicator(dep.frontmatter.status);
5637
6085
  console.log(` \u2192 ${sanitizeUserInput(dep.path)} ${status}`);
@@ -5640,7 +6088,7 @@ async function showDeps(specPath, options = {}) {
5640
6088
  }
5641
6089
  if ((mode === "complete" || mode === "downstream" || mode === "impact") && requiredBy.length > 0) {
5642
6090
  const label = mode === "complete" ? "Required By" : mode === "downstream" ? "Downstream Dependents" : "Downstream (Impact)";
5643
- console.log(chalk18.bold(`${label}:`));
6091
+ console.log(chalk19.bold(`${label}:`));
5644
6092
  for (const blocked of requiredBy) {
5645
6093
  const status = getStatusIndicator(blocked.frontmatter.status);
5646
6094
  console.log(` \u2190 ${sanitizeUserInput(blocked.path)} ${status}`);
@@ -5648,7 +6096,7 @@ async function showDeps(specPath, options = {}) {
5648
6096
  console.log("");
5649
6097
  }
5650
6098
  if ((mode === "complete" || mode === "impact") && related.length > 0) {
5651
- console.log(chalk18.bold("Related Specs:"));
6099
+ console.log(chalk19.bold("Related Specs:"));
5652
6100
  for (const rel of related) {
5653
6101
  const status = getStatusIndicator(rel.frontmatter.status);
5654
6102
  console.log(` \u27F7 ${sanitizeUserInput(rel.path)} ${status}`);
@@ -5656,15 +6104,15 @@ async function showDeps(specPath, options = {}) {
5656
6104
  console.log("");
5657
6105
  }
5658
6106
  if (mode === "complete" && (options.graph || dependsOn.length > 0)) {
5659
- console.log(chalk18.bold("Dependency Chain:"));
6107
+ console.log(chalk19.bold("Dependency Chain:"));
5660
6108
  const chain = buildDependencyChain(spec, specMap, options.depth || 3);
5661
6109
  displayChain(chain, 0);
5662
6110
  console.log("");
5663
6111
  }
5664
6112
  if (mode === "impact") {
5665
6113
  const total = dependsOn.length + requiredBy.length + related.length;
5666
- console.log(chalk18.bold(`Impact Summary:`));
5667
- console.log(` Changing this spec affects ${chalk18.yellow(total)} specs total`);
6114
+ console.log(chalk19.bold(`Impact Summary:`));
6115
+ console.log(` Changing this spec affects ${chalk19.yellow(total)} specs total`);
5668
6116
  console.log(` Upstream: ${dependsOn.length} | Downstream: ${requiredBy.length} | Related: ${related.length}`);
5669
6117
  console.log("");
5670
6118
  }
@@ -5694,7 +6142,7 @@ function buildDependencyChain(spec, specMap, maxDepth, currentDepth = 0, visited
5694
6142
  function displayChain(node, level) {
5695
6143
  const indent = " ".repeat(level);
5696
6144
  const status = getStatusIndicator(node.spec.frontmatter.status);
5697
- const name = level === 0 ? chalk18.cyan(node.spec.path) : node.spec.path;
6145
+ const name = level === 0 ? chalk19.cyan(node.spec.path) : node.spec.path;
5698
6146
  console.log(`${indent}${name} ${status}`);
5699
6147
  for (const dep of node.dependencies) {
5700
6148
  const prefix = " ".repeat(level) + "\u2514\u2500 ";
@@ -5706,7 +6154,7 @@ function displayChain(node, level) {
5706
6154
  }
5707
6155
  }
5708
6156
  function timelineCommand() {
5709
- return new Command("timeline").description("Show creation/completion over time").option("--days <n>", "Show last N days (default: 30)", parseInt).option("--by-tag", "Group by tag").option("--by-assignee", "Group by assignee").action(async (options) => {
6157
+ return new Command("timeline").description("Show creation/completion over time").option("--days <n>", "Show last N days (default: 30)", parseInt).option("--by-tag", "Group by tag").option("--by-assignee", "Group by assignee").option("--json", "Output as JSON").action(async (options) => {
5710
6158
  await showTimeline(options);
5711
6159
  });
5712
6160
  }
@@ -5745,19 +6193,29 @@ async function showTimeline(options) {
5745
6193
  }
5746
6194
  }
5747
6195
  }
5748
- console.log(chalk18.bold.cyan("\u{1F4C8} Spec Timeline"));
6196
+ if (options.json) {
6197
+ const jsonOutput = {
6198
+ days,
6199
+ createdByDate,
6200
+ completedByDate,
6201
+ createdByMonth
6202
+ };
6203
+ console.log(JSON.stringify(jsonOutput, null, 2));
6204
+ return;
6205
+ }
6206
+ console.log(chalk19.bold.cyan("\u{1F4C8} Spec Timeline"));
5749
6207
  console.log("");
5750
6208
  const allDates = /* @__PURE__ */ new Set([...Object.keys(createdByDate), ...Object.keys(completedByDate)]);
5751
6209
  const sortedDates = Array.from(allDates).sort();
5752
6210
  if (sortedDates.length > 0) {
5753
- console.log(chalk18.bold(`\u{1F4C5} Activity (Last ${days} Days)`));
6211
+ console.log(chalk19.bold(`\u{1F4C5} Activity (Last ${days} Days)`));
5754
6212
  console.log("");
5755
6213
  const labelWidth2 = 15;
5756
6214
  const barWidth = 20;
5757
6215
  const specsWidth = 3;
5758
6216
  const colWidth = barWidth + specsWidth;
5759
- console.log(` ${"Date".padEnd(labelWidth2)} ${chalk18.cyan("Created".padEnd(colWidth))} ${chalk18.green("Completed".padEnd(colWidth))}`);
5760
- console.log(` ${chalk18.dim("\u2500".repeat(labelWidth2))} ${chalk18.dim("\u2500".repeat(colWidth))} ${chalk18.dim("\u2500".repeat(colWidth))}`);
6217
+ console.log(` ${"Date".padEnd(labelWidth2)} ${chalk19.cyan("Created".padEnd(colWidth))} ${chalk19.green("Completed".padEnd(colWidth))}`);
6218
+ console.log(` ${chalk19.dim("\u2500".repeat(labelWidth2))} ${chalk19.dim("\u2500".repeat(colWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`);
5761
6219
  const maxCount = Math.max(...Object.values(createdByDate), ...Object.values(completedByDate));
5762
6220
  for (const date of sortedDates) {
5763
6221
  const created = createdByDate[date] || 0;
@@ -5766,7 +6224,7 @@ async function showTimeline(options) {
5766
6224
  const completedBar = createBar(completed, maxCount, barWidth);
5767
6225
  const createdCol = `${createdBar.padEnd(barWidth)}${created.toString().padStart(specsWidth)}`;
5768
6226
  const completedCol = `${completedBar.padEnd(barWidth)}${completed.toString().padStart(specsWidth)}`;
5769
- console.log(` ${chalk18.dim(date.padEnd(labelWidth2))} ${chalk18.cyan(createdCol)} ${chalk18.green(completedCol)}`);
6227
+ console.log(` ${chalk19.dim(date.padEnd(labelWidth2))} ${chalk19.cyan(createdCol)} ${chalk19.green(completedCol)}`);
5770
6228
  }
5771
6229
  console.log("");
5772
6230
  }
@@ -5776,18 +6234,18 @@ async function showTimeline(options) {
5776
6234
  return dateB.diff(dateA);
5777
6235
  }).slice(0, 6);
5778
6236
  if (sortedMonths.length > 0) {
5779
- console.log(chalk18.bold("\u{1F4CA} Monthly Overview"));
6237
+ console.log(chalk19.bold("\u{1F4CA} Monthly Overview"));
5780
6238
  console.log("");
5781
6239
  const labelWidth2 = 15;
5782
6240
  const barWidth = 20;
5783
6241
  const specsWidth = 3;
5784
6242
  const colWidth = barWidth + specsWidth;
5785
- console.log(` ${"Month".padEnd(labelWidth2)} ${chalk18.magenta("Specs".padEnd(colWidth))}`);
5786
- console.log(` ${chalk18.dim("\u2500".repeat(labelWidth2))} ${chalk18.dim("\u2500".repeat(colWidth))}`);
6243
+ console.log(` ${"Month".padEnd(labelWidth2)} ${chalk19.magenta("Specs".padEnd(colWidth))}`);
6244
+ console.log(` ${chalk19.dim("\u2500".repeat(labelWidth2))} ${chalk19.dim("\u2500".repeat(colWidth))}`);
5787
6245
  const maxCount = Math.max(...sortedMonths.map(([, count]) => count));
5788
6246
  for (const [month, count] of sortedMonths) {
5789
6247
  const bar = createBar(count, maxCount, barWidth);
5790
- console.log(` ${month.padEnd(labelWidth2)} ${chalk18.magenta(bar.padEnd(barWidth))}${chalk18.magenta(count.toString().padStart(specsWidth))}`);
6248
+ console.log(` ${month.padEnd(labelWidth2)} ${chalk19.magenta(bar.padEnd(barWidth))}${chalk19.magenta(count.toString().padStart(specsWidth))}`);
5791
6249
  }
5792
6250
  console.log("");
5793
6251
  }
@@ -5801,14 +6259,14 @@ async function showTimeline(options) {
5801
6259
  const completed = dayjs3(s.frontmatter.completed);
5802
6260
  return completed.isAfter(today.subtract(30, "day"));
5803
6261
  }).length;
5804
- console.log(chalk18.bold("\u2705 Completion Rate"));
6262
+ console.log(chalk19.bold("\u2705 Completion Rate"));
5805
6263
  console.log("");
5806
6264
  const labelWidth = 15;
5807
6265
  const valueWidth = 5;
5808
6266
  console.log(` ${"Period".padEnd(labelWidth)} ${"Specs".padStart(valueWidth)}`);
5809
- console.log(` ${chalk18.dim("\u2500".repeat(labelWidth))} ${chalk18.dim("\u2500".repeat(valueWidth))}`);
5810
- console.log(` ${"Last 7 days".padEnd(labelWidth)} ${chalk18.green(last7Days.toString().padStart(valueWidth))}`);
5811
- console.log(` ${"Last 30 days".padEnd(labelWidth)} ${chalk18.green(last30Days.toString().padStart(valueWidth))}`);
6267
+ console.log(` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`);
6268
+ console.log(` ${"Last 7 days".padEnd(labelWidth)} ${chalk19.green(last7Days.toString().padStart(valueWidth))}`);
6269
+ console.log(` ${"Last 30 days".padEnd(labelWidth)} ${chalk19.green(last30Days.toString().padStart(valueWidth))}`);
5812
6270
  console.log("");
5813
6271
  if (options.byTag) {
5814
6272
  const tagStats = {};
@@ -5830,9 +6288,9 @@ async function showTimeline(options) {
5830
6288
  }
5831
6289
  const sortedTags = Object.entries(tagStats).sort((a, b) => b[1].created - a[1].created).slice(0, 10);
5832
6290
  if (sortedTags.length > 0) {
5833
- console.log(chalk18.bold("\u{1F3F7}\uFE0F By Tag"));
6291
+ console.log(chalk19.bold("\u{1F3F7}\uFE0F By Tag"));
5834
6292
  for (const [tag, stats] of sortedTags) {
5835
- console.log(` ${chalk18.dim("#")}${tag.padEnd(20)} ${chalk18.cyan(stats.created)} created \xB7 ${chalk18.green(stats.completed)} completed`);
6293
+ console.log(` ${chalk19.dim("#")}${tag.padEnd(20)} ${chalk19.cyan(stats.created)} created \xB7 ${chalk19.green(stats.completed)} completed`);
5836
6294
  }
5837
6295
  console.log("");
5838
6296
  }
@@ -5857,9 +6315,9 @@ async function showTimeline(options) {
5857
6315
  }
5858
6316
  const sortedAssignees = Object.entries(assigneeStats).sort((a, b) => b[1].created - a[1].created);
5859
6317
  if (sortedAssignees.length > 0) {
5860
- console.log(chalk18.bold("\u{1F464} By Assignee"));
6318
+ console.log(chalk19.bold("\u{1F464} By Assignee"));
5861
6319
  for (const [assignee, stats] of sortedAssignees) {
5862
- console.log(` ${chalk18.dim("@")}${assignee.padEnd(20)} ${chalk18.cyan(stats.created)} created \xB7 ${chalk18.green(stats.completed)} completed`);
6320
+ console.log(` ${chalk19.dim("@")}${assignee.padEnd(20)} ${chalk19.cyan(stats.created)} created \xB7 ${chalk19.green(stats.completed)} completed`);
5863
6321
  }
5864
6322
  console.log("");
5865
6323
  }
@@ -5877,13 +6335,13 @@ var STATUS_CONFIG2 = {
5877
6335
  archived: { emoji: "\u{1F4E6}", color: "gray" }
5878
6336
  };
5879
6337
  var PRIORITY_CONFIG3 = {
5880
- critical: { emoji: "\u{1F534}", label: "CRITICAL", colorFn: chalk18.red },
5881
- high: { emoji: "\u{1F7E0}", label: "HIGH", colorFn: chalk18.hex("#FFA500") },
5882
- medium: { emoji: "\u{1F7E1}", label: "MEDIUM", colorFn: chalk18.yellow },
5883
- low: { emoji: "\u{1F7E2}", label: "LOW", colorFn: chalk18.green }
6338
+ critical: { emoji: "\u{1F534}", label: "CRITICAL", colorFn: chalk19.red },
6339
+ high: { emoji: "\u{1F7E0}", label: "HIGH", colorFn: chalk19.hex("#FFA500") },
6340
+ medium: { emoji: "\u{1F7E1}", label: "MEDIUM", colorFn: chalk19.yellow },
6341
+ low: { emoji: "\u{1F7E2}", label: "LOW", colorFn: chalk19.green }
5884
6342
  };
5885
6343
  function ganttCommand() {
5886
- return new Command("gantt").description("Show timeline with dependencies").option("--weeks <n>", "Show N weeks (default: 4)", parseInt).option("--show-complete", "Include completed specs").option("--critical-path", "Highlight critical path").action(async (options) => {
6344
+ return new Command("gantt").description("Show timeline with dependencies").option("--weeks <n>", "Show N weeks (default: 4)", parseInt).option("--show-complete", "Include completed specs").option("--critical-path", "Highlight critical path").option("--json", "Output as JSON").action(async (options) => {
5887
6345
  await showGantt(options);
5888
6346
  });
5889
6347
  }
@@ -5908,8 +6366,28 @@ async function showGantt(options) {
5908
6366
  return spec.frontmatter.status !== "archived";
5909
6367
  });
5910
6368
  if (relevantSpecs.length === 0) {
5911
- console.log(chalk18.dim("No active specs found."));
5912
- console.log(chalk18.dim("Tip: Use --show-complete to include completed specs."));
6369
+ if (options.json) {
6370
+ console.log(JSON.stringify({ specs: [], weeks }, null, 2));
6371
+ } else {
6372
+ console.log(chalk19.dim("No active specs found."));
6373
+ console.log(chalk19.dim("Tip: Use --show-complete to include completed specs."));
6374
+ }
6375
+ return;
6376
+ }
6377
+ if (options.json) {
6378
+ const jsonOutput = {
6379
+ weeks,
6380
+ specs: relevantSpecs.map((spec) => ({
6381
+ path: spec.path,
6382
+ status: spec.frontmatter.status,
6383
+ priority: spec.frontmatter.priority,
6384
+ created: spec.frontmatter.created,
6385
+ completed: spec.frontmatter.completed,
6386
+ due: spec.frontmatter.due,
6387
+ dependsOn: spec.frontmatter.depends_on
6388
+ }))
6389
+ };
6390
+ console.log(JSON.stringify(jsonOutput, null, 2));
5913
6391
  return;
5914
6392
  }
5915
6393
  const groupedSpecs = {
@@ -5945,7 +6423,7 @@ async function showGantt(options) {
5945
6423
  const overdue = relevantSpecs.filter(
5946
6424
  (s) => s.frontmatter.due && dayjs3(s.frontmatter.due).isBefore(today) && s.frontmatter.status !== "complete"
5947
6425
  ).length;
5948
- console.log(chalk18.bold.cyan(`\u{1F4C5} Gantt Chart (${weeks} weeks from ${startDate.format("MMM D, YYYY")})`));
6426
+ console.log(chalk19.bold.cyan(`\u{1F4C5} Gantt Chart (${weeks} weeks from ${startDate.format("MMM D, YYYY")})`));
5949
6427
  console.log("");
5950
6428
  const specHeader = "Spec".padEnd(SPEC_COLUMN_WIDTH);
5951
6429
  const timelineHeader = "Timeline";
@@ -5957,17 +6435,17 @@ async function showGantt(options) {
5957
6435
  calendarDates.push(dateStr);
5958
6436
  }
5959
6437
  const dateRow = " ".repeat(SPEC_COLUMN_WIDTH) + COLUMN_SEPARATOR + calendarDates.join("");
5960
- console.log(chalk18.dim(dateRow));
6438
+ console.log(chalk19.dim(dateRow));
5961
6439
  const specSeparator = "\u2500".repeat(SPEC_COLUMN_WIDTH);
5962
6440
  const timelineSeparator = "\u2500".repeat(timelineColumnWidth);
5963
- console.log(chalk18.dim(specSeparator + COLUMN_SEPARATOR + timelineSeparator));
6441
+ console.log(chalk19.dim(specSeparator + COLUMN_SEPARATOR + timelineSeparator));
5964
6442
  const todayWeekOffset = today.diff(startDate, "week");
5965
6443
  const todayMarkerPos = todayWeekOffset * 8;
5966
6444
  let todayMarker = " ".repeat(SPEC_COLUMN_WIDTH) + COLUMN_SEPARATOR;
5967
6445
  if (todayMarkerPos >= 0 && todayMarkerPos < timelineColumnWidth) {
5968
6446
  todayMarker += " ".repeat(todayMarkerPos) + "\u2502 Today";
5969
6447
  }
5970
- console.log(chalk18.dim(todayMarker));
6448
+ console.log(chalk19.dim(todayMarker));
5971
6449
  console.log("");
5972
6450
  const priorities = ["critical", "high", "medium", "low"];
5973
6451
  for (const priority of priorities) {
@@ -5985,9 +6463,9 @@ async function showGantt(options) {
5985
6463
  const summaryParts = [];
5986
6464
  if (inProgress > 0) summaryParts.push(`${inProgress} in-progress`);
5987
6465
  if (planned > 0) summaryParts.push(`${planned} planned`);
5988
- if (overdue > 0) summaryParts.push(chalk18.red(`${overdue} overdue`));
5989
- console.log(chalk18.bold("Summary: ") + summaryParts.join(" \xB7 "));
5990
- console.log(chalk18.dim('\u{1F4A1} Tip: Add "due: YYYY-MM-DD" to frontmatter for timeline planning'));
6466
+ if (overdue > 0) summaryParts.push(chalk19.red(`${overdue} overdue`));
6467
+ console.log(chalk19.bold("Summary: ") + summaryParts.join(" \xB7 "));
6468
+ console.log(chalk19.dim('\u{1F4A1} Tip: Add "due: YYYY-MM-DD" to frontmatter for timeline planning'));
5991
6469
  }
5992
6470
  function renderSpecRow(spec, startDate, endDate, weeks, today) {
5993
6471
  const statusConfig = STATUS_CONFIG2[spec.frontmatter.status];
@@ -6000,7 +6478,7 @@ function renderSpecRow(spec, startDate, endDate, weeks, today) {
6000
6478
  const specColumn = `${SPEC_INDENT}${emoji} ${specName}`.padEnd(SPEC_COLUMN_WIDTH);
6001
6479
  let timelineColumn;
6002
6480
  if (!spec.frontmatter.due) {
6003
- timelineColumn = chalk18.dim("(no due date set)");
6481
+ timelineColumn = chalk19.dim("(no due date set)");
6004
6482
  } else {
6005
6483
  timelineColumn = renderTimelineBar(spec, startDate, endDate, weeks, today);
6006
6484
  }
@@ -6023,13 +6501,13 @@ function renderTimelineBar(spec, startDate, endDate, weeks, today) {
6023
6501
  result += " ".repeat(barStart);
6024
6502
  }
6025
6503
  if (spec.frontmatter.status === "complete") {
6026
- result += chalk18.green(FILLED_BAR_CHAR.repeat(barLength));
6504
+ result += chalk19.green(FILLED_BAR_CHAR.repeat(barLength));
6027
6505
  } else if (spec.frontmatter.status === "in-progress") {
6028
6506
  const halfLength = Math.floor(barLength / 2);
6029
- result += chalk18.yellow(FILLED_BAR_CHAR.repeat(halfLength));
6030
- result += chalk18.dim(EMPTY_BAR_CHAR.repeat(barLength - halfLength));
6507
+ result += chalk19.yellow(FILLED_BAR_CHAR.repeat(halfLength));
6508
+ result += chalk19.dim(EMPTY_BAR_CHAR.repeat(barLength - halfLength));
6031
6509
  } else {
6032
- result += chalk18.dim(EMPTY_BAR_CHAR.repeat(barLength));
6510
+ result += chalk19.dim(EMPTY_BAR_CHAR.repeat(barLength));
6033
6511
  }
6034
6512
  const trailingSpace = totalChars - barEnd;
6035
6513
  if (trailingSpace > 0) {
@@ -6055,12 +6533,12 @@ async function countSpecTokens(specPath, options = {}) {
6055
6533
  try {
6056
6534
  const config = await loadConfig();
6057
6535
  const cwd = process.cwd();
6058
- const specsDir = path4.join(cwd, config.specsDir);
6536
+ const specsDir = path15.join(cwd, config.specsDir);
6059
6537
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
6060
6538
  if (!resolvedPath) {
6061
6539
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
6062
6540
  }
6063
- const specName = path4.basename(resolvedPath);
6541
+ const specName = path15.basename(resolvedPath);
6064
6542
  const result = await counter.countSpec(resolvedPath, {
6065
6543
  detailed: options.detailed,
6066
6544
  includeSubSpecs: options.includeSubSpecs
@@ -6073,42 +6551,42 @@ async function countSpecTokens(specPath, options = {}) {
6073
6551
  }, null, 2));
6074
6552
  return;
6075
6553
  }
6076
- console.log(chalk18.bold.cyan(`\u{1F4CA} Token Count: ${specName}`));
6554
+ console.log(chalk19.bold.cyan(`\u{1F4CA} Token Count: ${specName}`));
6077
6555
  console.log("");
6078
6556
  const indicators = counter.getPerformanceIndicators(result.total);
6079
6557
  const levelEmoji = indicators.level === "excellent" ? "\u2705" : indicators.level === "good" ? "\u{1F44D}" : indicators.level === "warning" ? "\u26A0\uFE0F" : "\u{1F534}";
6080
- console.log(` Total: ${chalk18.cyan(result.total.toLocaleString())} tokens ${levelEmoji}`);
6558
+ console.log(` Total: ${chalk19.cyan(result.total.toLocaleString())} tokens ${levelEmoji}`);
6081
6559
  console.log("");
6082
6560
  if (result.files.length > 1 || options.detailed) {
6083
- console.log(chalk18.bold("Files:"));
6561
+ console.log(chalk19.bold("Files:"));
6084
6562
  console.log("");
6085
6563
  for (const file of result.files) {
6086
- const lineInfo = file.lines ? chalk18.dim(` (${file.lines} lines)`) : "";
6087
- console.log(` ${file.path.padEnd(25)} ${chalk18.cyan(file.tokens.toLocaleString().padStart(6))} tokens${lineInfo}`);
6564
+ const lineInfo = file.lines ? chalk19.dim(` (${file.lines} lines)`) : "";
6565
+ console.log(` ${file.path.padEnd(25)} ${chalk19.cyan(file.tokens.toLocaleString().padStart(6))} tokens${lineInfo}`);
6088
6566
  }
6089
6567
  console.log("");
6090
6568
  }
6091
6569
  if (options.detailed && result.breakdown) {
6092
6570
  const b = result.breakdown;
6093
6571
  const total = b.code + b.prose + b.tables + b.frontmatter;
6094
- console.log(chalk18.bold("Content Breakdown:"));
6572
+ console.log(chalk19.bold("Content Breakdown:"));
6095
6573
  console.log("");
6096
- console.log(` Prose ${chalk18.cyan(b.prose.toLocaleString().padStart(6))} tokens ${chalk18.dim(`(${Math.round(b.prose / total * 100)}%)`)}`);
6097
- console.log(` Code ${chalk18.cyan(b.code.toLocaleString().padStart(6))} tokens ${chalk18.dim(`(${Math.round(b.code / total * 100)}%)`)}`);
6098
- console.log(` Tables ${chalk18.cyan(b.tables.toLocaleString().padStart(6))} tokens ${chalk18.dim(`(${Math.round(b.tables / total * 100)}%)`)}`);
6099
- console.log(` Frontmatter ${chalk18.cyan(b.frontmatter.toLocaleString().padStart(6))} tokens ${chalk18.dim(`(${Math.round(b.frontmatter / total * 100)}%)`)}`);
6574
+ console.log(` Prose ${chalk19.cyan(b.prose.toLocaleString().padStart(6))} tokens ${chalk19.dim(`(${Math.round(b.prose / total * 100)}%)`)}`);
6575
+ console.log(` Code ${chalk19.cyan(b.code.toLocaleString().padStart(6))} tokens ${chalk19.dim(`(${Math.round(b.code / total * 100)}%)`)}`);
6576
+ console.log(` Tables ${chalk19.cyan(b.tables.toLocaleString().padStart(6))} tokens ${chalk19.dim(`(${Math.round(b.tables / total * 100)}%)`)}`);
6577
+ console.log(` Frontmatter ${chalk19.cyan(b.frontmatter.toLocaleString().padStart(6))} tokens ${chalk19.dim(`(${Math.round(b.frontmatter / total * 100)}%)`)}`);
6100
6578
  console.log("");
6101
6579
  }
6102
- console.log(chalk18.bold("Performance Indicators:"));
6580
+ console.log(chalk19.bold("Performance Indicators:"));
6103
6581
  console.log("");
6104
- const costColor = indicators.costMultiplier < 2 ? chalk18.green : indicators.costMultiplier < 4 ? chalk18.yellow : chalk18.red;
6105
- const effectivenessColor = indicators.effectiveness >= 95 ? chalk18.green : indicators.effectiveness >= 85 ? chalk18.yellow : chalk18.red;
6106
- console.log(` Cost multiplier: ${costColor(`${indicators.costMultiplier}x`)} ${chalk18.dim("vs 1,200 token baseline")}`);
6107
- console.log(` AI effectiveness: ${effectivenessColor(`~${indicators.effectiveness}%`)} ${chalk18.dim("(hypothesis)")}`);
6582
+ const costColor = indicators.costMultiplier < 2 ? chalk19.green : indicators.costMultiplier < 4 ? chalk19.yellow : chalk19.red;
6583
+ const effectivenessColor = indicators.effectiveness >= 95 ? chalk19.green : indicators.effectiveness >= 85 ? chalk19.yellow : chalk19.red;
6584
+ console.log(` Cost multiplier: ${costColor(`${indicators.costMultiplier}x`)} ${chalk19.dim("vs 1,200 token baseline")}`);
6585
+ console.log(` AI effectiveness: ${effectivenessColor(`~${indicators.effectiveness}%`)} ${chalk19.dim("(hypothesis)")}`);
6108
6586
  console.log(` Context Economy: ${levelEmoji} ${indicators.recommendation}`);
6109
6587
  console.log("");
6110
6588
  if (!options.includeSubSpecs && result.files.length === 1) {
6111
- console.log(chalk18.dim("\u{1F4A1} Use `--include-sub-specs` to count all sub-spec files"));
6589
+ console.log(chalk19.dim("\u{1F4A1} Use `--include-sub-specs` to count all sub-spec files"));
6112
6590
  }
6113
6591
  } finally {
6114
6592
  counter.dispose();
@@ -6154,46 +6632,46 @@ async function tokensAllCommand(options = {}) {
6154
6632
  console.log(JSON.stringify(results, null, 2));
6155
6633
  return;
6156
6634
  }
6157
- console.log(chalk18.bold.cyan("\u{1F4CA} Token Counts"));
6635
+ console.log(chalk19.bold.cyan("\u{1F4CA} Token Counts"));
6158
6636
  console.log("");
6159
- console.log(chalk18.dim(`Sorted by: ${sortBy}`));
6637
+ console.log(chalk19.dim(`Sorted by: ${sortBy}`));
6160
6638
  console.log("");
6161
6639
  const totalTokens = results.reduce((sum, r) => sum + r.tokens, 0);
6162
6640
  const avgTokens = Math.round(totalTokens / results.length);
6163
6641
  const warningCount = results.filter((r) => r.level === "warning" || r.level === "problem").length;
6164
- console.log(chalk18.bold("Summary:"));
6642
+ console.log(chalk19.bold("Summary:"));
6165
6643
  console.log("");
6166
- console.log(` Total specs: ${chalk18.cyan(results.length)}`);
6167
- console.log(` Total tokens: ${chalk18.cyan(totalTokens.toLocaleString())}`);
6168
- console.log(` Average tokens: ${chalk18.cyan(avgTokens.toLocaleString())}`);
6644
+ console.log(` Total specs: ${chalk19.cyan(results.length)}`);
6645
+ console.log(` Total tokens: ${chalk19.cyan(totalTokens.toLocaleString())}`);
6646
+ console.log(` Average tokens: ${chalk19.cyan(avgTokens.toLocaleString())}`);
6169
6647
  if (warningCount > 0) {
6170
- console.log(` Needs review: ${chalk18.yellow(warningCount)} specs ${chalk18.dim("(\u26A0\uFE0F or \u{1F534})")}`);
6648
+ console.log(` Needs review: ${chalk19.yellow(warningCount)} specs ${chalk19.dim("(\u26A0\uFE0F or \u{1F534})")}`);
6171
6649
  }
6172
6650
  console.log("");
6173
6651
  const nameCol = 35;
6174
6652
  const tokensCol = 10;
6175
6653
  const linesCol = 8;
6176
- console.log(chalk18.bold(
6654
+ console.log(chalk19.bold(
6177
6655
  "Spec".padEnd(nameCol) + "Tokens".padStart(tokensCol) + "Lines".padStart(linesCol) + " Status"
6178
6656
  ));
6179
- console.log(chalk18.dim("\u2500".repeat(nameCol + tokensCol + linesCol + 10)));
6657
+ console.log(chalk19.dim("\u2500".repeat(nameCol + tokensCol + linesCol + 10)));
6180
6658
  const displayCount = options.all ? results.length : Math.min(20, results.length);
6181
6659
  for (let i = 0; i < displayCount; i++) {
6182
6660
  const r = results[i];
6183
6661
  const emoji = r.level === "excellent" ? "\u2705" : r.level === "good" ? "\u{1F44D}" : r.level === "warning" ? "\u26A0\uFE0F" : "\u{1F534}";
6184
- const tokensColor = r.level === "excellent" || r.level === "good" ? chalk18.cyan : r.level === "warning" ? chalk18.yellow : chalk18.red;
6662
+ const tokensColor = r.level === "excellent" || r.level === "good" ? chalk19.cyan : r.level === "warning" ? chalk19.yellow : chalk19.red;
6185
6663
  const name = r.name.length > nameCol - 2 ? r.name.substring(0, nameCol - 3) + "\u2026" : r.name;
6186
6664
  console.log(
6187
- name.padEnd(nameCol) + tokensColor(r.tokens.toLocaleString().padStart(tokensCol)) + chalk18.dim(r.lines.toString().padStart(linesCol)) + ` ${emoji}`
6665
+ name.padEnd(nameCol) + tokensColor(r.tokens.toLocaleString().padStart(tokensCol)) + chalk19.dim(r.lines.toString().padStart(linesCol)) + ` ${emoji}`
6188
6666
  );
6189
6667
  }
6190
6668
  if (results.length > displayCount) {
6191
6669
  console.log("");
6192
- console.log(chalk18.dim(`... and ${results.length - displayCount} more specs`));
6193
- console.log(chalk18.dim(`Use --all to show all specs`));
6670
+ console.log(chalk19.dim(`... and ${results.length - displayCount} more specs`));
6671
+ console.log(chalk19.dim(`Use --all to show all specs`));
6194
6672
  }
6195
6673
  console.log("");
6196
- console.log(chalk18.dim("Legend: \u2705 excellent (<2K) | \u{1F44D} good (<3.5K) | \u26A0\uFE0F warning (<5K) | \u{1F534} problem (>5K)"));
6674
+ console.log(chalk19.dim("Legend: \u2705 excellent (<2K) | \u{1F44D} good (<3.5K) | \u26A0\uFE0F warning (<5K) | \u{1F534} problem (>5K)"));
6197
6675
  console.log("");
6198
6676
  }
6199
6677
  function analyzeCommand() {
@@ -6207,13 +6685,13 @@ async function analyzeSpec(specPath, options = {}) {
6207
6685
  try {
6208
6686
  const config = await loadConfig();
6209
6687
  const cwd = process.cwd();
6210
- const specsDir = path4.join(cwd, config.specsDir);
6688
+ const specsDir = path15.join(cwd, config.specsDir);
6211
6689
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
6212
6690
  if (!resolvedPath) {
6213
6691
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
6214
6692
  }
6215
- const specName = path4.basename(resolvedPath);
6216
- const readmePath = path4.join(resolvedPath, "README.md");
6693
+ const specName = path15.basename(resolvedPath);
6694
+ const readmePath = path15.join(resolvedPath, "README.md");
6217
6695
  const content = await readFile(readmePath, "utf-8");
6218
6696
  const structure = analyzeMarkdownStructure(content);
6219
6697
  const tokenResult = await counter.countSpec(resolvedPath, {
@@ -6316,44 +6794,44 @@ function getThresholdLimit(level) {
6316
6794
  }
6317
6795
  }
6318
6796
  function displayAnalysis(result, verbose) {
6319
- console.log(chalk18.bold.cyan(`\u{1F4CA} Spec Analysis: ${result.spec}`));
6797
+ console.log(chalk19.bold.cyan(`\u{1F4CA} Spec Analysis: ${result.spec}`));
6320
6798
  console.log("");
6321
6799
  const statusEmoji = result.threshold.status === "excellent" ? "\u2705" : result.threshold.status === "good" ? "\u{1F44D}" : result.threshold.status === "warning" ? "\u26A0\uFE0F" : "\u{1F534}";
6322
- const tokenColor = result.threshold.status === "excellent" || result.threshold.status === "good" ? chalk18.cyan : result.threshold.status === "warning" ? chalk18.yellow : chalk18.red;
6323
- console.log(chalk18.bold("Token Count:"), tokenColor(result.metrics.tokens.toLocaleString()), "tokens", statusEmoji);
6324
- console.log(chalk18.dim(` Threshold: ${result.threshold.limit.toLocaleString()} tokens`));
6325
- console.log(chalk18.dim(` Status: ${result.threshold.message}`));
6800
+ const tokenColor = result.threshold.status === "excellent" || result.threshold.status === "good" ? chalk19.cyan : result.threshold.status === "warning" ? chalk19.yellow : chalk19.red;
6801
+ console.log(chalk19.bold("Token Count:"), tokenColor(result.metrics.tokens.toLocaleString()), "tokens", statusEmoji);
6802
+ console.log(chalk19.dim(` Threshold: ${result.threshold.limit.toLocaleString()} tokens`));
6803
+ console.log(chalk19.dim(` Status: ${result.threshold.message}`));
6326
6804
  console.log("");
6327
- console.log(chalk18.bold("Structure:"));
6328
- console.log(` Lines: ${chalk18.cyan(result.metrics.lines.toLocaleString())}`);
6329
- console.log(` Sections: ${chalk18.cyan(result.metrics.sections.total)} (H1:${result.metrics.sections.h1}, H2:${result.metrics.sections.h2}, H3:${result.metrics.sections.h3}, H4:${result.metrics.sections.h4})`);
6330
- console.log(` Code blocks: ${chalk18.cyan(result.metrics.codeBlocks)}`);
6331
- console.log(` Max nesting: ${chalk18.cyan(result.metrics.maxNesting)} levels`);
6805
+ console.log(chalk19.bold("Structure:"));
6806
+ console.log(` Lines: ${chalk19.cyan(result.metrics.lines.toLocaleString())}`);
6807
+ console.log(` Sections: ${chalk19.cyan(result.metrics.sections.total)} (H1:${result.metrics.sections.h1}, H2:${result.metrics.sections.h2}, H3:${result.metrics.sections.h3}, H4:${result.metrics.sections.h4})`);
6808
+ console.log(` Code blocks: ${chalk19.cyan(result.metrics.codeBlocks)}`);
6809
+ console.log(` Max nesting: ${chalk19.cyan(result.metrics.maxNesting)} levels`);
6332
6810
  console.log("");
6333
6811
  if (verbose && result.structure.length > 0) {
6334
6812
  const topSections = result.structure.filter((s) => s.level <= 2).sort((a, b) => b.tokens - a.tokens).slice(0, 5);
6335
- console.log(chalk18.bold("Top Sections by Size:"));
6813
+ console.log(chalk19.bold("Top Sections by Size:"));
6336
6814
  console.log("");
6337
6815
  for (let i = 0; i < topSections.length; i++) {
6338
6816
  const s = topSections[i];
6339
6817
  const percentage = Math.round(s.tokens / result.metrics.tokens * 100);
6340
6818
  const indent = " ".repeat(s.level - 1);
6341
6819
  console.log(` ${i + 1}. ${indent}${s.section}`);
6342
- console.log(` ${chalk18.cyan(s.tokens.toLocaleString())} tokens / ${s.lineRange[1] - s.lineRange[0] + 1} lines ${chalk18.dim(`(${percentage}%)`)}`);
6343
- console.log(chalk18.dim(` Lines ${s.lineRange[0]}-${s.lineRange[1]}`));
6820
+ console.log(` ${chalk19.cyan(s.tokens.toLocaleString())} tokens / ${s.lineRange[1] - s.lineRange[0] + 1} lines ${chalk19.dim(`(${percentage}%)`)}`);
6821
+ console.log(chalk19.dim(` Lines ${s.lineRange[0]}-${s.lineRange[1]}`));
6344
6822
  }
6345
6823
  console.log("");
6346
6824
  }
6347
- const actionColor = result.recommendation.action === "none" ? chalk18.green : result.recommendation.action === "compact" ? chalk18.yellow : result.recommendation.action === "split" ? chalk18.red : chalk18.blue;
6348
- console.log(chalk18.bold("Recommendation:"), actionColor(result.recommendation.action.toUpperCase()));
6349
- console.log(chalk18.dim(` ${result.recommendation.reason}`));
6350
- console.log(chalk18.dim(` Confidence: ${result.recommendation.confidence}`));
6825
+ const actionColor = result.recommendation.action === "none" ? chalk19.green : result.recommendation.action === "compact" ? chalk19.yellow : result.recommendation.action === "split" ? chalk19.red : chalk19.blue;
6826
+ console.log(chalk19.bold("Recommendation:"), actionColor(result.recommendation.action.toUpperCase()));
6827
+ console.log(chalk19.dim(` ${result.recommendation.reason}`));
6828
+ console.log(chalk19.dim(` Confidence: ${result.recommendation.confidence}`));
6351
6829
  console.log("");
6352
6830
  if (result.recommendation.action === "split") {
6353
- console.log(chalk18.dim("\u{1F4A1} Use `lean-spec split` to partition into sub-specs"));
6354
- console.log(chalk18.dim("\u{1F4A1} Consider splitting by H2 sections (concerns)"));
6831
+ console.log(chalk19.dim("\u{1F4A1} Use `lean-spec split` to partition into sub-specs"));
6832
+ console.log(chalk19.dim("\u{1F4A1} Consider splitting by H2 sections (concerns)"));
6355
6833
  } else if (result.recommendation.action === "compact") {
6356
- console.log(chalk18.dim("\u{1F4A1} Use `lean-spec compact` to remove redundancy"));
6834
+ console.log(chalk19.dim("\u{1F4A1} Use `lean-spec compact` to remove redundancy"));
6357
6835
  }
6358
6836
  console.log("");
6359
6837
  }
@@ -6385,13 +6863,13 @@ async function splitSpec(specPath, options) {
6385
6863
  }
6386
6864
  const config = await loadConfig();
6387
6865
  const cwd = process.cwd();
6388
- const specsDir = path4.join(cwd, config.specsDir);
6866
+ const specsDir = path15.join(cwd, config.specsDir);
6389
6867
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
6390
6868
  if (!resolvedPath) {
6391
6869
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
6392
6870
  }
6393
- const specName = path4.basename(resolvedPath);
6394
- const readmePath = path4.join(resolvedPath, "README.md");
6871
+ const specName = path15.basename(resolvedPath);
6872
+ const readmePath = path15.join(resolvedPath, "README.md");
6395
6873
  const content = await readFile(readmePath, "utf-8");
6396
6874
  const parsedOutputs = parseOutputSpecs(options.outputs);
6397
6875
  validateNoOverlaps(parsedOutputs);
@@ -6414,7 +6892,7 @@ async function splitSpec(specPath, options) {
6414
6892
  await executeSplit(resolvedPath, specName, content, extractions, options);
6415
6893
  } catch (error) {
6416
6894
  if (error instanceof Error) {
6417
- console.error(chalk18.red(`Error: ${error.message}`));
6895
+ console.error(chalk19.red(`Error: ${error.message}`));
6418
6896
  }
6419
6897
  throw error;
6420
6898
  }
@@ -6452,30 +6930,30 @@ function validateNoOverlaps(outputs) {
6452
6930
  }
6453
6931
  }
6454
6932
  async function displayDryRun(specName, extractions) {
6455
- console.log(chalk18.bold.cyan(`\u{1F4CB} Split Preview: ${specName}`));
6933
+ console.log(chalk19.bold.cyan(`\u{1F4CB} Split Preview: ${specName}`));
6456
6934
  console.log("");
6457
- console.log(chalk18.bold("Would create:"));
6935
+ console.log(chalk19.bold("Would create:"));
6458
6936
  console.log("");
6459
6937
  for (const ext of extractions) {
6460
- console.log(` ${chalk18.cyan(ext.file)}`);
6938
+ console.log(` ${chalk19.cyan(ext.file)}`);
6461
6939
  console.log(` Lines: ${ext.lines}`);
6462
6940
  const previewLines = ext.content.split("\n").slice(0, 3);
6463
- console.log(chalk18.dim(" Preview:"));
6941
+ console.log(chalk19.dim(" Preview:"));
6464
6942
  for (const line of previewLines) {
6465
- console.log(chalk18.dim(` ${line.substring(0, 60)}${line.length > 60 ? "..." : ""}`));
6943
+ console.log(chalk19.dim(` ${line.substring(0, 60)}${line.length > 60 ? "..." : ""}`));
6466
6944
  }
6467
6945
  console.log("");
6468
6946
  }
6469
- console.log(chalk18.dim("No files modified (dry run)"));
6470
- console.log(chalk18.dim("Run without --dry-run to apply changes"));
6947
+ console.log(chalk19.dim("No files modified (dry run)"));
6948
+ console.log(chalk19.dim("Run without --dry-run to apply changes"));
6471
6949
  console.log("");
6472
6950
  }
6473
6951
  async function executeSplit(specPath, specName, originalContent, extractions, options) {
6474
- console.log(chalk18.bold.cyan(`\u2702\uFE0F Splitting: ${specName}`));
6952
+ console.log(chalk19.bold.cyan(`\u2702\uFE0F Splitting: ${specName}`));
6475
6953
  console.log("");
6476
6954
  const frontmatter = parseFrontmatterFromString(originalContent);
6477
6955
  for (const ext of extractions) {
6478
- const outputPath = path4.join(specPath, ext.file);
6956
+ const outputPath = path15.join(specPath, ext.file);
6479
6957
  let finalContent = ext.content;
6480
6958
  if (ext.file === "README.md" && frontmatter) {
6481
6959
  const { content: contentWithFrontmatter } = createUpdatedFrontmatter(
@@ -6485,21 +6963,21 @@ async function executeSplit(specPath, specName, originalContent, extractions, op
6485
6963
  finalContent = contentWithFrontmatter;
6486
6964
  }
6487
6965
  await writeFile(outputPath, finalContent, "utf-8");
6488
- console.log(chalk18.green(`\u2713 Created ${ext.file} (${ext.lines} lines)`));
6966
+ console.log(chalk19.green(`\u2713 Created ${ext.file} (${ext.lines} lines)`));
6489
6967
  }
6490
6968
  if (options.updateRefs) {
6491
- const readmePath = path4.join(specPath, "README.md");
6969
+ const readmePath = path15.join(specPath, "README.md");
6492
6970
  const readmeContent = await readFile(readmePath, "utf-8");
6493
6971
  const updatedReadme = await addSubSpecLinks(
6494
6972
  readmeContent,
6495
6973
  extractions.map((e) => e.file).filter((f) => f !== "README.md")
6496
6974
  );
6497
6975
  await writeFile(readmePath, updatedReadme, "utf-8");
6498
- console.log(chalk18.green(`\u2713 Updated README.md with sub-spec links`));
6976
+ console.log(chalk19.green(`\u2713 Updated README.md with sub-spec links`));
6499
6977
  }
6500
6978
  console.log("");
6501
- console.log(chalk18.bold.green("Split complete!"));
6502
- console.log(chalk18.dim(`Created ${extractions.length} files in ${specName}`));
6979
+ console.log(chalk19.bold.green("Split complete!"));
6980
+ console.log(chalk19.dim(`Created ${extractions.length} files in ${specName}`));
6503
6981
  console.log("");
6504
6982
  }
6505
6983
  async function addSubSpecLinks(content, subSpecs) {
@@ -6567,13 +7045,13 @@ async function compactSpec(specPath, options) {
6567
7045
  }
6568
7046
  const config = await loadConfig();
6569
7047
  const cwd = process.cwd();
6570
- const specsDir = path4.join(cwd, config.specsDir);
7048
+ const specsDir = path15.join(cwd, config.specsDir);
6571
7049
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
6572
7050
  if (!resolvedPath) {
6573
7051
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
6574
7052
  }
6575
- const specName = path4.basename(resolvedPath);
6576
- const readmePath = path4.join(resolvedPath, "README.md");
7053
+ const specName = path15.basename(resolvedPath);
7054
+ const readmePath = path15.join(resolvedPath, "README.md");
6577
7055
  const content = await readFile(readmePath, "utf-8");
6578
7056
  const parsedRemoves = parseRemoveSpecs(options.removes);
6579
7057
  validateNoOverlaps2(parsedRemoves);
@@ -6584,7 +7062,7 @@ async function compactSpec(specPath, options) {
6584
7062
  await executeCompact(readmePath, specName, content, parsedRemoves);
6585
7063
  } catch (error) {
6586
7064
  if (error instanceof Error) {
6587
- console.error(chalk18.red(`Error: ${error.message}`));
7065
+ console.error(chalk19.red(`Error: ${error.message}`));
6588
7066
  }
6589
7067
  throw error;
6590
7068
  }
@@ -6623,9 +7101,9 @@ function validateNoOverlaps2(removes) {
6623
7101
  }
6624
7102
  }
6625
7103
  async function displayDryRun2(specName, content, removes) {
6626
- console.log(chalk18.bold.cyan(`\u{1F4CB} Compact Preview: ${specName}`));
7104
+ console.log(chalk19.bold.cyan(`\u{1F4CB} Compact Preview: ${specName}`));
6627
7105
  console.log("");
6628
- console.log(chalk18.bold("Would remove:"));
7106
+ console.log(chalk19.bold("Would remove:"));
6629
7107
  console.log("");
6630
7108
  let totalLines = 0;
6631
7109
  for (const remove of removes) {
@@ -6634,29 +7112,29 @@ async function displayDryRun2(specName, content, removes) {
6634
7112
  const removedContent = extractLines(content, remove.startLine, remove.endLine);
6635
7113
  const previewLines = removedContent.split("\n").slice(0, 3);
6636
7114
  console.log(` Lines ${remove.startLine}-${remove.endLine} (${lineCount} lines)`);
6637
- console.log(chalk18.dim(" Preview:"));
7115
+ console.log(chalk19.dim(" Preview:"));
6638
7116
  for (const line of previewLines) {
6639
- console.log(chalk18.dim(` ${line.substring(0, 60)}${line.length > 60 ? "..." : ""}`));
7117
+ console.log(chalk19.dim(` ${line.substring(0, 60)}${line.length > 60 ? "..." : ""}`));
6640
7118
  }
6641
7119
  if (removedContent.split("\n").length > 3) {
6642
- console.log(chalk18.dim(` ... (${removedContent.split("\n").length - 3} more lines)`));
7120
+ console.log(chalk19.dim(` ... (${removedContent.split("\n").length - 3} more lines)`));
6643
7121
  }
6644
7122
  console.log("");
6645
7123
  }
6646
7124
  const originalLines = countLines(content);
6647
7125
  const remainingLines = originalLines - totalLines;
6648
7126
  const percentage = Math.round(totalLines / originalLines * 100);
6649
- console.log(chalk18.bold("Summary:"));
6650
- console.log(` Original lines: ${chalk18.cyan(originalLines)}`);
6651
- console.log(` Removing: ${chalk18.yellow(totalLines)} lines (${percentage}%)`);
6652
- console.log(` Remaining lines: ${chalk18.cyan(remainingLines)}`);
7127
+ console.log(chalk19.bold("Summary:"));
7128
+ console.log(` Original lines: ${chalk19.cyan(originalLines)}`);
7129
+ console.log(` Removing: ${chalk19.yellow(totalLines)} lines (${percentage}%)`);
7130
+ console.log(` Remaining lines: ${chalk19.cyan(remainingLines)}`);
6653
7131
  console.log("");
6654
- console.log(chalk18.dim("No files modified (dry run)"));
6655
- console.log(chalk18.dim("Run without --dry-run to apply changes"));
7132
+ console.log(chalk19.dim("No files modified (dry run)"));
7133
+ console.log(chalk19.dim("Run without --dry-run to apply changes"));
6656
7134
  console.log("");
6657
7135
  }
6658
7136
  async function executeCompact(readmePath, specName, content, removes) {
6659
- console.log(chalk18.bold.cyan(`\u{1F5DC}\uFE0F Compacting: ${specName}`));
7137
+ console.log(chalk19.bold.cyan(`\u{1F5DC}\uFE0F Compacting: ${specName}`));
6660
7138
  console.log("");
6661
7139
  const sorted = [...removes].sort((a, b) => b.startLine - a.startLine);
6662
7140
  let updatedContent = content;
@@ -6665,22 +7143,22 @@ async function executeCompact(readmePath, specName, content, removes) {
6665
7143
  const lineCount = remove.endLine - remove.startLine + 1;
6666
7144
  updatedContent = removeLines(updatedContent, remove.startLine, remove.endLine);
6667
7145
  totalRemoved += lineCount;
6668
- console.log(chalk18.green(`\u2713 Removed lines ${remove.startLine}-${remove.endLine} (${lineCount} lines)`));
7146
+ console.log(chalk19.green(`\u2713 Removed lines ${remove.startLine}-${remove.endLine} (${lineCount} lines)`));
6669
7147
  }
6670
7148
  await writeFile(readmePath, updatedContent, "utf-8");
6671
7149
  const originalLines = countLines(content);
6672
7150
  const finalLines = countLines(updatedContent);
6673
7151
  const percentage = Math.round(totalRemoved / originalLines * 100);
6674
7152
  console.log("");
6675
- console.log(chalk18.bold.green("Compaction complete!"));
6676
- console.log(chalk18.dim(`Removed ${totalRemoved} lines (${percentage}%)`));
6677
- console.log(chalk18.dim(`${originalLines} \u2192 ${finalLines} lines`));
7153
+ console.log(chalk19.bold.green("Compaction complete!"));
7154
+ console.log(chalk19.dim(`Removed ${totalRemoved} lines (${percentage}%)`));
7155
+ console.log(chalk19.dim(`${originalLines} \u2192 ${finalLines} lines`));
6678
7156
  console.log("");
6679
7157
  }
6680
7158
  marked.use(markedTerminal());
6681
7159
  async function readSpecContent(specPath, cwd = process.cwd()) {
6682
7160
  const config = await loadConfig(cwd);
6683
- const specsDir = path4.join(cwd, config.specsDir);
7161
+ const specsDir = path15.join(cwd, config.specsDir);
6684
7162
  let resolvedPath = null;
6685
7163
  let targetFile = null;
6686
7164
  const pathParts = specPath.split("/").filter((p) => p);
@@ -6689,7 +7167,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
6689
7167
  const filePart = pathParts[pathParts.length - 1];
6690
7168
  resolvedPath = await resolveSpecPath(specPart, cwd, specsDir);
6691
7169
  if (resolvedPath) {
6692
- targetFile = path4.join(resolvedPath, filePart);
7170
+ targetFile = path15.join(resolvedPath, filePart);
6693
7171
  try {
6694
7172
  await fs9.access(targetFile);
6695
7173
  } catch {
@@ -6711,7 +7189,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
6711
7189
  return null;
6712
7190
  }
6713
7191
  const rawContent = await fs9.readFile(targetFile, "utf-8");
6714
- const fileName = path4.basename(targetFile);
7192
+ const fileName = path15.basename(targetFile);
6715
7193
  const isSubSpec = fileName !== config.structure.defaultFile;
6716
7194
  let frontmatter = null;
6717
7195
  if (!isSubSpec) {
@@ -6740,7 +7218,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
6740
7218
  }
6741
7219
  }
6742
7220
  const content = lines.slice(contentStartIndex).join("\n").trim();
6743
- const specName = path4.basename(resolvedPath);
7221
+ const specName = path15.basename(resolvedPath);
6744
7222
  const displayName = isSubSpec ? `${specName}/${fileName}` : specName;
6745
7223
  return {
6746
7224
  frontmatter,
@@ -6763,7 +7241,7 @@ function formatFrontmatter(frontmatter) {
6763
7241
  archived: "\u{1F4E6}"
6764
7242
  };
6765
7243
  const statusEmoji = statusEmojis[frontmatter.status] || "\u{1F4C4}";
6766
- lines.push(chalk18.bold(`${statusEmoji} Status: `) + chalk18.cyan(frontmatter.status));
7244
+ lines.push(chalk19.bold(`${statusEmoji} Status: `) + chalk19.cyan(frontmatter.status));
6767
7245
  if (frontmatter.priority) {
6768
7246
  const priorityEmojis = {
6769
7247
  low: "\u{1F7E2}",
@@ -6772,25 +7250,25 @@ function formatFrontmatter(frontmatter) {
6772
7250
  critical: "\u{1F534}"
6773
7251
  };
6774
7252
  const priorityEmoji = priorityEmojis[frontmatter.priority] || "";
6775
- lines.push(chalk18.bold(`${priorityEmoji} Priority: `) + chalk18.yellow(frontmatter.priority));
7253
+ lines.push(chalk19.bold(`${priorityEmoji} Priority: `) + chalk19.yellow(frontmatter.priority));
6776
7254
  }
6777
7255
  if (frontmatter.created) {
6778
- lines.push(chalk18.bold("\u{1F4C6} Created: ") + chalk18.gray(String(frontmatter.created)));
7256
+ lines.push(chalk19.bold("\u{1F4C6} Created: ") + chalk19.gray(String(frontmatter.created)));
6779
7257
  }
6780
7258
  if (frontmatter.tags && frontmatter.tags.length > 0) {
6781
- const tagStr = frontmatter.tags.map((tag) => chalk18.blue(`#${tag}`)).join(" ");
6782
- lines.push(chalk18.bold("\u{1F3F7}\uFE0F Tags: ") + tagStr);
7259
+ const tagStr = frontmatter.tags.map((tag) => chalk19.blue(`#${tag}`)).join(" ");
7260
+ lines.push(chalk19.bold("\u{1F3F7}\uFE0F Tags: ") + tagStr);
6783
7261
  }
6784
7262
  if (frontmatter.assignee) {
6785
- lines.push(chalk18.bold("\u{1F464} Assignee: ") + chalk18.green(frontmatter.assignee));
7263
+ lines.push(chalk19.bold("\u{1F464} Assignee: ") + chalk19.green(frontmatter.assignee));
6786
7264
  }
6787
7265
  const standardFields = ["status", "priority", "created", "tags", "assignee"];
6788
7266
  const customFields = Object.entries(frontmatter).filter(([key]) => !standardFields.includes(key)).filter(([_, value]) => value !== void 0 && value !== null);
6789
7267
  if (customFields.length > 0) {
6790
7268
  lines.push("");
6791
- lines.push(chalk18.bold("Custom Fields:"));
7269
+ lines.push(chalk19.bold("Custom Fields:"));
6792
7270
  for (const [key, value] of customFields) {
6793
- lines.push(` ${chalk18.gray(key)}: ${chalk18.white(String(value))}`);
7271
+ lines.push(` ${chalk19.gray(key)}: ${chalk19.white(String(value))}`);
6794
7272
  }
6795
7273
  }
6796
7274
  return lines.join("\n");
@@ -6798,11 +7276,11 @@ function formatFrontmatter(frontmatter) {
6798
7276
  function displayFormattedSpec(spec) {
6799
7277
  const output = [];
6800
7278
  output.push("");
6801
- output.push(chalk18.bold.cyan(`\u2501\u2501\u2501 ${spec.name} \u2501\u2501\u2501`));
7279
+ output.push(chalk19.bold.cyan(`\u2501\u2501\u2501 ${spec.name} \u2501\u2501\u2501`));
6802
7280
  output.push("");
6803
7281
  output.push(formatFrontmatter(spec.frontmatter));
6804
7282
  output.push("");
6805
- output.push(chalk18.gray("\u2500".repeat(60)));
7283
+ output.push(chalk19.gray("\u2500".repeat(60)));
6806
7284
  output.push("");
6807
7285
  return output.join("\n");
6808
7286
  }
@@ -6864,7 +7342,7 @@ function openCommand(specPath, options = {}) {
6864
7342
  async function openSpec(specPath, options = {}) {
6865
7343
  const cwd = process.cwd();
6866
7344
  const config = await loadConfig(cwd);
6867
- const specsDir = path4.join(cwd, config.specsDir);
7345
+ const specsDir = path15.join(cwd, config.specsDir);
6868
7346
  let resolvedPath = null;
6869
7347
  let targetFile = null;
6870
7348
  const pathParts = specPath.split("/").filter((p) => p);
@@ -6873,7 +7351,7 @@ async function openSpec(specPath, options = {}) {
6873
7351
  const filePart = pathParts[pathParts.length - 1];
6874
7352
  resolvedPath = await resolveSpecPath(specPart, cwd, specsDir);
6875
7353
  if (resolvedPath) {
6876
- targetFile = path4.join(resolvedPath, filePart);
7354
+ targetFile = path15.join(resolvedPath, filePart);
6877
7355
  try {
6878
7356
  await fs9.access(targetFile);
6879
7357
  } catch {
@@ -6907,7 +7385,7 @@ async function openSpec(specPath, options = {}) {
6907
7385
  editor = "xdg-open";
6908
7386
  }
6909
7387
  }
6910
- console.log(chalk18.gray(`Opening ${targetFile} with ${editor}...`));
7388
+ console.log(chalk19.gray(`Opening ${targetFile} with ${editor}...`));
6911
7389
  const child = spawn(editor, [targetFile], {
6912
7390
  stdio: "inherit",
6913
7391
  shell: true
@@ -6953,7 +7431,7 @@ async function startMcpServer() {
6953
7431
  process.exit(1);
6954
7432
  }
6955
7433
  }
6956
- function detectPackageManager(baseDir = process.cwd()) {
7434
+ function detectPackageManager2(baseDir = process.cwd()) {
6957
7435
  const userAgent = process.env.npm_config_user_agent || "";
6958
7436
  if (userAgent.includes("pnpm")) {
6959
7437
  return "pnpm";
@@ -6978,7 +7456,7 @@ function detectPackageManager(baseDir = process.cwd()) {
6978
7456
 
6979
7457
  // src/commands/ui.ts
6980
7458
  function uiCommand() {
6981
- return new Command("ui").description("Start local web UI for spec management").option("-s, --specs <dir>", "Specs directory (auto-detected if not specified)").option("-p, --port <port>", "Port to run on", "3000").option("--no-open", "Don't open browser automatically").option("--dev", "Run in development mode (only works in LeanSpec monorepo)").option("--dry-run", "Show what would run without executing").action(async (options) => {
7459
+ return new Command("ui").description("Start local web UI for spec management").option("-s, --specs <dir>", "Specs directory (auto-detected if not specified)").option("-p, --port <port>", "Port to run on", "3000").option("--no-open", "Don't open browser automatically").option("--multi-project", "Enable multi-project mode").option("--add-project <path>", "Add a project to multi-project registry").option("--discover <path>", "Discover LeanSpec projects in directory tree").option("--dev", "Run in development mode (only works in LeanSpec monorepo)").option("--dry-run", "Show what would run without executing").action(async (options) => {
6982
7460
  try {
6983
7461
  await startUi(options);
6984
7462
  } catch (error) {
@@ -6989,34 +7467,45 @@ function uiCommand() {
6989
7467
  async function startUi(options) {
6990
7468
  const portNum = parseInt(options.port, 10);
6991
7469
  if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
6992
- console.error(chalk18.red(`\u2717 Invalid port number: ${options.port}`));
6993
- console.log(chalk18.dim("Port must be between 1 and 65535"));
7470
+ console.error(chalk19.red(`\u2717 Invalid port number: ${options.port}`));
7471
+ console.log(chalk19.dim("Port must be between 1 and 65535"));
6994
7472
  throw new Error(`Invalid port: ${options.port}`);
6995
7473
  }
6996
7474
  const cwd = process.cwd();
6997
- let specsDir;
6998
- if (options.specs) {
6999
- specsDir = resolve(cwd, options.specs);
7475
+ const specsMode = options.multiProject ? "multi-project" : "filesystem";
7476
+ let specsDir = "";
7477
+ if (!options.multiProject) {
7478
+ if (options.specs) {
7479
+ specsDir = resolve(cwd, options.specs);
7480
+ } else {
7481
+ const config = await loadConfig(cwd);
7482
+ specsDir = join(cwd, config.specsDir);
7483
+ }
7484
+ if (!existsSync(specsDir)) {
7485
+ console.error(chalk19.red(`\u2717 Specs directory not found: ${specsDir}`));
7486
+ console.log(chalk19.dim("\nRun `lean-spec init` to initialize LeanSpec in this directory."));
7487
+ throw new Error(`Specs directory not found: ${specsDir}`);
7488
+ }
7000
7489
  } else {
7001
- const config = await loadConfig(cwd);
7002
- specsDir = join(cwd, config.specsDir);
7003
- }
7004
- if (!existsSync(specsDir)) {
7005
- console.error(chalk18.red(`\u2717 Specs directory not found: ${specsDir}`));
7006
- console.log(chalk18.dim("\nRun `lean-spec init` to initialize LeanSpec in this directory."));
7007
- throw new Error(`Specs directory not found: ${specsDir}`);
7490
+ console.log(chalk19.cyan("\u2192 Multi-project mode enabled"));
7491
+ if (options.addProject) {
7492
+ console.log(chalk19.dim(` Adding project: ${options.addProject}`));
7493
+ }
7494
+ if (options.discover) {
7495
+ console.log(chalk19.dim(` Will discover projects in: ${options.discover}`));
7496
+ }
7008
7497
  }
7009
7498
  if (options.dev) {
7010
7499
  const isLeanSpecMonorepo = checkIsLeanSpecMonorepo(cwd);
7011
7500
  if (!isLeanSpecMonorepo) {
7012
- console.error(chalk18.red(`\u2717 Development mode only works in the LeanSpec monorepo`));
7013
- console.log(chalk18.dim("Remove --dev flag to use production mode"));
7501
+ console.error(chalk19.red(`\u2717 Development mode only works in the LeanSpec monorepo`));
7502
+ console.log(chalk19.dim("Remove --dev flag to use production mode"));
7014
7503
  throw new Error("Not in LeanSpec monorepo");
7015
7504
  }
7016
7505
  const localUiDir = join(cwd, "packages/ui");
7017
- return runLocalWeb(localUiDir, specsDir, options);
7506
+ return runLocalWeb(localUiDir, specsDir, specsMode, options);
7018
7507
  }
7019
- return runPublishedUI(cwd, specsDir, options);
7508
+ return runPublishedUI(cwd, specsDir, specsMode, options);
7020
7509
  }
7021
7510
  function checkIsLeanSpecMonorepo(cwd) {
7022
7511
  const localUiDir = join(cwd, "packages/ui");
@@ -7031,24 +7520,24 @@ function checkIsLeanSpecMonorepo(cwd) {
7031
7520
  return false;
7032
7521
  }
7033
7522
  }
7034
- async function runLocalWeb(uiDir, specsDir, options) {
7035
- console.log(chalk18.dim("\u2192 Detected LeanSpec monorepo, using local ui package\n"));
7523
+ async function runLocalWeb(uiDir, specsDir, specsMode, options) {
7524
+ console.log(chalk19.dim("\u2192 Detected LeanSpec monorepo, using local ui package\n"));
7036
7525
  const repoRoot = resolve(uiDir, "..", "..");
7037
- const packageManager = detectPackageManager(repoRoot);
7526
+ const packageManager = detectPackageManager2(repoRoot);
7038
7527
  if (options.dryRun) {
7039
- console.log(chalk18.cyan("Would run:"));
7040
- console.log(chalk18.dim(` cd ${uiDir}`));
7041
- console.log(chalk18.dim(` SPECS_MODE=filesystem SPECS_DIR=${specsDir} PORT=${options.port} ${packageManager} run dev`));
7528
+ console.log(chalk19.cyan("Would run:"));
7529
+ console.log(chalk19.dim(` cd ${uiDir}`));
7530
+ console.log(chalk19.dim(` SPECS_MODE=${specsMode} SPECS_DIR=${specsDir} PORT=${options.port} ${packageManager} run dev`));
7042
7531
  if (options.open) {
7043
- console.log(chalk18.dim(` open http://localhost:${options.port}`));
7532
+ console.log(chalk19.dim(` open http://localhost:${options.port}`));
7044
7533
  }
7045
7534
  return;
7046
7535
  }
7047
7536
  const spinner = ora("Starting web UI...").start();
7048
7537
  const env = {
7049
7538
  ...process.env,
7050
- SPECS_MODE: "filesystem",
7051
- SPECS_DIR: specsDir,
7539
+ SPECS_MODE: specsMode,
7540
+ SPECS_DIR: specsDir || "",
7052
7541
  PORT: options.port
7053
7542
  };
7054
7543
  const child = spawn(packageManager, ["run", "dev"], {
@@ -7059,19 +7548,67 @@ async function runLocalWeb(uiDir, specsDir, options) {
7059
7548
  });
7060
7549
  const readyTimeout = setTimeout(async () => {
7061
7550
  spinner.succeed("Web UI running");
7062
- console.log(chalk18.green(`
7551
+ console.log(chalk19.green(`
7063
7552
  \u2728 LeanSpec UI: http://localhost:${options.port}
7064
7553
  `));
7065
- console.log(chalk18.dim("Press Ctrl+C to stop\n"));
7554
+ if (options.multiProject) {
7555
+ console.log(chalk19.cyan("Multi-project mode is active"));
7556
+ if (options.addProject) {
7557
+ try {
7558
+ const res = await fetch(`http://localhost:${options.port}/api/projects`, {
7559
+ method: "POST",
7560
+ headers: { "Content-Type": "application/json" },
7561
+ body: JSON.stringify({ path: options.addProject })
7562
+ });
7563
+ if (res.ok) {
7564
+ console.log(chalk19.green(` \u2713 Added project: ${options.addProject}`));
7565
+ } else {
7566
+ console.error(chalk19.red(` \u2717 Failed to add project: ${options.addProject}`));
7567
+ }
7568
+ } catch (e) {
7569
+ console.error(chalk19.red(` \u2717 Failed to connect to server for adding project`));
7570
+ }
7571
+ }
7572
+ if (options.discover) {
7573
+ console.log(chalk19.dim(` Discovering projects in: ${options.discover}...`));
7574
+ try {
7575
+ const res = await fetch(`http://localhost:${options.port}/api/local-projects/discover`, {
7576
+ method: "POST",
7577
+ headers: { "Content-Type": "application/json" },
7578
+ body: JSON.stringify({ path: options.discover })
7579
+ });
7580
+ if (res.ok) {
7581
+ const data = await res.json();
7582
+ const discovered = data.discovered || [];
7583
+ console.log(chalk19.green(` \u2713 Found ${discovered.length} projects`));
7584
+ for (const p of discovered) {
7585
+ const addRes = await fetch(`http://localhost:${options.port}/api/projects`, {
7586
+ method: "POST",
7587
+ headers: { "Content-Type": "application/json" },
7588
+ body: JSON.stringify({ path: p.path })
7589
+ });
7590
+ if (addRes.ok) {
7591
+ console.log(chalk19.dim(` + Added: ${p.name} (${p.path})`));
7592
+ }
7593
+ }
7594
+ } else {
7595
+ console.error(chalk19.red(` \u2717 Failed to discover projects`));
7596
+ }
7597
+ } catch (e) {
7598
+ console.error(chalk19.red(` \u2717 Failed to connect to server for discovery`));
7599
+ }
7600
+ }
7601
+ }
7602
+ console.log(chalk19.dim("\nPress Ctrl+C to stop\n"));
7066
7603
  if (options.open) {
7067
7604
  try {
7068
7605
  const openModule = await import('open');
7069
7606
  const open = openModule.default;
7070
7607
  await open(`http://localhost:${options.port}`);
7071
7608
  } catch (error) {
7072
- console.log(chalk18.yellow("\u26A0 Could not open browser automatically"));
7073
- console.log(chalk18.dim("Please visit the URL above manually\n"));
7074
- console.error(chalk18.dim(`Debug: ${error instanceof Error ? error.message : String(error)}`));
7609
+ console.log(chalk19.yellow("\u26A0 Could not open browser automatically"));
7610
+ console.log(chalk19.dim("Please visit the URL above manually\n"));
7611
+ console.error(chalk19.dim(`Debug: ${error instanceof Error ? error.message : String(error)}`));
7075
7612
  }
7076
7613
  }
7077
7614
  }, 3e3);
@@ -7088,7 +7625,7 @@ async function runLocalWeb(uiDir, specsDir, options) {
7088
7625
  }
7089
7626
  } catch (err) {
7090
7627
  }
7091
- console.log(chalk18.dim("\n\u2713 Web UI stopped"));
7628
+ console.log(chalk19.dim("\n\u2713 Web UI stopped"));
7092
7629
  process.exit(0);
7093
7630
  };
7094
7631
  process.once("SIGINT", () => shutdown("SIGINT"));
@@ -7102,20 +7639,20 @@ async function runLocalWeb(uiDir, specsDir, options) {
7102
7639
  spinner.stop();
7103
7640
  if (code !== 0 && code !== null) {
7104
7641
  spinner.fail("Web UI failed to start");
7105
- console.error(chalk18.red(`
7642
+ console.error(chalk19.red(`
7106
7643
  Process exited with code ${code}`));
7107
7644
  process.exit(code);
7108
7645
  }
7109
7646
  process.exit(0);
7110
7647
  });
7111
7648
  }
7112
- async function runPublishedUI(cwd, specsDir, options) {
7113
- console.log(chalk18.dim("\u2192 Using published @leanspec/ui package\n"));
7114
- const packageManager = detectPackageManager(cwd);
7115
- const { command, args, preview } = buildUiRunner(packageManager, specsDir, options.port, options.open);
7649
+ async function runPublishedUI(cwd, specsDir, specsMode, options) {
7650
+ console.log(chalk19.dim("\u2192 Using published @leanspec/ui package\n"));
7651
+ const packageManager = detectPackageManager2(cwd);
7652
+ const { command, args, preview } = buildUiRunner(packageManager, specsDir, specsMode, options.port, options.open, options.multiProject);
7116
7653
  if (options.dryRun) {
7117
- console.log(chalk18.cyan("Would run:"));
7118
- console.log(chalk18.dim(` ${preview}`));
7654
+ console.log(chalk19.cyan("Would run:"));
7655
+ console.log(chalk19.dim(` ${preview}`));
7119
7656
  return;
7120
7657
  }
7121
7658
  const child = spawn(command, args, {
@@ -7134,7 +7671,7 @@ async function runPublishedUI(cwd, specsDir, options) {
7134
7671
  }
7135
7672
  } catch (err) {
7136
7673
  }
7137
- console.log(chalk18.dim("\n\u2713 Web UI stopped"));
7674
+ console.log(chalk19.dim("\n\u2713 Web UI stopped"));
7138
7675
  process.exit(0);
7139
7676
  };
7140
7677
  process.once("SIGINT", () => shutdownPublished("SIGINT"));
@@ -7148,19 +7685,25 @@ async function runPublishedUI(cwd, specsDir, options) {
7148
7685
  process.exit(0);
7149
7686
  return;
7150
7687
  }
7151
- console.error(chalk18.red(`
7688
+ console.error(chalk19.red(`
7152
7689
  @leanspec/ui exited with code ${code}`));
7153
- console.log(chalk18.dim("Make sure npm can download @leanspec/ui (https://www.npmjs.com/package/@leanspec/ui)."));
7690
+ console.log(chalk19.dim("Make sure npm can download @leanspec/ui (https://www.npmjs.com/package/@leanspec/ui)."));
7154
7691
  process.exit(code);
7155
7692
  });
7156
7693
  child.on("error", (error) => {
7157
- console.error(chalk18.red(`Failed to launch @leanspec/ui: ${error instanceof Error ? error.message : String(error)}`));
7158
- console.log(chalk18.dim("You can also run it manually with `npx @leanspec/ui --specs <dir>`"));
7694
+ console.error(chalk19.red(`Failed to launch @leanspec/ui: ${error instanceof Error ? error.message : String(error)}`));
7695
+ console.log(chalk19.dim("You can also run it manually with `npx @leanspec/ui --specs <dir>`"));
7159
7696
  process.exit(1);
7160
7697
  });
7161
7698
  }
7162
- function buildUiRunner(packageManager, specsDir, port, openBrowser) {
7163
- const uiArgs = ["@leanspec/ui", "--specs", specsDir, "--port", port];
7699
+ function buildUiRunner(packageManager, specsDir, specsMode, port, openBrowser, multiProject) {
7700
+ const uiArgs = ["@leanspec/ui"];
7701
+ if (!multiProject) {
7702
+ uiArgs.push("--specs", specsDir);
7703
+ } else {
7704
+ uiArgs.push("--multi-project");
7705
+ }
7706
+ uiArgs.push("--port", port);
7164
7707
  if (!openBrowser) {
7165
7708
  uiArgs.push("--no-open");
7166
7709
  }
@@ -7492,7 +8035,7 @@ function createTool() {
7492
8035
  async function getDepsData(specPath, mode = "complete") {
7493
8036
  const config = await loadConfig();
7494
8037
  const cwd = process.cwd();
7495
- const specsDir = path4.join(cwd, config.specsDir);
8038
+ const specsDir = path15.join(cwd, config.specsDir);
7496
8039
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
7497
8040
  if (!resolvedPath) {
7498
8041
  throw new Error(`Spec not found: ${specPath}`);
@@ -7940,7 +8483,7 @@ function tokensTool() {
7940
8483
  try {
7941
8484
  const config = await loadConfig();
7942
8485
  const cwd = process.cwd();
7943
- const specsDir = path4.join(cwd, config.specsDir);
8486
+ const specsDir = path15.join(cwd, config.specsDir);
7944
8487
  const resolvedPath = await resolveSpecPath(input.specPath, cwd, specsDir);
7945
8488
  if (!resolvedPath) {
7946
8489
  return {
@@ -7951,7 +8494,7 @@ function tokensTool() {
7951
8494
  isError: true
7952
8495
  };
7953
8496
  }
7954
- const specName = path4.basename(resolvedPath);
8497
+ const specName = path15.basename(resolvedPath);
7955
8498
  const result = await counter.countSpec(resolvedPath, {
7956
8499
  detailed: input.detailed,
7957
8500
  includeSubSpecs: input.includeSubSpecs
@@ -8342,53 +8885,83 @@ function registerResources(server) {
8342
8885
  server.registerResource(...specResource());
8343
8886
  server.registerResource(...statsResource());
8344
8887
  }
8345
- function createFeatureSpecPrompt() {
8888
+ function planProjectRoadmapPrompt() {
8346
8889
  return [
8347
- "create-feature-spec",
8890
+ "plan-project-roadmap",
8348
8891
  {
8349
- title: "Create Feature Spec",
8350
- description: "Guided workflow to create a new feature specification",
8892
+ title: "Plan Project Roadmap",
8893
+ description: "Interactive roadmap planning with phases, tasks, and dependencies",
8351
8894
  argsSchema: {
8352
- featureName: z.string(),
8353
- description: z.string().optional()
8895
+ goal: z.string()
8354
8896
  }
8355
8897
  },
8356
- ({ featureName, description }) => ({
8898
+ ({ goal }) => ({
8357
8899
  messages: [
8358
8900
  {
8359
8901
  role: "user",
8360
8902
  content: {
8361
8903
  type: "text",
8362
- text: `Create a new feature specification for: ${featureName}${description ? `
8904
+ text: `Plan a project roadmap for: ${goal}
8363
8905
 
8364
- Description: ${description}` : ""}
8906
+ 1. **Review Existing Work**: Analyze current specs using \`list\`/\`board\`, identify what's already planned/in-progress, assess how existing work relates to the new goal
8907
+ 2. **Break Down Goal**: Decompose the goal into logical phases or milestones
8908
+ 3. **Identify Tasks**: List key tasks and work items for each phase
8909
+ 4. **Map Dependencies**: Establish dependencies between tasks (what must be done first)
8910
+ 5. **Create Specs**: Create specification documents for major work items using the \`create\` tool
8911
+ 6. **Set Relationships**: Use \`link\` tool to establish \`depends_on\` and \`related\` relationships
8912
+ 7. **Timeline Estimation**: Provide realistic timeline based on task complexity and project velocity
8913
+ 8. **Risk Analysis**: Identify risks, unknowns, and mitigation strategies
8365
8914
 
8366
- Please create this spec with appropriate metadata (status, priority, tags) and include standard sections like Overview, Design, Plan, and Test.`
8915
+ Use the following tools to build the roadmap:
8916
+ - \`list\` / \`board\` / \`stats\` - Understand current project state
8917
+ - \`create\` - Create new specs for roadmap items
8918
+ - \`link\` - Establish dependencies between specs
8919
+ - \`update\` - Set priority and metadata
8920
+
8921
+ Provide a clear roadmap with:
8922
+ - Phases/milestones with descriptions
8923
+ - Key specs to create
8924
+ - Dependency relationships
8925
+ - Recommended execution order
8926
+ - Actionable next steps to implement this plan`
8367
8927
  }
8368
8928
  }
8369
8929
  ]
8370
8930
  })
8371
8931
  ];
8372
8932
  }
8373
- function findRelatedSpecsPrompt() {
8933
+
8934
+ // src/mcp/prompts/project-progress-overview.ts
8935
+ function projectProgressOverviewPrompt() {
8374
8936
  return [
8375
- "find-related-specs",
8937
+ "project-progress-overview",
8376
8938
  {
8377
- title: "Find Related Specs",
8378
- description: "Discover specifications related to a topic or feature",
8379
- argsSchema: {
8380
- topic: z.string()
8381
- }
8939
+ title: "Project Progress Overview",
8940
+ description: "Generate comprehensive project status report combining specs, git history, and metrics"
8382
8941
  },
8383
- ({ topic }) => ({
8942
+ () => ({
8384
8943
  messages: [
8385
8944
  {
8386
8945
  role: "user",
8387
8946
  content: {
8388
8947
  type: "text",
8389
- text: `Find all specifications related to: ${topic}
8948
+ text: `Analyze project progress and provide a comprehensive overview:
8390
8949
 
8391
- Please search for this topic and show me the dependencies between related specs.`
8950
+ 1. **Spec Analysis**: Review all specs using \`board\` and \`stats\`, group by status (planned/in-progress/complete), highlight any blockers or dependencies
8951
+ 2. **Recent Activity**: Examine git commit history (last 2 weeks), identify key changes and development patterns
8952
+ 3. **Current State**: Assess what's actively being worked on, what's completed, what's planned
8953
+ 4. **Velocity Metrics**: Calculate completion rates, average time in each status, and throughput trends
8954
+ 5. **Risk Assessment**: Identify stalled specs, missing dependencies, potential bottlenecks
8955
+ 6. **Next Steps**: Recommend priority actions based on current project state
8956
+
8957
+ Use the following tools to gather data:
8958
+ - \`board\` - Get Kanban view of specs by status
8959
+ - \`stats\` - Get project metrics
8960
+ - \`list\` - List specs with filters
8961
+ - \`deps\` - Analyze dependencies for critical specs
8962
+ - Terminal git commands - Analyze recent commit history
8963
+
8964
+ Provide a clear, actionable summary that helps understand project health and next steps.`
8392
8965
  }
8393
8966
  }
8394
8967
  ]
@@ -8403,16 +8976,18 @@ function updateSpecStatusPrompt() {
8403
8976
  description: "Quick workflow to update specification status",
8404
8977
  argsSchema: {
8405
8978
  specPath: z.string(),
8406
- newStatus: z.enum(["planned", "in-progress", "complete", "archived"])
8979
+ status: z.enum(["planned", "in-progress", "complete", "archived"])
8407
8980
  }
8408
8981
  },
8409
- ({ specPath, newStatus }) => ({
8982
+ ({ specPath, status }) => ({
8410
8983
  messages: [
8411
8984
  {
8412
8985
  role: "user",
8413
8986
  content: {
8414
8987
  type: "text",
8415
- text: `Update the status of spec "${specPath}" to "${newStatus}". Use the update tool to make this change.`
8988
+ text: `Update the status of spec "${specPath}" to "${status}".
8989
+
8990
+ Use the \`update\` tool: \`update <spec> --status ${status}\``
8416
8991
  }
8417
8992
  }
8418
8993
  ]
@@ -8422,8 +8997,8 @@ function updateSpecStatusPrompt() {
8422
8997
 
8423
8998
  // src/mcp/prompts/registry.ts
8424
8999
  function registerPrompts(server) {
8425
- server.registerPrompt(...createFeatureSpecPrompt());
8426
- server.registerPrompt(...findRelatedSpecsPrompt());
9000
+ server.registerPrompt(...projectProgressOverviewPrompt());
9001
+ server.registerPrompt(...planProjectRoadmapPrompt());
8427
9002
  server.registerPrompt(...updateSpecStatusPrompt());
8428
9003
  }
8429
9004
 
@@ -8458,6 +9033,6 @@ if (import.meta.url === `file://${process.argv[1]}`) {
8458
9033
  main().catch(console.error);
8459
9034
  }
8460
9035
 
8461
- export { analyzeCommand, archiveCommand, backfillCommand, boardCommand, checkCommand, compactCommand, createCommand, createMcpServer, depsCommand, filesCommand, ganttCommand, initCommand, linkCommand, listCommand, mcpCommand, migrateCommand, openCommand, searchCommand, splitCommand, statsCommand, templatesCommand, timelineCommand, tokensCommand, uiCommand, unlinkCommand, updateCommand, validateCommand, viewCommand };
8462
- //# sourceMappingURL=chunk-7WXYOHZU.js.map
8463
- //# sourceMappingURL=chunk-7WXYOHZU.js.map
9036
+ export { analyzeCommand, archiveCommand, backfillCommand, boardCommand, checkCommand, compactCommand, createCommand, createMcpServer, depsCommand, examplesCommand, filesCommand, ganttCommand, initCommand, linkCommand, listCommand, mcpCommand, migrateCommand, openCommand, searchCommand, splitCommand, statsCommand, templatesCommand, timelineCommand, tokensCommand, uiCommand, unlinkCommand, updateCommand, validateCommand, viewCommand };
9037
+ //# sourceMappingURL=chunk-RTEGSMVL.js.map
9038
+ //# sourceMappingURL=chunk-RTEGSMVL.js.map