lean-spec 0.2.0 → 0.2.1

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.
@@ -1,23 +1,24 @@
1
- import {
2
- getSpecFile,
3
- matchesFilter,
4
- normalizeDateFields,
5
- parseFrontmatter,
6
- updateFrontmatter
7
- } from "./chunk-S4YNQ5KE.js";
1
+ import { normalizeDateFields, getSpecFile, updateFrontmatter, parseFrontmatter, matchesFilter } from './chunk-LVD7ZAVZ.js';
2
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { z } from 'zod';
5
+ import * as fs9 from 'fs/promises';
6
+ import * as path2 from 'path';
7
+ import { dirname, join } from 'path';
8
+ import chalk16 from 'chalk';
9
+ import matter from 'gray-matter';
10
+ import yaml from 'js-yaml';
11
+ import { spawn, execSync } from 'child_process';
12
+ import ora from 'ora';
13
+ import stripAnsi from 'strip-ansi';
14
+ import { fileURLToPath } from 'url';
15
+ import { select } from '@inquirer/prompts';
16
+ import { ComplexityValidator, TokenCounter, countTokens } from '@leanspec/core';
17
+ import dayjs2 from 'dayjs';
18
+ import { marked } from 'marked';
19
+ import { markedTerminal } from 'marked-terminal';
20
+ import { readFileSync } from 'fs';
8
21
 
