lean-spec 0.2.5-dev.20251124070920 → 0.2.5-dev.20251125010225

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 (55) hide show
  1. package/dist/{chunk-LV7XEQ4D.js → chunk-RTEGSMVL.js} +272 -46
  2. package/dist/chunk-RTEGSMVL.js.map +1 -0
  3. package/dist/cli.js +1 -1
  4. package/dist/mcp-server.js +1 -1
  5. package/package.json +1 -2
  6. package/templates/detailed/AGENTS.md +113 -0
  7. package/templates/detailed/README.md +28 -0
  8. package/templates/detailed/files/DESIGN.md +43 -0
  9. package/templates/detailed/files/PLAN.md +59 -0
  10. package/templates/detailed/files/README.md +30 -0
  11. package/templates/detailed/files/TEST.md +71 -0
  12. package/templates/standard/AGENTS.md +113 -0
  13. package/templates/standard/README.md +4 -2
  14. package/dist/chunk-LV7XEQ4D.js.map +0 -1
  15. package/templates/_shared/agents-components/core-rules-base-additions.md +0 -4
  16. package/templates/_shared/agents-components/core-rules-enterprise-additions.md +0 -4
  17. package/templates/_shared/agents-components/core-rules-shared.md +0 -1
  18. package/templates/_shared/agents-components/discovery-commands-enterprise-additions.md +0 -6
  19. package/templates/_shared/agents-components/discovery-commands-minimal-additions.md +0 -0
  20. package/templates/_shared/agents-components/discovery-commands-shared.md +0 -8
  21. package/templates/_shared/agents-components/discovery-commands-standard-additions.md +0 -3
  22. package/templates/_shared/agents-components/enterprise-approval.md +0 -12
  23. package/templates/_shared/agents-components/enterprise-compliance.md +0 -12
  24. package/templates/_shared/agents-components/enterprise-when-required.md +0 -13
  25. package/templates/_shared/agents-components/essential-commands-enterprise-additions.md +0 -29
  26. package/templates/_shared/agents-components/essential-commands-minimal-additions.md +0 -1
  27. package/templates/_shared/agents-components/essential-commands-shared.md +0 -15
  28. package/templates/_shared/agents-components/essential-commands-standard-additions.md +0 -18
  29. package/templates/_shared/agents-components/frontmatter-enterprise.md +0 -33
  30. package/templates/_shared/agents-components/frontmatter-minimal.md +0 -18
  31. package/templates/_shared/agents-components/frontmatter-standard.md +0 -23
  32. package/templates/_shared/agents-components/quality-standards-enterprise-additions.md +0 -4
  33. package/templates/_shared/agents-components/quality-standards-minimal-additions.md +0 -3
  34. package/templates/_shared/agents-components/quality-standards-shared.md +0 -6
  35. package/templates/_shared/agents-components/status-update-triggers.md +0 -14
  36. package/templates/_shared/agents-components/when-to-use-enterprise.md +0 -11
  37. package/templates/_shared/agents-components/when-to-use-minimal.md +0 -9
  38. package/templates/_shared/agents-components/when-to-use-standard.md +0 -9
  39. package/templates/_shared/agents-components/workflow-enterprise.md +0 -13
  40. package/templates/_shared/agents-components/workflow-standard-detailed.md +0 -12
  41. package/templates/_shared/agents-components/workflow-standard.md +0 -10
  42. package/templates/_shared/agents-template.hbs +0 -43
  43. package/templates/enterprise/README.md +0 -25
  44. package/templates/enterprise/agents-config.json +0 -16
  45. package/templates/enterprise/files/AGENTS.md +0 -198
  46. package/templates/enterprise/spec-template.md +0 -80
  47. package/templates/minimal/README.md +0 -18
  48. package/templates/minimal/agents-config.json +0 -13
  49. package/templates/minimal/config.json +0 -15
  50. package/templates/minimal/files/AGENTS.md +0 -118
  51. package/templates/minimal/spec-template.md +0 -25
  52. package/templates/standard/agents-config.json +0 -13
  53. package/templates/standard/files/AGENTS.md +0 -144
  54. /package/templates/{enterprise → detailed}/config.json +0 -0
  55. /package/templates/standard/{spec-template.md → files/README.md} +0 -0