9
- // src/mcp-server.ts
10
- import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
11
- import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
12
- import { z } from "zod";
13
-
14
- // src/spec-loader.ts
15
- import * as fs2 from "fs/promises";
16
- import * as path2 from "path";
17
-
18
- // src/config.ts
19
- import * as fs from "fs/promises";
20
- import * as path from "path";
21
22
  var DEFAULT_CONFIG = {
22
23
  template: "spec-template.md",
23
24
  templates: {
@@ -39,9 +40,9 @@ var DEFAULT_CONFIG = {
39
40
  }
40
41
  };
41
42
  async function loadConfig(cwd = process.cwd()) {
42
- const configPath = path.join(cwd, ".lean-spec", "config.json");
43
+ const configPath = path2.join(cwd, ".lean-spec", "config.json");
43
44
  try {
44
- const content = await fs.readFile(configPath, "utf-8");
45
+ const content = await fs9.readFile(configPath, "utf-8");
45
46
  const userConfig = JSON.parse(content);
46
47
  const merged = { ...DEFAULT_CONFIG, ...userConfig };
47
48
  normalizeLegacyPattern(merged);
@@ -51,10 +52,10 @@ async function loadConfig(cwd = process.cwd()) {
51
52
  }
52
53
  }
53
54
  async function saveConfig(config, cwd = process.cwd()) {
54
- const configDir = path.join(cwd, ".lean-spec");
55
- const configPath = path.join(configDir, "config.json");
56
- await fs.mkdir(configDir, { recursive: true });
57
- await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
55
+ const configDir = path2.join(cwd, ".lean-spec");
56
+ const configPath = path2.join(configDir, "config.json");
57
+ await fs9.mkdir(configDir, { recursive: true });
58
+ await fs9.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
58
59
  }
59
60
  function getToday(format = "YYYYMMDD") {
60
61
  const now = /* @__PURE__ */ new Date();
@@ -136,12 +137,12 @@ function extractGroup(extractor, dateFormat = "YYYYMMDD", fields, fallback) {
136
137
  async function loadSubFiles(specDir, options = {}) {
137
138
  const subFiles = [];
138
139
  try {
139
- const entries = await fs2.readdir(specDir, { withFileTypes: true });
140
+ const entries = await fs9.readdir(specDir, { withFileTypes: true });
140
141
  for (const entry of entries) {
141
142
  if (entry.name === "README.md") continue;
142
143
  if (entry.isDirectory()) continue;
143
144
  const filePath = path2.join(specDir, entry.name);
144
- const stat5 = await fs2.stat(filePath);
145
+ const stat5 = await fs9.stat(filePath);
145
146
  const ext = path2.extname(entry.name).toLowerCase();
146
147
  const isDocument = ext === ".md";
147
148
  const subFile = {
@@ -151,7 +152,7 @@ async function loadSubFiles(specDir, options = {}) {
151
152
  type: isDocument ? "document" : "asset"
152
153
  };
153
154
  if (isDocument && options.includeContent) {
154
- subFile.content = await fs2.readFile(filePath, "utf-8");
155
+ subFile.content = await fs9.readFile(filePath, "utf-8");
155
156
  }
156
157
  subFiles.push(subFile);
157
158
  }
@@ -171,14 +172,14 @@ async function loadAllSpecs(options = {}) {
171
172
  const specsDir = path2.join(cwd, config.specsDir);
172
173
  const specs = [];
173
174
  try {
174
- await fs2.access(specsDir);
175
+ await fs9.access(specsDir);
175
176
  } catch {
176
177
  return [];
177
178
  }
178
179
  const specPattern = /^(\d{2,})-/;
179
180
  async function loadSpecsFromDir(dir, relativePath = "") {
180
181
  try {
181
- const entries = await fs2.readdir(dir, { withFileTypes: true });
182
+ const entries = await fs9.readdir(dir, { withFileTypes: true });
182
183
  for (const entry of entries) {
183
184
  if (!entry.isDirectory()) continue;
184
185
  if (entry.name === "archived" && relativePath === "") continue;
@@ -212,7 +213,7 @@ async function loadAllSpecs(options = {}) {
212
213
  frontmatter
213
214
  };
214
215
  if (options.includeContent) {
215
- specInfo.content = await fs2.readFile(specFile, "utf-8");
216
+ specInfo.content = await fs9.readFile(specFile, "utf-8");
216
217
  }
217
218
  if (options.includeSubFiles) {
218
219
  specInfo.subFiles = await loadSubFiles(entryPath, {
@@ -287,7 +288,7 @@ async function getSpec(specPath) {
287
288
  fullPath = path2.join(specsDir, specPath);
288
289
  }
289
290
  try {
290
- await fs2.access(fullPath);
291
+ await fs9.access(fullPath);
291
292
  } catch {
292
293
  return null;
293
294
  }
@@ -295,7 +296,7 @@ async function getSpec(specPath) {
295
296
  if (!specFile) return null;
296
297
  const frontmatter = await parseFrontmatter(specFile, config);
297
298
  if (!frontmatter) return null;
298
- const content = await fs2.readFile(specFile, "utf-8");
299
+ const content = await fs9.readFile(specFile, "utf-8");
299
300
  const relativePath = path2.relative(specsDir, fullPath);
300
301
  const parts = relativePath.split(path2.sep);
301
302
  const date = parts[0] === "archived" ? parts[1] : parts[0];
@@ -310,17 +311,6 @@ async function getSpec(specPath) {
310
311
  content
311
312
  };
312
313
  }
313
-
314
- // src/commands/create.ts
315
- import * as fs5 from "fs/promises";
316
- import * as path6 from "path";
317
- import chalk4 from "chalk";
318
- import matter from "gray-matter";
319
- import yaml from "js-yaml";
320
-
321
- // src/utils/path-helpers.ts
322
- import * as fs3 from "fs/promises";
323
- import * as path3 from "path";
324
314
  function createSpecDirPattern() {
325
315
  return /(?:^|\D)(\d{2,4})-[a-z]/i;
326
316
  }
@@ -330,7 +320,7 @@ async function getGlobalNextSeq(specsDir, digits) {
330
320
  const specPattern = createSpecDirPattern();
331
321
  async function scanDirectory(dir) {
332
322
  try {
333
- const entries = await fs3.readdir(dir, { withFileTypes: true });
323
+ const entries = await fs9.readdir(dir, { withFileTypes: true });
334
324
  for (const entry of entries) {
335
325
  if (!entry.isDirectory()) continue;
336
326
  const match = entry.name.match(specPattern);
@@ -341,7 +331,7 @@ async function getGlobalNextSeq(specsDir, digits) {
341
331
  }
342
332
  }
343
333
  if (entry.name === "archived") continue;
344
- const subDir = path3.join(dir, entry.name);
334
+ const subDir = path2.join(dir, entry.name);
345
335
  await scanDirectory(subDir);
346
336
  }
347
337
  } catch {
@@ -358,23 +348,23 @@ async function getGlobalNextSeq(specsDir, digits) {
358
348
  }
359
349
  }
360
350
  async function resolveSpecPath(specPath, cwd, specsDir) {
361
- if (path3.isAbsolute(specPath)) {
351
+ if (path2.isAbsolute(specPath)) {
362
352
  try {
363
- await fs3.access(specPath);
353
+ await fs9.access(specPath);
364
354
  return specPath;
365
355
  } catch {
366
356
  return null;
367
357
  }
368
358
  }
369
- const cwdPath = path3.resolve(cwd, specPath);
359
+ const cwdPath = path2.resolve(cwd, specPath);
370
360
  try {
371
- await fs3.access(cwdPath);
361
+ await fs9.access(cwdPath);
372
362
  return cwdPath;
373
363
  } catch {
374
364
  }
375
- const specsPath = path3.join(specsDir, specPath);
365
+ const specsPath = path2.join(specsDir, specPath);
376
366
  try {
377
- await fs3.access(specsPath);
367
+ await fs9.access(specsPath);
378
368
  return specsPath;
379
369
  } catch {
380
370
  }
@@ -392,17 +382,17 @@ async function searchBySequence(specsDir, seqNum) {
392
382
  const specPattern = createSpecDirPattern();
393
383
  async function scanDirectory(dir) {
394
384
  try {
395
- const entries = await fs3.readdir(dir, { withFileTypes: true });
385
+ const entries = await fs9.readdir(dir, { withFileTypes: true });
396
386
  for (const entry of entries) {
397
387
  if (!entry.isDirectory()) continue;
398
388
  const match = entry.name.match(specPattern);
399
389
  if (match) {
400
390
  const entrySeq = parseInt(match[1], 10);
401
391
  if (entrySeq === seqNum) {
402
- return path3.join(dir, entry.name);
392
+ return path2.join(dir, entry.name);
403
393
  }
404
394
  }
405
- const subDir = path3.join(dir, entry.name);
395
+ const subDir = path2.join(dir, entry.name);
406
396
  const result = await scanDirectory(subDir);
407
397
  if (result) return result;
408
398
  }
@@ -415,13 +405,13 @@ async function searchBySequence(specsDir, seqNum) {
415
405
  async function searchInAllDirectories(specsDir, specName) {
416
406
  async function scanDirectory(dir) {
417
407
  try {
418
- const entries = await fs3.readdir(dir, { withFileTypes: true });
408
+ const entries = await fs9.readdir(dir, { withFileTypes: true });
419
409
  for (const entry of entries) {
420
410
  if (!entry.isDirectory()) continue;
421
411
  if (entry.name === specName) {
422
- return path3.join(dir, entry.name);
412
+ return path2.join(dir, entry.name);
423
413
  }
424
- const subDir = path3.join(dir, entry.name);
414
+ const subDir = path2.join(dir, entry.name);
425
415
  const result = await scanDirectory(subDir);
426
416
  if (result) return result;
427
417
  }
@@ -431,11 +421,6 @@ async function searchInAllDirectories(specsDir, specName) {
431
421
  }
432
422
  return scanDirectory(specsDir);
433
423
  }
434
-
435
- // src/utils/variable-resolver.ts
436
- import * as fs4 from "fs/promises";
437
- import * as path4 from "path";
438
- import { execSync } from "child_process";
439
424
  async function getGitInfo() {
440
425
  try {
441
426
  const user = execSync("git config user.name", { encoding: "utf-8" }).trim();
@@ -457,8 +442,8 @@ async function getGitInfo() {
457
442
  }
458
443
  async function getProjectName(cwd = process.cwd()) {
459
444
  try {
460
- const packageJsonPath = path4.join(cwd, "package.json");
461
- const content = await fs4.readFile(packageJsonPath, "utf-8");
445
+ const packageJsonPath = path2.join(cwd, "package.json");
446
+ const content = await fs9.readFile(packageJsonPath, "utf-8");
462
447
  const packageJson2 = JSON.parse(content);
463
448
  return packageJson2.name || null;
464
449
  } catch {
@@ -539,18 +524,6 @@ async function buildVariableContext(config, options = {}) {
539
524
  context.gitInfo = await getGitInfo() ?? void 0;
540
525
  return context;
541
526
  }
542
-
543
- // src/commands/check.ts
544
- import * as path5 from "path";
545
- import chalk3 from "chalk";
546
-
547
- // src/utils/ui.ts
548
- import ora from "ora";
549
- import chalk2 from "chalk";
550
-
551
- // src/utils/safe-output.ts
552
- import chalk from "chalk";
553
- import stripAnsi from "strip-ansi";
554
527
  function sanitizeUserInput(input) {
555
528
  if (typeof input !== "string") {
556
529
  return "";
@@ -571,7 +544,7 @@ async function withSpinner(text, fn, options) {
571
544
  spinner.succeed(options?.successText || text);
572
545
  return result;
573
546
  } catch (error) {
574
- spinner.fail(options?.failText || `${text} failed`);
547
+ spinner.fail(`${text} failed`);
575
548
  throw error;
576
549
  }
577
550
  }
@@ -580,12 +553,12 @@ async function withSpinner(text, fn, options) {
580
553
  async function checkSpecs(options = {}) {
581
554
  const config = await loadConfig();
582
555
  const cwd = process.cwd();
583
- const specsDir = path5.join(cwd, config.specsDir);
556
+ path2.join(cwd, config.specsDir);
584
557
  const specs = await loadAllSpecs();
585
558
  const sequenceMap = /* @__PURE__ */ new Map();
586
559
  const specPattern = createSpecDirPattern();
587
560
  for (const spec of specs) {
588
- const specName = path5.basename(spec.path);
561
+ const specName = path2.basename(spec.path);
589
562
  const match = specName.match(specPattern);
590
563
  if (match) {
591
564
  const seq = parseInt(match[1], 10);
@@ -600,30 +573,30 @@ async function checkSpecs(options = {}) {
600
573
  const conflicts = Array.from(sequenceMap.entries()).filter(([_, paths]) => paths.length > 1).sort(([a], [b]) => a - b);
601
574
  if (conflicts.length === 0) {
602
575
  if (!options.quiet && !options.silent) {
603
- console.log(chalk3.green("\u2713 No sequence conflicts detected"));
576
+ console.log(chalk16.green("\u2713 No sequence conflicts detected"));
604
577
  }
605
578
  return true;
606
579
  }
607
580
  if (!options.silent) {
608
581
  if (!options.quiet) {
609
582
  console.log("");
610
- console.log(chalk3.yellow("\u26A0\uFE0F Sequence conflicts detected:\n"));
583
+ console.log(chalk16.yellow("\u26A0\uFE0F Sequence conflicts detected:\n"));
611
584
  for (const [seq, paths] of conflicts) {
612
- console.log(chalk3.red(` Sequence ${String(seq).padStart(config.structure.sequenceDigits, "0")}:`));
585
+ console.log(chalk16.red(` Sequence ${String(seq).padStart(config.structure.sequenceDigits, "0")}:`));
613
586
  for (const p of paths) {
614
- console.log(chalk3.gray(` - ${sanitizeUserInput(p)}`));
587
+ console.log(chalk16.gray(` - ${sanitizeUserInput(p)}`));
615
588
  }
616
589
  console.log("");
617
590
  }
618
- console.log(chalk3.cyan("Tip: Use date prefix to prevent conflicts:"));
619
- console.log(chalk3.gray(' Edit .lean-spec/config.json \u2192 structure.prefix: "{YYYYMMDD}-"'));
591
+ console.log(chalk16.cyan("Tip: Use date prefix to prevent conflicts:"));
592
+ console.log(chalk16.gray(' Edit .lean-spec/config.json \u2192 structure.prefix: "{YYYYMMDD}-"'));
620
593
  console.log("");
621
- console.log(chalk3.cyan("Or rename folders manually to resolve."));
594
+ console.log(chalk16.cyan("Or rename folders manually to resolve."));
622
595
  console.log("");
623
596
  } else {
624
597
  console.log("");
625
- console.log(chalk3.yellow(`\u26A0\uFE0F Conflict warning: ${conflicts.length} sequence conflict(s) detected`));
626
- console.log(chalk3.gray("Run: lean-spec check"));
598
+ console.log(chalk16.yellow(`\u26A0\uFE0F Conflict warning: ${conflicts.length} sequence conflict(s) detected`));
599
+ console.log(chalk16.gray("Run: lean-spec check"));
627
600
  console.log("");
628
601
  }
629
602
  }
@@ -644,8 +617,8 @@ async function autoCheckIfEnabled() {
644
617
  async function createSpec(name, options = {}) {
645
618
  const config = await loadConfig();
646
619
  const cwd = process.cwd();
647
- const specsDir = path6.join(cwd, config.specsDir);
648
- await fs5.mkdir(specsDir, { recursive: true });
620
+ const specsDir = path2.join(cwd, config.specsDir);
621
+ await fs9.mkdir(specsDir, { recursive: true });
649
622
  const seq = await getGlobalNextSeq(specsDir, config.structure.sequenceDigits);
650
623
  let specRelativePath;
651
624
  if (config.structure.pattern === "flat") {
@@ -665,19 +638,18 @@ async function createSpec(name, options = {}) {
665
638
  } else {
666
639
  throw new Error(`Unknown pattern: ${config.structure.pattern}`);
667
640
  }
668
- const specDir = path6.join(specsDir, specRelativePath);
669
- const specFile = path6.join(specDir, config.structure.defaultFile);
641
+ const specDir = path2.join(specsDir, specRelativePath);
642
+ const specFile = path2.join(specDir, config.structure.defaultFile);
670
643
  try {
671
- await fs5.access(specDir);
644
+ await fs9.access(specDir);
672
645
  throw new Error(`Spec already exists: ${sanitizeUserInput(specDir)}`);
673
646
  } catch (error) {
674
- if (error.code === "ENOENT") {
675
- } else {
647
+ if (error.code === "ENOENT") ; else {
676
648
  throw error;
677
649
  }
678
650
  }
679
- await fs5.mkdir(specDir, { recursive: true });
680
- const templatesDir = path6.join(cwd, ".lean-spec", "templates");
651
+ await fs9.mkdir(specDir, { recursive: true });
652
+ const templatesDir = path2.join(cwd, ".lean-spec", "templates");
681
653
  let templateName;
682
654
  if (options.template) {
683
655
  if (config.templates?.[options.template]) {
@@ -689,10 +661,10 @@ async function createSpec(name, options = {}) {
689
661
  } else {
690
662
  templateName = config.template || "spec-template.md";
691
663
  }
692
- const templatePath = path6.join(templatesDir, templateName);
664
+ const templatePath = path2.join(templatesDir, templateName);
693
665
  let content;
694
666
  try {
695
- const template = await fs5.readFile(templatePath, "utf-8");
667
+ const template = await fs9.readFile(templatePath, "utf-8");
696
668
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
697
669
  const title = options.title || name;
698
670
  const varContext = await buildVariableContext(config, { name: title, date });
@@ -722,7 +694,7 @@ async function createSpec(name, options = {}) {
722
694
  frontmatter: parsed.data
723
695
  };
724
696
  parsed.content = resolveVariables(parsed.content, contextWithFrontmatter);
725
- const { enrichWithTimestamps } = await import("./frontmatter-26SOQGYM.js");
697
+ const { enrichWithTimestamps } = await import('./frontmatter-R2DANL5X.js');
726
698
  enrichWithTimestamps(parsed.data);
727
699
  content = matter.stringify(parsed.content, parsed.data);
728
700
  if (options.description) {
@@ -736,21 +708,16 @@ ${options.description}`
736
708
  } catch (error) {
737
709
  throw new Error(`Template not found: ${templatePath}. Run: lean-spec init`);
738
710
  }
739
- await fs5.writeFile(specFile, content, "utf-8");
740
- console.log(chalk4.green(`\u2713 Created: ${sanitizeUserInput(specDir)}/`));
741
- console.log(chalk4.gray(` Edit: ${sanitizeUserInput(specFile)}`));
711
+ await fs9.writeFile(specFile, content, "utf-8");
712
+ console.log(chalk16.green(`\u2713 Created: ${sanitizeUserInput(specDir)}/`));
713
+ console.log(chalk16.gray(` Edit: ${sanitizeUserInput(specFile)}`));
742
714
  await autoCheckIfEnabled();
743
715
  }
744
-
745
- // src/commands/archive.ts
746
- import * as fs6 from "fs/promises";
747
- import * as path7 from "path";
748
- import chalk5 from "chalk";
749
716
  async function archiveSpec(specPath) {
750
717
  await autoCheckIfEnabled();
751
718
  const config = await loadConfig();
752
719
  const cwd = process.cwd();
753
- const specsDir = path7.join(cwd, config.specsDir);
720
+ const specsDir = path2.join(cwd, config.specsDir);
754
721
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
755
722
  if (!resolvedPath) {
756
723
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
@@ -759,19 +726,14 @@ async function archiveSpec(specPath) {
759
726
  if (specFile) {
760
727
  await updateFrontmatter(specFile, { status: "archived" });
761
728
  }
762
- const archiveDir = path7.join(specsDir, "archived");
763
- await fs6.mkdir(archiveDir, { recursive: true });
764
- const specName = path7.basename(resolvedPath);
765
- const archivePath = path7.join(archiveDir, specName);
766
- await fs6.rename(resolvedPath, archivePath);
767
- console.log(chalk5.green(`\u2713 Archived: ${sanitizeUserInput(archivePath)}`));
729
+ const archiveDir = path2.join(specsDir, "archived");
730
+ await fs9.mkdir(archiveDir, { recursive: true });
731
+ const specName = path2.basename(resolvedPath);
732
+ const archivePath = path2.join(archiveDir, specName);
733
+ await fs9.rename(resolvedPath, archivePath);
734
+ console.log(chalk16.green(`\u2713 Archived: ${sanitizeUserInput(archivePath)}`));
768
735
  }
769
736
 
770
- // src/commands/list.ts
771
- import chalk7 from "chalk";
772
- import * as fs7 from "fs/promises";
773
- import * as path8 from "path";
774
-
775
737
  // src/utils/pattern-detection.ts
776
738
  function detectPatternType(config) {
777
739
  const { pattern, groupExtractor } = config.structure;
@@ -795,66 +757,63 @@ function detectPatternType(config) {
795
757
  shouldGroup: false
796
758
  };
797
759
  }
798
-
799
- // src/utils/colors.ts
800
- import chalk6 from "chalk";
801
760
  var STATUS_CONFIG = {
802
761
  planned: {
803
762
  emoji: "\u{1F4C5}",
804
763
  label: "Planned",
805
- colorFn: chalk6.blue,
806
- badge: (s = "planned") => chalk6.blue(`[${s}]`)
764
+ colorFn: chalk16.blue,
765
+ badge: (s = "planned") => chalk16.blue(`[${s}]`)
807
766
  },
808
767
  "in-progress": {
809
768
  emoji: "\u23F3",
810
769
  label: "In Progress",
811
- colorFn: chalk6.yellow,
812
- badge: (s = "in-progress") => chalk6.yellow(`[${s}]`)
770
+ colorFn: chalk16.yellow,
771
+ badge: (s = "in-progress") => chalk16.yellow(`[${s}]`)
813
772
  },
814
773
  complete: {
815
774
  emoji: "\u2705",
816
775
  label: "Complete",
817
- colorFn: chalk6.green,
818
- badge: (s = "complete") => chalk6.green(`[${s}]`)
776
+ colorFn: chalk16.green,
777
+ badge: (s = "complete") => chalk16.green(`[${s}]`)
819
778
  },
820
779
  archived: {
821
780
  emoji: "\u{1F4E6}",
822
781
  label: "Archived",
823
- colorFn: chalk6.gray,
824
- badge: (s = "archived") => chalk6.gray(`[${s}]`)
782
+ colorFn: chalk16.gray,
783
+ badge: (s = "archived") => chalk16.gray(`[${s}]`)
825
784
  }
826
785
  };
827
786
  var PRIORITY_CONFIG = {
828
787
  critical: {
829
788
  emoji: "\u{1F534}",
830
- colorFn: chalk6.red.bold,
831
- badge: (s = "critical") => chalk6.red.bold(`[${s}]`)
789
+ colorFn: chalk16.red.bold,
790
+ badge: (s = "critical") => chalk16.red.bold(`[${s}]`)
832
791
  },
833
792
  high: {
834
793
  emoji: "\u{1F7E0}",
835
- colorFn: chalk6.hex("#FFA500"),
836
- badge: (s = "high") => chalk6.hex("#FFA500")(`[${s}]`)
794
+ colorFn: chalk16.hex("#FFA500"),
795
+ badge: (s = "high") => chalk16.hex("#FFA500")(`[${s}]`)
837
796
  },
838
797
  medium: {
839
798
  emoji: "\u{1F7E1}",
840
- colorFn: chalk6.yellow,
841
- badge: (s = "medium") => chalk6.yellow(`[${s}]`)
799
+ colorFn: chalk16.yellow,
800
+ badge: (s = "medium") => chalk16.yellow(`[${s}]`)
842
801
  },
843
802
  low: {
844
803
  emoji: "\u{1F7E2}",
845
- colorFn: chalk6.gray,
846
- badge: (s = "low") => chalk6.gray(`[${s}]`)
804
+ colorFn: chalk16.gray,
805
+ badge: (s = "low") => chalk16.gray(`[${s}]`)
847
806
  }
848
807
  };
849
808
  function formatStatusBadge(status) {
850
- return STATUS_CONFIG[status]?.badge() || chalk6.white(`[${status}]`);
809
+ return STATUS_CONFIG[status]?.badge() || chalk16.white(`[${status}]`);
851
810
  }
852
811
  function formatPriorityBadge(priority) {
853
- return PRIORITY_CONFIG[priority]?.badge() || chalk6.white(`[${priority}]`);
812
+ return PRIORITY_CONFIG[priority]?.badge() || chalk16.white(`[${priority}]`);
854
813
  }
855
814
  function getStatusIndicator(status) {
856
815
  const config = STATUS_CONFIG[status];
857
- if (!config) return chalk6.gray("[unknown]");
816
+ if (!config) return chalk16.gray("[unknown]");
858
817
  return config.colorFn(`[${status}]`);
859
818
  }
860
819
  function getStatusEmoji(status) {
@@ -869,9 +828,9 @@ async function listSpecs(options = {}) {
869
828
  await autoCheckIfEnabled();
870
829
  const config = await loadConfig();
871
830
  const cwd = process.cwd();
872
- const specsDir = path8.join(cwd, config.specsDir);
831
+ const specsDir = path2.join(cwd, config.specsDir);
873
832
  try {
874
- await fs7.access(specsDir);
833
+ await fs9.access(specsDir);
875
834
  } catch {
876
835
  console.log("");
877
836
  console.log("No specs directory found. Initialize with: lean-spec init");
@@ -894,10 +853,10 @@ async function listSpecs(options = {}) {
894
853
  })
895
854
  );
896
855
  if (specs.length === 0) {
897
- console.log(chalk7.dim("No specs found."));
856
+ console.log(chalk16.dim("No specs found."));
898
857
  return;
899
858
  }
900
- console.log(chalk7.bold.cyan("\u{1F4C4} Spec List"));
859
+ console.log(chalk16.bold.cyan("\u{1F4C4} Spec List"));
901
860
  const filterParts = [];
902
861
  if (options.status) {
903
862
  const statusStr = Array.isArray(options.status) ? options.status.join(",") : options.status;
@@ -910,7 +869,7 @@ async function listSpecs(options = {}) {
910
869
  }
911
870
  if (options.assignee) filterParts.push(`assignee=${options.assignee}`);
912
871
  if (filterParts.length > 0) {
913
- console.log(chalk7.dim(`Filtered by: ${filterParts.join(", ")}`));
872
+ console.log(chalk16.dim(`Filtered by: ${filterParts.join(", ")}`));
914
873
  }
915
874
  console.log("");
916
875
  const patternInfo = detectPatternType(config);
@@ -920,7 +879,7 @@ async function listSpecs(options = {}) {
920
879
  renderFlatList(specs);
921
880
  }
922
881
  console.log("");
923
- console.log(chalk7.bold(`Total: ${chalk7.green(specs.length)} spec${specs.length !== 1 ? "s" : ""}`));
882
+ console.log(chalk16.bold(`Total: ${chalk16.green(specs.length)} spec${specs.length !== 1 ? "s" : ""}`));
924
883
  }
925
884
  function renderFlatList(specs) {
926
885
  for (const spec of specs) {
@@ -928,18 +887,18 @@ function renderFlatList(specs) {
928
887
  const priorityEmoji = getPriorityEmoji(spec.frontmatter.priority);
929
888
  let assigneeStr = "";
930
889
  if (spec.frontmatter.assignee) {
931
- assigneeStr = " " + chalk7.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
890
+ assigneeStr = " " + chalk16.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
932
891
  }
933
892
  let tagsStr = "";
934
893
  if (spec.frontmatter.tags?.length) {
935
894
  const tags = Array.isArray(spec.frontmatter.tags) ? spec.frontmatter.tags : [];
936
895
  if (tags.length > 0) {
937
896
  const tagStr = tags.map((tag) => `#${sanitizeUserInput(tag)}`).join(" ");
938
- tagsStr = " " + chalk7.dim(chalk7.magenta(tagStr));
897
+ tagsStr = " " + chalk16.dim(chalk16.magenta(tagStr));
939
898
  }
940
899
  }
941
900
  const priorityPrefix = priorityEmoji ? `${priorityEmoji} ` : "";
942
- console.log(`${priorityPrefix}${statusEmoji} ${chalk7.cyan(sanitizeUserInput(spec.path))}${assigneeStr}${tagsStr}`);
901
+ console.log(`${priorityPrefix}${statusEmoji} ${chalk16.cyan(sanitizeUserInput(spec.path))}${assigneeStr}${tagsStr}`);
943
902
  }
944
903
  }
945
904
  function renderGroupedList(specs, groupExtractor) {
@@ -968,7 +927,7 @@ function renderGroupedList(specs, groupExtractor) {
968
927
  const groupName = sortedGroups[i];
969
928
  const groupSpecs = groups.get(groupName);
970
929
  const groupEmoji = /^\d{8}$/.test(groupName) ? "\u{1F4C5}" : groupName.startsWith("milestone") ? "\u{1F3AF}" : "\u{1F4C1}";
971
- console.log(`${chalk7.bold.cyan(`${groupEmoji} ${groupName}/`)} ${chalk7.dim(`(${groupSpecs.length})`)}`);
930
+ console.log(`${chalk16.bold.cyan(`${groupEmoji} ${groupName}/`)} ${chalk16.dim(`(${groupSpecs.length})`)}`);
972
931
  console.log("");
973
932
  for (const spec of groupSpecs) {
974
933
  const statusEmoji = getStatusEmoji(spec.frontmatter.status);
@@ -976,33 +935,29 @@ function renderGroupedList(specs, groupExtractor) {
976
935
  const displayPath = spec.path.includes("/") ? spec.path.split("/").slice(1).join("/") : spec.path;
977
936
  let assigneeStr = "";
978
937
  if (spec.frontmatter.assignee) {
979
- assigneeStr = " " + chalk7.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
938
+ assigneeStr = " " + chalk16.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
980
939
  }
981
940
  let tagsStr = "";
982
941
  if (spec.frontmatter.tags?.length) {
983
942
  const tags = Array.isArray(spec.frontmatter.tags) ? spec.frontmatter.tags : [];
984
943
  if (tags.length > 0) {
985
944
  const tagStr = tags.map((tag) => `#${sanitizeUserInput(tag)}`).join(" ");
986
- tagsStr = " " + chalk7.dim(chalk7.magenta(tagStr));
945
+ tagsStr = " " + chalk16.dim(chalk16.magenta(tagStr));
987
946
  }
988
947
  }
989
948
  const priorityPrefix = priorityEmoji ? `${priorityEmoji} ` : "";
990
- console.log(` ${priorityPrefix}${statusEmoji} ${chalk7.cyan(sanitizeUserInput(displayPath))}${assigneeStr}${tagsStr}`);
949
+ console.log(` ${priorityPrefix}${statusEmoji} ${chalk16.cyan(sanitizeUserInput(displayPath))}${assigneeStr}${tagsStr}`);
991
950
  }
992
951
  if (i < sortedGroups.length - 1) {
993
952
  console.log("");
994
953
  }
995
954
  }
996
955
  }
997
-
998
- // src/commands/update.ts
999
- import * as path9 from "path";
1000
- import chalk8 from "chalk";
1001
956
  async function updateSpec(specPath, updates) {
1002
957
  await autoCheckIfEnabled();
1003
958
  const config = await loadConfig();
1004
959
  const cwd = process.cwd();
1005
- const specsDir = path9.join(cwd, config.specsDir);
960
+ const specsDir = path2.join(cwd, config.specsDir);
1006
961
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
1007
962
  if (!resolvedPath) {
1008
963
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}. Tried: ${sanitizeUserInput(specPath)}, specs/${sanitizeUserInput(specPath)}, and searching in date directories`);
@@ -1024,22 +979,16 @@ async function updateSpec(specPath, updates) {
1024
979
  });
1025
980
  }
1026
981
  await updateFrontmatter(specFile, allUpdates);
1027
- console.log(chalk8.green(`\u2713 Updated: ${sanitizeUserInput(path9.relative(cwd, resolvedPath))}`));
982
+ console.log(chalk16.green(`\u2713 Updated: ${sanitizeUserInput(path2.relative(cwd, resolvedPath))}`));
1028
983
  const updatedFields = Object.keys(updates).filter((k) => k !== "customFields");
1029
984
  if (updates.customFields) {
1030
985
  updatedFields.push(...Object.keys(updates.customFields));
1031
986
  }
1032
- console.log(chalk8.gray(` Fields: ${updatedFields.join(", ")}`));
987
+ console.log(chalk16.gray(` Fields: ${updatedFields.join(", ")}`));
1033
988
  }
1034
-
1035
- // src/commands/backfill.ts
1036
- import * as path10 from "path";
1037
-
1038
- // src/utils/git-timestamps.ts
1039
- import { execSync as execSync2 } from "child_process";
1040
989
  function isGitRepository() {
1041
990
  try {
1042
- execSync2("git rev-parse --is-inside-work-tree", {
991
+ execSync("git rev-parse --is-inside-work-tree", {
1043
992
  stdio: "ignore",
1044
993
  encoding: "utf-8"
1045
994
  });
@@ -1050,7 +999,7 @@ function isGitRepository() {
1050
999
  }
1051
1000
  function getFirstCommitTimestamp(filePath) {
1052
1001
  try {
1053
- const timestamp = execSync2(
1002
+ const timestamp = execSync(
1054
1003
  `git log --follow --format="%aI" --diff-filter=A -- "${filePath}" | tail -1`,
1055
1004
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1056
1005
  ).trim();
@@ -1061,7 +1010,7 @@ function getFirstCommitTimestamp(filePath) {
1061
1010
  }
1062
1011
  function getLastCommitTimestamp(filePath) {
1063
1012
  try {
1064
- const timestamp = execSync2(
1013
+ const timestamp = execSync(
1065
1014
  `git log --format="%aI" -n 1 -- "${filePath}"`,
1066
1015
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1067
1016
  ).trim();
@@ -1072,7 +1021,7 @@ function getLastCommitTimestamp(filePath) {
1072
1021
  }
1073
1022
  function getCompletionTimestamp(filePath) {
1074
1023
  try {
1075
- const gitLog = execSync2(
1024
+ const gitLog = execSync(
1076
1025
  `git log --format="%H|%aI" -p -- "${filePath}"`,
1077
1026
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1078
1027
  );
@@ -1093,7 +1042,7 @@ function getCompletionTimestamp(filePath) {
1093
1042
  }
1094
1043
  function getFirstCommitAuthor(filePath) {
1095
1044
  try {
1096
- const author = execSync2(
1045
+ const author = execSync(
1097
1046
  `git log --follow --format="%an" --diff-filter=A -- "${filePath}" | tail -1`,
1098
1047
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1099
1048
  ).trim();
@@ -1105,7 +1054,7 @@ function getFirstCommitAuthor(filePath) {
1105
1054
  function parseStatusTransitions(filePath) {
1106
1055
  const transitions = [];
1107
1056
  try {
1108
- const gitLog = execSync2(
1057
+ const gitLog = execSync(
1109
1058
  `git log --format="%H|%aI" -p --reverse -- "${filePath}"`,
1110
1059
  { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
1111
1060
  );
@@ -1153,7 +1102,7 @@ function extractGitTimestamps(filePath, options = {}) {
1153
1102
  }
1154
1103
  function fileExistsInGit(filePath) {
1155
1104
  try {
1156
- execSync2(
1105
+ execSync(
1157
1106
  `git log -n 1 -- "${filePath}"`,
1158
1107
  { stdio: "ignore", encoding: "utf-8" }
1159
1108
  );
@@ -1176,7 +1125,7 @@ async function backfillTimestamps(options = {}) {
1176
1125
  specs = [];
1177
1126
  const config = await loadConfig();
1178
1127
  const cwd = process.cwd();
1179
- const specsDir = path10.join(cwd, config.specsDir);
1128
+ const specsDir = path2.join(cwd, config.specsDir);
1180
1129
  for (const specPath of options.specs) {
1181
1130
  const resolved = await resolveSpecPath(specPath, cwd, specsDir);
1182
1131
  if (!resolved) {
@@ -1348,86 +1297,81 @@ function printSummary(results, options) {
1348
1297
  console.log(" Run \x1B[36mlspec stats\x1B[0m to see velocity metrics");
1349
1298
  }
1350
1299
  }
1351
-
1352
- // src/commands/templates.ts
1353
- import * as fs8 from "fs/promises";
1354
- import * as path11 from "path";
1355
- import chalk9 from "chalk";
1356
1300
  async function listTemplates(cwd = process.cwd()) {
1357
1301
  const config = await loadConfig(cwd);
1358
- const templatesDir = path11.join(cwd, ".lean-spec", "templates");
1302
+ const templatesDir = path2.join(cwd, ".lean-spec", "templates");
1359
1303
  console.log("");
1360
- console.log(chalk9.green("=== Project Templates ==="));
1304
+ console.log(chalk16.green("=== Project Templates ==="));
1361
1305
  console.log("");
1362
1306
  try {
1363
- await fs8.access(templatesDir);
1307
+ await fs9.access(templatesDir);
1364
1308
  } catch {
1365
- console.log(chalk9.yellow("No templates directory found."));
1366
- console.log(chalk9.gray("Run: lean-spec init"));
1309
+ console.log(chalk16.yellow("No templates directory found."));
1310
+ console.log(chalk16.gray("Run: lean-spec init"));
1367
1311
  console.log("");
1368
1312
  return;
1369
1313
  }
1370
- const files = await fs8.readdir(templatesDir);
1314
+ const files = await fs9.readdir(templatesDir);
1371
1315
  const templateFiles = files.filter((f) => f.endsWith(".md"));
1372
1316
  if (templateFiles.length === 0) {
1373
- console.log(chalk9.yellow("No templates found."));
1317
+ console.log(chalk16.yellow("No templates found."));
1374
1318
  console.log("");
1375
1319
  return;
1376
1320
  }
1377
1321
  if (config.templates && Object.keys(config.templates).length > 0) {
1378
- console.log(chalk9.cyan("Registered:"));
1322
+ console.log(chalk16.cyan("Registered:"));
1379
1323
  for (const [name, file] of Object.entries(config.templates)) {
1380
1324
  const isDefault = config.template === file;
1381
- const marker = isDefault ? chalk9.green("\u2713 (default)") : "";
1382
- console.log(` ${chalk9.bold(name)}: ${file} ${marker}`);
1325
+ const marker = isDefault ? chalk16.green("\u2713 (default)") : "";
1326
+ console.log(` ${chalk16.bold(name)}: ${file} ${marker}`);
1383
1327
  }
1384
1328
  console.log("");
1385
1329
  }
1386
- console.log(chalk9.cyan("Available files:"));
1330
+ console.log(chalk16.cyan("Available files:"));
1387
1331
  for (const file of templateFiles) {
1388
- const filePath = path11.join(templatesDir, file);
1389
- const stat5 = await fs8.stat(filePath);
1332
+ const filePath = path2.join(templatesDir, file);
1333
+ const stat5 = await fs9.stat(filePath);
1390
1334
  const sizeKB = (stat5.size / 1024).toFixed(1);
1391
1335
  console.log(` ${file} (${sizeKB} KB)`);
1392
1336
  }
1393
1337
  console.log("");
1394
- console.log(chalk9.gray("Use templates with: lean-spec create <name> --template=<template-name>"));
1338
+ console.log(chalk16.gray("Use templates with: lean-spec create <name> --template=<template-name>"));
1395
1339
  console.log("");
1396
1340
  }
1397
1341
  async function showTemplate(templateName, cwd = process.cwd()) {
1398
1342
  const config = await loadConfig(cwd);
1399
1343
  if (!config.templates?.[templateName]) {
1400
- console.error(chalk9.red(`Template not found: ${templateName}`));
1401
- console.error(chalk9.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
1344
+ console.error(chalk16.red(`Template not found: ${templateName}`));
1345
+ console.error(chalk16.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
1402
1346
  process.exit(1);
1403
1347
  }
1404
- const templatesDir = path11.join(cwd, ".lean-spec", "templates");
1348
+ const templatesDir = path2.join(cwd, ".lean-spec", "templates");
1405
1349
  const templateFile = config.templates[templateName];
1406
- const templatePath = path11.join(templatesDir, templateFile);
1350
+ const templatePath = path2.join(templatesDir, templateFile);
1407
1351
  try {
1408
- const content = await fs8.readFile(templatePath, "utf-8");
1352
+ const content = await fs9.readFile(templatePath, "utf-8");
1409
1353
  console.log("");
1410
- console.log(chalk9.cyan(`=== Template: ${templateName} (${templateFile}) ===`));
1354
+ console.log(chalk16.cyan(`=== Template: ${templateName} (${templateFile}) ===`));
1411
1355
  console.log("");
1412
1356
  console.log(content);
1413
1357
  console.log("");
1414
1358
  } catch (error) {
1415
- console.error(chalk9.red(`Error reading template: ${templateFile}`));
1359
+ console.error(chalk16.red(`Error reading template: ${templateFile}`));
1416
1360
  console.error(error);
1417
1361
  process.exit(1);
1418
1362
  }
1419
1363
  }
1420
1364
  async function addTemplate(name, file, cwd = process.cwd()) {
1421
1365
  const config = await loadConfig(cwd);
1422
- const templatesDir = path11.join(cwd, ".lean-spec", "templates");
1423
- const templatePath = path11.join(templatesDir, file);
1366
+ const templatesDir = path2.join(cwd, ".lean-spec", "templates");
1367
+ const templatePath = path2.join(templatesDir, file);
1424
1368
  try {
1425
- await fs8.access(templatePath);
1369
+ await fs9.access(templatePath);
1426
1370
  } catch {
1427
- console.error(chalk9.red(`Template file not found: ${file}`));
1428
- console.error(chalk9.gray(`Expected at: ${templatePath}`));
1371
+ console.error(chalk16.red(`Template file not found: ${file}`));
1372
+ console.error(chalk16.gray(`Expected at: ${templatePath}`));
1429
1373
  console.error(
1430
- chalk9.yellow("Create the file first or use: lean-spec templates copy <source> <target>")
1374
+ chalk16.yellow("Create the file first or use: lean-spec templates copy <source> <target>")
1431
1375
  );
1432
1376
  process.exit(1);
1433
1377
  }
@@ -1435,73 +1379,61 @@ async function addTemplate(name, file, cwd = process.cwd()) {
1435
1379
  config.templates = {};
1436
1380
  }
1437
1381
  if (config.templates[name]) {
1438
- console.log(chalk9.yellow(`Warning: Template '${name}' already exists, updating...`));
1382
+ console.log(chalk16.yellow(`Warning: Template '${name}' already exists, updating...`));
1439
1383
  }
1440
1384
  config.templates[name] = file;
1441
1385
  await saveConfig(config, cwd);
1442
- console.log(chalk9.green(`\u2713 Added template: ${name} \u2192 ${file}`));
1443
- console.log(chalk9.gray(` Use with: lean-spec create <spec-name> --template=${name}`));
1386
+ console.log(chalk16.green(`\u2713 Added template: ${name} \u2192 ${file}`));
1387
+ console.log(chalk16.gray(` Use with: lean-spec create <spec-name> --template=${name}`));
1444
1388
  }
1445
1389
  async function removeTemplate(name, cwd = process.cwd()) {
1446
1390
  const config = await loadConfig(cwd);
1447
1391
  if (!config.templates?.[name]) {
1448
- console.error(chalk9.red(`Template not found: ${name}`));
1449
- console.error(chalk9.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
1392
+ console.error(chalk16.red(`Template not found: ${name}`));
1393
+ console.error(chalk16.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
1450
1394
  process.exit(1);
1451
1395
  }
1452
1396
  if (name === "default") {
1453
- console.error(chalk9.red("Cannot remove default template"));
1397
+ console.error(chalk16.red("Cannot remove default template"));
1454
1398
  process.exit(1);
1455
1399
  }
1456
1400
  const file = config.templates[name];
1457
1401
  delete config.templates[name];
1458
1402
  await saveConfig(config, cwd);
1459
- console.log(chalk9.green(`\u2713 Removed template: ${name}`));
1460
- console.log(chalk9.gray(` Note: Template file ${file} still exists in .lean-spec/templates/`));
1403
+ console.log(chalk16.green(`\u2713 Removed template: ${name}`));
1404
+ console.log(chalk16.gray(` Note: Template file ${file} still exists in .lean-spec/templates/`));
1461
1405
  }
1462
1406
  async function copyTemplate(source, target, cwd = process.cwd()) {
1463
1407
  const config = await loadConfig(cwd);
1464
- const templatesDir = path11.join(cwd, ".lean-spec", "templates");
1408
+ const templatesDir = path2.join(cwd, ".lean-spec", "templates");
1465
1409
  let sourceFile;
1466
1410
  if (config.templates?.[source]) {
1467
1411
  sourceFile = config.templates[source];
1468
1412
  } else {
1469
1413
  sourceFile = source;
1470
1414
  }
1471
- const sourcePath = path11.join(templatesDir, sourceFile);
1415
+ const sourcePath = path2.join(templatesDir, sourceFile);
1472
1416
  try {
1473
- await fs8.access(sourcePath);
1417
+ await fs9.access(sourcePath);
1474
1418
  } catch {
1475
- console.error(chalk9.red(`Source template not found: ${source}`));
1476
- console.error(chalk9.gray(`Expected at: ${sourcePath}`));
1419
+ console.error(chalk16.red(`Source template not found: ${source}`));
1420
+ console.error(chalk16.gray(`Expected at: ${sourcePath}`));
1477
1421
  process.exit(1);
1478
1422
  }
1479
1423
  const targetFile = target.endsWith(".md") ? target : `${target}.md`;
1480
- const targetPath = path11.join(templatesDir, targetFile);
1481
- await fs8.copyFile(sourcePath, targetPath);
1482
- console.log(chalk9.green(`\u2713 Copied: ${sourceFile} \u2192 ${targetFile}`));
1424
+ const targetPath = path2.join(templatesDir, targetFile);
1425
+ await fs9.copyFile(sourcePath, targetPath);
1426
+ console.log(chalk16.green(`\u2713 Copied: ${sourceFile} \u2192 ${targetFile}`));
1483
1427
  if (!config.templates) {
1484
1428
  config.templates = {};
1485
1429
  }
1486
1430
  const templateName = target.replace(/\.md$/, "");
1487
1431
  config.templates[templateName] = targetFile;
1488
1432
  await saveConfig(config, cwd);
1489
- console.log(chalk9.green(`\u2713 Registered template: ${templateName}`));
1490
- console.log(chalk9.gray(` Edit: ${targetPath}`));
1491
- console.log(chalk9.gray(` Use with: lean-spec create <spec-name> --template=${templateName}`));
1433
+ console.log(chalk16.green(`\u2713 Registered template: ${templateName}`));
1434
+ console.log(chalk16.gray(` Edit: ${targetPath}`));
1435
+ console.log(chalk16.gray(` Use with: lean-spec create <spec-name> --template=${templateName}`));
1492
1436
  }
1493
-
1494
- // src/commands/init.ts
1495
- import * as fs10 from "fs/promises";
1496
- import * as path13 from "path";
1497
- import { fileURLToPath } from "url";
1498
- import chalk11 from "chalk";
1499
- import { select } from "@inquirer/prompts";
1500
-
1501
- // src/utils/template-helpers.ts
1502
- import * as fs9 from "fs/promises";
1503
- import * as path12 from "path";
1504
- import chalk10 from "chalk";
1505
1437
  async function detectExistingSystemPrompts(cwd) {
1506
1438
  const commonFiles = [
1507
1439
  "AGENTS.md",
@@ -1511,7 +1443,7 @@ async function detectExistingSystemPrompts(cwd) {
1511
1443
  const found = [];
1512
1444
  for (const file of commonFiles) {
1513
1445
  try {
1514
- await fs9.access(path12.join(cwd, file));
1446
+ await fs9.access(path2.join(cwd, file));
1515
1447
  found.push(file);
1516
1448
  } catch {
1517
1449
  }
@@ -1520,8 +1452,8 @@ async function detectExistingSystemPrompts(cwd) {
1520
1452
  }
1521
1453
  async function handleExistingFiles(action, existingFiles, templateDir, cwd, variables = {}) {
1522
1454
  for (const file of existingFiles) {
1523
- const filePath = path12.join(cwd, file);
1524
- const templateFilePath = path12.join(templateDir, "files", file);
1455
+ const filePath = path2.join(cwd, file);
1456
+ const templateFilePath = path2.join(templateDir, "files", file);
1525
1457
  try {
1526
1458
  await fs9.access(templateFilePath);
1527
1459
  } catch {
@@ -1533,7 +1465,7 @@ async function handleExistingFiles(action, existingFiles, templateDir, cwd, vari
1533
1465
  for (const [key, value] of Object.entries(variables)) {
1534
1466
  template = template.replace(new RegExp(`\\{${key}\\}`, "g"), value);
1535
1467
  }
1536
- const promptPath = path12.join(cwd, ".lean-spec", "MERGE-AGENTS-PROMPT.md");
1468
+ const promptPath = path2.join(cwd, ".lean-spec", "MERGE-AGENTS-PROMPT.md");
1537
1469
  const aiPrompt = `# AI Prompt: Consolidate AGENTS.md
1538
1470
 
1539
1471
  ## Task
@@ -1565,16 +1497,16 @@ Create a single consolidated AGENTS.md that:
1565
1497
  - Maintains clear structure and readability
1566
1498
  - Removes any duplicate or conflicting guidance
1567
1499
  `;
1568
- await fs9.mkdir(path12.dirname(promptPath), { recursive: true });
1500
+ await fs9.mkdir(path2.dirname(promptPath), { recursive: true });
1569
1501
  await fs9.writeFile(promptPath, aiPrompt, "utf-8");
1570
- console.log(chalk10.green(`\u2713 Created AI consolidation prompt`));
1571
- console.log(chalk10.cyan(` \u2192 ${promptPath}`));
1502
+ console.log(chalk16.green(`\u2713 Created AI consolidation prompt`));
1503
+ console.log(chalk16.cyan(` \u2192 ${promptPath}`));
1572
1504
  console.log("");
1573
- console.log(chalk10.yellow("\u{1F4DD} Next steps:"));
1574
- console.log(chalk10.gray(" 1. Open .lean-spec/MERGE-AGENTS-PROMPT.md"));
1575
- console.log(chalk10.gray(" 2. Send it to your AI coding assistant (GitHub Copilot, Cursor, etc.)"));
1576
- console.log(chalk10.gray(" 3. Let AI create the consolidated AGENTS.md"));
1577
- console.log(chalk10.gray(" 4. Review and commit the result"));
1505
+ console.log(chalk16.yellow("\u{1F4DD} Next steps:"));
1506
+ console.log(chalk16.gray(" 1. Open .lean-spec/MERGE-AGENTS-PROMPT.md"));
1507
+ console.log(chalk16.gray(" 2. Send it to your AI coding assistant (GitHub Copilot, Cursor, etc.)"));
1508
+ console.log(chalk16.gray(" 3. Let AI create the consolidated AGENTS.md"));
1509
+ console.log(chalk16.gray(" 4. Review and commit the result"));
1578
1510
  console.log("");
1579
1511
  } else if (action === "merge-append" && file === "AGENTS.md") {
1580
1512
  const existing = await fs9.readFile(filePath, "utf-8");
@@ -1590,19 +1522,19 @@ Create a single consolidated AGENTS.md that:
1590
1522
 
1591
1523
  ${template.split("\n").slice(1).join("\n")}`;
1592
1524
  await fs9.writeFile(filePath, merged, "utf-8");
1593
- console.log(chalk10.green(`\u2713 Appended LeanSpec section to ${file}`));
1594
- console.log(chalk10.yellow(" \u26A0 Note: May be verbose. Consider consolidating later."));
1525
+ console.log(chalk16.green(`\u2713 Appended LeanSpec section to ${file}`));
1526
+ console.log(chalk16.yellow(" \u26A0 Note: May be verbose. Consider consolidating later."));
1595
1527
  } else if (action === "overwrite") {
1596
1528
  const backupPath = `${filePath}.backup`;
1597
1529
  await fs9.rename(filePath, backupPath);
1598
- console.log(chalk10.yellow(`\u2713 Backed up ${file} \u2192 ${file}.backup`));
1530
+ console.log(chalk16.yellow(`\u2713 Backed up ${file} \u2192 ${file}.backup`));
1599
1531
  let content = await fs9.readFile(templateFilePath, "utf-8");
1600
1532
  for (const [key, value] of Object.entries(variables)) {
1601
1533
  content = content.replace(new RegExp(`\\{${key}\\}`, "g"), value);
1602
1534
  }
1603
1535
  await fs9.writeFile(filePath, content, "utf-8");
1604
- console.log(chalk10.green(`\u2713 Created new ${file}`));
1605
- console.log(chalk10.gray(` \u{1F4A1} Your original content is preserved in ${file}.backup`));
1536
+ console.log(chalk16.green(`\u2713 Created new ${file}`));
1537
+ console.log(chalk16.gray(` \u{1F4A1} Your original content is preserved in ${file}.backup`));
1606
1538
  }
1607
1539
  }
1608
1540
  }
@@ -1610,8 +1542,8 @@ async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
1610
1542
  await fs9.mkdir(dest, { recursive: true });
1611
1543
  const entries = await fs9.readdir(src, { withFileTypes: true });
1612
1544
  for (const entry of entries) {
1613
- const srcPath = path12.join(src, entry.name);
1614
- const destPath = path12.join(dest, entry.name);
1545
+ const srcPath = path2.join(src, entry.name);
1546
+ const destPath = path2.join(dest, entry.name);
1615
1547
  if (skipFiles.includes(entry.name)) {
1616
1548
  continue;
1617
1549
  }
@@ -1632,7 +1564,7 @@ async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
1632
1564
  }
1633
1565
  async function getProjectName2(cwd) {
1634
1566
  try {
1635
- const packageJsonPath = path12.join(cwd, "package.json");
1567
+ const packageJsonPath = path2.join(cwd, "package.json");
1636
1568
  const content = await fs9.readFile(packageJsonPath, "utf-8");
1637
1569
  const pkg = JSON.parse(content);
1638
1570
  if (pkg.name) {
@@ -1640,23 +1572,23 @@ async function getProjectName2(cwd) {
1640
1572
  }
1641
1573
  } catch {
1642
1574
  }
1643
- return path12.basename(cwd);
1575
+ return path2.basename(cwd);
1644
1576
  }
1645
1577
 
1646
1578
  // src/commands/init.ts
1647
- var __dirname2 = path13.dirname(fileURLToPath(import.meta.url));
1648
- var TEMPLATES_DIR = path13.join(__dirname2, "..", "templates");
1579
+ var __dirname = path2.dirname(fileURLToPath(import.meta.url));
1580
+ var TEMPLATES_DIR = path2.join(__dirname, "..", "templates");
1649
1581
  async function initProject() {
1650
1582
  const cwd = process.cwd();
1651
1583
  try {
1652
- await fs10.access(path13.join(cwd, ".lean-spec", "config.json"));
1653
- console.log(chalk11.yellow("\u26A0 LeanSpec already initialized in this directory."));
1654
- console.log(chalk11.gray("To reinitialize, delete .lean-spec/ directory first."));
1584
+ await fs9.access(path2.join(cwd, ".lean-spec", "config.json"));
1585
+ console.log(chalk16.yellow("\u26A0 LeanSpec already initialized in this directory."));
1586
+ console.log(chalk16.gray("To reinitialize, delete .lean-spec/ directory first."));
1655
1587
  return;
1656
1588
  } catch {
1657
1589
  }
1658
1590
  console.log("");
1659
- console.log(chalk11.green("Welcome to LeanSpec!"));
1591
+ console.log(chalk16.green("Welcome to LeanSpec!"));
1660
1592
  console.log("");
1661
1593
  const setupMode = await select({
1662
1594
  message: "How would you like to set up?",
@@ -1694,14 +1626,14 @@ async function initProject() {
1694
1626
  ]
1695
1627
  });
1696
1628
  }
1697
- const templateDir = path13.join(TEMPLATES_DIR, templateName);
1698
- const templateConfigPath = path13.join(templateDir, "config.json");
1629
+ const templateDir = path2.join(TEMPLATES_DIR, templateName);
1630
+ const templateConfigPath = path2.join(templateDir, "config.json");
1699
1631
  let templateConfig;
1700
1632
  try {
1701
- const content = await fs10.readFile(templateConfigPath, "utf-8");
1633
+ const content = await fs9.readFile(templateConfigPath, "utf-8");
1702
1634
  templateConfig = JSON.parse(content).config;
1703
1635
  } catch {
1704
- console.error(chalk11.red(`Error: Template not found: ${templateName}`));
1636
+ console.error(chalk16.red(`Error: Template not found: ${templateName}`));
1705
1637
  process.exit(1);
1706
1638
  }
1707
1639
  let patternChoice = "simple";
@@ -1744,27 +1676,27 @@ async function initProject() {
1744
1676
  templateConfig.structure.prefix = "{YYYYMMDD}-";
1745
1677
  } else if (patternChoice === "custom") {
1746
1678
  console.log("");
1747
- console.log(chalk11.yellow("\u26A0 Custom pattern input is not yet implemented."));
1748
- console.log(chalk11.gray(" You can manually edit .lean-spec/config.json after initialization."));
1749
- console.log(chalk11.gray(" Using simple pattern for now."));
1679
+ console.log(chalk16.yellow("\u26A0 Custom pattern input is not yet implemented."));
1680
+ console.log(chalk16.gray(" You can manually edit .lean-spec/config.json after initialization."));
1681
+ console.log(chalk16.gray(" Using simple pattern for now."));
1750
1682
  console.log("");
1751
1683
  templateConfig.structure.pattern = "flat";
1752
1684
  templateConfig.structure.prefix = "";
1753
1685
  }
1754
- const templatesDir = path13.join(cwd, ".lean-spec", "templates");
1686
+ const templatesDir = path2.join(cwd, ".lean-spec", "templates");
1755
1687
  try {
1756
- await fs10.mkdir(templatesDir, { recursive: true });
1688
+ await fs9.mkdir(templatesDir, { recursive: true });
1757
1689
  } catch (error) {
1758
- console.error(chalk11.red("Error creating templates directory:"), error);
1690
+ console.error(chalk16.red("Error creating templates directory:"), error);
1759
1691
  process.exit(1);
1760
1692
  }
1761
- const templateSpecPath = path13.join(templateDir, "spec-template.md");
1762
- const targetSpecPath = path13.join(templatesDir, "spec-template.md");
1693
+ const templateSpecPath = path2.join(templateDir, "spec-template.md");
1694
+ const targetSpecPath = path2.join(templatesDir, "spec-template.md");
1763
1695
  try {
1764
- await fs10.copyFile(templateSpecPath, targetSpecPath);
1765
- console.log(chalk11.green("\u2713 Created .lean-spec/templates/spec-template.md"));
1696
+ await fs9.copyFile(templateSpecPath, targetSpecPath);
1697
+ console.log(chalk16.green("\u2713 Created .lean-spec/templates/spec-template.md"));
1766
1698
  } catch (error) {
1767
- console.error(chalk11.red("Error copying template:"), error);
1699
+ console.error(chalk16.red("Error copying template:"), error);
1768
1700
  process.exit(1);
1769
1701
  }
1770
1702
  templateConfig.template = "spec-template.md";
@@ -1772,12 +1704,12 @@ async function initProject() {
1772
1704
  default: "spec-template.md"
1773
1705
  };
1774
1706
  await saveConfig(templateConfig, cwd);
1775
- console.log(chalk11.green("\u2713 Created .lean-spec/config.json"));
1707
+ console.log(chalk16.green("\u2713 Created .lean-spec/config.json"));
1776
1708
  const existingFiles = await detectExistingSystemPrompts(cwd);
1777
1709
  let skipFiles = [];
1778
1710
  if (existingFiles.length > 0) {
1779
1711
  console.log("");
1780
- console.log(chalk11.yellow(`Found existing: ${existingFiles.join(", ")}`));
1712
+ console.log(chalk16.yellow(`Found existing: ${existingFiles.join(", ")}`));
1781
1713
  const action = await select({
1782
1714
  message: "How would you like to handle existing AGENTS.md?",
1783
1715
  choices: [
@@ -1810,33 +1742,28 @@ async function initProject() {
1810
1742
  }
1811
1743
  }
1812
1744
  const projectName = await getProjectName2(cwd);
1813
- const filesDir = path13.join(templateDir, "files");
1745
+ const filesDir = path2.join(templateDir, "files");
1814
1746
  try {
1815
1747
  await copyDirectory(filesDir, cwd, skipFiles, { project_name: projectName });
1816
- console.log(chalk11.green("\u2713 Initialized project structure"));
1748
+ console.log(chalk16.green("\u2713 Initialized project structure"));
1817
1749
  } catch (error) {
1818
- console.error(chalk11.red("Error copying template files:"), error);
1750
+ console.error(chalk16.red("Error copying template files:"), error);
1819
1751
  process.exit(1);
1820
1752
  }
1821
1753
  console.log("");
1822
- console.log(chalk11.green("\u2713 LeanSpec initialized!"));
1754
+ console.log(chalk16.green("\u2713 LeanSpec initialized!"));
1823
1755
  console.log("");
1824
1756
  console.log("Next steps:");
1825
- console.log(chalk11.gray(" - Review and customize AGENTS.md"));
1826
- console.log(chalk11.gray(" - Check out example spec in specs/"));
1827
- console.log(chalk11.gray(" - Create your first spec: lean-spec create my-feature"));
1757
+ console.log(chalk16.gray(" - Review and customize AGENTS.md"));
1758
+ console.log(chalk16.gray(" - Check out example spec in specs/"));
1759
+ console.log(chalk16.gray(" - Create your first spec: lean-spec create my-feature"));
1828
1760
  console.log("");
1829
1761
  }
1830
-
1831
- // src/commands/files.ts
1832
- import * as fs11 from "fs/promises";
1833
- import * as path14 from "path";
1834
- import chalk12 from "chalk";
1835
1762
  async function filesCommand(specPath, options = {}) {
1836
1763
  await autoCheckIfEnabled();
1837
1764
  const config = await loadConfig();
1838
1765
  const cwd = process.cwd();
1839
- const specsDir = path14.join(cwd, config.specsDir);
1766
+ const specsDir = path2.join(cwd, config.specsDir);
1840
1767
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
1841
1768
  if (!resolvedPath) {
1842
1769
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}. Try using the full path or spec name (e.g., 001-my-spec)`);
@@ -1847,12 +1774,12 @@ async function filesCommand(specPath, options = {}) {
1847
1774
  }
1848
1775
  const subFiles = await loadSubFiles(spec.fullPath);
1849
1776
  console.log("");
1850
- console.log(chalk12.cyan(`\u{1F4C4} Files in ${sanitizeUserInput(spec.name)}`));
1777
+ console.log(chalk16.cyan(`\u{1F4C4} Files in ${sanitizeUserInput(spec.name)}`));
1851
1778
  console.log("");
1852
- console.log(chalk12.green("Required:"));
1853
- const readmeStat = await fs11.stat(spec.filePath);
1779
+ console.log(chalk16.green("Required:"));
1780
+ const readmeStat = await fs9.stat(spec.filePath);
1854
1781
  const readmeSize = formatSize(readmeStat.size);
1855
- console.log(chalk12.green(` \u2713 README.md (${readmeSize}) Main spec`));
1782
+ console.log(chalk16.green(` \u2713 README.md (${readmeSize}) Main spec`));
1856
1783
  console.log("");
1857
1784
  let filteredFiles = subFiles;
1858
1785
  if (options.type === "docs") {
@@ -1861,25 +1788,25 @@ async function filesCommand(specPath, options = {}) {
1861
1788
  filteredFiles = subFiles.filter((f) => f.type === "asset");
1862
1789
  }
1863
1790
  if (filteredFiles.length === 0) {
1864
- console.log(chalk12.gray("No additional files"));
1791
+ console.log(chalk16.gray("No additional files"));
1865
1792
  console.log("");
1866
1793
  return;
1867
1794
  }
1868
1795
  const documents = filteredFiles.filter((f) => f.type === "document");
1869
1796
  const assets = filteredFiles.filter((f) => f.type === "asset");
1870
1797
  if (documents.length > 0 && (!options.type || options.type === "docs")) {
1871
- console.log(chalk12.cyan("Documents:"));
1798
+ console.log(chalk16.cyan("Documents:"));
1872
1799
  for (const file of documents) {
1873
1800
  const size = formatSize(file.size);
1874
- console.log(chalk12.cyan(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size})`));
1801
+ console.log(chalk16.cyan(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size})`));
1875
1802
  }
1876
1803
  console.log("");
1877
1804
  }
1878
1805
  if (assets.length > 0 && (!options.type || options.type === "assets")) {
1879
- console.log(chalk12.yellow("Assets:"));
1806
+ console.log(chalk16.yellow("Assets:"));
1880
1807
  for (const file of assets) {
1881
1808
  const size = formatSize(file.size);
1882
- console.log(chalk12.yellow(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size})`));
1809
+ console.log(chalk16.yellow(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size})`));
1883
1810
  }
1884
1811
  console.log("");
1885
1812
  }
@@ -1887,7 +1814,7 @@ async function filesCommand(specPath, options = {}) {
1887
1814
  const totalSize = formatSize(
1888
1815
  readmeStat.size + filteredFiles.reduce((sum, f) => sum + f.size, 0)
1889
1816
  );
1890
- console.log(chalk12.gray(`Total: ${totalFiles} files, ${totalSize}`));
1817
+ console.log(chalk16.gray(`Total: ${totalFiles} files, ${totalSize}`));
1891
1818
  console.log("");
1892
1819
  }
1893
1820
  function formatSize(bytes) {
@@ -1899,55 +1826,6 @@ function formatSize(bytes) {
1899
1826
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1900
1827
  }
1901
1828
  }
1902
-
1903
- // src/commands/validate.ts
1904
- import * as fs12 from "fs/promises";
1905
- import * as path16 from "path";
1906
- import chalk14 from "chalk";
1907
-
1908
- // src/validators/line-count.ts
1909
- var LineCountValidator = class {
1910
- name = "max-lines";
1911
- description = "Enforce Context Economy: specs must be <400 lines";
1912
- maxLines;
1913
- warningThreshold;
1914
- constructor(options = {}) {
1915
- this.maxLines = options.maxLines ?? 400;
1916
- this.warningThreshold = options.warningThreshold ?? 300;
1917
- }
1918
- validate(_spec, content) {
1919
- const lines = content.split("\n").length;
1920
- if (lines > this.maxLines) {
1921
- return {
1922
- passed: false,
1923
- errors: [{
1924
- message: `Spec exceeds ${this.maxLines} lines (${lines} lines)`,
1925
- suggestion: "Consider splitting into sub-specs using spec 012 pattern"
1926
- }],
1927
- warnings: []
1928
- };
1929
- }
1930
- if (lines > this.warningThreshold) {
1931
- return {
1932
- passed: true,
1933
- errors: [],
1934
- warnings: [{
1935
- message: `Spec approaching limit (${lines}/${this.maxLines} lines)`,
1936
- suggestion: "Consider simplification or splitting"
1937
- }]
1938
- };
1939
- }
1940
- return {
1941
- passed: true,
1942
- errors: [],
1943
- warnings: []
1944
- };
1945
- }
1946
- };
1947
-
1948
- // src/validators/frontmatter.ts
1949
- import matter2 from "gray-matter";
1950
- import yaml2 from "js-yaml";
1951
1829
  var FrontmatterValidator = class {
1952
1830
  name = "frontmatter";
1953
1831
  description = "Validate spec frontmatter for required fields and valid values";
@@ -1962,9 +1840,9 @@ var FrontmatterValidator = class {
1962
1840
  const warnings = [];
1963
1841
  let parsed;
1964
1842
  try {
1965
- parsed = matter2(content, {
1843
+ parsed = matter(content, {
1966
1844
  engines: {
1967
- yaml: (str) => yaml2.load(str, { schema: yaml2.FAILSAFE_SCHEMA })
1845
+ yaml: (str) => yaml.load(str, { schema: yaml.FAILSAFE_SCHEMA })
1968
1846
  }
1969
1847
  });
1970
1848
  } catch (error) {
@@ -2078,9 +1956,6 @@ var FrontmatterValidator = class {
2078
1956
  return { valid: true };
2079
1957
  }
2080
1958
  };
2081
-
2082
- // src/validators/structure.ts
2083
- import matter3 from "gray-matter";
2084
1959
  var StructureValidator = class {
2085
1960
  name = "structure";
2086
1961
  description = "Validate spec structure and required sections";
@@ -2095,7 +1970,7 @@ var StructureValidator = class {
2095
1970
  const warnings = [];
2096
1971
  let parsed;
2097
1972
  try {
2098
- parsed = matter3(content);
1973
+ parsed = matter(content);
2099
1974
  } catch (error) {
2100
1975
  errors.push({
2101
1976
  message: "Failed to parse frontmatter",
@@ -2424,18 +2299,19 @@ var CorruptionValidator = class {
2424
2299
  return errors;
2425
2300
  }
2426
2301
  };
2427
-
2428
- // src/validators/sub-spec.ts
2429
- import * as path15 from "path";
2430
2302
  var SubSpecValidator = class {
2431
2303
  name = "sub-specs";
2432
- description = "Validate sub-spec files per spec 012 conventions";
2433
- maxLines;
2304
+ description = "Validate sub-spec files using direct token thresholds (spec 071)";
2305
+ excellentThreshold;
2306
+ goodThreshold;
2434
2307
  warningThreshold;
2308
+ maxLines;
2435
2309
  checkCrossReferences;
2436
2310
  constructor(options = {}) {
2437
- this.maxLines = options.maxLines ?? 400;
2438
- this.warningThreshold = options.warningThreshold ?? 300;
2311
+ this.excellentThreshold = options.excellentThreshold ?? 2e3;
2312
+ this.goodThreshold = options.goodThreshold ?? 3500;
2313
+ this.warningThreshold = options.warningThreshold ?? 5e3;
2314
+ this.maxLines = options.maxLines ?? 500;
2439
2315
  this.checkCrossReferences = options.checkCrossReferences ?? true;
2440
2316
  }
2441
2317
  async validate(spec, content) {
@@ -2447,7 +2323,7 @@ var SubSpecValidator = class {
2447
2323
  return { passed: true, errors, warnings };
2448
2324
  }
2449
2325
  this.validateNamingConventions(subSpecs, warnings);
2450
- await this.validateLineCounts(subSpecs, errors, warnings);
2326
+ await this.validateComplexity(subSpecs, errors, warnings);
2451
2327
  this.checkOrphanedSubSpecs(subSpecs, content, warnings);
2452
2328
  if (this.checkCrossReferences) {
2453
2329
  await this.validateCrossReferences(subSpecs, spec, warnings);
@@ -2464,7 +2340,7 @@ var SubSpecValidator = class {
2464
2340
  */
2465
2341
  validateNamingConventions(subSpecs, warnings) {
2466
2342
  for (const subSpec of subSpecs) {
2467
- const baseName = path15.basename(subSpec.name, ".md");
2343
+ const baseName = path2.basename(subSpec.name, ".md");
2468
2344
  if (baseName !== baseName.toUpperCase()) {
2469
2345
  warnings.push({
2470
2346
  message: `Sub-spec filename should be uppercase: ${subSpec.name}`,
@@ -2474,23 +2350,50 @@ var SubSpecValidator = class {
2474
2350
  }
2475
2351
  }
2476
2352
  /**
2477
- * Validate line counts for each sub-spec file
2353
+ * Validate complexity for each sub-spec file using direct token thresholds
2354
+ * Same approach as ComplexityValidator (spec 071)
2478
2355
  */
2479
- async validateLineCounts(subSpecs, errors, warnings) {
2356
+ async validateComplexity(subSpecs, errors, warnings) {
2480
2357
  for (const subSpec of subSpecs) {
2481
2358
  if (!subSpec.content) {
2482
2359
  continue;
2483
2360
  }
2484
- const lines = subSpec.content.split("\n").length;
2485
- if (lines > this.maxLines) {
2361
+ const lines = subSpec.content.split("\n");
2362
+ const lineCount = lines.length;
2363
+ let sectionCount = 0;
2364
+ let inCodeBlock = false;
2365
+ for (const line of lines) {
2366
+ if (line.trim().startsWith("```")) {
2367
+ inCodeBlock = !inCodeBlock;
2368
+ continue;
2369
+ }
2370
+ if (!inCodeBlock && line.match(/^#{2,4}\s/)) {
2371
+ sectionCount++;
2372
+ }
2373
+ }
2374
+ const tokenResult = await countTokens(subSpec.content);
2375
+ const tokenCount = tokenResult.total;
2376
+ if (tokenCount > this.warningThreshold) {
2486
2377
  errors.push({
2487
- message: `Sub-spec ${subSpec.name} exceeds ${this.maxLines} lines (${lines} lines)`,
2488
- suggestion: "Consider further splitting or simplification"
2378
+ message: `Sub-spec ${subSpec.name} has ${tokenCount.toLocaleString()} tokens (threshold: ${this.warningThreshold.toLocaleString()}) - should split`,
2379
+ suggestion: "Consider splitting for Context Economy (attention and cognitive load)"
2380
+ });
2381
+ } else if (tokenCount > this.goodThreshold) {
2382
+ warnings.push({
2383
+ message: `Sub-spec ${subSpec.name} has ${tokenCount.toLocaleString()} tokens (threshold: ${this.goodThreshold.toLocaleString()})`,
2384
+ suggestion: "Consider simplification or further splitting"
2385
+ });
2386
+ }
2387
+ if (sectionCount < 8 && lineCount > 200) {
2388
+ warnings.push({
2389
+ message: `Sub-spec ${subSpec.name} has only ${sectionCount} sections - too monolithic`,
2390
+ suggestion: "Break into 15-35 sections for better readability (7\xB12 cognitive chunks)"
2489
2391
  });
2490
- } else if (lines > this.warningThreshold) {
2392
+ }
2393
+ if (lineCount > this.maxLines) {
2491
2394
  warnings.push({
2492
- message: `Sub-spec ${subSpec.name} approaching limit (${lines}/${this.maxLines} lines)`,
2493
- suggestion: "Consider simplification"
2395
+ message: `Sub-spec ${subSpec.name} is very long (${lineCount} lines)`,
2396
+ suggestion: "Consider splitting even if token count is acceptable"
2494
2397
  });
2495
2398
  }
2496
2399
  }
@@ -2536,9 +2439,6 @@ var SubSpecValidator = class {
2536
2439
  }
2537
2440
  }
2538
2441
  };
2539
-
2540
- // src/utils/validate-formatter.ts
2541
- import chalk13 from "chalk";
2542
2442
  function groupIssuesByFile(results) {
2543
2443
  const fileMap = /* @__PURE__ */ new Map();
2544
2444
  const addIssue = (filePath, issue, spec) => {
@@ -2605,17 +2505,17 @@ function formatFileIssues(fileResult, specsDir) {
2605
2505
  const priority = fileResult.spec.frontmatter.priority || "medium";
2606
2506
  const statusBadge = formatStatusBadge(status);
2607
2507
  const priorityBadge = formatPriorityBadge(priority);
2608
- lines.push(chalk13.bold.cyan(`${specName} ${statusBadge} ${priorityBadge}`));
2508
+ lines.push(chalk16.bold.cyan(`${specName} ${statusBadge} ${priorityBadge}`));
2609
2509
  } else {
2610
- lines.push(chalk13.cyan.underline(relativePath));
2510
+ lines.push(chalk16.cyan.underline(relativePath));
2611
2511
  }
2612
2512
  for (const issue of fileResult.issues) {
2613
- const severityColor = issue.severity === "error" ? chalk13.red : chalk13.yellow;
2513
+ const severityColor = issue.severity === "error" ? chalk16.red : chalk16.yellow;
2614
2514
  const severityText = severityColor(issue.severity.padEnd(9));
2615
- const ruleText = chalk13.gray(issue.ruleName);
2515
+ const ruleText = chalk16.gray(issue.ruleName);
2616
2516
  lines.push(` ${severityText}${issue.message.padEnd(60)} ${ruleText}`);
2617
2517
  if (issue.suggestion) {
2618
- lines.push(chalk13.gray(` \u2192 ${issue.suggestion}`));
2518
+ lines.push(chalk16.gray(` \u2192 ${issue.suggestion}`));
2619
2519
  }
2620
2520
  }
2621
2521
  lines.push("");
@@ -2625,25 +2525,25 @@ function formatSummary(totalSpecs, errorCount, warningCount, cleanCount) {
2625
2525
  if (errorCount > 0) {
2626
2526
  const errorText = errorCount === 1 ? "error" : "errors";
2627
2527
  const warningText = warningCount === 1 ? "warning" : "warnings";
2628
- return chalk13.red.bold(
2528
+ return chalk16.red.bold(
2629
2529
  `\u2716 ${errorCount} ${errorText}, ${warningCount} ${warningText} (${totalSpecs} specs checked, ${cleanCount} clean)`
2630
2530
  );
2631
2531
  } else if (warningCount > 0) {
2632
2532
  const warningText = warningCount === 1 ? "warning" : "warnings";
2633
- return chalk13.yellow.bold(
2533
+ return chalk16.yellow.bold(
2634
2534
  `\u26A0 ${warningCount} ${warningText} (${totalSpecs} specs checked, ${cleanCount} clean)`
2635
2535
  );
2636
2536
  } else {
2637
- return chalk13.green.bold(`\u2713 All ${totalSpecs} specs passed`);
2537
+ return chalk16.green.bold(`\u2713 All ${totalSpecs} specs passed`);
2638
2538
  }
2639
2539
  }
2640
2540
  function formatPassingSpecs(specs, specsDir) {
2641
2541
  const lines = [];
2642
- lines.push(chalk13.green.bold(`
2542
+ lines.push(chalk16.green.bold(`
2643
2543
  \u2713 ${specs.length} specs passed:`));
2644
2544
  for (const spec of specs) {
2645
2545
  const relativePath = normalizeFilePath(spec.filePath);
2646
- lines.push(chalk13.gray(` ${relativePath}`));
2546
+ lines.push(chalk16.gray(` ${relativePath}`));
2647
2547
  }
2648
2548
  return lines.join("\n");
2649
2549
  }
@@ -2689,16 +2589,16 @@ function formatValidationResults(results, specs, specsDir, options = {}) {
2689
2589
  return formatJson(displayResults, specs.length, errorCount2, warningCount2);
2690
2590
  }
2691
2591
  const lines = [];
2692
- lines.push(chalk13.bold(`
2592
+ lines.push(chalk16.bold(`
2693
2593
  Validating ${specs.length} specs...
2694
2594
  `));
2695
2595
  let previousSpecName;
2696
2596
  for (const fileResult of displayResults) {
2697
2597
  if (fileResult.spec && previousSpecName && fileResult.spec.name !== previousSpecName) {
2698
- lines.push(chalk13.gray("\u2500".repeat(80)));
2598
+ lines.push(chalk16.gray("\u2500".repeat(80)));
2699
2599
  lines.push("");
2700
2600
  }
2701
- lines.push(formatFileIssues(fileResult, specsDir));
2601
+ lines.push(formatFileIssues(fileResult));
2702
2602
  if (fileResult.spec) {
2703
2603
  previousSpecName = fileResult.spec.name;
2704
2604
  }
@@ -2716,10 +2616,10 @@ Validating ${specs.length} specs...
2716
2616
  if (options.verbose && cleanCount > 0) {
2717
2617
  const specsWithIssues = new Set(fileResults.map((fr) => fr.filePath));
2718
2618
  const passingSpecs = specs.filter((spec) => !specsWithIssues.has(spec.filePath));
2719
- lines.push(formatPassingSpecs(passingSpecs, specsDir));
2619
+ lines.push(formatPassingSpecs(passingSpecs));
2720
2620
  }
2721
2621
  if (!options.verbose && cleanCount > 0 && displayResults.length > 0) {
2722
- lines.push(chalk13.gray("\nRun with --verbose to see passing specs."));
2622
+ lines.push(chalk16.gray("\nRun with --verbose to see passing specs."));
2723
2623
  }
2724
2624
  return lines.join("\n");
2725
2625
  }
@@ -2733,12 +2633,12 @@ async function validateCommand(options = {}) {
2733
2633
  specs = [];
2734
2634
  for (const specPath of options.specs) {
2735
2635
  const spec = allSpecs.find(
2736
- (s) => s.path.includes(specPath) || path16.basename(s.path).includes(specPath)
2636
+ (s) => s.path.includes(specPath) || path2.basename(s.path).includes(specPath)
2737
2637
  );
2738
2638
  if (spec) {
2739
2639
  specs.push(spec);
2740
2640
  } else {
2741
- console.error(chalk14.red(`Error: Spec not found: ${specPath}`));
2641
+ console.error(chalk16.red(`Error: Spec not found: ${specPath}`));
2742
2642
  return false;
2743
2643
  }
2744
2644
  }
@@ -2753,7 +2653,8 @@ async function validateCommand(options = {}) {
2753
2653
  return true;
2754
2654
  }
2755
2655
  const validators = [
2756
- new LineCountValidator({ maxLines: options.maxLines }),
2656
+ new ComplexityValidator({ maxLines: options.maxLines }),
2657
+ // Token-based complexity (primary), line count (backstop)
2757
2658
  new FrontmatterValidator(),
2758
2659
  new StructureValidator(),
2759
2660
  new CorruptionValidator(),
@@ -2763,19 +2664,23 @@ async function validateCommand(options = {}) {
2763
2664
  for (const spec of specs) {
2764
2665
  let content;
2765
2666
  try {
2766
- content = await fs12.readFile(spec.filePath, "utf-8");
2667
+ content = await fs9.readFile(spec.filePath, "utf-8");
2767
2668
  } catch (error) {
2768
- console.error(chalk14.red(`Error reading ${spec.filePath}:`), error);
2669
+ console.error(chalk16.red(`Error reading ${spec.filePath}:`), error);
2769
2670
  continue;
2770
2671
  }
2771
2672
  for (const validator of validators) {
2772
- const result = await validator.validate(spec, content);
2773
- results.push({
2774
- spec,
2775
- validatorName: validator.name,
2776
- result,
2777
- content
2778
- });
2673
+ try {
2674
+ const result = await validator.validate(spec, content);
2675
+ results.push({
2676
+ spec,
2677
+ validatorName: validator.name,
2678
+ result,
2679
+ content
2680
+ });
2681
+ } catch (error) {
2682
+ console.error(chalk16.yellow(`Warning: Validator ${validator.name} failed:`), error instanceof Error ? error.message : error);
2683
+ }
2779
2684
  }
2780
2685
  }
2781
2686
  const formatOptions = {
@@ -2789,14 +2694,10 @@ async function validateCommand(options = {}) {
2789
2694
  const hasErrors = results.some((r) => !r.result.passed);
2790
2695
  return !hasErrors;
2791
2696
  }
2792
-
2793
- // src/commands/migrate.ts
2794
- import * as fs13 from "fs/promises";
2795
- import * as path17 from "path";
2796
2697
  async function migrateCommand(inputPath, options = {}) {
2797
2698
  const config = await loadConfig();
2798
2699
  try {
2799
- const stats = await fs13.stat(inputPath);
2700
+ const stats = await fs9.stat(inputPath);
2800
2701
  if (!stats.isDirectory()) {
2801
2702
  console.error("\x1B[31m\u274C Error:\x1B[0m Input path must be a directory");
2802
2703
  process.exit(1);
@@ -2824,16 +2725,16 @@ async function migrateCommand(inputPath, options = {}) {
2824
2725
  async function scanDocuments(dirPath) {
2825
2726
  const documents = [];
2826
2727
  async function scanRecursive(currentPath) {
2827
- const entries = await fs13.readdir(currentPath, { withFileTypes: true });
2728
+ const entries = await fs9.readdir(currentPath, { withFileTypes: true });
2828
2729
  for (const entry of entries) {
2829
- const fullPath = path17.join(currentPath, entry.name);
2730
+ const fullPath = path2.join(currentPath, entry.name);
2830
2731
  if (entry.isDirectory()) {
2831
2732
  if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
2832
2733
  await scanRecursive(fullPath);
2833
2734
  }
2834
2735
  } else if (entry.isFile()) {
2835
2736
  if (entry.name.endsWith(".md") || entry.name.endsWith(".markdown")) {
2836
- const stats = await fs13.stat(fullPath);
2737
+ const stats = await fs9.stat(fullPath);
2837
2738
  documents.push({
2838
2739
  path: fullPath,
2839
2740
  name: entry.name,
@@ -2847,7 +2748,7 @@ async function scanDocuments(dirPath) {
2847
2748
  return documents;
2848
2749
  }
2849
2750
  async function outputManualInstructions(inputPath, documents, config) {
2850
- const specsDir = config.specsDir || "specs";
2751
+ config.specsDir || "specs";
2851
2752
  console.log("\u2550".repeat(70));
2852
2753
  console.log("\x1B[1m\x1B[36m\u{1F4CB} LeanSpec Migration Instructions\x1B[0m");
2853
2754
  console.log("\u2550".repeat(70));
@@ -2966,7 +2867,7 @@ async function verifyAITool(provider) {
2966
2867
  let installed = false;
2967
2868
  let version;
2968
2869
  try {
2969
- const { execSync: execSync3 } = await import("child_process");
2870
+ const { execSync: execSync3 } = await import('child_process');
2970
2871
  execSync3(`which ${toolDef.cliCommand}`, { stdio: "ignore" });
2971
2872
  installed = true;
2972
2873
  try {
@@ -3003,12 +2904,6 @@ function satisfiesVersion(version, minVersion) {
3003
2904
  }
3004
2905
  return true;
3005
2906
  }
3006
-
3007
- // src/commands/board.ts
3008
- import chalk15 from "chalk";
3009
-
3010
- // src/utils/completion.ts
3011
- import dayjs from "dayjs";
3012
2907
  function isCriticalOverdue(spec) {
3013
2908
  if (spec.frontmatter.status === "complete" || spec.frontmatter.status === "archived") {
3014
2909
  return false;
@@ -3016,7 +2911,7 @@ function isCriticalOverdue(spec) {
3016
2911
  if (!spec.frontmatter.due) {
3017
2912
  return false;
3018
2913
  }
3019
- const isOverdue = dayjs(spec.frontmatter.due).isBefore(dayjs(), "day");
2914
+ const isOverdue = dayjs2(spec.frontmatter.due).isBefore(dayjs2(), "day");
3020
2915
  const isCritical = spec.frontmatter.priority === "critical" || spec.frontmatter.priority === "high";
3021
2916
  return isOverdue && isCritical;
3022
2917
  }
@@ -3028,7 +2923,7 @@ function isLongRunning(spec) {
3028
2923
  if (!updatedAt) {
3029
2924
  return false;
3030
2925
  }
3031
- const daysSinceUpdate = dayjs().diff(dayjs(updatedAt), "day");
2926
+ const daysSinceUpdate = dayjs2().diff(dayjs2(updatedAt), "day");
3032
2927
  return daysSinceUpdate > 7;
3033
2928
  }
3034
2929
  function calculateCompletion(specs) {
@@ -3071,9 +2966,6 @@ function getCompletionStatus(score) {
3071
2966
  return { emoji: "\u2717", label: "Needs Attention", color: "red" };
3072
2967
  }
3073
2968
  }
3074
-
3075
- // src/utils/velocity.ts
3076
- import dayjs2 from "dayjs";
3077
2969
  function calculateCycleTime(spec) {
3078
2970
  if (spec.frontmatter.status !== "complete" && spec.frontmatter.status !== "archived") {
3079
2971
  return null;
@@ -3196,7 +3088,7 @@ async function boardCommand(options) {
3196
3088
  })
3197
3089
  );
3198
3090
  if (specs.length === 0) {
3199
- console.log(chalk15.dim("No specs found."));
3091
+ console.log(chalk16.dim("No specs found."));
3200
3092
  return;
3201
3093
  }
3202
3094
  const columns = {
@@ -3211,18 +3103,18 @@ async function boardCommand(options) {
3211
3103
  columns[status].push(spec);
3212
3104
  }
3213
3105
  }
3214
- console.log(chalk15.bold.cyan("\u{1F4CB} Spec Kanban Board"));
3106
+ console.log(chalk16.bold.cyan("\u{1F4CB} Spec Kanban Board"));
3215
3107
  if (options.tag || options.assignee) {
3216
3108
  const filterParts = [];
3217
3109
  if (options.tag) filterParts.push(`tag=${options.tag}`);
3218
3110
  if (options.assignee) filterParts.push(`assignee=${options.assignee}`);
3219
- console.log(chalk15.dim(`Filtered by: ${filterParts.join(", ")}`));
3111
+ console.log(chalk16.dim(`Filtered by: ${filterParts.join(", ")}`));
3220
3112
  }
3221
3113
  console.log("");
3222
3114
  if (!options.simple) {
3223
3115
  const completionMetrics = calculateCompletion(specs);
3224
3116
  const velocityMetrics = calculateVelocityMetrics(specs);
3225
- const completionStatus = getCompletionStatus(completionMetrics.score);
3117
+ getCompletionStatus(completionMetrics.score);
3226
3118
  const boxWidth = 62;
3227
3119
  const topBorder = "\u2554" + "\u2550".repeat(boxWidth - 2) + "\u2557";
3228
3120
  const bottomBorder = "\u255A" + "\u2550".repeat(boxWidth - 2) + "\u255D";
@@ -3231,12 +3123,12 @@ async function boardCommand(options) {
3231
3123
  const padding = boxWidth - 2 - visibleLength;
3232
3124
  return content + " ".repeat(Math.max(0, padding));
3233
3125
  };
3234
- console.log(chalk15.dim(topBorder));
3235
- const headerLine = chalk15.bold(" Project Overview");
3236
- console.log(chalk15.dim("\u2551") + padLine(headerLine) + chalk15.dim("\u2551"));
3237
- const percentageColor = completionMetrics.score >= 70 ? chalk15.green : completionMetrics.score >= 40 ? chalk15.yellow : chalk15.red;
3126
+ console.log(chalk16.dim(topBorder));
3127
+ const headerLine = chalk16.bold(" Project Overview");
3128
+ console.log(chalk16.dim("\u2551") + padLine(headerLine) + chalk16.dim("\u2551"));
3129
+ const percentageColor = completionMetrics.score >= 70 ? chalk16.green : completionMetrics.score >= 40 ? chalk16.yellow : chalk16.red;
3238
3130
  const line1 = ` ${completionMetrics.totalSpecs} total \xB7 ${completionMetrics.activeSpecs} active \xB7 ${completionMetrics.completeSpecs} complete ${percentageColor("(" + completionMetrics.score + "%)")}`;
3239
- console.log(chalk15.dim("\u2551") + padLine(line1) + chalk15.dim("\u2551"));
3131
+ console.log(chalk16.dim("\u2551") + padLine(line1) + chalk16.dim("\u2551"));
3240
3132
  if (completionMetrics.criticalIssues.length > 0 || completionMetrics.warnings.length > 0) {
3241
3133
  const alerts = [];
3242
3134
  if (completionMetrics.criticalIssues.length > 0) {
@@ -3245,27 +3137,27 @@ async function boardCommand(options) {
3245
3137
  if (completionMetrics.warnings.length > 0) {
3246
3138
  alerts.push(`${completionMetrics.warnings.length} specs WIP > 7 days`);
3247
3139
  }
3248
- const alertLine = ` ${chalk15.yellow("\u26A0\uFE0F " + alerts.join(" \xB7 "))}`;
3249
- console.log(chalk15.dim("\u2551") + padLine(alertLine) + chalk15.dim("\u2551"));
3140
+ const alertLine = ` ${chalk16.yellow("\u26A0\uFE0F " + alerts.join(" \xB7 "))}`;
3141
+ console.log(chalk16.dim("\u2551") + padLine(alertLine) + chalk16.dim("\u2551"));
3250
3142
  }
3251
- const velocityLine = ` ${chalk15.cyan("\u{1F680} Velocity:")} ${velocityMetrics.cycleTime.average.toFixed(1)}d avg cycle \xB7 ${(velocityMetrics.throughput.perWeek / 7 * 7).toFixed(1)}/wk throughput`;
3252
- console.log(chalk15.dim("\u2551") + padLine(velocityLine) + chalk15.dim("\u2551"));
3253
- console.log(chalk15.dim(bottomBorder));
3143
+ const velocityLine = ` ${chalk16.cyan("\u{1F680} Velocity:")} ${velocityMetrics.cycleTime.average.toFixed(1)}d avg cycle \xB7 ${(velocityMetrics.throughput.perWeek / 7 * 7).toFixed(1)}/wk throughput`;
3144
+ console.log(chalk16.dim("\u2551") + padLine(velocityLine) + chalk16.dim("\u2551"));
3145
+ console.log(chalk16.dim(bottomBorder));
3254
3146
  console.log("");
3255
3147
  if (options.completionOnly) {
3256
3148
  return;
3257
3149
  }
3258
3150
  }
3259
3151
  renderColumn(STATUS_CONFIG.planned.label, STATUS_CONFIG.planned.emoji, columns.planned, true, STATUS_CONFIG.planned.colorFn);
3260
- console.log(chalk15.dim("\u2501".repeat(70)));
3152
+ console.log(chalk16.dim("\u2501".repeat(70)));
3261
3153
  console.log("");
3262
3154
  renderColumn(STATUS_CONFIG["in-progress"].label, STATUS_CONFIG["in-progress"].emoji, columns["in-progress"], true, STATUS_CONFIG["in-progress"].colorFn);
3263
- console.log(chalk15.dim("\u2501".repeat(70)));
3155
+ console.log(chalk16.dim("\u2501".repeat(70)));
3264
3156
  console.log("");
3265
3157
  renderColumn(STATUS_CONFIG.complete.label, STATUS_CONFIG.complete.emoji, columns.complete, options.showComplete || false, STATUS_CONFIG.complete.colorFn);
3266
3158
  }
3267
3159
  function renderColumn(title, emoji, specs, expanded, colorFn) {
3268
- console.log(`${emoji} ${colorFn(chalk15.bold(`${title} (${specs.length})`))}`);
3160
+ console.log(`${emoji} ${colorFn(chalk16.bold(`${title} (${specs.length})`))}`);
3269
3161
  console.log("");
3270
3162
  if (expanded && specs.length > 0) {
3271
3163
  const priorityGroups = {
@@ -3290,31 +3182,30 @@ function renderColumn(title, emoji, specs, expanded, colorFn) {
3290
3182
  firstGroup = false;
3291
3183
  const priorityLabel = priority === "none" ? "No Priority" : priority.charAt(0).toUpperCase() + priority.slice(1);
3292
3184
  const priorityEmoji = priority === "none" ? "\u26AA" : PRIORITY_CONFIG[priority].emoji;
3293
- const priorityColor = priority === "none" ? chalk15.dim : PRIORITY_CONFIG[priority].colorFn;
3294
- console.log(` ${priorityColor(`${priorityEmoji} ${chalk15.bold(priorityLabel)} ${chalk15.dim(`(${groupSpecs.length})`)}`)}`);
3295
- ;
3185
+ const priorityColor = priority === "none" ? chalk16.dim : PRIORITY_CONFIG[priority].colorFn;
3186
+ console.log(` ${priorityColor(`${priorityEmoji} ${chalk16.bold(priorityLabel)} ${chalk16.dim(`(${groupSpecs.length})`)}`)}`);
3296
3187
  for (const spec of groupSpecs) {
3297
3188
  let assigneeStr = "";
3298
3189
  if (spec.frontmatter.assignee) {
3299
- assigneeStr = " " + chalk15.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
3190
+ assigneeStr = " " + chalk16.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
3300
3191
  }
3301
3192
  let tagsStr = "";
3302
3193
  if (spec.frontmatter.tags?.length) {
3303
3194
  const tags = Array.isArray(spec.frontmatter.tags) ? spec.frontmatter.tags : [];
3304
3195
  if (tags.length > 0) {
3305
3196
  const tagStr = tags.map((tag) => `#${sanitizeUserInput(tag)}`).join(" ");
3306
- tagsStr = " " + chalk15.dim(chalk15.magenta(tagStr));
3197
+ tagsStr = " " + chalk16.dim(chalk16.magenta(tagStr));
3307
3198
  }
3308
3199
  }
3309
- console.log(` ${chalk15.cyan(sanitizeUserInput(spec.path))}${assigneeStr}${tagsStr}`);
3200
+ console.log(` ${chalk16.cyan(sanitizeUserInput(spec.path))}${assigneeStr}${tagsStr}`);
3310
3201
  }
3311
3202
  }
3312
3203
  console.log("");
3313
3204
  } else if (!expanded && specs.length > 0) {
3314
- console.log(` ${chalk15.dim("(collapsed, use --complete to expand)")}`);
3205
+ console.log(` ${chalk16.dim("(collapsed, use --complete to expand)")}`);
3315
3206
  console.log("");
3316
3207
  } else {
3317
- console.log(` ${chalk15.dim("(empty)")}`);
3208
+ console.log(` ${chalk16.dim("(empty)")}`);
3318
3209
  console.log("");
3319
3210
  }
3320
3211
  }
@@ -3322,10 +3213,6 @@ function stripAnsi2(str) {
3322
3213
  return str.replace(/\u001b\[\d+m/g, "");
3323
3214
  }
3324
3215
 
3325
- // src/commands/stats.ts
3326
- import chalk16 from "chalk";
3327
- import dayjs4 from "dayjs";
3328
-
3329
3216
  // src/utils/spec-stats.ts
3330
3217
  function countSpecsByStatusAndPriority(specs) {
3331
3218
  const statusCounts = {
@@ -3359,13 +3246,10 @@ function countSpecsByStatusAndPriority(specs) {
3359
3246
  }
3360
3247
  return { statusCounts, priorityCounts, tagCounts };
3361
3248
  }
3362
-
3363
- // src/utils/insights.ts
3364
- import dayjs3 from "dayjs";
3365
3249
  function generateInsights(specs) {
3366
3250
  const insights = [];
3367
3251
  const criticalOverdue = specs.filter(
3368
- (s) => s.frontmatter.priority === "critical" && s.frontmatter.due && dayjs3(s.frontmatter.due).isBefore(dayjs3(), "day") && s.frontmatter.status !== "complete" && s.frontmatter.status !== "archived"
3252
+ (s) => s.frontmatter.priority === "critical" && s.frontmatter.due && dayjs2(s.frontmatter.due).isBefore(dayjs2(), "day") && s.frontmatter.status !== "complete" && s.frontmatter.status !== "archived"
3369
3253
  );
3370
3254
  if (criticalOverdue.length > 0) {
3371
3255
  insights.push({
@@ -3375,7 +3259,7 @@ function generateInsights(specs) {
3375
3259
  });
3376
3260
  }
3377
3261
  const highOverdue = specs.filter(
3378
- (s) => s.frontmatter.priority === "high" && s.frontmatter.due && dayjs3(s.frontmatter.due).isBefore(dayjs3(), "day") && s.frontmatter.status !== "complete" && s.frontmatter.status !== "archived"
3262
+ (s) => s.frontmatter.priority === "high" && s.frontmatter.due && dayjs2(s.frontmatter.due).isBefore(dayjs2(), "day") && s.frontmatter.status !== "complete" && s.frontmatter.status !== "archived"
3379
3263
  );
3380
3264
  if (highOverdue.length > 0) {
3381
3265
  insights.push({
@@ -3392,7 +3276,7 @@ function generateInsights(specs) {
3392
3276
  if (!updatedAt) {
3393
3277
  return false;
3394
3278
  }
3395
- const daysSinceUpdate = dayjs3().diff(dayjs3(updatedAt), "day");
3279
+ const daysSinceUpdate = dayjs2().diff(dayjs2(updatedAt), "day");
3396
3280
  return daysSinceUpdate > 7;
3397
3281
  });
3398
3282
  if (longRunning.length > 0) {
@@ -3425,14 +3309,14 @@ function generateInsights(specs) {
3425
3309
  return insights.slice(0, 5);
3426
3310
  }
3427
3311
  function getSpecInsightDetails(spec) {
3428
- if (spec.frontmatter.due && dayjs3(spec.frontmatter.due).isBefore(dayjs3(), "day") && spec.frontmatter.status !== "complete" && spec.frontmatter.status !== "archived") {
3429
- const daysOverdue = dayjs3().diff(dayjs3(spec.frontmatter.due), "day");
3312
+ if (spec.frontmatter.due && dayjs2(spec.frontmatter.due).isBefore(dayjs2(), "day") && spec.frontmatter.status !== "complete" && spec.frontmatter.status !== "archived") {
3313
+ const daysOverdue = dayjs2().diff(dayjs2(spec.frontmatter.due), "day");
3430
3314
  return `overdue by ${daysOverdue} day${daysOverdue > 1 ? "s" : ""}`;
3431
3315
  }
3432
3316
  if (spec.frontmatter.status === "in-progress") {
3433
3317
  const updatedAt = spec.frontmatter.updated || spec.frontmatter.updated_at || spec.frontmatter.created || spec.frontmatter.created_at;
3434
3318
  if (updatedAt) {
3435
- const daysSinceUpdate = dayjs3().diff(dayjs3(updatedAt), "day");
3319
+ const daysSinceUpdate = dayjs2().diff(dayjs2(updatedAt), "day");
3436
3320
  if (daysSinceUpdate > 7) {
3437
3321
  return `in-progress for ${daysSinceUpdate} days`;
3438
3322
  }
@@ -3462,7 +3346,7 @@ async function statsCommand(options) {
3462
3346
  console.log("No specs found.");
3463
3347
  return;
3464
3348
  }
3465
- const showFull = options.full || false;
3349
+ options.full || false;
3466
3350
  const showStats = options.full || !options.timeline && !options.velocity;
3467
3351
  const showTimeline = options.timeline || options.full;
3468
3352
  const showVelocity = options.velocity || options.full;
@@ -3532,7 +3416,7 @@ async function statsCommand(options) {
3532
3416
  const criticalInProgress = specs.filter((s) => s.frontmatter.priority === "critical" && s.frontmatter.status === "in-progress").length;
3533
3417
  const criticalComplete = specs.filter((s) => s.frontmatter.priority === "critical" && s.frontmatter.status === "complete").length;
3534
3418
  const criticalOverdue = specs.filter(
3535
- (s) => s.frontmatter.priority === "critical" && s.frontmatter.due && dayjs4(s.frontmatter.due).isBefore(dayjs4(), "day") && s.frontmatter.status !== "complete"
3419
+ (s) => s.frontmatter.priority === "critical" && s.frontmatter.due && dayjs2(s.frontmatter.due).isBefore(dayjs2(), "day") && s.frontmatter.status !== "complete"
3536
3420
  ).length;
3537
3421
  const parts = [];
3538
3422
  if (criticalPlanned > 0) parts.push(chalk16.dim(`${criticalPlanned} planned`));
@@ -3546,7 +3430,7 @@ async function statsCommand(options) {
3546
3430
  const highInProgress = specs.filter((s) => s.frontmatter.priority === "high" && s.frontmatter.status === "in-progress").length;
3547
3431
  const highComplete = specs.filter((s) => s.frontmatter.priority === "high" && s.frontmatter.status === "complete").length;
3548
3432
  const highOverdue = specs.filter(
3549
- (s) => s.frontmatter.priority === "high" && s.frontmatter.due && dayjs4(s.frontmatter.due).isBefore(dayjs4(), "day") && s.frontmatter.status !== "complete"
3433
+ (s) => s.frontmatter.priority === "high" && s.frontmatter.due && dayjs2(s.frontmatter.due).isBefore(dayjs2(), "day") && s.frontmatter.status !== "complete"
3550
3434
  ).length;
3551
3435
  const parts = [];
3552
3436
  if (highPlanned > 0) parts.push(chalk16.dim(`${highPlanned} planned`));
@@ -3708,18 +3592,18 @@ async function statsCommand(options) {
3708
3592
  }
3709
3593
  if (showTimeline) {
3710
3594
  const days = 30;
3711
- const today = dayjs4();
3595
+ const today = dayjs2();
3712
3596
  const startDate = today.subtract(days, "day");
3713
3597
  const createdByDate = {};
3714
3598
  const completedByDate = {};
3715
3599
  for (const spec of specs) {
3716
- const created = dayjs4(spec.frontmatter.created);
3600
+ const created = dayjs2(spec.frontmatter.created);
3717
3601
  if (created.isAfter(startDate)) {
3718
3602
  const dateKey = created.format("YYYY-MM-DD");
3719
3603
  createdByDate[dateKey] = (createdByDate[dateKey] || 0) + 1;
3720
3604
  }
3721
3605
  if (spec.frontmatter.completed) {
3722
- const completed = dayjs4(spec.frontmatter.completed);
3606
+ const completed = dayjs2(spec.frontmatter.completed);
3723
3607
  if (completed.isAfter(startDate)) {
3724
3608
  const dateKey = completed.format("YYYY-MM-DD");
3725
3609
  completedByDate[dateKey] = (completedByDate[dateKey] || 0) + 1;
@@ -3838,9 +3722,6 @@ async function statsCommand(options) {
3838
3722
  }
3839
3723
  }
3840
3724
  }
3841
-
3842
- // src/commands/search.ts
3843
- import chalk17 from "chalk";
3844
3725
  async function searchCommand(query, options) {
3845
3726
  await autoCheckIfEnabled();
3846
3727
  const filter = {};
@@ -3884,32 +3765,32 @@ async function searchCommand(query, options) {
3884
3765
  }
3885
3766
  if (results.length === 0) {
3886
3767
  console.log("");
3887
- console.log(chalk17.yellow(`\u{1F50D} No specs found matching "${sanitizeUserInput(query)}"`));
3768
+ console.log(chalk16.yellow(`\u{1F50D} No specs found matching "${sanitizeUserInput(query)}"`));
3888
3769
  if (Object.keys(filter).length > 0) {
3889
3770
  const filters = [];
3890
3771
  if (options.status) filters.push(`status=${sanitizeUserInput(options.status)}`);
3891
3772
  if (options.tag) filters.push(`tag=${sanitizeUserInput(options.tag)}`);
3892
3773
  if (options.priority) filters.push(`priority=${sanitizeUserInput(options.priority)}`);
3893
3774
  if (options.assignee) filters.push(`assignee=${sanitizeUserInput(options.assignee)}`);
3894
- console.log(chalk17.gray(`With filters: ${filters.join(", ")}`));
3775
+ console.log(chalk16.gray(`With filters: ${filters.join(", ")}`));
3895
3776
  }
3896
3777
  console.log("");
3897
3778
  return;
3898
3779
  }
3899
3780
  console.log("");
3900
- console.log(chalk17.green(`\u{1F50D} Found ${results.length} spec${results.length === 1 ? "" : "s"} matching "${sanitizeUserInput(query)}"`));
3781
+ console.log(chalk16.green(`\u{1F50D} Found ${results.length} spec${results.length === 1 ? "" : "s"} matching "${sanitizeUserInput(query)}"`));
3901
3782
  if (Object.keys(filter).length > 0) {
3902
3783
  const filters = [];
3903
3784
  if (options.status) filters.push(`status=${sanitizeUserInput(options.status)}`);
3904
3785
  if (options.tag) filters.push(`tag=${sanitizeUserInput(options.tag)}`);
3905
3786
  if (options.priority) filters.push(`priority=${sanitizeUserInput(options.priority)}`);
3906
3787
  if (options.assignee) filters.push(`assignee=${sanitizeUserInput(options.assignee)}`);
3907
- console.log(chalk17.gray(`With filters: ${filters.join(", ")}`));
3788
+ console.log(chalk16.gray(`With filters: ${filters.join(", ")}`));
3908
3789
  }
3909
3790
  console.log("");
3910
3791
  for (const result of results) {
3911
3792
  const { spec, matches } = result;
3912
- console.log(chalk17.cyan(`${spec.frontmatter.status === "in-progress" ? "\u{1F528}" : spec.frontmatter.status === "complete" ? "\u2705" : "\u{1F4C5}"} ${sanitizeUserInput(spec.path)}`));
3793
+ console.log(chalk16.cyan(`${spec.frontmatter.status === "in-progress" ? "\u{1F528}" : spec.frontmatter.status === "complete" ? "\u2705" : "\u{1F4C5}"} ${sanitizeUserInput(spec.path)}`));
3913
3794
  const meta = [];
3914
3795
  if (spec.frontmatter.priority) {
3915
3796
  const priorityEmoji = spec.frontmatter.priority === "critical" ? "\u{1F534}" : spec.frontmatter.priority === "high" ? "\u{1F7E1}" : spec.frontmatter.priority === "medium" ? "\u{1F7E0}" : "\u{1F7E2}";
@@ -3919,34 +3800,30 @@ async function searchCommand(query, options) {
3919
3800
  meta.push(`[${spec.frontmatter.tags.map((tag) => sanitizeUserInput(tag)).join(", ")}]`);
3920
3801
  }
3921
3802
  if (meta.length > 0) {
3922
- console.log(chalk17.gray(` ${meta.join(" \u2022 ")}`));
3803
+ console.log(chalk16.gray(` ${meta.join(" \u2022 ")}`));
3923
3804
  }
3924
3805
  const maxMatches = 3;
3925
3806
  for (let i = 0; i < Math.min(matches.length, maxMatches); i++) {
3926
- console.log(` ${chalk17.gray("Match:")} ${matches[i].trim()}`);
3807
+ console.log(` ${chalk16.gray("Match:")} ${matches[i].trim()}`);
3927
3808
  }
3928
3809
  if (matches.length > maxMatches) {
3929
- console.log(chalk17.gray(` ... and ${matches.length - maxMatches} more match${matches.length - maxMatches === 1 ? "" : "es"}`));
3810
+ console.log(chalk16.gray(` ... and ${matches.length - maxMatches} more match${matches.length - maxMatches === 1 ? "" : "es"}`));
3930
3811
  }
3931
3812
  console.log("");
3932
3813
  }
3933
3814
  }
3934
3815
  function highlightMatch(text, query) {
3935
3816
  const regex = new RegExp(`(${escapeRegex(query)})`, "gi");
3936
- return text.replace(regex, chalk17.yellow("$1"));
3817
+ return text.replace(regex, chalk16.yellow("$1"));
3937
3818
  }
3938
3819
  function escapeRegex(str) {
3939
3820
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3940
3821
  }
3941
-
3942
- // src/commands/deps.ts
3943
- import chalk18 from "chalk";
3944
- import * as path18 from "path";
3945
3822
  async function depsCommand(specPath, options) {
3946
3823
  await autoCheckIfEnabled();
3947
3824
  const config = await loadConfig();
3948
3825
  const cwd = process.cwd();
3949
- const specsDir = path18.join(cwd, config.specsDir);
3826
+ const specsDir = path2.join(cwd, config.specsDir);
3950
3827
  const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
3951
3828
  if (!resolvedPath) {
3952
3829
  throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
@@ -3975,16 +3852,16 @@ async function depsCommand(specPath, options) {
3975
3852
  return;
3976
3853
  }
3977
3854
  console.log("");
3978
- console.log(chalk18.green(`\u{1F4E6} Dependencies for ${chalk18.cyan(sanitizeUserInput(spec.path))}`));
3855
+ console.log(chalk16.green(`\u{1F4E6} Dependencies for ${chalk16.cyan(sanitizeUserInput(spec.path))}`));
3979
3856
  console.log("");
3980
3857
  const hasAnyRelationships = dependsOn.length > 0 || blocks.length > 0 || relatedSpecs.length > 0;
3981
3858
  if (!hasAnyRelationships) {
3982
- console.log(chalk18.gray(" No dependencies or relationships"));
3859
+ console.log(chalk16.gray(" No dependencies or relationships"));
3983
3860
  console.log("");
3984
3861
  return;
3985
3862
  }
3986
3863
  if (dependsOn.length > 0) {
3987
- console.log(chalk18.bold("Depends On:"));
3864
+ console.log(chalk16.bold("Depends On:"));
3988
3865
  for (const dep of dependsOn) {
3989
3866
  const status = getStatusIndicator(dep.frontmatter.status);
3990
3867
  console.log(` \u2192 ${sanitizeUserInput(dep.path)} ${status}`);
@@ -3992,7 +3869,7 @@ async function depsCommand(specPath, options) {
3992
3869
  console.log("");
3993
3870
  }
3994
3871
  if (blocks.length > 0) {
3995
- console.log(chalk18.bold("Required By:"));
3872
+ console.log(chalk16.bold("Required By:"));
3996
3873
  for (const blocked of blocks) {
3997
3874
  const status = getStatusIndicator(blocked.frontmatter.status);
3998
3875
  console.log(` \u2190 ${sanitizeUserInput(blocked.path)} ${status}`);
@@ -4000,7 +3877,7 @@ async function depsCommand(specPath, options) {
4000
3877
  console.log("");
4001
3878
  }
4002
3879
  if (relatedSpecs.length > 0) {
4003
- console.log(chalk18.bold("Related Specs:"));
3880
+ console.log(chalk16.bold("Related Specs:"));
4004
3881
  for (const rel of relatedSpecs) {
4005
3882
  const status = getStatusIndicator(rel.frontmatter.status);
4006
3883
  console.log(` \u27F7 ${sanitizeUserInput(rel.path)} ${status}`);
@@ -4008,7 +3885,7 @@ async function depsCommand(specPath, options) {
4008
3885
  console.log("");
4009
3886
  }
4010
3887
  if (options.graph || dependsOn.length > 0) {
4011
- console.log(chalk18.bold("Dependency Chain:"));
3888
+ console.log(chalk16.bold("Dependency Chain:"));
4012
3889
  const chain = buildDependencyChain(spec, specMap, options.depth || 3);
4013
3890
  displayChain(chain, 0);
4014
3891
  console.log("");
@@ -4022,8 +3899,8 @@ function findDependencies(spec, specMap) {
4022
3899
  if (dep) {
4023
3900
  deps.push(dep);
4024
3901
  } else {
4025
- for (const [path20, s] of specMap.entries()) {
4026
- if (path20.includes(depPath)) {
3902
+ for (const [path21, s] of specMap.entries()) {
3903
+ if (path21.includes(depPath)) {
4027
3904
  deps.push(s);
4028
3905
  break;
4029
3906
  }
@@ -4055,8 +3932,8 @@ function findRelated(spec, specMap) {
4055
3932
  if (rel) {
4056
3933
  related.push(rel);
4057
3934
  } else {
4058
- for (const [path20, s] of specMap.entries()) {
4059
- if (path20.includes(relPath)) {
3935
+ for (const [path21, s] of specMap.entries()) {
3936
+ if (path21.includes(relPath)) {
4060
3937
  related.push(s);
4061
3938
  break;
4062
3939
  }
@@ -4114,7 +3991,7 @@ function buildDependencyChain(spec, specMap, maxDepth, currentDepth = 0, visited
4114
3991
  function displayChain(node, level) {
4115
3992
  const indent = " ".repeat(level);
4116
3993
  const status = getStatusIndicator(node.spec.frontmatter.status);
4117
- const name = level === 0 ? chalk18.cyan(node.spec.path) : node.spec.path;
3994
+ const name = level === 0 ? chalk16.cyan(node.spec.path) : node.spec.path;
4118
3995
  console.log(`${indent}${name} ${status}`);
4119
3996
  for (const dep of node.dependencies) {
4120
3997
  const prefix = " ".repeat(level) + "\u2514\u2500 ";
@@ -4125,10 +4002,6 @@ function displayChain(node, level) {
4125
4002
  }
4126
4003
  }
4127
4004
  }
4128
-
4129
- // src/commands/timeline.ts
4130
- import chalk19 from "chalk";
4131
- import dayjs5 from "dayjs";
4132
4005
  async function timelineCommand(options) {
4133
4006
  await autoCheckIfEnabled();
4134
4007
  const createBar = (count, maxCount, width, char = "\u2501") => {
@@ -4143,13 +4016,13 @@ async function timelineCommand(options) {
4143
4016
  console.log("No specs found.");
4144
4017
  return;
4145
4018
  }
4146
- const today = dayjs5();
4019
+ const today = dayjs2();
4147
4020
  const startDate = today.subtract(days, "day");
4148
4021
  const createdByDate = {};
4149
4022
  const completedByDate = {};
4150
4023
  const createdByMonth = {};
4151
4024
  for (const spec of specs) {
4152
- const created = dayjs5(spec.frontmatter.created);
4025
+ const created = dayjs2(spec.frontmatter.created);
4153
4026
  if (created.isAfter(startDate)) {
4154
4027
  const dateKey = created.format("YYYY-MM-DD");
4155
4028
  createdByDate[dateKey] = (createdByDate[dateKey] || 0) + 1;
@@ -4157,26 +4030,26 @@ async function timelineCommand(options) {
4157
4030
  const monthKey = created.format("MMM YYYY");
4158
4031
  createdByMonth[monthKey] = (createdByMonth[monthKey] || 0) + 1;
4159
4032
  if (spec.frontmatter.completed) {
4160
- const completed = dayjs5(spec.frontmatter.completed);
4033
+ const completed = dayjs2(spec.frontmatter.completed);
4161
4034
  if (completed.isAfter(startDate)) {
4162
4035
  const dateKey = completed.format("YYYY-MM-DD");
4163
4036
  completedByDate[dateKey] = (completedByDate[dateKey] || 0) + 1;
4164
4037
  }
4165
4038
  }
4166
4039
  }
4167
- console.log(chalk19.bold.cyan("\u{1F4C8} Spec Timeline"));
4040
+ console.log(chalk16.bold.cyan("\u{1F4C8} Spec Timeline"));
4168
4041
  console.log("");
4169
4042
  const allDates = /* @__PURE__ */ new Set([...Object.keys(createdByDate), ...Object.keys(completedByDate)]);
4170
4043
  const sortedDates = Array.from(allDates).sort();
4171
4044
  if (sortedDates.length > 0) {
4172
- console.log(chalk19.bold(`\u{1F4C5} Activity (Last ${days} Days)`));
4045
+ console.log(chalk16.bold(`\u{1F4C5} Activity (Last ${days} Days)`));
4173
4046
  console.log("");
4174
4047
  const labelWidth2 = 15;
4175
4048
  const barWidth = 20;
4176
4049
  const specsWidth = 3;
4177
4050
  const colWidth = barWidth + specsWidth;
4178
- console.log(` ${"Date".padEnd(labelWidth2)} ${chalk19.cyan("Created".padEnd(colWidth))} ${chalk19.green("Completed".padEnd(colWidth))}`);
4179
- console.log(` ${chalk19.dim("\u2500".repeat(labelWidth2))} ${chalk19.dim("\u2500".repeat(colWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`);
4051
+ console.log(` ${"Date".padEnd(labelWidth2)} ${chalk16.cyan("Created".padEnd(colWidth))} ${chalk16.green("Completed".padEnd(colWidth))}`);
4052
+ console.log(` ${chalk16.dim("\u2500".repeat(labelWidth2))} ${chalk16.dim("\u2500".repeat(colWidth))} ${chalk16.dim("\u2500".repeat(colWidth))}`);
4180
4053
  const maxCount = Math.max(...Object.values(createdByDate), ...Object.values(completedByDate));
4181
4054
  for (const date of sortedDates) {
4182
4055
  const created = createdByDate[date] || 0;
@@ -4185,61 +4058,61 @@ async function timelineCommand(options) {
4185
4058
  const completedBar = createBar(completed, maxCount, barWidth);
4186
4059
  const createdCol = `${createdBar.padEnd(barWidth)}${created.toString().padStart(specsWidth)}`;
4187
4060
  const completedCol = `${completedBar.padEnd(barWidth)}${completed.toString().padStart(specsWidth)}`;
4188
- console.log(` ${chalk19.dim(date.padEnd(labelWidth2))} ${chalk19.cyan(createdCol)} ${chalk19.green(completedCol)}`);
4061
+ console.log(` ${chalk16.dim(date.padEnd(labelWidth2))} ${chalk16.cyan(createdCol)} ${chalk16.green(completedCol)}`);
4189
4062
  }
4190
4063
  console.log("");
4191
4064
  }
4192
4065
  const sortedMonths = Object.entries(createdByMonth).sort((a, b) => {
4193
- const dateA = dayjs5(a[0], "MMM YYYY");
4194
- const dateB = dayjs5(b[0], "MMM YYYY");
4066
+ const dateA = dayjs2(a[0], "MMM YYYY");
4067
+ const dateB = dayjs2(b[0], "MMM YYYY");
4195
4068
  return dateB.diff(dateA);
4196
4069
  }).slice(0, 6);
4197
4070
  if (sortedMonths.length > 0) {
4198
- console.log(chalk19.bold("\u{1F4CA} Monthly Overview"));
4071
+ console.log(chalk16.bold("\u{1F4CA} Monthly Overview"));
4199
4072
  console.log("");
4200
4073
  const labelWidth2 = 15;
4201
4074
  const barWidth = 20;
4202
4075
  const specsWidth = 3;
4203
4076
  const colWidth = barWidth + specsWidth;
4204
- console.log(` ${"Month".padEnd(labelWidth2)} ${chalk19.magenta("Specs".padEnd(colWidth))}`);
4205
- console.log(` ${chalk19.dim("\u2500".repeat(labelWidth2))} ${chalk19.dim("\u2500".repeat(colWidth))}`);
4077
+ console.log(` ${"Month".padEnd(labelWidth2)} ${chalk16.magenta("Specs".padEnd(colWidth))}`);
4078
+ console.log(` ${chalk16.dim("\u2500".repeat(labelWidth2))} ${chalk16.dim("\u2500".repeat(colWidth))}`);
4206
4079
  const maxCount = Math.max(...sortedMonths.map(([, count]) => count));
4207
4080
  for (const [month, count] of sortedMonths) {
4208
4081
  const bar = createBar(count, maxCount, barWidth);
4209
- console.log(` ${month.padEnd(labelWidth2)} ${chalk19.magenta(bar.padEnd(barWidth))}${chalk19.magenta(count.toString().padStart(specsWidth))}`);
4082
+ console.log(` ${month.padEnd(labelWidth2)} ${chalk16.magenta(bar.padEnd(barWidth))}${chalk16.magenta(count.toString().padStart(specsWidth))}`);
4210
4083
  }
4211
4084
  console.log("");
4212
4085
  }
4213
4086
  const last7Days = specs.filter((s) => {
4214
4087
  if (!s.frontmatter.completed) return false;
4215
- const completed = dayjs5(s.frontmatter.completed);
4088
+ const completed = dayjs2(s.frontmatter.completed);
4216
4089
  return completed.isAfter(today.subtract(7, "day"));
4217
4090
  }).length;
4218
4091
  const last30Days = specs.filter((s) => {
4219
4092
  if (!s.frontmatter.completed) return false;
4220
- const completed = dayjs5(s.frontmatter.completed);
4093
+ const completed = dayjs2(s.frontmatter.completed);
4221
4094
  return completed.isAfter(today.subtract(30, "day"));
4222
4095
  }).length;
4223
- console.log(chalk19.bold("\u2705 Completion Rate"));
4096
+ console.log(chalk16.bold("\u2705 Completion Rate"));
4224
4097
  console.log("");
4225
4098
  const labelWidth = 15;
4226
4099
  const valueWidth = 5;
4227
4100
  console.log(` ${"Period".padEnd(labelWidth)} ${"Specs".padStart(valueWidth)}`);
4228
- console.log(` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`);
4229
- console.log(` ${"Last 7 days".padEnd(labelWidth)} ${chalk19.green(last7Days.toString().padStart(valueWidth))}`);
4230
- console.log(` ${"Last 30 days".padEnd(labelWidth)} ${chalk19.green(last30Days.toString().padStart(valueWidth))}`);
4101
+ console.log(` ${chalk16.dim("\u2500".repeat(labelWidth))} ${chalk16.dim("\u2500".repeat(valueWidth))}`);
4102
+ console.log(` ${"Last 7 days".padEnd(labelWidth)} ${chalk16.green(last7Days.toString().padStart(valueWidth))}`);
4103
+ console.log(` ${"Last 30 days".padEnd(labelWidth)} ${chalk16.green(last30Days.toString().padStart(valueWidth))}`);
4231
4104
  console.log("");
4232
4105
  if (options.byTag) {
4233
4106
  const tagStats = {};
4234
4107
  for (const spec of specs) {
4235
- const created = dayjs5(spec.frontmatter.created);
4108
+ const created = dayjs2(spec.frontmatter.created);
4236
4109
  const isInRange = created.isAfter(startDate);
4237
4110
  if (isInRange && spec.frontmatter.tags) {
4238
4111
  for (const tag of spec.frontmatter.tags) {
4239
4112
  if (!tagStats[tag]) tagStats[tag] = { created: 0, completed: 0 };
4240
4113
  tagStats[tag].created++;
4241
4114
  if (spec.frontmatter.completed) {
4242
- const completed = dayjs5(spec.frontmatter.completed);
4115
+ const completed = dayjs2(spec.frontmatter.completed);
4243
4116
  if (completed.isAfter(startDate)) {
4244
4117
  tagStats[tag].completed++;
4245
4118
  }
@@ -4249,9 +4122,9 @@ async function timelineCommand(options) {
4249
4122
  }
4250
4123
  const sortedTags = Object.entries(tagStats).sort((a, b) => b[1].created - a[1].created).slice(0, 10);
4251
4124
  if (sortedTags.length > 0) {
4252
- console.log(chalk19.bold("\u{1F3F7}\uFE0F By Tag"));
4125
+ console.log(chalk16.bold("\u{1F3F7}\uFE0F By Tag"));
4253
4126
  for (const [tag, stats] of sortedTags) {
4254
- console.log(` ${chalk19.dim("#")}${tag.padEnd(20)} ${chalk19.cyan(stats.created)} created \xB7 ${chalk19.green(stats.completed)} completed`);
4127
+ console.log(` ${chalk16.dim("#")}${tag.padEnd(20)} ${chalk16.cyan(stats.created)} created \xB7 ${chalk16.green(stats.completed)} completed`);
4255
4128
  }
4256
4129
  console.log("");
4257
4130
  }
@@ -4260,14 +4133,14 @@ async function timelineCommand(options) {
4260
4133
  const assigneeStats = {};
4261
4134
  for (const spec of specs) {
4262
4135
  if (!spec.frontmatter.assignee) continue;
4263
- const created = dayjs5(spec.frontmatter.created);
4136
+ const created = dayjs2(spec.frontmatter.created);
4264
4137
  const isInRange = created.isAfter(startDate);
4265
4138
  if (isInRange) {
4266
4139
  const assignee = spec.frontmatter.assignee;
4267
4140
  if (!assigneeStats[assignee]) assigneeStats[assignee] = { created: 0, completed: 0 };
4268
4141
  assigneeStats[assignee].created++;
4269
4142
  if (spec.frontmatter.completed) {
4270
- const completed = dayjs5(spec.frontmatter.completed);
4143
+ const completed = dayjs2(spec.frontmatter.completed);
4271
4144
  if (completed.isAfter(startDate)) {
4272
4145
  assigneeStats[assignee].completed++;
4273
4146
  }
@@ -4276,18 +4149,14 @@ async function timelineCommand(options) {
4276
4149
  }
4277
4150
  const sortedAssignees = Object.entries(assigneeStats).sort((a, b) => b[1].created - a[1].created);
4278
4151
  if (sortedAssignees.length > 0) {
4279
- console.log(chalk19.bold("\u{1F464} By Assignee"));
4152
+ console.log(chalk16.bold("\u{1F464} By Assignee"));
4280
4153
  for (const [assignee, stats] of sortedAssignees) {
4281
- console.log(` ${chalk19.dim("@")}${assignee.padEnd(20)} ${chalk19.cyan(stats.created)} created \xB7 ${chalk19.green(stats.completed)} completed`);
4154
+ console.log(` ${chalk16.dim("@")}${assignee.padEnd(20)} ${chalk16.cyan(stats.created)} created \xB7 ${chalk16.green(stats.completed)} completed`);
4282
4155
  }
4283
4156
  console.log("");
4284
4157
  }
4285
4158
  }
4286
4159
  }
4287
-
4288
- // src/commands/gantt.ts
4289
- import chalk20 from "chalk";
4290
- import dayjs6 from "dayjs";
4291
4160
  var SPEC_COLUMN_WIDTH = 43;
4292
4161
  var COLUMN_SEPARATOR = " ";
4293
4162
  var SPEC_INDENT = " ";
@@ -4300,10 +4169,10 @@ var STATUS_CONFIG2 = {
4300
4169
  archived: { emoji: "\u{1F4E6}", color: "gray" }
4301
4170
  };
4302
4171
  var PRIORITY_CONFIG3 = {
4303
- critical: { emoji: "\u{1F534}", label: "CRITICAL", colorFn: chalk20.red },
4304
- high: { emoji: "\u{1F7E0}", label: "HIGH", colorFn: chalk20.hex("#FFA500") },
4305
- medium: { emoji: "\u{1F7E1}", label: "MEDIUM", colorFn: chalk20.yellow },
4306
- low: { emoji: "\u{1F7E2}", label: "LOW", colorFn: chalk20.green }
4172
+ critical: { emoji: "\u{1F534}", label: "CRITICAL", colorFn: chalk16.red },
4173
+ high: { emoji: "\u{1F7E0}", label: "HIGH", colorFn: chalk16.hex("#FFA500") },
4174
+ medium: { emoji: "\u{1F7E1}", label: "MEDIUM", colorFn: chalk16.yellow },
4175
+ low: { emoji: "\u{1F7E2}", label: "LOW", colorFn: chalk16.green }
4307
4176
  };
4308
4177
  async function ganttCommand(options) {
4309
4178
  await autoCheckIfEnabled();
@@ -4326,8 +4195,8 @@ async function ganttCommand(options) {
4326
4195
  return spec.frontmatter.status !== "archived";
4327
4196
  });
4328
4197
  if (relevantSpecs.length === 0) {
4329
- console.log(chalk20.dim("No active specs found."));
4330
- console.log(chalk20.dim("Tip: Use --show-complete to include completed specs."));
4198
+ console.log(chalk16.dim("No active specs found."));
4199
+ console.log(chalk16.dim("Tip: Use --show-complete to include completed specs."));
4331
4200
  return;
4332
4201
  }
4333
4202
  const groupedSpecs = {
@@ -4336,12 +4205,9 @@ async function ganttCommand(options) {
4336
4205
  medium: [],
4337
4206
  low: []
4338
4207
  };
4339
- const noPrioritySpecs = [];
4340
4208
  for (const spec of relevantSpecs) {
4341
4209
  if (spec.frontmatter.priority && spec.frontmatter.priority in groupedSpecs) {
4342
4210
  groupedSpecs[spec.frontmatter.priority].push(spec);
4343
- } else {
4344
- noPrioritySpecs.push(spec);
4345
4211
  }
4346
4212
  }
4347
4213
  const sortSpecs = (specs2) => {
@@ -4353,20 +4219,20 @@ async function ganttCommand(options) {
4353
4219
  if (a.frontmatter.due && !b.frontmatter.due) return -1;
4354
4220
  if (!a.frontmatter.due && b.frontmatter.due) return 1;
4355
4221
  if (a.frontmatter.due && b.frontmatter.due) {
4356
- return dayjs6(a.frontmatter.due).diff(dayjs6(b.frontmatter.due));
4222
+ return dayjs2(a.frontmatter.due).diff(dayjs2(b.frontmatter.due));
4357
4223
  }
4358
4224
  return 0;
4359
4225
  });
4360
4226
  };
4361
- const today = dayjs6();
4227
+ const today = dayjs2();
4362
4228
  const startDate = today.startOf("week");
4363
4229
  const endDate = startDate.add(weeks, "week");
4364
4230
  const inProgress = relevantSpecs.filter((s) => s.frontmatter.status === "in-progress").length;
4365
4231
  const planned = relevantSpecs.filter((s) => s.frontmatter.status === "planned").length;
4366
4232
  const overdue = relevantSpecs.filter(
4367
- (s) => s.frontmatter.due && dayjs6(s.frontmatter.due).isBefore(today) && s.frontmatter.status !== "complete"
4233
+ (s) => s.frontmatter.due && dayjs2(s.frontmatter.due).isBefore(today) && s.frontmatter.status !== "complete"
4368
4234
  ).length;
4369
- console.log(chalk20.bold.cyan(`\u{1F4C5} Gantt Chart (${weeks} weeks from ${startDate.format("MMM D, YYYY")})`));
4235
+ console.log(chalk16.bold.cyan(`\u{1F4C5} Gantt Chart (${weeks} weeks from ${startDate.format("MMM D, YYYY")})`));
4370
4236
  console.log("");
4371
4237
  const specHeader = "Spec".padEnd(SPEC_COLUMN_WIDTH);
4372
4238
  const timelineHeader = "Timeline";
@@ -4378,17 +4244,17 @@ async function ganttCommand(options) {
4378
4244
  calendarDates.push(dateStr);
4379
4245
  }
4380
4246
  const dateRow = " ".repeat(SPEC_COLUMN_WIDTH) + COLUMN_SEPARATOR + calendarDates.join("");
4381
- console.log(chalk20.dim(dateRow));
4247
+ console.log(chalk16.dim(dateRow));
4382
4248
  const specSeparator = "\u2500".repeat(SPEC_COLUMN_WIDTH);
4383
4249
  const timelineSeparator = "\u2500".repeat(timelineColumnWidth);
4384
- console.log(chalk20.dim(specSeparator + COLUMN_SEPARATOR + timelineSeparator));
4250
+ console.log(chalk16.dim(specSeparator + COLUMN_SEPARATOR + timelineSeparator));
4385
4251
  const todayWeekOffset = today.diff(startDate, "week");
4386
4252
  const todayMarkerPos = todayWeekOffset * 8;
4387
4253
  let todayMarker = " ".repeat(SPEC_COLUMN_WIDTH) + COLUMN_SEPARATOR;
4388
4254
  if (todayMarkerPos >= 0 && todayMarkerPos < timelineColumnWidth) {
4389
4255
  todayMarker += " ".repeat(todayMarkerPos) + "\u2502 Today";
4390
4256
  }
4391
- console.log(chalk20.dim(todayMarker));
4257
+ console.log(chalk16.dim(todayMarker));
4392
4258
  console.log("");
4393
4259
  const priorities = ["critical", "high", "medium", "low"];
4394
4260
  for (const priority of priorities) {
@@ -4406,13 +4272,12 @@ async function ganttCommand(options) {
4406
4272
  const summaryParts = [];
4407
4273
  if (inProgress > 0) summaryParts.push(`${inProgress} in-progress`);
4408
4274
  if (planned > 0) summaryParts.push(`${planned} planned`);
4409
- if (overdue > 0) summaryParts.push(chalk20.red(`${overdue} overdue`));
4410
- console.log(chalk20.bold("Summary: ") + summaryParts.join(" \xB7 "));
4411
- console.log(chalk20.dim('\u{1F4A1} Tip: Add "due: YYYY-MM-DD" to frontmatter for timeline planning'));
4275
+ if (overdue > 0) summaryParts.push(chalk16.red(`${overdue} overdue`));
4276
+ console.log(chalk16.bold("Summary: ") + summaryParts.join(" \xB7 "));
4277
+ console.log(chalk16.dim('\u{1F4A1} Tip: Add "due: YYYY-MM-DD" to frontmatter for timeline planning'));
4412
4278
  }
4413
4279
  function renderSpecRow(spec, startDate, endDate, weeks, today) {
4414
4280
  const statusConfig = STATUS_CONFIG2[spec.frontmatter.status];
4415
- const timelineColumnWidth = weeks * 8;
4416
4281
  const emoji = statusConfig.emoji;
4417
4282
  const maxNameLength = SPEC_COLUMN_WIDTH - 2;
4418
4283
  let specName = spec.name;
@@ -4422,7 +4287,7 @@ function renderSpecRow(spec, startDate, endDate, weeks, today) {
4422
4287
  const specColumn = `${SPEC_INDENT}${emoji} ${specName}`.padEnd(SPEC_COLUMN_WIDTH);
4423
4288
  let timelineColumn;
4424
4289
  if (!spec.frontmatter.due) {
4425
- timelineColumn = chalk20.dim("(no due date set)");
4290
+ timelineColumn = chalk16.dim("(no due date set)");
4426
4291
  } else {
4427
4292
  timelineColumn = renderTimelineBar(spec, startDate, endDate, weeks, today);
4428
4293
  }
@@ -4431,7 +4296,7 @@ function renderSpecRow(spec, startDate, endDate, weeks, today) {
4431
4296
  function renderTimelineBar(spec, startDate, endDate, weeks, today) {
4432
4297
  const charsPerWeek = 8;
4433
4298
  const totalChars = weeks * charsPerWeek;
4434
- const due = dayjs6(spec.frontmatter.due);
4299
+ const due = dayjs2(spec.frontmatter.due);
4435
4300
  const specStart = today;
4436
4301
  const startDaysFromStart = specStart.diff(startDate, "day");
4437
4302
  const dueDaysFromStart = due.diff(startDate, "day");
@@ -4445,13 +4310,13 @@ function renderTimelineBar(spec, startDate, endDate, weeks, today) {
4445
4310
  result += " ".repeat(barStart);
4446
4311
  }
4447
4312
  if (spec.frontmatter.status === "complete") {
4448
- result += chalk20.green(FILLED_BAR_CHAR.repeat(barLength));
4313
+ result += chalk16.green(FILLED_BAR_CHAR.repeat(barLength));
4449
4314
  } else if (spec.frontmatter.status === "in-progress") {
4450
4315
  const halfLength = Math.floor(barLength / 2);
4451
- result += chalk20.yellow(FILLED_BAR_CHAR.repeat(halfLength));
4452
- result += chalk20.dim(EMPTY_BAR_CHAR.repeat(barLength - halfLength));
4316
+ result += chalk16.yellow(FILLED_BAR_CHAR.repeat(halfLength));
4317
+ result += chalk16.dim(EMPTY_BAR_CHAR.repeat(barLength - halfLength));
4453
4318
  } else {
4454
- result += chalk20.dim(EMPTY_BAR_CHAR.repeat(barLength));
4319
+ result += chalk16.dim(EMPTY_BAR_CHAR.repeat(barLength));
4455
4320
  }
4456
4321
  const trailingSpace = totalChars - barEnd;
4457
4322
  if (trailingSpace > 0) {
@@ -4459,18 +4324,157 @@ function renderTimelineBar(spec, startDate, endDate, weeks, today) {
4459
4324
  }
4460
4325
  return result;
4461
4326
  }
4462
-
4463
- // src/commands/viewer.ts
4464
- import * as fs14 from "fs/promises";
4465
- import * as path19 from "path";
4466
- import chalk21 from "chalk";
4467
- import { marked } from "marked";
4468
- import { markedTerminal } from "marked-terminal";
4469
- import { spawn } from "child_process";
4327
+ async function tokensCommand(specPath, options = {}) {
4328
+ await autoCheckIfEnabled();
4329
+ const counter = new TokenCounter();
4330
+ try {
4331
+ const config = await loadConfig();
4332
+ const cwd = process.cwd();
4333
+ const specsDir = path2.join(cwd, config.specsDir);
4334
+ const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
4335
+ if (!resolvedPath) {
4336
+ throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
4337
+ }
4338
+ const specName = path2.basename(resolvedPath);
4339
+ const result = await counter.countSpec(resolvedPath, {
4340
+ detailed: options.detailed,
4341
+ includeSubSpecs: options.includeSubSpecs
4342
+ });
4343
+ if (options.json) {
4344
+ console.log(JSON.stringify({
4345
+ spec: specName,
4346
+ path: resolvedPath,
4347
+ ...result
4348
+ }, null, 2));
4349
+ return;
4350
+ }
4351
+ console.log(chalk16.bold.cyan(`\u{1F4CA} Token Count: ${specName}`));
4352
+ console.log("");
4353
+ const indicators = counter.getPerformanceIndicators(result.total);
4354
+ const levelEmoji = indicators.level === "excellent" ? "\u2705" : indicators.level === "good" ? "\u{1F44D}" : indicators.level === "warning" ? "\u26A0\uFE0F" : "\u{1F534}";
4355
+ console.log(` Total: ${chalk16.cyan(result.total.toLocaleString())} tokens ${levelEmoji}`);
4356
+ console.log("");
4357
+ if (result.files.length > 1 || options.detailed) {
4358
+ console.log(chalk16.bold("Files:"));
4359
+ console.log("");
4360
+ for (const file of result.files) {
4361
+ const lineInfo = file.lines ? chalk16.dim(` (${file.lines} lines)`) : "";
4362
+ console.log(` ${file.path.padEnd(25)} ${chalk16.cyan(file.tokens.toLocaleString().padStart(6))} tokens${lineInfo}`);
4363
+ }
4364
+ console.log("");
4365
+ }
4366
+ if (options.detailed && result.breakdown) {
4367
+ const b = result.breakdown;
4368
+ const total = b.code + b.prose + b.tables + b.frontmatter;
4369
+ console.log(chalk16.bold("Content Breakdown:"));
4370
+ console.log("");
4371
+ console.log(` Prose ${chalk16.cyan(b.prose.toLocaleString().padStart(6))} tokens ${chalk16.dim(`(${Math.round(b.prose / total * 100)}%)`)}`);
4372
+ console.log(` Code ${chalk16.cyan(b.code.toLocaleString().padStart(6))} tokens ${chalk16.dim(`(${Math.round(b.code / total * 100)}%)`)}`);
4373
+ console.log(` Tables ${chalk16.cyan(b.tables.toLocaleString().padStart(6))} tokens ${chalk16.dim(`(${Math.round(b.tables / total * 100)}%)`)}`);
4374
+ console.log(` Frontmatter ${chalk16.cyan(b.frontmatter.toLocaleString().padStart(6))} tokens ${chalk16.dim(`(${Math.round(b.frontmatter / total * 100)}%)`)}`);
4375
+ console.log("");
4376
+ }
4377
+ console.log(chalk16.bold("Performance Indicators:"));
4378
+ console.log("");
4379
+ const costColor = indicators.costMultiplier < 2 ? chalk16.green : indicators.costMultiplier < 4 ? chalk16.yellow : chalk16.red;
4380
+ const effectivenessColor = indicators.effectiveness >= 95 ? chalk16.green : indicators.effectiveness >= 85 ? chalk16.yellow : chalk16.red;
4381
+ console.log(` Cost multiplier: ${costColor(`${indicators.costMultiplier}x`)} ${chalk16.dim("vs 1,200 token baseline")}`);
4382
+ console.log(` AI effectiveness: ${effectivenessColor(`~${indicators.effectiveness}%`)} ${chalk16.dim("(hypothesis)")}`);
4383
+ console.log(` Context Economy: ${levelEmoji} ${indicators.recommendation}`);
4384
+ console.log("");
4385
+ if (!options.includeSubSpecs && result.files.length === 1) {
4386
+ console.log(chalk16.dim("\u{1F4A1} Use `--include-sub-specs` to count all sub-spec files"));
4387
+ }
4388
+ } finally {
4389
+ counter.dispose();
4390
+ }
4391
+ }
4392
+ async function tokensAllCommand(options = {}) {
4393
+ await autoCheckIfEnabled();
4394
+ const specs = await withSpinner(
4395
+ "Loading specs...",
4396
+ () => loadAllSpecs({ includeArchived: false })
4397
+ );
4398
+ if (specs.length === 0) {
4399
+ console.log("No specs found.");
4400
+ return;
4401
+ }
4402
+ const counter = new TokenCounter();
4403
+ const results = [];
4404
+ try {
4405
+ for (const spec of specs) {
4406
+ const result = await counter.countSpec(spec.fullPath, {
4407
+ includeSubSpecs: options.includeSubSpecs
4408
+ });
4409
+ const indicators = counter.getPerformanceIndicators(result.total);
4410
+ const totalLines = result.files.reduce((sum, f) => sum + (f.lines || 0), 0);
4411
+ results.push({
4412
+ name: spec.name,
4413
+ path: spec.fullPath,
4414
+ tokens: result.total,
4415
+ lines: totalLines,
4416
+ level: indicators.level
4417
+ });
4418
+ }
4419
+ } finally {
4420
+ counter.dispose();
4421
+ }
4422
+ const sortBy = options.sortBy || "tokens";
4423
+ results.sort((a, b) => {
4424
+ if (sortBy === "tokens") return b.tokens - a.tokens;
4425
+ if (sortBy === "lines") return b.lines - a.lines;
4426
+ return a.name.localeCompare(b.name);
4427
+ });
4428
+ if (options.json) {
4429
+ console.log(JSON.stringify(results, null, 2));
4430
+ return;
4431
+ }
4432
+ console.log(chalk16.bold.cyan("\u{1F4CA} Token Counts"));
4433
+ console.log("");
4434
+ console.log(chalk16.dim(`Sorted by: ${sortBy}`));
4435
+ console.log("");
4436
+ const totalTokens = results.reduce((sum, r) => sum + r.tokens, 0);
4437
+ const avgTokens = Math.round(totalTokens / results.length);
4438
+ const warningCount = results.filter((r) => r.level === "warning" || r.level === "problem").length;
4439
+ console.log(chalk16.bold("Summary:"));
4440
+ console.log("");
4441
+ console.log(` Total specs: ${chalk16.cyan(results.length)}`);
4442
+ console.log(` Total tokens: ${chalk16.cyan(totalTokens.toLocaleString())}`);
4443
+ console.log(` Average tokens: ${chalk16.cyan(avgTokens.toLocaleString())}`);
4444
+ if (warningCount > 0) {
4445
+ console.log(` Needs review: ${chalk16.yellow(warningCount)} specs ${chalk16.dim("(\u26A0\uFE0F or \u{1F534})")}`);
4446
+ }
4447
+ console.log("");
4448
+ const nameCol = 35;
4449
+ const tokensCol = 10;
4450
+ const linesCol = 8;
4451
+ console.log(chalk16.bold(
4452
+ "Spec".padEnd(nameCol) + "Tokens".padStart(tokensCol) + "Lines".padStart(linesCol) + " Status"
4453
+ ));
4454
+ console.log(chalk16.dim("\u2500".repeat(nameCol + tokensCol + linesCol + 10)));
4455
+ const displayCount = options.all ? results.length : Math.min(20, results.length);
4456
+ for (let i = 0; i < displayCount; i++) {
4457
+ const r = results[i];
4458
+ const emoji = r.level === "excellent" ? "\u2705" : r.level === "good" ? "\u{1F44D}" : r.level === "warning" ? "\u26A0\uFE0F" : "\u{1F534}";
4459
+ const tokensColor = r.level === "excellent" || r.level === "good" ? chalk16.cyan : r.level === "warning" ? chalk16.yellow : chalk16.red;
4460
+ const name = r.name.length > nameCol - 2 ? r.name.substring(0, nameCol - 3) + "\u2026" : r.name;
4461
+ console.log(
4462
+ name.padEnd(nameCol) + tokensColor(r.tokens.toLocaleString().padStart(tokensCol)) + chalk16.dim(r.lines.toString().padStart(linesCol)) + ` ${emoji}`
4463
+ );
4464
+ }
4465
+ if (results.length > displayCount) {
4466
+ console.log("");
4467
+ console.log(chalk16.dim(`... and ${results.length - displayCount} more specs`));
4468
+ console.log(chalk16.dim(`Use --all to show all specs`));
4469
+ }
4470
+ console.log("");
4471
+ console.log(chalk16.dim("Legend: \u2705 excellent (<2K) | \u{1F44D} good (<3.5K) | \u26A0\uFE0F warning (<5K) | \u{1F534} problem (>5K)"));
4472
+ console.log("");
4473
+ }
4470
4474
  marked.use(markedTerminal());
4471
4475
  async function readSpecContent(specPath, cwd = process.cwd()) {
4472
4476
  const config = await loadConfig(cwd);
4473
- const specsDir = path19.join(cwd, config.specsDir);
4477
+ const specsDir = path2.join(cwd, config.specsDir);
4474
4478
  let resolvedPath = null;
4475
4479
  let targetFile = null;
4476
4480
  const pathParts = specPath.split("/").filter((p) => p);
@@ -4479,9 +4483,9 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
4479
4483
  const filePart = pathParts[pathParts.length - 1];
4480
4484
  resolvedPath = await resolveSpecPath(specPart, cwd, specsDir);
4481
4485
  if (resolvedPath) {
4482
- targetFile = path19.join(resolvedPath, filePart);
4486
+ targetFile = path2.join(resolvedPath, filePart);
4483
4487
  try {
4484
- await fs14.access(targetFile);
4488
+ await fs9.access(targetFile);
4485
4489
  } catch {
4486
4490
  return null;
4487
4491
  }
@@ -4500,8 +4504,8 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
4500
4504
  if (!targetFile) {
4501
4505
  return null;
4502
4506
  }
4503
- const rawContent = await fs14.readFile(targetFile, "utf-8");
4504
- const fileName = path19.basename(targetFile);
4507
+ const rawContent = await fs9.readFile(targetFile, "utf-8");
4508
+ const fileName = path2.basename(targetFile);
4505
4509
  const isSubSpec = fileName !== config.structure.defaultFile;
4506
4510
  let frontmatter = null;
4507
4511
  if (!isSubSpec) {
@@ -4530,7 +4534,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
4530
4534
  }
4531
4535
  }
4532
4536
  const content = lines.slice(contentStartIndex).join("\n").trim();
4533
- const specName = path19.basename(resolvedPath);
4537
+ const specName = path2.basename(resolvedPath);
4534
4538
  const displayName = isSubSpec ? `${specName}/${fileName}` : specName;
4535
4539
  return {
4536
4540
  frontmatter,
@@ -4551,7 +4555,7 @@ function formatFrontmatter(frontmatter) {
4551
4555
  archived: "\u{1F4E6}"
4552
4556
  };
4553
4557
  const statusEmoji = statusEmojis[frontmatter.status] || "\u{1F4C4}";
4554
- lines.push(chalk21.bold(`${statusEmoji} Status: `) + chalk21.cyan(frontmatter.status));
4558
+ lines.push(chalk16.bold(`${statusEmoji} Status: `) + chalk16.cyan(frontmatter.status));
4555
4559
  if (frontmatter.priority) {
4556
4560
  const priorityEmojis = {
4557
4561
  low: "\u{1F7E2}",
@@ -4560,25 +4564,25 @@ function formatFrontmatter(frontmatter) {
4560
4564
  critical: "\u{1F534}"
4561
4565
  };
4562
4566
  const priorityEmoji = priorityEmojis[frontmatter.priority] || "";
4563
- lines.push(chalk21.bold(`${priorityEmoji} Priority: `) + chalk21.yellow(frontmatter.priority));
4567
+ lines.push(chalk16.bold(`${priorityEmoji} Priority: `) + chalk16.yellow(frontmatter.priority));
4564
4568
  }
4565
4569
  if (frontmatter.created) {
4566
- lines.push(chalk21.bold("\u{1F4C6} Created: ") + chalk21.gray(String(frontmatter.created)));
4570
+ lines.push(chalk16.bold("\u{1F4C6} Created: ") + chalk16.gray(String(frontmatter.created)));
4567
4571
  }
4568
4572
  if (frontmatter.tags && frontmatter.tags.length > 0) {
4569
- const tagStr = frontmatter.tags.map((tag) => chalk21.blue(`#${tag}`)).join(" ");
4570
- lines.push(chalk21.bold("\u{1F3F7}\uFE0F Tags: ") + tagStr);
4573
+ const tagStr = frontmatter.tags.map((tag) => chalk16.blue(`#${tag}`)).join(" ");
4574
+ lines.push(chalk16.bold("\u{1F3F7}\uFE0F Tags: ") + tagStr);
4571
4575
  }
4572
4576
  if (frontmatter.assignee) {
4573
- lines.push(chalk21.bold("\u{1F464} Assignee: ") + chalk21.green(frontmatter.assignee));
4577
+ lines.push(chalk16.bold("\u{1F464} Assignee: ") + chalk16.green(frontmatter.assignee));
4574
4578
  }
4575
4579
  const standardFields = ["status", "priority", "created", "tags", "assignee"];
4576
4580
  const customFields = Object.entries(frontmatter).filter(([key]) => !standardFields.includes(key)).filter(([_, value]) => value !== void 0 && value !== null);
4577
4581
  if (customFields.length > 0) {
4578
4582
  lines.push("");
4579
- lines.push(chalk21.bold("Custom Fields:"));
4583
+ lines.push(chalk16.bold("Custom Fields:"));
4580
4584
  for (const [key, value] of customFields) {
4581
- lines.push(` ${chalk21.gray(key)}: ${chalk21.white(String(value))}`);
4585
+ lines.push(` ${chalk16.gray(key)}: ${chalk16.white(String(value))}`);
4582
4586
  }
4583
4587
  }
4584
4588
  return lines.join("\n");
@@ -4586,11 +4590,11 @@ function formatFrontmatter(frontmatter) {
4586
4590
  function displayFormattedSpec(spec) {
4587
4591
  const output = [];
4588
4592
  output.push("");
4589
- output.push(chalk21.bold.cyan(`\u2501\u2501\u2501 ${spec.name} \u2501\u2501\u2501`));
4593
+ output.push(chalk16.bold.cyan(`\u2501\u2501\u2501 ${spec.name} \u2501\u2501\u2501`));
4590
4594
  output.push("");
4591
4595
  output.push(formatFrontmatter(spec.frontmatter));
4592
4596
  output.push("");
4593
- output.push(chalk21.gray("\u2500".repeat(60)));
4597
+ output.push(chalk16.gray("\u2500".repeat(60)));
4594
4598
  output.push("");
4595
4599
  return output.join("\n");
4596
4600
  }
@@ -4620,7 +4624,7 @@ async function viewCommand(specPath, options = {}) {
4620
4624
  async function openCommand(specPath, options = {}) {
4621
4625
  const cwd = process.cwd();
4622
4626
  const config = await loadConfig(cwd);
4623
- const specsDir = path19.join(cwd, config.specsDir);
4627
+ const specsDir = path2.join(cwd, config.specsDir);
4624
4628
  let resolvedPath = null;
4625
4629
  let targetFile = null;
4626
4630
  const pathParts = specPath.split("/").filter((p) => p);
@@ -4629,9 +4633,9 @@ async function openCommand(specPath, options = {}) {
4629
4633
  const filePart = pathParts[pathParts.length - 1];
4630
4634
  resolvedPath = await resolveSpecPath(specPart, cwd, specsDir);
4631
4635
  if (resolvedPath) {
4632
- targetFile = path19.join(resolvedPath, filePart);
4636
+ targetFile = path2.join(resolvedPath, filePart);
4633
4637
  try {
4634
- await fs14.access(targetFile);
4638
+ await fs9.access(targetFile);
4635
4639
  } catch {
4636
4640
  targetFile = null;
4637
4641
  }
@@ -4649,7 +4653,6 @@ async function openCommand(specPath, options = {}) {
4649
4653
  } else if (!targetFile) {
4650
4654
  throw new Error(`Sub-spec file not found: ${specPath}`);
4651
4655
  }
4652
- const specFile = targetFile;
4653
4656
  let editor = options.editor;
4654
4657
  if (!editor) {
4655
4658
  editor = process.env.VISUAL || process.env.EDITOR;
@@ -4664,7 +4667,7 @@ async function openCommand(specPath, options = {}) {
4664
4667
  editor = "xdg-open";
4665
4668
  }
4666
4669
  }
4667
- console.log(chalk21.gray(`Opening ${targetFile} with ${editor}...`));
4670
+ console.log(chalk16.gray(`Opening ${targetFile} with ${editor}...`));
4668
4671
  const child = spawn(editor, [targetFile], {
4669
4672
  stdio: "inherit",
4670
4673
  shell: true
@@ -4694,9 +4697,6 @@ async function openCommand(specPath, options = {}) {
4694
4697
  });
4695
4698
  }
4696
4699
  }
4697
-
4698
- // src/commands/mcp.ts
4699
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4700
4700
  async function mcpCommand() {
4701
4701
  try {
4702
4702
  const server = await createMcpServer();
@@ -4708,15 +4708,10 @@ async function mcpCommand() {
4708
4708
  process.exit(1);
4709
4709
  }
4710
4710
  }
4711
-
4712
- // src/mcp-server.ts
4713
- import { readFileSync } from "fs";
4714
- import { fileURLToPath as fileURLToPath2 } from "url";
4715
- import { dirname as dirname3, join as join18 } from "path";
4716
- var __filename2 = fileURLToPath2(import.meta.url);
4717
- var __dirname3 = dirname3(__filename2);
4711
+ var __filename = fileURLToPath(import.meta.url);
4712
+ var __dirname2 = dirname(__filename);
4718
4713
  var packageJson = JSON.parse(
4719
- readFileSync(join18(__dirname3, "../package.json"), "utf-8")
4714
+ readFileSync(join(__dirname2, "../package.json"), "utf-8")
4720
4715
  );
4721
4716
  function formatErrorMessage(prefix, error) {
4722
4717
  const errorMsg = error instanceof Error ? error.message : String(error);
@@ -5626,31 +5621,6 @@ Please search for this topic and show me the dependencies between related specs.
5626
5621
  return server;
5627
5622
  }
5628
5623
 
5629
- export {
5630
- checkSpecs,
5631
- createSpec,
5632
- archiveSpec,
5633
- listSpecs,
5634
- updateSpec,
5635
- backfillTimestamps,
5636
- listTemplates,
5637
- showTemplate,
5638
- addTemplate,
5639
- removeTemplate,
5640
- copyTemplate,
5641
- initProject,
5642
- filesCommand,
5643
- validateCommand,
5644
- migrateCommand,
5645
- boardCommand,
5646
- statsCommand,
5647
- searchCommand,
5648
- depsCommand,
5649
- timelineCommand,
5650
- ganttCommand,
5651
- viewCommand,
5652
- openCommand,
5653
- createMcpServer,
5654
- mcpCommand
5655
- };
5656
- //# sourceMappingURL=chunk-J7ZSZ5VJ.js.map
5624
+ export { addTemplate, archiveSpec, backfillTimestamps, boardCommand, checkSpecs, copyTemplate, createMcpServer, createSpec, depsCommand, filesCommand, ganttCommand, initProject, listSpecs, listTemplates, mcpCommand, migrateCommand, openCommand, removeTemplate, searchCommand, showTemplate, statsCommand, timelineCommand, tokensAllCommand, tokensCommand, updateSpec, validateCommand, viewCommand };
5625
+ //# sourceMappingURL=chunk-ER23B6KS.js.map
5626
+ //# sourceMappingURL=chunk-ER23B6KS.js.map