@@ -550,7 +550,7 @@ async function withSpinner(text, fn, options) {
550
550
 
551
551
  // src/commands/check.ts
552
552
  function checkCommand() {
553
- 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) => {
554
554
  const hasNoConflicts = await checkSpecs(options);
555
555
  process.exit(hasNoConflicts ? 0 : 1);
556
556
  });
@@ -578,10 +578,25 @@ async function checkSpecs(options = {}) {
578
578
  const conflicts = Array.from(sequenceMap.entries()).filter(([_, paths]) => paths.length > 1).sort(([a], [b]) => a - b);
579
579
  if (conflicts.length === 0) {
580
580
  if (!options.quiet && !options.silent) {
581
- console.log(chalk19.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
+ }
582
586
  }
583
587
  return true;
584
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
+ }
585
600
  if (!options.silent) {
586
601
  if (!options.quiet) {
587
602
  console.log("");
@@ -698,13 +713,33 @@ async function createSpec(name, options = {}) {
698
713
  } else {
699
714
  templateName = config.template || "spec-template.md";
700
715
  }
701
- const templatePath = path15.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
+ }
702
736
  let content;
737
+ let varContext;
703
738
  try {
704
739
  const template = await fs9.readFile(templatePath, "utf-8");
705
740
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
706
741
  const title = options.title || name;
707
- const varContext = await buildVariableContext(config, { name: title, date });
742
+ varContext = await buildVariableContext(config, { name: title, date });
708
743
  content = resolveVariables(template, varContext);
709
744
  const parsed = matter2(content, {
710
745
  engines: {
@@ -746,8 +781,29 @@ ${options.description}`
746
781
  throw new Error(`Template not found: ${templatePath}. Run: lean-spec init`);
747
782
  }
748
783
  await fs9.writeFile(specFile, content, "utf-8");
749
- console.log(chalk19.green(`\u2713 Created: ${sanitizeUserInput(specDir)}/`));
750
- console.log(chalk19.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
+ }
751
807
  await autoCheckIfEnabled();
752
808
  }
753
809
  function archiveCommand() {
@@ -867,7 +923,7 @@ function getPriorityEmoji(priority) {
867
923
 
868
924
  // src/commands/list.ts
869
925
  function listCommand() {
870
- 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) => {
871
927
  const customFields = parseCustomFieldOptions(options.field);
872
928
  const listOptions = {
873
929
  showArchived: options.archived,
@@ -877,7 +933,8 @@ function listCommand() {
877
933
  assignee: options.assignee,
878
934
  customFields: Object.keys(customFields).length > 0 ? customFields : void 0,
879
935
  sortBy: options.sort || "id",
880
- sortOrder: options.order || "desc"
936
+ sortOrder: options.order || "desc",
937
+ json: options.json
881
938
  };
882
939
  await listSpecs(listOptions);
883
940
  });
@@ -913,7 +970,30 @@ async function listSpecs(options = {}) {
913
970
  })
914
971
  );
915
972
  if (specs.length === 0) {
916
- console.log(chalk19.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
+ }
978
+ return;
979
+ }
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));
917
997
  return;
918
998
  }
919
999
  console.log(chalk19.bold.cyan("\u{1F4C4} Spec List"));
@@ -1458,13 +1538,14 @@ function fileExistsInGit(filePath) {
1458
1538
 
1459
1539
  // src/commands/backfill.ts
1460
1540
  function backfillCommand() {
1461
- 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) => {
1462
1542
  await backfillTimestamps({
1463
1543
  dryRun: options.dryRun,
1464
1544
  force: options.force,
1465
1545
  includeAssignee: options.assignee || options.all,
1466
1546
  includeTransitions: options.transitions || options.all,
1467
- specs: specs && specs.length > 0 ? specs : void 0
1547
+ specs: specs && specs.length > 0 ? specs : void 0,
1548
+ json: options.json
1468
1549
  });
1469
1550
  });
1470
1551
  }
@@ -1830,7 +1911,7 @@ async function detectExistingSystemPrompts(cwd) {
1830
1911
  async function handleExistingFiles(action, existingFiles, templateDir, cwd, variables = {}) {
1831
1912
  for (const file of existingFiles) {
1832
1913
  const filePath = path15.join(cwd, file);
1833
- const templateFilePath = path15.join(templateDir, "files", file);
1914
+ const templateFilePath = path15.join(templateDir, file);
1834
1915
  try {
1835
1916
  await fs9.access(templateFilePath);
1836
1917
  } catch {
@@ -2003,7 +2084,7 @@ var __dirname = path15.dirname(fileURLToPath(import.meta.url));
2003
2084
  var TEMPLATES_DIR = path15.join(__dirname, "..", "templates");
2004
2085
  var EXAMPLES_DIR = path15.join(TEMPLATES_DIR, "examples");
2005
2086
  function initCommand() {
2006
- return new Command("init").description("Initialize LeanSpec in current directory").option("-y, --yes", "Skip prompts and use defaults (quick start with standard template)").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) => {
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) => {
2007
2088
  if (options.list) {
2008
2089
  await listExamples();
2009
2090
  return;
@@ -2012,10 +2093,10 @@ function initCommand() {
2012
2093
  await scaffoldExample(options.example, options.name);
2013
2094
  return;
2014
2095
  }
2015
- await initProject(options.yes);
2096
+ await initProject(options.yes, options.template);
2016
2097
  });
2017
2098
  }
2018
- async function initProject(skipPrompts = false) {
2099
+ async function initProject(skipPrompts = false, templateOption) {
2019
2100
  const cwd = process.cwd();
2020
2101
  try {
2021
2102
  await fs9.access(path15.join(cwd, ".lean-spec", "config.json"));
@@ -2028,11 +2109,11 @@ async function initProject(skipPrompts = false) {
2028
2109
  console.log(chalk19.green("Welcome to LeanSpec!"));
2029
2110
  console.log("");
2030
2111
  let setupMode = "quick";
2031
- let templateName = "standard";
2112
+ let templateName = templateOption || "standard";
2032
2113
  if (skipPrompts) {
2033
2114
  console.log(chalk19.gray("Using defaults: quick start with standard template"));
2034
2115
  console.log("");
2035
- } else {
2116
+ } else if (!templateOption) {
2036
2117
  setupMode = await select({
2037
2118
  message: "How would you like to set up?",
2038
2119
  choices: [
@@ -2044,7 +2125,7 @@ async function initProject(skipPrompts = false) {
2044
2125
  {
2045
2126
  name: "Choose template",
2046
2127
  value: "template",
2047
- description: "Pick from: minimal, standard, enterprise"
2128
+ description: "Pick from: standard, detailed"
2048
2129
  }
2049
2130
  // TODO: Re-enable when custom setup mode is implemented
2050
2131
  // {
@@ -2058,17 +2139,27 @@ async function initProject(skipPrompts = false) {
2058
2139
  templateName = await select({
2059
2140
  message: "Select template:",
2060
2141
  choices: [
2061
- { name: "minimal", value: "minimal", description: "Just folder structure, no extras" },
2062
- { name: "standard", value: "standard", description: "Recommended - includes AGENTS.md" },
2142
+ { name: "standard", value: "standard", description: "Recommended - single-file specs with AGENTS.md" },
2063
2143
  {
2064
- name: "enterprise",
2065
- value: "enterprise",
2066
- description: "Governance with approvals and compliance"
2144
+ name: "detailed",
2145
+ value: "detailed",
2146
+ description: "Complex specs with sub-spec structure (DESIGN, PLAN, TEST)"
2067
2147
  }
2068
2148
  ]
2069
2149
  });
2070
2150
  }
2071
2151
  }
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
+ }
2072
2163
  const templateDir = path15.join(TEMPLATES_DIR, templateName);
2073
2164
  const templateConfigPath = path15.join(templateDir, "config.json");
2074
2165
  let templateConfig;
@@ -2133,19 +2224,35 @@ async function initProject(skipPrompts = false) {
2133
2224
  console.error(chalk19.red("Error creating templates directory:"), error);
2134
2225
  process.exit(1);
2135
2226
  }
2136
- const templateSpecPath = path15.join(templateDir, "spec-template.md");
2137
- const targetSpecPath = path15.join(templatesDir, "spec-template.md");
2227
+ const templateFilesDir = path15.join(templateDir, "files");
2138
2228
  try {
2139
- await fs9.copyFile(templateSpecPath, targetSpecPath);
2140
- console.log(chalk19.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
+ }
2141
2252
  } catch (error) {
2142
- console.error(chalk19.red("Error copying template:"), error);
2253
+ console.error(chalk19.red("Error copying template files:"), error);
2143
2254
  process.exit(1);
2144
2255
  }
2145
- templateConfig.template = "spec-template.md";
2146
- templateConfig.templates = {
2147
- default: "spec-template.md"
2148
- };
2149
2256
  await saveConfig(templateConfig, cwd);
2150
2257
  console.log(chalk19.green("\u2713 Created .lean-spec/config.json"));
2151
2258
  const existingFiles = await detectExistingSystemPrompts(cwd);
@@ -2191,9 +2298,26 @@ async function initProject(skipPrompts = false) {
2191
2298
  }
2192
2299
  }
2193
2300
  const projectName = await getProjectName2(cwd);
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
+ }
2194
2314
  const filesDir = path15.join(templateDir, "files");
2195
2315
  try {
2196
- await copyDirectory(filesDir, cwd, skipFiles, { project_name: projectName });
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
+ }
2197
2321
  console.log(chalk19.green("\u2713 Initialized project structure"));
2198
2322
  } catch (error) {
2199
2323
  console.error(chalk19.red("Error copying template files:"), error);
@@ -3648,7 +3772,7 @@ function filesCommand(specPath, options = {}) {
3648
3772
  if (typeof specPath === "string") {
3649
3773
  return showFiles(specPath, options);
3650
3774
  }
3651
- 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) => {
3652
3776
  await showFiles(target, opts);
3653
3777
  });
3654
3778
  }
@@ -3666,6 +3790,31 @@ async function showFiles(specPath, options = {}) {
3666
3790
  throw new Error(`Could not load spec: ${sanitizeUserInput(specPath)}`);
3667
3791
  }
3668
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
+ }
3669
3818
  console.log("");
3670
3819
  console.log(chalk19.cyan(`\u{1F4C4} Files in ${sanitizeUserInput(spec.name)}`));
3671
3820
  console.log("");
@@ -4530,13 +4679,13 @@ Validating ${specs.length} specs...
4530
4679
 
4531
4680
  // src/commands/validate.ts
4532
4681
  function validateCommand() {
4533
- 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) => {
4534
4683
  const passed = await validateSpecs({
4535
4684
  maxLines: options.maxLines,
4536
4685
  specs: specs && specs.length > 0 ? specs : void 0,
4537
4686
  verbose: options.verbose,
4538
4687
  quiet: options.quiet,
4539
- format: options.format,
4688
+ format: options.json ? "json" : options.format,
4540
4689
  rule: options.rule,
4541
4690
  warningsOnly: options.warningsOnly
4542
4691
  });
@@ -5011,7 +5160,7 @@ function calculateVelocityMetrics(specs) {
5011
5160
 
5012
5161
  // src/commands/board.ts
5013
5162
  function boardCommand() {
5014
- 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) => {
5015
5164
  await showBoard(options);
5016
5165
  });
5017
5166
  }
@@ -5032,7 +5181,11 @@ async function showBoard(options) {
5032
5181
  })
5033
5182
  );
5034
5183
  if (specs.length === 0) {
5035
- console.log(chalk19.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
+ }
5036
5189
  return;
5037
5190
  }
5038
5191
  const columns = {
@@ -5047,6 +5200,29 @@ async function showBoard(options) {
5047
5200
  columns[status].push(spec);
5048
5201
  }
5049
5202
  }
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
+ }
5050
5226
  console.log(chalk19.bold.cyan("\u{1F4CB} Spec Kanban Board"));
5051
5227
  if (options.tag || options.assignee) {
5052
5228
  const filterParts = [];
@@ -5672,14 +5848,15 @@ async function showStats(options) {
5672
5848
  }
5673
5849
  }
5674
5850
  function searchCommand() {
5675
- 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) => {
5676
5852
  const customFields = parseCustomFieldOptions(options.field);
5677
5853
  await performSearch(query, {
5678
5854
  status: options.status,
5679
5855
  tag: options.tag,
5680
5856
  priority: options.priority,
5681
5857
  assignee: options.assignee,
5682
- customFields: Object.keys(customFields).length > 0 ? customFields : void 0
5858
+ customFields: Object.keys(customFields).length > 0 ? customFields : void 0,
5859
+ json: options.json
5683
5860
  });
5684
5861
  });
5685
5862
  }
@@ -5718,6 +5895,25 @@ async function performSearch(query, options) {
5718
5895
  contextLength: 80
5719
5896
  });
5720
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
+ }
5721
5917
  if (results.length === 0) {
5722
5918
  console.log("");
5723
5919
  console.log(chalk19.yellow(`\u{1F50D} No specs found matching "${sanitizeUserInput(query)}"`));
@@ -5958,7 +6154,7 @@ function displayChain(node, level) {
5958
6154
  }
5959
6155
  }
5960
6156
  function timelineCommand() {
5961
- 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) => {
5962
6158
  await showTimeline(options);
5963
6159
  });
5964
6160
  }
@@ -5997,6 +6193,16 @@ async function showTimeline(options) {
5997
6193
  }
5998
6194
  }
5999
6195
  }
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
+ }
6000
6206
  console.log(chalk19.bold.cyan("\u{1F4C8} Spec Timeline"));
6001
6207
  console.log("");
6002
6208
  const allDates = /* @__PURE__ */ new Set([...Object.keys(createdByDate), ...Object.keys(completedByDate)]);
@@ -6135,7 +6341,7 @@ var PRIORITY_CONFIG3 = {
6135
6341
  low: { emoji: "\u{1F7E2}", label: "LOW", colorFn: chalk19.green }
6136
6342
  };
6137
6343
  function ganttCommand() {
6138
- 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) => {
6139
6345
  await showGantt(options);
6140
6346
  });
6141
6347
  }
@@ -6160,8 +6366,28 @@ async function showGantt(options) {
6160
6366
  return spec.frontmatter.status !== "archived";
6161
6367
  });
6162
6368
  if (relevantSpecs.length === 0) {
6163
- console.log(chalk19.dim("No active specs found."));
6164
- console.log(chalk19.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));
6165
6391
  return;
6166
6392
  }
6167
6393
  const groupedSpecs = {
@@ -8808,5 +9034,5 @@ if (import.meta.url === `file://${process.argv[1]}`) {
8808
9034
  }
8809
9035
 
8810
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 };
8811
- //# sourceMappingURL=chunk-LV7XEQ4D.js.map
8812
- //# sourceMappingURL=chunk-LV7XEQ4D.js.map
9037
+ //# sourceMappingURL=chunk-RTEGSMVL.js.map
9038
+ //# sourceMappingURL=chunk-RTEGSMVL.js.map