lean-spec 0.2.5-dev.20251124045153 → 0.2.5-dev.20251124054130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-IGNO4GX2.js → chunk-6FKLWECL.js} +760 -550
- package/dist/chunk-6FKLWECL.js.map +1 -0
- package/dist/cli.js +6 -1
- package/dist/cli.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
- package/templates/examples/api-refactor/README.md +81 -0
- package/templates/examples/api-refactor/package.json +16 -0
- package/templates/examples/api-refactor/src/app.js +40 -0
- package/templates/examples/api-refactor/src/services/currencyService.js +43 -0
- package/templates/examples/api-refactor/src/services/timezoneService.js +41 -0
- package/templates/examples/api-refactor/src/services/weatherService.js +42 -0
- package/templates/examples/dark-theme/README.md +55 -0
- package/templates/examples/dark-theme/package.json +16 -0
- package/templates/examples/dark-theme/src/public/app.js +92 -0
- package/templates/examples/dark-theme/src/public/index.html +38 -0
- package/templates/examples/dark-theme/src/public/style.css +163 -0
- package/templates/examples/dark-theme/src/server.js +17 -0
- package/templates/examples/dashboard-widgets/README.md +70 -0
- package/templates/examples/dashboard-widgets/index.html +12 -0
- package/templates/examples/dashboard-widgets/package.json +22 -0
- package/templates/examples/dashboard-widgets/src/App.css +20 -0
- package/templates/examples/dashboard-widgets/src/App.jsx +16 -0
- package/templates/examples/dashboard-widgets/src/components/Dashboard.css +17 -0
- package/templates/examples/dashboard-widgets/src/components/Dashboard.jsx +15 -0
- package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.css +23 -0
- package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.jsx +16 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.css +33 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.jsx +28 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.css +24 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.jsx +22 -0
- package/templates/examples/dashboard-widgets/src/index.css +13 -0
- package/templates/examples/dashboard-widgets/src/main.jsx +10 -0
- package/templates/examples/dashboard-widgets/src/utils/mockData.js +30 -0
- package/templates/examples/dashboard-widgets/vite.config.js +6 -0
- package/dist/chunk-IGNO4GX2.js.map +0 -1
|
@@ -3,12 +3,12 @@ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mc
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { readFileSync, existsSync } from 'fs';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import * as
|
|
6
|
+
import * as path15 from 'path';
|
|
7
7
|
import { dirname, join, resolve } from 'path';
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import * as fs9 from 'fs/promises';
|
|
10
10
|
import { readFile, writeFile } from 'fs/promises';
|
|
11
|
-
import
|
|
11
|
+
import chalk19 from 'chalk';
|
|
12
12
|
import matter2 from 'gray-matter';
|
|
13
13
|
import yaml2 from 'js-yaml';
|
|
14
14
|
import { Command } from 'commander';
|
|
@@ -41,7 +41,7 @@ var DEFAULT_CONFIG = {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
async function loadConfig(cwd = process.cwd()) {
|
|
44
|
-
const configPath =
|
|
44
|
+
const configPath = path15.join(cwd, ".lean-spec", "config.json");
|
|
45
45
|
try {
|
|
46
46
|
const content = await fs9.readFile(configPath, "utf-8");
|
|
47
47
|
const userConfig = JSON.parse(content);
|
|
@@ -53,8 +53,8 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
async function saveConfig(config, cwd = process.cwd()) {
|
|
56
|
-
const configDir =
|
|
57
|
-
const configPath =
|
|
56
|
+
const configDir = path15.join(cwd, ".lean-spec");
|
|
57
|
+
const configPath = path15.join(configDir, "config.json");
|
|
58
58
|
await fs9.mkdir(configDir, { recursive: true });
|
|
59
59
|
await fs9.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
60
60
|
}
|
|
@@ -153,7 +153,7 @@ async function getGlobalNextSeq(specsDir, digits) {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
if (entry.name === "archived") continue;
|
|
156
|
-
const subDir =
|
|
156
|
+
const subDir = path15.join(dir, entry.name);
|
|
157
157
|
await scanDirectory(subDir);
|
|
158
158
|
}
|
|
159
159
|
} catch {
|
|
@@ -170,7 +170,7 @@ async function getGlobalNextSeq(specsDir, digits) {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
async function resolveSpecPath(specPath, cwd, specsDir) {
|
|
173
|
-
if (
|
|
173
|
+
if (path15.isAbsolute(specPath)) {
|
|
174
174
|
try {
|
|
175
175
|
await fs9.access(specPath);
|
|
176
176
|
return specPath;
|
|
@@ -178,13 +178,13 @@ async function resolveSpecPath(specPath, cwd, specsDir) {
|
|
|
178
178
|
return null;
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
-
const cwdPath =
|
|
181
|
+
const cwdPath = path15.resolve(cwd, specPath);
|
|
182
182
|
try {
|
|
183
183
|
await fs9.access(cwdPath);
|
|
184
184
|
return cwdPath;
|
|
185
185
|
} catch {
|
|
186
186
|
}
|
|
187
|
-
const specsPath =
|
|
187
|
+
const specsPath = path15.join(specsDir, specPath);
|
|
188
188
|
try {
|
|
189
189
|
await fs9.access(specsPath);
|
|
190
190
|
return specsPath;
|
|
@@ -211,10 +211,10 @@ async function searchBySequence(specsDir, seqNum) {
|
|
|
211
211
|
if (match) {
|
|
212
212
|
const entrySeq = parseInt(match[1], 10);
|
|
213
213
|
if (entrySeq === seqNum) {
|
|
214
|
-
return
|
|
214
|
+
return path15.join(dir, entry.name);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
-
const subDir =
|
|
217
|
+
const subDir = path15.join(dir, entry.name);
|
|
218
218
|
const result = await scanDirectory(subDir);
|
|
219
219
|
if (result) return result;
|
|
220
220
|
}
|
|
@@ -231,9 +231,9 @@ async function searchInAllDirectories(specsDir, specName) {
|
|
|
231
231
|
for (const entry of entries) {
|
|
232
232
|
if (!entry.isDirectory()) continue;
|
|
233
233
|
if (entry.name === specName) {
|
|
234
|
-
return
|
|
234
|
+
return path15.join(dir, entry.name);
|
|
235
235
|
}
|
|
236
|
-
const subDir =
|
|
236
|
+
const subDir = path15.join(dir, entry.name);
|
|
237
237
|
const result = await scanDirectory(subDir);
|
|
238
238
|
if (result) return result;
|
|
239
239
|
}
|
|
@@ -264,7 +264,7 @@ async function getGitInfo() {
|
|
|
264
264
|
}
|
|
265
265
|
async function getProjectName(cwd = process.cwd()) {
|
|
266
266
|
try {
|
|
267
|
-
const packageJsonPath =
|
|
267
|
+
const packageJsonPath = path15.join(cwd, "package.json");
|
|
268
268
|
const content = await fs9.readFile(packageJsonPath, "utf-8");
|
|
269
269
|
const packageJson2 = JSON.parse(content);
|
|
270
270
|
return packageJson2.name || null;
|
|
@@ -353,9 +353,9 @@ async function loadSubFiles(specDir, options = {}) {
|
|
|
353
353
|
for (const entry of entries) {
|
|
354
354
|
if (entry.name === "README.md") continue;
|
|
355
355
|
if (entry.isDirectory()) continue;
|
|
356
|
-
const filePath =
|
|
356
|
+
const filePath = path15.join(specDir, entry.name);
|
|
357
357
|
const stat6 = await fs9.stat(filePath);
|
|
358
|
-
const ext =
|
|
358
|
+
const ext = path15.extname(entry.name).toLowerCase();
|
|
359
359
|
const isDocument = ext === ".md";
|
|
360
360
|
const subFile = {
|
|
361
361
|
name: entry.name,
|
|
@@ -381,7 +381,7 @@ async function loadSubFiles(specDir, options = {}) {
|
|
|
381
381
|
async function loadAllSpecs(options = {}) {
|
|
382
382
|
const config = await loadConfig();
|
|
383
383
|
const cwd = process.cwd();
|
|
384
|
-
const specsDir =
|
|
384
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
385
385
|
const specs = [];
|
|
386
386
|
try {
|
|
387
387
|
await fs9.access(specsDir);
|
|
@@ -395,7 +395,7 @@ async function loadAllSpecs(options = {}) {
|
|
|
395
395
|
for (const entry of entries) {
|
|
396
396
|
if (!entry.isDirectory()) continue;
|
|
397
397
|
if (entry.name === "archived" && relativePath === "") continue;
|
|
398
|
-
const entryPath =
|
|
398
|
+
const entryPath = path15.join(dir, entry.name);
|
|
399
399
|
const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
400
400
|
if (specPattern.test(entry.name)) {
|
|
401
401
|
const specFile = await getSpecFile(entryPath, config.structure.defaultFile);
|
|
@@ -444,7 +444,7 @@ async function loadAllSpecs(options = {}) {
|
|
|
444
444
|
}
|
|
445
445
|
await loadSpecsFromDir(specsDir);
|
|
446
446
|
if (options.includeArchived) {
|
|
447
|
-
const archivedPath =
|
|
447
|
+
const archivedPath = path15.join(specsDir, "archived");
|
|
448
448
|
await loadSpecsFromDir(archivedPath, "archived");
|
|
449
449
|
}
|
|
450
450
|
const sortBy = options.sortBy || "id";
|
|
@@ -492,12 +492,12 @@ async function loadAllSpecs(options = {}) {
|
|
|
492
492
|
async function getSpec(specPath) {
|
|
493
493
|
const config = await loadConfig();
|
|
494
494
|
const cwd = process.cwd();
|
|
495
|
-
const specsDir =
|
|
495
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
496
496
|
let fullPath;
|
|
497
|
-
if (
|
|
497
|
+
if (path15.isAbsolute(specPath)) {
|
|
498
498
|
fullPath = specPath;
|
|
499
499
|
} else {
|
|
500
|
-
fullPath =
|
|
500
|
+
fullPath = path15.join(specsDir, specPath);
|
|
501
501
|
}
|
|
502
502
|
try {
|
|
503
503
|
await fs9.access(fullPath);
|
|
@@ -509,8 +509,8 @@ async function getSpec(specPath) {
|
|
|
509
509
|
const frontmatter = await parseFrontmatter(specFile, config);
|
|
510
510
|
if (!frontmatter) return null;
|
|
511
511
|
const content = await fs9.readFile(specFile, "utf-8");
|
|
512
|
-
const relativePath =
|
|
513
|
-
const parts = relativePath.split(
|
|
512
|
+
const relativePath = path15.relative(specsDir, fullPath);
|
|
513
|
+
const parts = relativePath.split(path15.sep);
|
|
514
514
|
const date = parts[0] === "archived" ? parts[1] : parts[0];
|
|
515
515
|
const name = parts[parts.length - 1];
|
|
516
516
|
return {
|
|
@@ -558,12 +558,12 @@ function checkCommand() {
|
|
|
558
558
|
async function checkSpecs(options = {}) {
|
|
559
559
|
const config = await loadConfig();
|
|
560
560
|
const cwd = process.cwd();
|
|
561
|
-
|
|
561
|
+
path15.join(cwd, config.specsDir);
|
|
562
562
|
const specs = await loadAllSpecs();
|
|
563
563
|
const sequenceMap = /* @__PURE__ */ new Map();
|
|
564
564
|
const specPattern = createSpecDirPattern();
|
|
565
565
|
for (const spec of specs) {
|
|
566
|
-
const specName =
|
|
566
|
+
const specName = path15.basename(spec.path);
|
|
567
567
|
const match = specName.match(specPattern);
|
|
568
568
|
if (match) {
|
|
569
569
|
const seq = parseInt(match[1], 10);
|
|
@@ -578,30 +578,30 @@ async function checkSpecs(options = {}) {
|
|
|
578
578
|
const conflicts = Array.from(sequenceMap.entries()).filter(([_, paths]) => paths.length > 1).sort(([a], [b]) => a - b);
|
|
579
579
|
if (conflicts.length === 0) {
|
|
580
580
|
if (!options.quiet && !options.silent) {
|
|
581
|
-
console.log(
|
|
581
|
+
console.log(chalk19.green("\u2713 No sequence conflicts detected"));
|
|
582
582
|
}
|
|
583
583
|
return true;
|
|
584
584
|
}
|
|
585
585
|
if (!options.silent) {
|
|
586
586
|
if (!options.quiet) {
|
|
587
587
|
console.log("");
|
|
588
|
-
console.log(
|
|
588
|
+
console.log(chalk19.yellow("\u26A0\uFE0F Sequence conflicts detected:\n"));
|
|
589
589
|
for (const [seq, paths] of conflicts) {
|
|
590
|
-
console.log(
|
|
590
|
+
console.log(chalk19.red(` Sequence ${String(seq).padStart(config.structure.sequenceDigits, "0")}:`));
|
|
591
591
|
for (const p of paths) {
|
|
592
|
-
console.log(
|
|
592
|
+
console.log(chalk19.gray(` - ${sanitizeUserInput(p)}`));
|
|
593
593
|
}
|
|
594
594
|
console.log("");
|
|
595
595
|
}
|
|
596
|
-
console.log(
|
|
597
|
-
console.log(
|
|
596
|
+
console.log(chalk19.cyan("Tip: Use date prefix to prevent conflicts:"));
|
|
597
|
+
console.log(chalk19.gray(' Edit .lean-spec/config.json \u2192 structure.prefix: "{YYYYMMDD}-"'));
|
|
598
598
|
console.log("");
|
|
599
|
-
console.log(
|
|
599
|
+
console.log(chalk19.cyan("Or rename folders manually to resolve."));
|
|
600
600
|
console.log("");
|
|
601
601
|
} else {
|
|
602
602
|
console.log("");
|
|
603
|
-
console.log(
|
|
604
|
-
console.log(
|
|
603
|
+
console.log(chalk19.yellow(`\u26A0\uFE0F Conflict warning: ${conflicts.length} sequence conflict(s) detected`));
|
|
604
|
+
console.log(chalk19.gray("Run: lean-spec check"));
|
|
605
605
|
console.log("");
|
|
606
606
|
}
|
|
607
607
|
}
|
|
@@ -654,7 +654,7 @@ function createCommand() {
|
|
|
654
654
|
async function createSpec(name, options = {}) {
|
|
655
655
|
const config = await loadConfig();
|
|
656
656
|
const cwd = process.cwd();
|
|
657
|
-
const specsDir =
|
|
657
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
658
658
|
await fs9.mkdir(specsDir, { recursive: true });
|
|
659
659
|
const seq = await getGlobalNextSeq(specsDir, config.structure.sequenceDigits);
|
|
660
660
|
let specRelativePath;
|
|
@@ -675,8 +675,8 @@ async function createSpec(name, options = {}) {
|
|
|
675
675
|
} else {
|
|
676
676
|
throw new Error(`Unknown pattern: ${config.structure.pattern}`);
|
|
677
677
|
}
|
|
678
|
-
const specDir =
|
|
679
|
-
const specFile =
|
|
678
|
+
const specDir = path15.join(specsDir, specRelativePath);
|
|
679
|
+
const specFile = path15.join(specDir, config.structure.defaultFile);
|
|
680
680
|
try {
|
|
681
681
|
await fs9.access(specDir);
|
|
682
682
|
throw new Error(`Spec already exists: ${sanitizeUserInput(specDir)}`);
|
|
@@ -686,7 +686,7 @@ async function createSpec(name, options = {}) {
|
|
|
686
686
|
}
|
|
687
687
|
}
|
|
688
688
|
await fs9.mkdir(specDir, { recursive: true });
|
|
689
|
-
const templatesDir =
|
|
689
|
+
const templatesDir = path15.join(cwd, ".lean-spec", "templates");
|
|
690
690
|
let templateName;
|
|
691
691
|
if (options.template) {
|
|
692
692
|
if (config.templates?.[options.template]) {
|
|
@@ -698,7 +698,7 @@ async function createSpec(name, options = {}) {
|
|
|
698
698
|
} else {
|
|
699
699
|
templateName = config.template || "spec-template.md";
|
|
700
700
|
}
|
|
701
|
-
const templatePath =
|
|
701
|
+
const templatePath = path15.join(templatesDir, templateName);
|
|
702
702
|
let content;
|
|
703
703
|
try {
|
|
704
704
|
const template = await fs9.readFile(templatePath, "utf-8");
|
|
@@ -746,8 +746,8 @@ ${options.description}`
|
|
|
746
746
|
throw new Error(`Template not found: ${templatePath}. Run: lean-spec init`);
|
|
747
747
|
}
|
|
748
748
|
await fs9.writeFile(specFile, content, "utf-8");
|
|
749
|
-
console.log(
|
|
750
|
-
console.log(
|
|
749
|
+
console.log(chalk19.green(`\u2713 Created: ${sanitizeUserInput(specDir)}/`));
|
|
750
|
+
console.log(chalk19.gray(` Edit: ${sanitizeUserInput(specFile)}`));
|
|
751
751
|
await autoCheckIfEnabled();
|
|
752
752
|
}
|
|
753
753
|
function archiveCommand() {
|
|
@@ -759,7 +759,7 @@ async function archiveSpec(specPath) {
|
|
|
759
759
|
await autoCheckIfEnabled();
|
|
760
760
|
const config = await loadConfig();
|
|
761
761
|
const cwd = process.cwd();
|
|
762
|
-
const specsDir =
|
|
762
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
763
763
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
764
764
|
if (!resolvedPath) {
|
|
765
765
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
@@ -768,12 +768,12 @@ async function archiveSpec(specPath) {
|
|
|
768
768
|
if (specFile) {
|
|
769
769
|
await updateFrontmatter(specFile, { status: "archived" });
|
|
770
770
|
}
|
|
771
|
-
const archiveDir =
|
|
771
|
+
const archiveDir = path15.join(specsDir, "archived");
|
|
772
772
|
await fs9.mkdir(archiveDir, { recursive: true });
|
|
773
|
-
const specName =
|
|
774
|
-
const archivePath =
|
|
773
|
+
const specName = path15.basename(resolvedPath);
|
|
774
|
+
const archivePath = path15.join(archiveDir, specName);
|
|
775
775
|
await fs9.rename(resolvedPath, archivePath);
|
|
776
|
-
console.log(
|
|
776
|
+
console.log(chalk19.green(`\u2713 Archived: ${sanitizeUserInput(archivePath)}`));
|
|
777
777
|
}
|
|
778
778
|
|
|
779
779
|
// src/utils/pattern-detection.ts
|
|
@@ -803,59 +803,59 @@ var STATUS_CONFIG = {
|
|
|
803
803
|
planned: {
|
|
804
804
|
emoji: "\u{1F4C5}",
|
|
805
805
|
label: "Planned",
|
|
806
|
-
colorFn:
|
|
807
|
-
badge: (s = "planned") =>
|
|
806
|
+
colorFn: chalk19.blue,
|
|
807
|
+
badge: (s = "planned") => chalk19.blue(`[${s}]`)
|
|
808
808
|
},
|
|
809
809
|
"in-progress": {
|
|
810
810
|
emoji: "\u23F3",
|
|
811
811
|
label: "In Progress",
|
|
812
|
-
colorFn:
|
|
813
|
-
badge: (s = "in-progress") =>
|
|
812
|
+
colorFn: chalk19.yellow,
|
|
813
|
+
badge: (s = "in-progress") => chalk19.yellow(`[${s}]`)
|
|
814
814
|
},
|
|
815
815
|
complete: {
|
|
816
816
|
emoji: "\u2705",
|
|
817
817
|
label: "Complete",
|
|
818
|
-
colorFn:
|
|
819
|
-
badge: (s = "complete") =>
|
|
818
|
+
colorFn: chalk19.green,
|
|
819
|
+
badge: (s = "complete") => chalk19.green(`[${s}]`)
|
|
820
820
|
},
|
|
821
821
|
archived: {
|
|
822
822
|
emoji: "\u{1F4E6}",
|
|
823
823
|
label: "Archived",
|
|
824
|
-
colorFn:
|
|
825
|
-
badge: (s = "archived") =>
|
|
824
|
+
colorFn: chalk19.gray,
|
|
825
|
+
badge: (s = "archived") => chalk19.gray(`[${s}]`)
|
|
826
826
|
}
|
|
827
827
|
};
|
|
828
828
|
var PRIORITY_CONFIG = {
|
|
829
829
|
critical: {
|
|
830
830
|
emoji: "\u{1F534}",
|
|
831
|
-
colorFn:
|
|
832
|
-
badge: (s = "critical") =>
|
|
831
|
+
colorFn: chalk19.red.bold,
|
|
832
|
+
badge: (s = "critical") => chalk19.red.bold(`[${s}]`)
|
|
833
833
|
},
|
|
834
834
|
high: {
|
|
835
835
|
emoji: "\u{1F7E0}",
|
|
836
|
-
colorFn:
|
|
837
|
-
badge: (s = "high") =>
|
|
836
|
+
colorFn: chalk19.hex("#FFA500"),
|
|
837
|
+
badge: (s = "high") => chalk19.hex("#FFA500")(`[${s}]`)
|
|
838
838
|
},
|
|
839
839
|
medium: {
|
|
840
840
|
emoji: "\u{1F7E1}",
|
|
841
|
-
colorFn:
|
|
842
|
-
badge: (s = "medium") =>
|
|
841
|
+
colorFn: chalk19.yellow,
|
|
842
|
+
badge: (s = "medium") => chalk19.yellow(`[${s}]`)
|
|
843
843
|
},
|
|
844
844
|
low: {
|
|
845
845
|
emoji: "\u{1F7E2}",
|
|
846
|
-
colorFn:
|
|
847
|
-
badge: (s = "low") =>
|
|
846
|
+
colorFn: chalk19.gray,
|
|
847
|
+
badge: (s = "low") => chalk19.gray(`[${s}]`)
|
|
848
848
|
}
|
|
849
849
|
};
|
|
850
850
|
function formatStatusBadge(status) {
|
|
851
|
-
return STATUS_CONFIG[status]?.badge() ||
|
|
851
|
+
return STATUS_CONFIG[status]?.badge() || chalk19.white(`[${status}]`);
|
|
852
852
|
}
|
|
853
853
|
function formatPriorityBadge(priority) {
|
|
854
|
-
return PRIORITY_CONFIG[priority]?.badge() ||
|
|
854
|
+
return PRIORITY_CONFIG[priority]?.badge() || chalk19.white(`[${priority}]`);
|
|
855
855
|
}
|
|
856
856
|
function getStatusIndicator(status) {
|
|
857
857
|
const config = STATUS_CONFIG[status];
|
|
858
|
-
if (!config) return
|
|
858
|
+
if (!config) return chalk19.gray("[unknown]");
|
|
859
859
|
return config.colorFn(`[${status}]`);
|
|
860
860
|
}
|
|
861
861
|
function getStatusEmoji(status) {
|
|
@@ -886,7 +886,7 @@ async function listSpecs(options = {}) {
|
|
|
886
886
|
await autoCheckIfEnabled();
|
|
887
887
|
const config = await loadConfig();
|
|
888
888
|
const cwd = process.cwd();
|
|
889
|
-
const specsDir =
|
|
889
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
890
890
|
try {
|
|
891
891
|
await fs9.access(specsDir);
|
|
892
892
|
} catch {
|
|
@@ -913,10 +913,10 @@ async function listSpecs(options = {}) {
|
|
|
913
913
|
})
|
|
914
914
|
);
|
|
915
915
|
if (specs.length === 0) {
|
|
916
|
-
console.log(
|
|
916
|
+
console.log(chalk19.dim("No specs found."));
|
|
917
917
|
return;
|
|
918
918
|
}
|
|
919
|
-
console.log(
|
|
919
|
+
console.log(chalk19.bold.cyan("\u{1F4C4} Spec List"));
|
|
920
920
|
const filterParts = [];
|
|
921
921
|
if (options.status) {
|
|
922
922
|
const statusStr = Array.isArray(options.status) ? options.status.join(",") : options.status;
|
|
@@ -929,7 +929,7 @@ async function listSpecs(options = {}) {
|
|
|
929
929
|
}
|
|
930
930
|
if (options.assignee) filterParts.push(`assignee=${options.assignee}`);
|
|
931
931
|
if (filterParts.length > 0) {
|
|
932
|
-
console.log(
|
|
932
|
+
console.log(chalk19.dim(`Filtered by: ${filterParts.join(", ")}`));
|
|
933
933
|
}
|
|
934
934
|
console.log("");
|
|
935
935
|
const patternInfo = detectPatternType(config);
|
|
@@ -939,7 +939,7 @@ async function listSpecs(options = {}) {
|
|
|
939
939
|
renderFlatList(specs);
|
|
940
940
|
}
|
|
941
941
|
console.log("");
|
|
942
|
-
console.log(
|
|
942
|
+
console.log(chalk19.bold(`Total: ${chalk19.green(specs.length)} spec${specs.length !== 1 ? "s" : ""}`));
|
|
943
943
|
}
|
|
944
944
|
function renderFlatList(specs) {
|
|
945
945
|
for (const spec of specs) {
|
|
@@ -947,25 +947,25 @@ function renderFlatList(specs) {
|
|
|
947
947
|
const priorityEmoji = getPriorityEmoji(spec.frontmatter.priority);
|
|
948
948
|
let assigneeStr = "";
|
|
949
949
|
if (spec.frontmatter.assignee) {
|
|
950
|
-
assigneeStr = " " +
|
|
950
|
+
assigneeStr = " " + chalk19.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
|
|
951
951
|
}
|
|
952
952
|
let tagsStr = "";
|
|
953
953
|
if (spec.frontmatter.tags?.length) {
|
|
954
954
|
const tags = Array.isArray(spec.frontmatter.tags) ? spec.frontmatter.tags : [];
|
|
955
955
|
if (tags.length > 0) {
|
|
956
956
|
const tagStr = tags.map((tag) => `#${sanitizeUserInput(tag)}`).join(" ");
|
|
957
|
-
tagsStr = " " +
|
|
957
|
+
tagsStr = " " + chalk19.dim(chalk19.magenta(tagStr));
|
|
958
958
|
}
|
|
959
959
|
}
|
|
960
960
|
let subSpecStr = "";
|
|
961
961
|
if (spec.subFiles) {
|
|
962
962
|
const docCount = spec.subFiles.filter((f) => f.type === "document").length;
|
|
963
963
|
if (docCount > 0) {
|
|
964
|
-
subSpecStr = " " +
|
|
964
|
+
subSpecStr = " " + chalk19.dim(chalk19.yellow(`(+${docCount} sub-spec${docCount > 1 ? "s" : ""})`));
|
|
965
965
|
}
|
|
966
966
|
}
|
|
967
967
|
const priorityPrefix = priorityEmoji ? `${priorityEmoji} ` : "";
|
|
968
|
-
console.log(`${priorityPrefix}${statusEmoji} ${
|
|
968
|
+
console.log(`${priorityPrefix}${statusEmoji} ${chalk19.cyan(sanitizeUserInput(spec.path))}${assigneeStr}${tagsStr}${subSpecStr}`);
|
|
969
969
|
}
|
|
970
970
|
}
|
|
971
971
|
function renderGroupedList(specs, groupExtractor) {
|
|
@@ -994,7 +994,7 @@ function renderGroupedList(specs, groupExtractor) {
|
|
|
994
994
|
const groupName = sortedGroups[i];
|
|
995
995
|
const groupSpecs = groups.get(groupName);
|
|
996
996
|
const groupEmoji = /^\d{8}$/.test(groupName) ? "\u{1F4C5}" : groupName.startsWith("milestone") ? "\u{1F3AF}" : "\u{1F4C1}";
|
|
997
|
-
console.log(`${
|
|
997
|
+
console.log(`${chalk19.bold.cyan(`${groupEmoji} ${groupName}/`)} ${chalk19.dim(`(${groupSpecs.length})`)}`);
|
|
998
998
|
console.log("");
|
|
999
999
|
for (const spec of groupSpecs) {
|
|
1000
1000
|
const statusEmoji = getStatusEmoji(spec.frontmatter.status);
|
|
@@ -1002,25 +1002,25 @@ function renderGroupedList(specs, groupExtractor) {
|
|
|
1002
1002
|
const displayPath = spec.path.includes("/") ? spec.path.split("/").slice(1).join("/") : spec.path;
|
|
1003
1003
|
let assigneeStr = "";
|
|
1004
1004
|
if (spec.frontmatter.assignee) {
|
|
1005
|
-
assigneeStr = " " +
|
|
1005
|
+
assigneeStr = " " + chalk19.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
|
|
1006
1006
|
}
|
|
1007
1007
|
let tagsStr = "";
|
|
1008
1008
|
if (spec.frontmatter.tags?.length) {
|
|
1009
1009
|
const tags = Array.isArray(spec.frontmatter.tags) ? spec.frontmatter.tags : [];
|
|
1010
1010
|
if (tags.length > 0) {
|
|
1011
1011
|
const tagStr = tags.map((tag) => `#${sanitizeUserInput(tag)}`).join(" ");
|
|
1012
|
-
tagsStr = " " +
|
|
1012
|
+
tagsStr = " " + chalk19.dim(chalk19.magenta(tagStr));
|
|
1013
1013
|
}
|
|
1014
1014
|
}
|
|
1015
1015
|
let subSpecStr = "";
|
|
1016
1016
|
if (spec.subFiles) {
|
|
1017
1017
|
const docCount = spec.subFiles.filter((f) => f.type === "document").length;
|
|
1018
1018
|
if (docCount > 0) {
|
|
1019
|
-
subSpecStr = " " +
|
|
1019
|
+
subSpecStr = " " + chalk19.dim(chalk19.yellow(`(+${docCount} sub-spec${docCount > 1 ? "s" : ""})`));
|
|
1020
1020
|
}
|
|
1021
1021
|
}
|
|
1022
1022
|
const priorityPrefix = priorityEmoji ? `${priorityEmoji} ` : "";
|
|
1023
|
-
console.log(` ${priorityPrefix}${statusEmoji} ${
|
|
1023
|
+
console.log(` ${priorityPrefix}${statusEmoji} ${chalk19.cyan(sanitizeUserInput(displayPath))}${assigneeStr}${tagsStr}${subSpecStr}`);
|
|
1024
1024
|
}
|
|
1025
1025
|
if (i < sortedGroups.length - 1) {
|
|
1026
1026
|
console.log("");
|
|
@@ -1053,7 +1053,7 @@ async function updateSpec(specPath, updates, options = {}) {
|
|
|
1053
1053
|
await autoCheckIfEnabled();
|
|
1054
1054
|
const cwd = options.cwd ?? process.cwd();
|
|
1055
1055
|
const config = await loadConfig(cwd);
|
|
1056
|
-
const specsDir =
|
|
1056
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
1057
1057
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
1058
1058
|
if (!resolvedPath) {
|
|
1059
1059
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}. Tried: ${sanitizeUserInput(specPath)}, specs/${sanitizeUserInput(specPath)}, and searching in date directories`);
|
|
@@ -1075,12 +1075,12 @@ async function updateSpec(specPath, updates, options = {}) {
|
|
|
1075
1075
|
});
|
|
1076
1076
|
}
|
|
1077
1077
|
await updateFrontmatter(specFile, allUpdates);
|
|
1078
|
-
console.log(
|
|
1078
|
+
console.log(chalk19.green(`\u2713 Updated: ${sanitizeUserInput(path15.relative(cwd, resolvedPath))}`));
|
|
1079
1079
|
const updatedFields = Object.keys(updates).filter((k) => k !== "customFields");
|
|
1080
1080
|
if (updates.customFields) {
|
|
1081
1081
|
updatedFields.push(...Object.keys(updates.customFields));
|
|
1082
1082
|
}
|
|
1083
|
-
console.log(
|
|
1083
|
+
console.log(chalk19.gray(` Fields: ${updatedFields.join(", ")}`));
|
|
1084
1084
|
}
|
|
1085
1085
|
function linkCommand() {
|
|
1086
1086
|
return new Command("link").description("Add relationships between specs (depends_on, related)").argument("<spec>", "Spec to update").option("--depends-on <specs>", "Add dependencies (comma-separated spec numbers or names)").option("--related <specs>", "Add related specs (comma-separated spec numbers or names)").action(async (specPath, options) => {
|
|
@@ -1095,7 +1095,7 @@ async function linkSpec(specPath, options) {
|
|
|
1095
1095
|
await autoCheckIfEnabled();
|
|
1096
1096
|
const config = await loadConfig();
|
|
1097
1097
|
const cwd = process.cwd();
|
|
1098
|
-
const specsDir =
|
|
1098
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
1099
1099
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
1100
1100
|
if (!resolvedPath) {
|
|
1101
1101
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
@@ -1108,7 +1108,7 @@ async function linkSpec(specPath, options) {
|
|
|
1108
1108
|
const specMap = new Map(allSpecs.map((s) => [s.path, s]));
|
|
1109
1109
|
const dependsOnSpecs = options.dependsOn ? options.dependsOn.split(",").map((s) => s.trim()) : [];
|
|
1110
1110
|
const relatedSpecs = options.related ? options.related.split(",").map((s) => s.trim()) : [];
|
|
1111
|
-
const targetSpecName =
|
|
1111
|
+
const targetSpecName = path15.basename(resolvedPath);
|
|
1112
1112
|
const allRelationshipSpecs = [...dependsOnSpecs, ...relatedSpecs];
|
|
1113
1113
|
const resolvedRelationships = /* @__PURE__ */ new Map();
|
|
1114
1114
|
for (const relSpec of allRelationshipSpecs) {
|
|
@@ -1122,7 +1122,7 @@ async function linkSpec(specPath, options) {
|
|
|
1122
1122
|
if (relResolvedPath === resolvedPath) {
|
|
1123
1123
|
throw new Error(`Cannot link spec to itself: ${sanitizeUserInput(relSpec)}`);
|
|
1124
1124
|
}
|
|
1125
|
-
const relSpecName =
|
|
1125
|
+
const relSpecName = path15.basename(relResolvedPath);
|
|
1126
1126
|
resolvedRelationships.set(relSpec, relSpecName);
|
|
1127
1127
|
}
|
|
1128
1128
|
const { parseFrontmatter: parseFrontmatter2 } = await import('./frontmatter-R2DANL5X.js');
|
|
@@ -1142,7 +1142,7 @@ async function linkSpec(specPath, options) {
|
|
|
1142
1142
|
}
|
|
1143
1143
|
updates.depends_on = newDependsOn;
|
|
1144
1144
|
if (added === 0) {
|
|
1145
|
-
console.log(
|
|
1145
|
+
console.log(chalk19.gray(`\u2139 Dependencies already exist, no changes made`));
|
|
1146
1146
|
}
|
|
1147
1147
|
}
|
|
1148
1148
|
if (relatedSpecs.length > 0) {
|
|
@@ -1169,19 +1169,19 @@ async function linkSpec(specPath, options) {
|
|
|
1169
1169
|
await updateFrontmatter(relSpecFile, {
|
|
1170
1170
|
related: [...relCurrentRelated, targetSpecName]
|
|
1171
1171
|
});
|
|
1172
|
-
console.log(
|
|
1172
|
+
console.log(chalk19.gray(` Updated: ${sanitizeUserInput(relSpecName)} (bidirectional)`));
|
|
1173
1173
|
}
|
|
1174
1174
|
}
|
|
1175
1175
|
}
|
|
1176
1176
|
}
|
|
1177
1177
|
if (added === 0) {
|
|
1178
|
-
console.log(
|
|
1178
|
+
console.log(chalk19.gray(`\u2139 Related specs already exist, no changes made`));
|
|
1179
1179
|
}
|
|
1180
1180
|
}
|
|
1181
1181
|
if (updates.depends_on && updates.depends_on.length > 0) {
|
|
1182
1182
|
const cycles = detectCycles(targetSpecName, updates.depends_on, specMap);
|
|
1183
1183
|
if (cycles.length > 0) {
|
|
1184
|
-
console.log(
|
|
1184
|
+
console.log(chalk19.yellow(`\u26A0\uFE0F Dependency cycle detected: ${cycles.join(" \u2192 ")}`));
|
|
1185
1185
|
}
|
|
1186
1186
|
}
|
|
1187
1187
|
await updateFrontmatter(specFile, updates);
|
|
@@ -1192,8 +1192,8 @@ async function linkSpec(specPath, options) {
|
|
|
1192
1192
|
if (relatedSpecs.length > 0) {
|
|
1193
1193
|
updatedFields.push(`related: ${relatedSpecs.join(", ")}`);
|
|
1194
1194
|
}
|
|
1195
|
-
console.log(
|
|
1196
|
-
console.log(
|
|
1195
|
+
console.log(chalk19.green(`\u2713 Added relationships: ${updatedFields.join(", ")}`));
|
|
1196
|
+
console.log(chalk19.gray(` Updated: ${sanitizeUserInput(path15.relative(cwd, resolvedPath))}`));
|
|
1197
1197
|
}
|
|
1198
1198
|
function detectCycles(startSpec, dependsOn, specMap, visited = /* @__PURE__ */ new Set(), path31 = []) {
|
|
1199
1199
|
if (visited.has(startSpec)) {
|
|
@@ -1229,7 +1229,7 @@ async function unlinkSpec(specPath, options) {
|
|
|
1229
1229
|
await autoCheckIfEnabled();
|
|
1230
1230
|
const config = await loadConfig();
|
|
1231
1231
|
const cwd = process.cwd();
|
|
1232
|
-
const specsDir =
|
|
1232
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
1233
1233
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
1234
1234
|
if (!resolvedPath) {
|
|
1235
1235
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
@@ -1238,7 +1238,7 @@ async function unlinkSpec(specPath, options) {
|
|
|
1238
1238
|
if (!specFile) {
|
|
1239
1239
|
throw new Error(`No spec file found in: ${sanitizeUserInput(specPath)}`);
|
|
1240
1240
|
}
|
|
1241
|
-
const targetSpecName =
|
|
1241
|
+
const targetSpecName = path15.basename(resolvedPath);
|
|
1242
1242
|
const { parseFrontmatter: parseFrontmatter2 } = await import('./frontmatter-R2DANL5X.js');
|
|
1243
1243
|
const currentFrontmatter = await parseFrontmatter2(specFile);
|
|
1244
1244
|
const currentDependsOn = currentFrontmatter?.depends_on || [];
|
|
@@ -1255,7 +1255,7 @@ async function unlinkSpec(specPath, options) {
|
|
|
1255
1255
|
for (const spec of toRemove) {
|
|
1256
1256
|
const resolvedSpecPath = await resolveSpecPath(spec, cwd, specsDir);
|
|
1257
1257
|
if (resolvedSpecPath) {
|
|
1258
|
-
resolvedToRemove.add(
|
|
1258
|
+
resolvedToRemove.add(path15.basename(resolvedSpecPath));
|
|
1259
1259
|
} else {
|
|
1260
1260
|
resolvedToRemove.add(spec);
|
|
1261
1261
|
}
|
|
@@ -1279,7 +1279,7 @@ async function unlinkSpec(specPath, options) {
|
|
|
1279
1279
|
await updateFrontmatter(relSpecFile, {
|
|
1280
1280
|
related: relNewRelated
|
|
1281
1281
|
});
|
|
1282
|
-
console.log(
|
|
1282
|
+
console.log(chalk19.gray(` Updated: ${sanitizeUserInput(relSpec)} (bidirectional)`));
|
|
1283
1283
|
}
|
|
1284
1284
|
}
|
|
1285
1285
|
}
|
|
@@ -1292,7 +1292,7 @@ async function unlinkSpec(specPath, options) {
|
|
|
1292
1292
|
for (const spec of toRemove) {
|
|
1293
1293
|
const resolvedSpecPath = await resolveSpecPath(spec, cwd, specsDir);
|
|
1294
1294
|
if (resolvedSpecPath) {
|
|
1295
|
-
const specName =
|
|
1295
|
+
const specName = path15.basename(resolvedSpecPath);
|
|
1296
1296
|
resolvedToRemove.add(specName);
|
|
1297
1297
|
const relSpecFile = await getSpecFile(resolvedSpecPath, config.structure.defaultFile);
|
|
1298
1298
|
if (relSpecFile) {
|
|
@@ -1303,7 +1303,7 @@ async function unlinkSpec(specPath, options) {
|
|
|
1303
1303
|
await updateFrontmatter(relSpecFile, {
|
|
1304
1304
|
related: relNewRelated
|
|
1305
1305
|
});
|
|
1306
|
-
console.log(
|
|
1306
|
+
console.log(chalk19.gray(` Updated: ${sanitizeUserInput(specName)} (bidirectional)`));
|
|
1307
1307
|
}
|
|
1308
1308
|
}
|
|
1309
1309
|
} else {
|
|
@@ -1317,7 +1317,7 @@ async function unlinkSpec(specPath, options) {
|
|
|
1317
1317
|
}
|
|
1318
1318
|
await updateFrontmatter(specFile, updates);
|
|
1319
1319
|
if (removedCount === 0) {
|
|
1320
|
-
console.log(
|
|
1320
|
+
console.log(chalk19.gray(`\u2139 No matching relationships found to remove`));
|
|
1321
1321
|
} else {
|
|
1322
1322
|
const updatedFields = [];
|
|
1323
1323
|
if (options.dependsOn !== void 0) {
|
|
@@ -1326,8 +1326,8 @@ async function unlinkSpec(specPath, options) {
|
|
|
1326
1326
|
if (options.related !== void 0) {
|
|
1327
1327
|
updatedFields.push(`related`);
|
|
1328
1328
|
}
|
|
1329
|
-
console.log(
|
|
1330
|
-
console.log(
|
|
1329
|
+
console.log(chalk19.green(`\u2713 Removed relationships: ${updatedFields.join(", ")} (${removedCount} total)`));
|
|
1330
|
+
console.log(chalk19.gray(` Updated: ${sanitizeUserInput(path15.relative(cwd, resolvedPath))}`));
|
|
1331
1331
|
}
|
|
1332
1332
|
}
|
|
1333
1333
|
function isGitRepository() {
|
|
@@ -1480,7 +1480,7 @@ async function backfillTimestamps(options = {}) {
|
|
|
1480
1480
|
specs = [];
|
|
1481
1481
|
const config = await loadConfig();
|
|
1482
1482
|
const cwd = process.cwd();
|
|
1483
|
-
const specsDir =
|
|
1483
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
1484
1484
|
for (const specPath of options.specs) {
|
|
1485
1485
|
const resolved = await resolveSpecPath(specPath, cwd, specsDir);
|
|
1486
1486
|
if (!resolved) {
|
|
@@ -1676,79 +1676,79 @@ function templatesCommand() {
|
|
|
1676
1676
|
}
|
|
1677
1677
|
async function listTemplates(cwd = process.cwd()) {
|
|
1678
1678
|
const config = await loadConfig(cwd);
|
|
1679
|
-
const templatesDir =
|
|
1679
|
+
const templatesDir = path15.join(cwd, ".lean-spec", "templates");
|
|
1680
1680
|
console.log("");
|
|
1681
|
-
console.log(
|
|
1681
|
+
console.log(chalk19.green("=== Project Templates ==="));
|
|
1682
1682
|
console.log("");
|
|
1683
1683
|
try {
|
|
1684
1684
|
await fs9.access(templatesDir);
|
|
1685
1685
|
} catch {
|
|
1686
|
-
console.log(
|
|
1687
|
-
console.log(
|
|
1686
|
+
console.log(chalk19.yellow("No templates directory found."));
|
|
1687
|
+
console.log(chalk19.gray("Run: lean-spec init"));
|
|
1688
1688
|
console.log("");
|
|
1689
1689
|
return;
|
|
1690
1690
|
}
|
|
1691
1691
|
const files = await fs9.readdir(templatesDir);
|
|
1692
1692
|
const templateFiles = files.filter((f) => f.endsWith(".md"));
|
|
1693
1693
|
if (templateFiles.length === 0) {
|
|
1694
|
-
console.log(
|
|
1694
|
+
console.log(chalk19.yellow("No templates found."));
|
|
1695
1695
|
console.log("");
|
|
1696
1696
|
return;
|
|
1697
1697
|
}
|
|
1698
1698
|
if (config.templates && Object.keys(config.templates).length > 0) {
|
|
1699
|
-
console.log(
|
|
1699
|
+
console.log(chalk19.cyan("Registered:"));
|
|
1700
1700
|
for (const [name, file] of Object.entries(config.templates)) {
|
|
1701
1701
|
const isDefault = config.template === file;
|
|
1702
|
-
const marker = isDefault ?
|
|
1703
|
-
console.log(` ${
|
|
1702
|
+
const marker = isDefault ? chalk19.green("\u2713 (default)") : "";
|
|
1703
|
+
console.log(` ${chalk19.bold(name)}: ${file} ${marker}`);
|
|
1704
1704
|
}
|
|
1705
1705
|
console.log("");
|
|
1706
1706
|
}
|
|
1707
|
-
console.log(
|
|
1707
|
+
console.log(chalk19.cyan("Available files:"));
|
|
1708
1708
|
for (const file of templateFiles) {
|
|
1709
|
-
const filePath =
|
|
1709
|
+
const filePath = path15.join(templatesDir, file);
|
|
1710
1710
|
const stat6 = await fs9.stat(filePath);
|
|
1711
1711
|
const sizeKB = (stat6.size / 1024).toFixed(1);
|
|
1712
1712
|
console.log(` ${file} (${sizeKB} KB)`);
|
|
1713
1713
|
}
|
|
1714
1714
|
console.log("");
|
|
1715
|
-
console.log(
|
|
1715
|
+
console.log(chalk19.gray("Use templates with: lean-spec create <name> --template=<template-name>"));
|
|
1716
1716
|
console.log("");
|
|
1717
1717
|
}
|
|
1718
1718
|
async function showTemplate(templateName, cwd = process.cwd()) {
|
|
1719
1719
|
const config = await loadConfig(cwd);
|
|
1720
1720
|
if (!config.templates?.[templateName]) {
|
|
1721
|
-
console.error(
|
|
1722
|
-
console.error(
|
|
1721
|
+
console.error(chalk19.red(`Template not found: ${templateName}`));
|
|
1722
|
+
console.error(chalk19.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
|
|
1723
1723
|
process.exit(1);
|
|
1724
1724
|
}
|
|
1725
|
-
const templatesDir =
|
|
1725
|
+
const templatesDir = path15.join(cwd, ".lean-spec", "templates");
|
|
1726
1726
|
const templateFile = config.templates[templateName];
|
|
1727
|
-
const templatePath =
|
|
1727
|
+
const templatePath = path15.join(templatesDir, templateFile);
|
|
1728
1728
|
try {
|
|
1729
1729
|
const content = await fs9.readFile(templatePath, "utf-8");
|
|
1730
1730
|
console.log("");
|
|
1731
|
-
console.log(
|
|
1731
|
+
console.log(chalk19.cyan(`=== Template: ${templateName} (${templateFile}) ===`));
|
|
1732
1732
|
console.log("");
|
|
1733
1733
|
console.log(content);
|
|
1734
1734
|
console.log("");
|
|
1735
1735
|
} catch (error) {
|
|
1736
|
-
console.error(
|
|
1736
|
+
console.error(chalk19.red(`Error reading template: ${templateFile}`));
|
|
1737
1737
|
console.error(error);
|
|
1738
1738
|
process.exit(1);
|
|
1739
1739
|
}
|
|
1740
1740
|
}
|
|
1741
1741
|
async function addTemplate(name, file, cwd = process.cwd()) {
|
|
1742
1742
|
const config = await loadConfig(cwd);
|
|
1743
|
-
const templatesDir =
|
|
1744
|
-
const templatePath =
|
|
1743
|
+
const templatesDir = path15.join(cwd, ".lean-spec", "templates");
|
|
1744
|
+
const templatePath = path15.join(templatesDir, file);
|
|
1745
1745
|
try {
|
|
1746
1746
|
await fs9.access(templatePath);
|
|
1747
1747
|
} catch {
|
|
1748
|
-
console.error(
|
|
1749
|
-
console.error(
|
|
1748
|
+
console.error(chalk19.red(`Template file not found: ${file}`));
|
|
1749
|
+
console.error(chalk19.gray(`Expected at: ${templatePath}`));
|
|
1750
1750
|
console.error(
|
|
1751
|
-
|
|
1751
|
+
chalk19.yellow("Create the file first or use: lean-spec templates copy <source> <target>")
|
|
1752
1752
|
);
|
|
1753
1753
|
process.exit(1);
|
|
1754
1754
|
}
|
|
@@ -1756,60 +1756,60 @@ async function addTemplate(name, file, cwd = process.cwd()) {
|
|
|
1756
1756
|
config.templates = {};
|
|
1757
1757
|
}
|
|
1758
1758
|
if (config.templates[name]) {
|
|
1759
|
-
console.log(
|
|
1759
|
+
console.log(chalk19.yellow(`Warning: Template '${name}' already exists, updating...`));
|
|
1760
1760
|
}
|
|
1761
1761
|
config.templates[name] = file;
|
|
1762
1762
|
await saveConfig(config, cwd);
|
|
1763
|
-
console.log(
|
|
1764
|
-
console.log(
|
|
1763
|
+
console.log(chalk19.green(`\u2713 Added template: ${name} \u2192 ${file}`));
|
|
1764
|
+
console.log(chalk19.gray(` Use with: lean-spec create <spec-name> --template=${name}`));
|
|
1765
1765
|
}
|
|
1766
1766
|
async function removeTemplate(name, cwd = process.cwd()) {
|
|
1767
1767
|
const config = await loadConfig(cwd);
|
|
1768
1768
|
if (!config.templates?.[name]) {
|
|
1769
|
-
console.error(
|
|
1770
|
-
console.error(
|
|
1769
|
+
console.error(chalk19.red(`Template not found: ${name}`));
|
|
1770
|
+
console.error(chalk19.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
|
|
1771
1771
|
process.exit(1);
|
|
1772
1772
|
}
|
|
1773
1773
|
if (name === "default") {
|
|
1774
|
-
console.error(
|
|
1774
|
+
console.error(chalk19.red("Cannot remove default template"));
|
|
1775
1775
|
process.exit(1);
|
|
1776
1776
|
}
|
|
1777
1777
|
const file = config.templates[name];
|
|
1778
1778
|
delete config.templates[name];
|
|
1779
1779
|
await saveConfig(config, cwd);
|
|
1780
|
-
console.log(
|
|
1781
|
-
console.log(
|
|
1780
|
+
console.log(chalk19.green(`\u2713 Removed template: ${name}`));
|
|
1781
|
+
console.log(chalk19.gray(` Note: Template file ${file} still exists in .lean-spec/templates/`));
|
|
1782
1782
|
}
|
|
1783
1783
|
async function copyTemplate(source, target, cwd = process.cwd()) {
|
|
1784
1784
|
const config = await loadConfig(cwd);
|
|
1785
|
-
const templatesDir =
|
|
1785
|
+
const templatesDir = path15.join(cwd, ".lean-spec", "templates");
|
|
1786
1786
|
let sourceFile;
|
|
1787
1787
|
if (config.templates?.[source]) {
|
|
1788
1788
|
sourceFile = config.templates[source];
|
|
1789
1789
|
} else {
|
|
1790
1790
|
sourceFile = source;
|
|
1791
1791
|
}
|
|
1792
|
-
const sourcePath =
|
|
1792
|
+
const sourcePath = path15.join(templatesDir, sourceFile);
|
|
1793
1793
|
try {
|
|
1794
1794
|
await fs9.access(sourcePath);
|
|
1795
1795
|
} catch {
|
|
1796
|
-
console.error(
|
|
1797
|
-
console.error(
|
|
1796
|
+
console.error(chalk19.red(`Source template not found: ${source}`));
|
|
1797
|
+
console.error(chalk19.gray(`Expected at: ${sourcePath}`));
|
|
1798
1798
|
process.exit(1);
|
|
1799
1799
|
}
|
|
1800
1800
|
const targetFile = target.endsWith(".md") ? target : `${target}.md`;
|
|
1801
|
-
const targetPath =
|
|
1801
|
+
const targetPath = path15.join(templatesDir, targetFile);
|
|
1802
1802
|
await fs9.copyFile(sourcePath, targetPath);
|
|
1803
|
-
console.log(
|
|
1803
|
+
console.log(chalk19.green(`\u2713 Copied: ${sourceFile} \u2192 ${targetFile}`));
|
|
1804
1804
|
if (!config.templates) {
|
|
1805
1805
|
config.templates = {};
|
|
1806
1806
|
}
|
|
1807
1807
|
const templateName = target.replace(/\.md$/, "");
|
|
1808
1808
|
config.templates[templateName] = targetFile;
|
|
1809
1809
|
await saveConfig(config, cwd);
|
|
1810
|
-
console.log(
|
|
1811
|
-
console.log(
|
|
1812
|
-
console.log(
|
|
1810
|
+
console.log(chalk19.green(`\u2713 Registered template: ${templateName}`));
|
|
1811
|
+
console.log(chalk19.gray(` Edit: ${targetPath}`));
|
|
1812
|
+
console.log(chalk19.gray(` Use with: lean-spec create <spec-name> --template=${templateName}`));
|
|
1813
1813
|
}
|
|
1814
1814
|
async function detectExistingSystemPrompts(cwd) {
|
|
1815
1815
|
const commonFiles = [
|
|
@@ -1820,7 +1820,7 @@ async function detectExistingSystemPrompts(cwd) {
|
|
|
1820
1820
|
const found = [];
|
|
1821
1821
|
for (const file of commonFiles) {
|
|
1822
1822
|
try {
|
|
1823
|
-
await fs9.access(
|
|
1823
|
+
await fs9.access(path15.join(cwd, file));
|
|
1824
1824
|
found.push(file);
|
|
1825
1825
|
} catch {
|
|
1826
1826
|
}
|
|
@@ -1829,8 +1829,8 @@ async function detectExistingSystemPrompts(cwd) {
|
|
|
1829
1829
|
}
|
|
1830
1830
|
async function handleExistingFiles(action, existingFiles, templateDir, cwd, variables = {}) {
|
|
1831
1831
|
for (const file of existingFiles) {
|
|
1832
|
-
const filePath =
|
|
1833
|
-
const templateFilePath =
|
|
1832
|
+
const filePath = path15.join(cwd, file);
|
|
1833
|
+
const templateFilePath = path15.join(templateDir, "files", file);
|
|
1834
1834
|
try {
|
|
1835
1835
|
await fs9.access(templateFilePath);
|
|
1836
1836
|
} catch {
|
|
@@ -1842,7 +1842,7 @@ async function handleExistingFiles(action, existingFiles, templateDir, cwd, vari
|
|
|
1842
1842
|
for (const [key, value] of Object.entries(variables)) {
|
|
1843
1843
|
template = template.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
1844
1844
|
}
|
|
1845
|
-
const promptPath =
|
|
1845
|
+
const promptPath = path15.join(cwd, ".lean-spec", "MERGE-AGENTS-PROMPT.md");
|
|
1846
1846
|
const aiPrompt = `# AI Prompt: Consolidate AGENTS.md
|
|
1847
1847
|
|
|
1848
1848
|
## Task
|
|
@@ -1874,16 +1874,16 @@ Create a single consolidated AGENTS.md that:
|
|
|
1874
1874
|
- Maintains clear structure and readability
|
|
1875
1875
|
- Removes any duplicate or conflicting guidance
|
|
1876
1876
|
`;
|
|
1877
|
-
await fs9.mkdir(
|
|
1877
|
+
await fs9.mkdir(path15.dirname(promptPath), { recursive: true });
|
|
1878
1878
|
await fs9.writeFile(promptPath, aiPrompt, "utf-8");
|
|
1879
|
-
console.log(
|
|
1880
|
-
console.log(
|
|
1879
|
+
console.log(chalk19.green(`\u2713 Created AI consolidation prompt`));
|
|
1880
|
+
console.log(chalk19.cyan(` \u2192 ${promptPath}`));
|
|
1881
1881
|
console.log("");
|
|
1882
|
-
console.log(
|
|
1883
|
-
console.log(
|
|
1884
|
-
console.log(
|
|
1885
|
-
console.log(
|
|
1886
|
-
console.log(
|
|
1882
|
+
console.log(chalk19.yellow("\u{1F4DD} Next steps:"));
|
|
1883
|
+
console.log(chalk19.gray(" 1. Open .lean-spec/MERGE-AGENTS-PROMPT.md"));
|
|
1884
|
+
console.log(chalk19.gray(" 2. Send it to your AI coding assistant (GitHub Copilot, Cursor, etc.)"));
|
|
1885
|
+
console.log(chalk19.gray(" 3. Let AI create the consolidated AGENTS.md"));
|
|
1886
|
+
console.log(chalk19.gray(" 4. Review and commit the result"));
|
|
1887
1887
|
console.log("");
|
|
1888
1888
|
} else if (action === "merge-append" && file === "AGENTS.md") {
|
|
1889
1889
|
const existing = await fs9.readFile(filePath, "utf-8");
|
|
@@ -1899,19 +1899,19 @@ Create a single consolidated AGENTS.md that:
|
|
|
1899
1899
|
|
|
1900
1900
|
${template.split("\n").slice(1).join("\n")}`;
|
|
1901
1901
|
await fs9.writeFile(filePath, merged, "utf-8");
|
|
1902
|
-
console.log(
|
|
1903
|
-
console.log(
|
|
1902
|
+
console.log(chalk19.green(`\u2713 Appended LeanSpec section to ${file}`));
|
|
1903
|
+
console.log(chalk19.yellow(" \u26A0 Note: May be verbose. Consider consolidating later."));
|
|
1904
1904
|
} else if (action === "overwrite") {
|
|
1905
1905
|
const backupPath = `${filePath}.backup`;
|
|
1906
1906
|
await fs9.rename(filePath, backupPath);
|
|
1907
|
-
console.log(
|
|
1907
|
+
console.log(chalk19.yellow(`\u2713 Backed up ${file} \u2192 ${file}.backup`));
|
|
1908
1908
|
let content = await fs9.readFile(templateFilePath, "utf-8");
|
|
1909
1909
|
for (const [key, value] of Object.entries(variables)) {
|
|
1910
1910
|
content = content.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
1911
1911
|
}
|
|
1912
1912
|
await fs9.writeFile(filePath, content, "utf-8");
|
|
1913
|
-
console.log(
|
|
1914
|
-
console.log(
|
|
1913
|
+
console.log(chalk19.green(`\u2713 Created new ${file}`));
|
|
1914
|
+
console.log(chalk19.gray(` \u{1F4A1} Your original content is preserved in ${file}.backup`));
|
|
1915
1915
|
}
|
|
1916
1916
|
}
|
|
1917
1917
|
}
|
|
@@ -1919,8 +1919,8 @@ async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
|
|
|
1919
1919
|
await fs9.mkdir(dest, { recursive: true });
|
|
1920
1920
|
const entries = await fs9.readdir(src, { withFileTypes: true });
|
|
1921
1921
|
for (const entry of entries) {
|
|
1922
|
-
const srcPath =
|
|
1923
|
-
const destPath =
|
|
1922
|
+
const srcPath = path15.join(src, entry.name);
|
|
1923
|
+
const destPath = path15.join(dest, entry.name);
|
|
1924
1924
|
if (skipFiles.includes(entry.name)) {
|
|
1925
1925
|
continue;
|
|
1926
1926
|
}
|
|
@@ -1941,7 +1941,7 @@ async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
|
|
|
1941
1941
|
}
|
|
1942
1942
|
async function getProjectName2(cwd) {
|
|
1943
1943
|
try {
|
|
1944
|
-
const packageJsonPath =
|
|
1944
|
+
const packageJsonPath = path15.join(cwd, "package.json");
|
|
1945
1945
|
const content = await fs9.readFile(packageJsonPath, "utf-8");
|
|
1946
1946
|
const pkg = JSON.parse(content);
|
|
1947
1947
|
if (pkg.name) {
|
|
@@ -1949,33 +1949,88 @@ async function getProjectName2(cwd) {
|
|
|
1949
1949
|
}
|
|
1950
1950
|
} catch {
|
|
1951
1951
|
}
|
|
1952
|
-
return
|
|
1952
|
+
return path15.basename(cwd);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// src/utils/examples.ts
|
|
1956
|
+
var EXAMPLES = {
|
|
1957
|
+
"dark-theme": {
|
|
1958
|
+
name: "dark-theme",
|
|
1959
|
+
title: "Dark Theme Support",
|
|
1960
|
+
description: "Add dark theme support with automatic system preference detection",
|
|
1961
|
+
difficulty: "beginner",
|
|
1962
|
+
tutorial: "Your First Spec with AI",
|
|
1963
|
+
tutorialUrl: "https://leanspec.dev/docs/tutorials/first-spec-with-ai",
|
|
1964
|
+
tech: ["HTML", "CSS", "JavaScript", "Express.js"],
|
|
1965
|
+
files: 6,
|
|
1966
|
+
lines: 150
|
|
1967
|
+
},
|
|
1968
|
+
"dashboard-widgets": {
|
|
1969
|
+
name: "dashboard-widgets",
|
|
1970
|
+
title: "Dashboard Widgets",
|
|
1971
|
+
description: "Add three new widgets to an analytics dashboard",
|
|
1972
|
+
difficulty: "intermediate",
|
|
1973
|
+
tutorial: "Managing Multiple Features",
|
|
1974
|
+
tutorialUrl: "https://leanspec.dev/docs/tutorials/multiple-features",
|
|
1975
|
+
tech: ["React", "Vite"],
|
|
1976
|
+
files: 17,
|
|
1977
|
+
lines: 300
|
|
1978
|
+
},
|
|
1979
|
+
"api-refactor": {
|
|
1980
|
+
name: "api-refactor",
|
|
1981
|
+
title: "API Client Refactor",
|
|
1982
|
+
description: "Extract reusable API client from monolithic code",
|
|
1983
|
+
difficulty: "intermediate",
|
|
1984
|
+
tutorial: "Refactoring with Specs",
|
|
1985
|
+
tutorialUrl: "https://leanspec.dev/docs/tutorials/refactoring-specs",
|
|
1986
|
+
tech: ["Node.js"],
|
|
1987
|
+
files: 7,
|
|
1988
|
+
lines: 250
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
function getExamplesList() {
|
|
1992
|
+
return Object.values(EXAMPLES);
|
|
1993
|
+
}
|
|
1994
|
+
function getExample(name) {
|
|
1995
|
+
return EXAMPLES[name];
|
|
1996
|
+
}
|
|
1997
|
+
function exampleExists(name) {
|
|
1998
|
+
return name in EXAMPLES;
|
|
1953
1999
|
}
|
|
1954
2000
|
|
|
1955
2001
|
// src/commands/init.ts
|
|
1956
|
-
var __dirname =
|
|
1957
|
-
var TEMPLATES_DIR =
|
|
2002
|
+
var __dirname = path15.dirname(fileURLToPath(import.meta.url));
|
|
2003
|
+
var TEMPLATES_DIR = path15.join(__dirname, "..", "templates");
|
|
2004
|
+
var EXAMPLES_DIR = path15.join(TEMPLATES_DIR, "examples");
|
|
1958
2005
|
function initCommand() {
|
|
1959
|
-
return new Command("init").description("Initialize LeanSpec in current directory").option("-y, --yes", "Skip prompts and use defaults (quick start with standard template)").action(async (options) => {
|
|
2006
|
+
return new Command("init").description("Initialize LeanSpec in current directory").option("-y, --yes", "Skip prompts and use defaults (quick start with standard template)").option("--example <name>", "Scaffold an example project for tutorials").option("--name <dirname>", "Custom directory name for example project").option("--list", "List available example projects").action(async (options) => {
|
|
2007
|
+
if (options.list) {
|
|
2008
|
+
await listExamples();
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
if (options.example !== void 0) {
|
|
2012
|
+
await scaffoldExample(options.example, options.name);
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
1960
2015
|
await initProject(options.yes);
|
|
1961
2016
|
});
|
|
1962
2017
|
}
|
|
1963
2018
|
async function initProject(skipPrompts = false) {
|
|
1964
2019
|
const cwd = process.cwd();
|
|
1965
2020
|
try {
|
|
1966
|
-
await fs9.access(
|
|
1967
|
-
console.log(
|
|
1968
|
-
console.log(
|
|
2021
|
+
await fs9.access(path15.join(cwd, ".lean-spec", "config.json"));
|
|
2022
|
+
console.log(chalk19.yellow("\u26A0 LeanSpec already initialized in this directory."));
|
|
2023
|
+
console.log(chalk19.gray("To reinitialize, delete .lean-spec/ directory first."));
|
|
1969
2024
|
return;
|
|
1970
2025
|
} catch {
|
|
1971
2026
|
}
|
|
1972
2027
|
console.log("");
|
|
1973
|
-
console.log(
|
|
2028
|
+
console.log(chalk19.green("Welcome to LeanSpec!"));
|
|
1974
2029
|
console.log("");
|
|
1975
2030
|
let setupMode = "quick";
|
|
1976
2031
|
let templateName = "standard";
|
|
1977
2032
|
if (skipPrompts) {
|
|
1978
|
-
console.log(
|
|
2033
|
+
console.log(chalk19.gray("Using defaults: quick start with standard template"));
|
|
1979
2034
|
console.log("");
|
|
1980
2035
|
} else {
|
|
1981
2036
|
setupMode = await select({
|
|
@@ -2014,14 +2069,14 @@ async function initProject(skipPrompts = false) {
|
|
|
2014
2069
|
});
|
|
2015
2070
|
}
|
|
2016
2071
|
}
|
|
2017
|
-
const templateDir =
|
|
2018
|
-
const templateConfigPath =
|
|
2072
|
+
const templateDir = path15.join(TEMPLATES_DIR, templateName);
|
|
2073
|
+
const templateConfigPath = path15.join(templateDir, "config.json");
|
|
2019
2074
|
let templateConfig;
|
|
2020
2075
|
try {
|
|
2021
2076
|
const content = await fs9.readFile(templateConfigPath, "utf-8");
|
|
2022
2077
|
templateConfig = JSON.parse(content).config;
|
|
2023
2078
|
} catch {
|
|
2024
|
-
console.error(
|
|
2079
|
+
console.error(chalk19.red(`Error: Template not found: ${templateName}`));
|
|
2025
2080
|
process.exit(1);
|
|
2026
2081
|
}
|
|
2027
2082
|
let patternChoice = "simple";
|
|
@@ -2064,27 +2119,27 @@ async function initProject(skipPrompts = false) {
|
|
|
2064
2119
|
templateConfig.structure.prefix = "{YYYYMMDD}-";
|
|
2065
2120
|
} else if (patternChoice === "custom") {
|
|
2066
2121
|
console.log("");
|
|
2067
|
-
console.log(
|
|
2068
|
-
console.log(
|
|
2069
|
-
console.log(
|
|
2122
|
+
console.log(chalk19.yellow("\u26A0 Custom pattern input is not yet implemented."));
|
|
2123
|
+
console.log(chalk19.gray(" You can manually edit .lean-spec/config.json after initialization."));
|
|
2124
|
+
console.log(chalk19.gray(" Using simple pattern for now."));
|
|
2070
2125
|
console.log("");
|
|
2071
2126
|
templateConfig.structure.pattern = "flat";
|
|
2072
2127
|
templateConfig.structure.prefix = "";
|
|
2073
2128
|
}
|
|
2074
|
-
const templatesDir =
|
|
2129
|
+
const templatesDir = path15.join(cwd, ".lean-spec", "templates");
|
|
2075
2130
|
try {
|
|
2076
2131
|
await fs9.mkdir(templatesDir, { recursive: true });
|
|
2077
2132
|
} catch (error) {
|
|
2078
|
-
console.error(
|
|
2133
|
+
console.error(chalk19.red("Error creating templates directory:"), error);
|
|
2079
2134
|
process.exit(1);
|
|
2080
2135
|
}
|
|
2081
|
-
const templateSpecPath =
|
|
2082
|
-
const targetSpecPath =
|
|
2136
|
+
const templateSpecPath = path15.join(templateDir, "spec-template.md");
|
|
2137
|
+
const targetSpecPath = path15.join(templatesDir, "spec-template.md");
|
|
2083
2138
|
try {
|
|
2084
2139
|
await fs9.copyFile(templateSpecPath, targetSpecPath);
|
|
2085
|
-
console.log(
|
|
2140
|
+
console.log(chalk19.green("\u2713 Created .lean-spec/templates/spec-template.md"));
|
|
2086
2141
|
} catch (error) {
|
|
2087
|
-
console.error(
|
|
2142
|
+
console.error(chalk19.red("Error copying template:"), error);
|
|
2088
2143
|
process.exit(1);
|
|
2089
2144
|
}
|
|
2090
2145
|
templateConfig.template = "spec-template.md";
|
|
@@ -2092,14 +2147,14 @@ async function initProject(skipPrompts = false) {
|
|
|
2092
2147
|
default: "spec-template.md"
|
|
2093
2148
|
};
|
|
2094
2149
|
await saveConfig(templateConfig, cwd);
|
|
2095
|
-
console.log(
|
|
2150
|
+
console.log(chalk19.green("\u2713 Created .lean-spec/config.json"));
|
|
2096
2151
|
const existingFiles = await detectExistingSystemPrompts(cwd);
|
|
2097
2152
|
let skipFiles = [];
|
|
2098
2153
|
if (existingFiles.length > 0) {
|
|
2099
2154
|
console.log("");
|
|
2100
|
-
console.log(
|
|
2155
|
+
console.log(chalk19.yellow(`Found existing: ${existingFiles.join(", ")}`));
|
|
2101
2156
|
if (skipPrompts) {
|
|
2102
|
-
console.log(
|
|
2157
|
+
console.log(chalk19.gray("Using AI-Assisted Merge for existing AGENTS.md"));
|
|
2103
2158
|
const projectName2 = await getProjectName2(cwd);
|
|
2104
2159
|
await handleExistingFiles("merge-ai", existingFiles, templateDir, cwd, { project_name: projectName2 });
|
|
2105
2160
|
} else {
|
|
@@ -2136,31 +2191,151 @@ async function initProject(skipPrompts = false) {
|
|
|
2136
2191
|
}
|
|
2137
2192
|
}
|
|
2138
2193
|
const projectName = await getProjectName2(cwd);
|
|
2139
|
-
const filesDir =
|
|
2194
|
+
const filesDir = path15.join(templateDir, "files");
|
|
2140
2195
|
try {
|
|
2141
2196
|
await copyDirectory(filesDir, cwd, skipFiles, { project_name: projectName });
|
|
2142
|
-
console.log(
|
|
2197
|
+
console.log(chalk19.green("\u2713 Initialized project structure"));
|
|
2143
2198
|
} catch (error) {
|
|
2144
|
-
console.error(
|
|
2199
|
+
console.error(chalk19.red("Error copying template files:"), error);
|
|
2145
2200
|
process.exit(1);
|
|
2146
2201
|
}
|
|
2147
|
-
const specsDir =
|
|
2202
|
+
const specsDir = path15.join(cwd, "specs");
|
|
2148
2203
|
try {
|
|
2149
2204
|
await fs9.mkdir(specsDir, { recursive: true });
|
|
2150
|
-
console.log(
|
|
2205
|
+
console.log(chalk19.green("\u2713 Created specs/ directory"));
|
|
2151
2206
|
} catch (error) {
|
|
2152
|
-
console.error(
|
|
2207
|
+
console.error(chalk19.red("Error creating specs directory:"), error);
|
|
2208
|
+
process.exit(1);
|
|
2209
|
+
}
|
|
2210
|
+
console.log("");
|
|
2211
|
+
console.log(chalk19.green("\u2713 LeanSpec initialized!"));
|
|
2212
|
+
console.log("");
|
|
2213
|
+
console.log("Next steps:");
|
|
2214
|
+
console.log(chalk19.gray(" - Review and customize AGENTS.md"));
|
|
2215
|
+
console.log(chalk19.gray(" - Check out example spec in specs/"));
|
|
2216
|
+
console.log(chalk19.gray(" - Create your first spec: lean-spec create my-feature"));
|
|
2217
|
+
console.log("");
|
|
2218
|
+
}
|
|
2219
|
+
async function listExamples() {
|
|
2220
|
+
const examples = getExamplesList();
|
|
2221
|
+
console.log("");
|
|
2222
|
+
console.log(chalk19.bold("Available Examples:"));
|
|
2223
|
+
console.log("");
|
|
2224
|
+
for (const example of examples) {
|
|
2225
|
+
const difficultyColor = example.difficulty === "beginner" ? chalk19.green : example.difficulty === "intermediate" ? chalk19.yellow : chalk19.red;
|
|
2226
|
+
console.log(chalk19.cyan(` ${example.name}`));
|
|
2227
|
+
console.log(` ${example.description}`);
|
|
2228
|
+
console.log(` ${difficultyColor(example.difficulty)} \u2022 ${example.tech.join(", ")} \u2022 ~${example.lines} lines`);
|
|
2229
|
+
console.log(` Tutorial: ${chalk19.gray(example.tutorialUrl)}`);
|
|
2230
|
+
console.log("");
|
|
2231
|
+
}
|
|
2232
|
+
console.log("Usage:");
|
|
2233
|
+
console.log(chalk19.gray(" lean-spec init --example <name>"));
|
|
2234
|
+
console.log(chalk19.gray(" lean-spec init --example dark-theme"));
|
|
2235
|
+
console.log("");
|
|
2236
|
+
}
|
|
2237
|
+
async function scaffoldExample(exampleName, customName) {
|
|
2238
|
+
if (!exampleName) {
|
|
2239
|
+
exampleName = await selectExample();
|
|
2240
|
+
}
|
|
2241
|
+
if (!exampleExists(exampleName)) {
|
|
2242
|
+
console.error(chalk19.red(`Error: Example "${exampleName}" not found.`));
|
|
2243
|
+
console.log("");
|
|
2244
|
+
console.log("Available examples:");
|
|
2245
|
+
getExamplesList().forEach((ex) => {
|
|
2246
|
+
console.log(` - ${ex.name}`);
|
|
2247
|
+
});
|
|
2248
|
+
console.log("");
|
|
2249
|
+
console.log("Use: lean-spec init --list");
|
|
2153
2250
|
process.exit(1);
|
|
2154
2251
|
}
|
|
2252
|
+
const example = getExample(exampleName);
|
|
2253
|
+
const targetDirName = customName || exampleName;
|
|
2254
|
+
const targetPath = path15.join(process.cwd(), targetDirName);
|
|
2255
|
+
try {
|
|
2256
|
+
const files = await fs9.readdir(targetPath);
|
|
2257
|
+
const nonGitFiles = files.filter((f) => f !== ".git");
|
|
2258
|
+
if (nonGitFiles.length > 0) {
|
|
2259
|
+
console.error(chalk19.red(`Error: Directory "${targetDirName}" already exists and is not empty.`));
|
|
2260
|
+
console.log(chalk19.gray("Choose a different name with --name option."));
|
|
2261
|
+
process.exit(1);
|
|
2262
|
+
}
|
|
2263
|
+
} catch {
|
|
2264
|
+
}
|
|
2265
|
+
console.log("");
|
|
2266
|
+
console.log(chalk19.green(`Setting up example: ${example.title}`));
|
|
2267
|
+
console.log(chalk19.gray(example.description));
|
|
2268
|
+
console.log("");
|
|
2269
|
+
await fs9.mkdir(targetPath, { recursive: true });
|
|
2270
|
+
console.log(chalk19.green(`\u2713 Created directory: ${targetDirName}/`));
|
|
2271
|
+
const examplePath = path15.join(EXAMPLES_DIR, exampleName);
|
|
2272
|
+
await copyDirectoryRecursive(examplePath, targetPath);
|
|
2273
|
+
console.log(chalk19.green("\u2713 Copied example project"));
|
|
2274
|
+
const packageManager = await detectPackageManager();
|
|
2275
|
+
console.log(chalk19.gray(`Installing dependencies with ${packageManager}...`));
|
|
2276
|
+
try {
|
|
2277
|
+
const { execSync: execSync3 } = await import('child_process');
|
|
2278
|
+
execSync3(`${packageManager} install`, {
|
|
2279
|
+
cwd: targetPath,
|
|
2280
|
+
stdio: "inherit"
|
|
2281
|
+
});
|
|
2282
|
+
console.log(chalk19.green("\u2713 Installed dependencies"));
|
|
2283
|
+
} catch (error) {
|
|
2284
|
+
console.log(chalk19.yellow("\u26A0 Failed to install dependencies automatically"));
|
|
2285
|
+
console.log(chalk19.gray(` Run: cd ${targetDirName} && ${packageManager} install`));
|
|
2286
|
+
}
|
|
2155
2287
|
console.log("");
|
|
2156
|
-
console.log(
|
|
2288
|
+
console.log(chalk19.green("\u2713 Example project ready!"));
|
|
2157
2289
|
console.log("");
|
|
2158
2290
|
console.log("Next steps:");
|
|
2159
|
-
console.log(
|
|
2160
|
-
console.log(
|
|
2161
|
-
console.log(
|
|
2291
|
+
console.log(chalk19.cyan(` 1. cd ${targetDirName}`));
|
|
2292
|
+
console.log(chalk19.cyan(" 2. Open this project in your editor"));
|
|
2293
|
+
console.log(chalk19.cyan(` 3. Follow the tutorial: ${example.tutorialUrl}`));
|
|
2294
|
+
console.log(chalk19.cyan(` 4. Ask your AI: "Help me with this tutorial using LeanSpec"`));
|
|
2162
2295
|
console.log("");
|
|
2163
2296
|
}
|
|
2297
|
+
async function selectExample() {
|
|
2298
|
+
const examples = getExamplesList();
|
|
2299
|
+
const choice = await select({
|
|
2300
|
+
message: "Select an example project:",
|
|
2301
|
+
choices: examples.map((ex) => {
|
|
2302
|
+
const difficultyLabel = ex.difficulty === "beginner" ? "\u2605\u2606\u2606" : ex.difficulty === "intermediate" ? "\u2605\u2605\u2606" : "\u2605\u2605\u2605";
|
|
2303
|
+
return {
|
|
2304
|
+
name: `${ex.title} (${difficultyLabel})`,
|
|
2305
|
+
value: ex.name,
|
|
2306
|
+
description: `${ex.description} \u2022 ${ex.tech.join(", ")}`
|
|
2307
|
+
};
|
|
2308
|
+
})
|
|
2309
|
+
});
|
|
2310
|
+
return choice;
|
|
2311
|
+
}
|
|
2312
|
+
async function copyDirectoryRecursive(src, dest) {
|
|
2313
|
+
const entries = await fs9.readdir(src, { withFileTypes: true });
|
|
2314
|
+
for (const entry of entries) {
|
|
2315
|
+
const srcPath = path15.join(src, entry.name);
|
|
2316
|
+
const destPath = path15.join(dest, entry.name);
|
|
2317
|
+
if (entry.isDirectory()) {
|
|
2318
|
+
await fs9.mkdir(destPath, { recursive: true });
|
|
2319
|
+
await copyDirectoryRecursive(srcPath, destPath);
|
|
2320
|
+
} else {
|
|
2321
|
+
await fs9.copyFile(srcPath, destPath);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
async function detectPackageManager() {
|
|
2326
|
+
const cwd = process.cwd();
|
|
2327
|
+
try {
|
|
2328
|
+
await fs9.access(path15.join(cwd, "..", "pnpm-lock.yaml"));
|
|
2329
|
+
return "pnpm";
|
|
2330
|
+
} catch {
|
|
2331
|
+
}
|
|
2332
|
+
try {
|
|
2333
|
+
await fs9.access(path15.join(cwd, "..", "yarn.lock"));
|
|
2334
|
+
return "yarn";
|
|
2335
|
+
} catch {
|
|
2336
|
+
}
|
|
2337
|
+
return "npm";
|
|
2338
|
+
}
|
|
2164
2339
|
function normalizeDateFields2(data) {
|
|
2165
2340
|
const dateFields = ["created", "completed", "updated", "due"];
|
|
2166
2341
|
for (const field of dateFields) {
|
|
@@ -2552,7 +2727,7 @@ var TokenCounter = class {
|
|
|
2552
2727
|
};
|
|
2553
2728
|
}
|
|
2554
2729
|
for (const file of filesToCount) {
|
|
2555
|
-
const filePath =
|
|
2730
|
+
const filePath = path15.join(specPath, file);
|
|
2556
2731
|
const content = await fs9.readFile(filePath, "utf-8");
|
|
2557
2732
|
const tokens = await this.countString(content);
|
|
2558
2733
|
const lines = content.split("\n").length;
|
|
@@ -2888,7 +3063,7 @@ var ComplexityValidator = class {
|
|
|
2888
3063
|
let hasSubSpecs = false;
|
|
2889
3064
|
let subSpecCount = 0;
|
|
2890
3065
|
try {
|
|
2891
|
-
const specDir =
|
|
3066
|
+
const specDir = path15.dirname(spec.filePath);
|
|
2892
3067
|
const files = await fs9.readdir(specDir);
|
|
2893
3068
|
const mdFiles = files.filter(
|
|
2894
3069
|
(f) => f.endsWith(".md") && f !== "README.md"
|
|
@@ -3465,7 +3640,7 @@ async function showFiles(specPath, options = {}) {
|
|
|
3465
3640
|
await autoCheckIfEnabled();
|
|
3466
3641
|
const config = await loadConfig();
|
|
3467
3642
|
const cwd = process.cwd();
|
|
3468
|
-
const specsDir =
|
|
3643
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
3469
3644
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
3470
3645
|
if (!resolvedPath) {
|
|
3471
3646
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}. Try using the full path or spec name (e.g., 001-my-spec)`);
|
|
@@ -3476,14 +3651,14 @@ async function showFiles(specPath, options = {}) {
|
|
|
3476
3651
|
}
|
|
3477
3652
|
const subFiles = await loadSubFiles(spec.fullPath);
|
|
3478
3653
|
console.log("");
|
|
3479
|
-
console.log(
|
|
3654
|
+
console.log(chalk19.cyan(`\u{1F4C4} Files in ${sanitizeUserInput(spec.name)}`));
|
|
3480
3655
|
console.log("");
|
|
3481
|
-
console.log(
|
|
3656
|
+
console.log(chalk19.green("Required:"));
|
|
3482
3657
|
const readmeStat = await fs9.stat(spec.filePath);
|
|
3483
3658
|
const readmeSize = formatSize(readmeStat.size);
|
|
3484
3659
|
const readmeContent = await fs9.readFile(spec.filePath, "utf-8");
|
|
3485
3660
|
const readmeTokens = await countTokens({ content: readmeContent });
|
|
3486
|
-
console.log(
|
|
3661
|
+
console.log(chalk19.green(` \u2713 README.md (${readmeSize}, ~${readmeTokens.total.toLocaleString()} tokens) Main spec`));
|
|
3487
3662
|
console.log("");
|
|
3488
3663
|
let filteredFiles = subFiles;
|
|
3489
3664
|
if (options.type === "docs") {
|
|
@@ -3492,27 +3667,27 @@ async function showFiles(specPath, options = {}) {
|
|
|
3492
3667
|
filteredFiles = subFiles.filter((f) => f.type === "asset");
|
|
3493
3668
|
}
|
|
3494
3669
|
if (filteredFiles.length === 0) {
|
|
3495
|
-
console.log(
|
|
3670
|
+
console.log(chalk19.gray("No additional files"));
|
|
3496
3671
|
console.log("");
|
|
3497
3672
|
return;
|
|
3498
3673
|
}
|
|
3499
3674
|
const documents = filteredFiles.filter((f) => f.type === "document");
|
|
3500
3675
|
const assets = filteredFiles.filter((f) => f.type === "asset");
|
|
3501
3676
|
if (documents.length > 0 && (!options.type || options.type === "docs")) {
|
|
3502
|
-
console.log(
|
|
3677
|
+
console.log(chalk19.cyan("Documents:"));
|
|
3503
3678
|
for (const file of documents) {
|
|
3504
3679
|
const size = formatSize(file.size);
|
|
3505
3680
|
const content = await fs9.readFile(file.path, "utf-8");
|
|
3506
3681
|
const tokenCount = await countTokens({ content });
|
|
3507
|
-
console.log(
|
|
3682
|
+
console.log(chalk19.cyan(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size}, ~${tokenCount.total.toLocaleString()} tokens)`));
|
|
3508
3683
|
}
|
|
3509
3684
|
console.log("");
|
|
3510
3685
|
}
|
|
3511
3686
|
if (assets.length > 0 && (!options.type || options.type === "assets")) {
|
|
3512
|
-
console.log(
|
|
3687
|
+
console.log(chalk19.yellow("Assets:"));
|
|
3513
3688
|
for (const file of assets) {
|
|
3514
3689
|
const size = formatSize(file.size);
|
|
3515
|
-
console.log(
|
|
3690
|
+
console.log(chalk19.yellow(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size})`));
|
|
3516
3691
|
}
|
|
3517
3692
|
console.log("");
|
|
3518
3693
|
}
|
|
@@ -3520,7 +3695,7 @@ async function showFiles(specPath, options = {}) {
|
|
|
3520
3695
|
const totalSize = formatSize(
|
|
3521
3696
|
readmeStat.size + filteredFiles.reduce((sum, f) => sum + f.size, 0)
|
|
3522
3697
|
);
|
|
3523
|
-
console.log(
|
|
3698
|
+
console.log(chalk19.gray(`Total: ${totalFiles} files, ${totalSize}`));
|
|
3524
3699
|
console.log("");
|
|
3525
3700
|
}
|
|
3526
3701
|
function formatSize(bytes) {
|
|
@@ -3532,6 +3707,41 @@ function formatSize(bytes) {
|
|
|
3532
3707
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
3533
3708
|
}
|
|
3534
3709
|
}
|
|
3710
|
+
function examplesCommand() {
|
|
3711
|
+
return new Command("examples").description("List available example projects for tutorials").action(async () => {
|
|
3712
|
+
await listExamples2();
|
|
3713
|
+
});
|
|
3714
|
+
}
|
|
3715
|
+
async function listExamples2() {
|
|
3716
|
+
const examples = getExamplesList();
|
|
3717
|
+
console.log("");
|
|
3718
|
+
console.log(chalk19.bold("LeanSpec Example Projects"));
|
|
3719
|
+
console.log("");
|
|
3720
|
+
console.log("Scaffold complete example projects to follow tutorials:");
|
|
3721
|
+
console.log("");
|
|
3722
|
+
for (const example of examples) {
|
|
3723
|
+
const difficultyColor = example.difficulty === "beginner" ? chalk19.green : example.difficulty === "intermediate" ? chalk19.yellow : chalk19.red;
|
|
3724
|
+
const difficultyStars = example.difficulty === "beginner" ? "\u2605\u2606\u2606" : example.difficulty === "intermediate" ? "\u2605\u2605\u2606" : "\u2605\u2605\u2605";
|
|
3725
|
+
console.log(chalk19.cyan.bold(` ${example.title}`));
|
|
3726
|
+
console.log(` ${chalk19.gray(example.name)}`);
|
|
3727
|
+
console.log(` ${example.description}`);
|
|
3728
|
+
console.log(` ${difficultyColor(difficultyStars + " " + example.difficulty)} \u2022 ${example.tech.join(", ")} \u2022 ~${example.lines} lines`);
|
|
3729
|
+
console.log(` ${chalk19.gray("Tutorial:")} ${example.tutorial}`);
|
|
3730
|
+
console.log(` ${chalk19.gray(example.tutorialUrl)}`);
|
|
3731
|
+
console.log("");
|
|
3732
|
+
}
|
|
3733
|
+
console.log(chalk19.bold("Usage:"));
|
|
3734
|
+
console.log("");
|
|
3735
|
+
console.log(" # Scaffold an example");
|
|
3736
|
+
console.log(chalk19.cyan(" lean-spec init --example <name>"));
|
|
3737
|
+
console.log("");
|
|
3738
|
+
console.log(" # Interactive selection");
|
|
3739
|
+
console.log(chalk19.cyan(" lean-spec init --example"));
|
|
3740
|
+
console.log("");
|
|
3741
|
+
console.log(" # Custom directory name");
|
|
3742
|
+
console.log(chalk19.cyan(" lean-spec init --example dark-theme --name my-demo"));
|
|
3743
|
+
console.log("");
|
|
3744
|
+
}
|
|
3535
3745
|
var FrontmatterValidator = class {
|
|
3536
3746
|
name = "frontmatter";
|
|
3537
3747
|
description = "Validate spec frontmatter for required fields and valid values";
|
|
@@ -4019,7 +4229,7 @@ var SubSpecValidator = class {
|
|
|
4019
4229
|
*/
|
|
4020
4230
|
validateNamingConventions(subSpecs, warnings) {
|
|
4021
4231
|
for (const subSpec of subSpecs) {
|
|
4022
|
-
const baseName =
|
|
4232
|
+
const baseName = path15.basename(subSpec.name, ".md");
|
|
4023
4233
|
if (baseName !== baseName.toUpperCase()) {
|
|
4024
4234
|
warnings.push({
|
|
4025
4235
|
message: `Sub-spec filename should be uppercase: ${subSpec.name}`,
|
|
@@ -4183,17 +4393,17 @@ function formatFileIssues(fileResult, specsDir) {
|
|
|
4183
4393
|
const priority = fileResult.spec.frontmatter.priority || "medium";
|
|
4184
4394
|
const statusBadge = formatStatusBadge(status);
|
|
4185
4395
|
const priorityBadge = formatPriorityBadge(priority);
|
|
4186
|
-
lines.push(
|
|
4396
|
+
lines.push(chalk19.bold.cyan(`${specName} ${statusBadge} ${priorityBadge}`));
|
|
4187
4397
|
} else {
|
|
4188
|
-
lines.push(
|
|
4398
|
+
lines.push(chalk19.cyan.underline(relativePath));
|
|
4189
4399
|
}
|
|
4190
4400
|
for (const issue of fileResult.issues) {
|
|
4191
|
-
const severityColor = issue.severity === "error" ?
|
|
4401
|
+
const severityColor = issue.severity === "error" ? chalk19.red : chalk19.yellow;
|
|
4192
4402
|
const severityText = severityColor(issue.severity.padEnd(9));
|
|
4193
|
-
const ruleText =
|
|
4403
|
+
const ruleText = chalk19.gray(issue.ruleName);
|
|
4194
4404
|
lines.push(` ${severityText}${issue.message.padEnd(60)} ${ruleText}`);
|
|
4195
4405
|
if (issue.suggestion) {
|
|
4196
|
-
lines.push(
|
|
4406
|
+
lines.push(chalk19.gray(` \u2192 ${issue.suggestion}`));
|
|
4197
4407
|
}
|
|
4198
4408
|
}
|
|
4199
4409
|
lines.push("");
|
|
@@ -4203,25 +4413,25 @@ function formatSummary(totalSpecs, errorCount, warningCount, cleanCount) {
|
|
|
4203
4413
|
if (errorCount > 0) {
|
|
4204
4414
|
const errorText = errorCount === 1 ? "error" : "errors";
|
|
4205
4415
|
const warningText = warningCount === 1 ? "warning" : "warnings";
|
|
4206
|
-
return
|
|
4416
|
+
return chalk19.red.bold(
|
|
4207
4417
|
`\u2716 ${errorCount} ${errorText}, ${warningCount} ${warningText} (${totalSpecs} specs checked, ${cleanCount} clean)`
|
|
4208
4418
|
);
|
|
4209
4419
|
} else if (warningCount > 0) {
|
|
4210
4420
|
const warningText = warningCount === 1 ? "warning" : "warnings";
|
|
4211
|
-
return
|
|
4421
|
+
return chalk19.yellow.bold(
|
|
4212
4422
|
`\u26A0 ${warningCount} ${warningText} (${totalSpecs} specs checked, ${cleanCount} clean)`
|
|
4213
4423
|
);
|
|
4214
4424
|
} else {
|
|
4215
|
-
return
|
|
4425
|
+
return chalk19.green.bold(`\u2713 All ${totalSpecs} specs passed`);
|
|
4216
4426
|
}
|
|
4217
4427
|
}
|
|
4218
4428
|
function formatPassingSpecs(specs, specsDir) {
|
|
4219
4429
|
const lines = [];
|
|
4220
|
-
lines.push(
|
|
4430
|
+
lines.push(chalk19.green.bold(`
|
|
4221
4431
|
\u2713 ${specs.length} specs passed:`));
|
|
4222
4432
|
for (const spec of specs) {
|
|
4223
4433
|
const relativePath = normalizeFilePath(spec.filePath);
|
|
4224
|
-
lines.push(
|
|
4434
|
+
lines.push(chalk19.gray(` ${relativePath}`));
|
|
4225
4435
|
}
|
|
4226
4436
|
return lines.join("\n");
|
|
4227
4437
|
}
|
|
@@ -4267,13 +4477,13 @@ function formatValidationResults(results, specs, specsDir, options = {}) {
|
|
|
4267
4477
|
return formatJson(displayResults, specs.length, errorCount2, warningCount2);
|
|
4268
4478
|
}
|
|
4269
4479
|
const lines = [];
|
|
4270
|
-
lines.push(
|
|
4480
|
+
lines.push(chalk19.bold(`
|
|
4271
4481
|
Validating ${specs.length} specs...
|
|
4272
4482
|
`));
|
|
4273
4483
|
let previousSpecName;
|
|
4274
4484
|
for (const fileResult of displayResults) {
|
|
4275
4485
|
if (fileResult.spec && previousSpecName && fileResult.spec.name !== previousSpecName) {
|
|
4276
|
-
lines.push(
|
|
4486
|
+
lines.push(chalk19.gray("\u2500".repeat(80)));
|
|
4277
4487
|
lines.push("");
|
|
4278
4488
|
}
|
|
4279
4489
|
lines.push(formatFileIssues(fileResult));
|
|
@@ -4297,7 +4507,7 @@ Validating ${specs.length} specs...
|
|
|
4297
4507
|
lines.push(formatPassingSpecs(passingSpecs));
|
|
4298
4508
|
}
|
|
4299
4509
|
if (!options.verbose && cleanCount > 0 && displayResults.length > 0) {
|
|
4300
|
-
lines.push(
|
|
4510
|
+
lines.push(chalk19.gray("\nRun with --verbose to see passing specs."));
|
|
4301
4511
|
}
|
|
4302
4512
|
return lines.join("\n");
|
|
4303
4513
|
}
|
|
@@ -4325,12 +4535,12 @@ async function validateSpecs(options = {}) {
|
|
|
4325
4535
|
specs = [];
|
|
4326
4536
|
for (const specPath of options.specs) {
|
|
4327
4537
|
const spec = allSpecs.find(
|
|
4328
|
-
(s) => s.path.includes(specPath) ||
|
|
4538
|
+
(s) => s.path.includes(specPath) || path15.basename(s.path).includes(specPath)
|
|
4329
4539
|
);
|
|
4330
4540
|
if (spec) {
|
|
4331
4541
|
specs.push(spec);
|
|
4332
4542
|
} else {
|
|
4333
|
-
console.error(
|
|
4543
|
+
console.error(chalk19.red(`Error: Spec not found: ${specPath}`));
|
|
4334
4544
|
return false;
|
|
4335
4545
|
}
|
|
4336
4546
|
}
|
|
@@ -4358,7 +4568,7 @@ async function validateSpecs(options = {}) {
|
|
|
4358
4568
|
try {
|
|
4359
4569
|
content = await fs9.readFile(spec.filePath, "utf-8");
|
|
4360
4570
|
} catch (error) {
|
|
4361
|
-
console.error(
|
|
4571
|
+
console.error(chalk19.red(`Error reading ${spec.filePath}:`), error);
|
|
4362
4572
|
continue;
|
|
4363
4573
|
}
|
|
4364
4574
|
for (const validator of validators) {
|
|
@@ -4371,7 +4581,7 @@ async function validateSpecs(options = {}) {
|
|
|
4371
4581
|
content
|
|
4372
4582
|
});
|
|
4373
4583
|
} catch (error) {
|
|
4374
|
-
console.error(
|
|
4584
|
+
console.error(chalk19.yellow(`Warning: Validator ${validator.name} failed:`), error instanceof Error ? error.message : error);
|
|
4375
4585
|
}
|
|
4376
4586
|
}
|
|
4377
4587
|
}
|
|
@@ -4440,7 +4650,7 @@ async function scanDocuments(dirPath) {
|
|
|
4440
4650
|
async function scanRecursive(currentPath) {
|
|
4441
4651
|
const entries = await fs9.readdir(currentPath, { withFileTypes: true });
|
|
4442
4652
|
for (const entry of entries) {
|
|
4443
|
-
const fullPath =
|
|
4653
|
+
const fullPath = path15.join(currentPath, entry.name);
|
|
4444
4654
|
if (entry.isDirectory()) {
|
|
4445
4655
|
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
4446
4656
|
await scanRecursive(fullPath);
|
|
@@ -4806,7 +5016,7 @@ async function showBoard(options) {
|
|
|
4806
5016
|
})
|
|
4807
5017
|
);
|
|
4808
5018
|
if (specs.length === 0) {
|
|
4809
|
-
console.log(
|
|
5019
|
+
console.log(chalk19.dim("No specs found."));
|
|
4810
5020
|
return;
|
|
4811
5021
|
}
|
|
4812
5022
|
const columns = {
|
|
@@ -4821,12 +5031,12 @@ async function showBoard(options) {
|
|
|
4821
5031
|
columns[status].push(spec);
|
|
4822
5032
|
}
|
|
4823
5033
|
}
|
|
4824
|
-
console.log(
|
|
5034
|
+
console.log(chalk19.bold.cyan("\u{1F4CB} Spec Kanban Board"));
|
|
4825
5035
|
if (options.tag || options.assignee) {
|
|
4826
5036
|
const filterParts = [];
|
|
4827
5037
|
if (options.tag) filterParts.push(`tag=${options.tag}`);
|
|
4828
5038
|
if (options.assignee) filterParts.push(`assignee=${options.assignee}`);
|
|
4829
|
-
console.log(
|
|
5039
|
+
console.log(chalk19.dim(`Filtered by: ${filterParts.join(", ")}`));
|
|
4830
5040
|
}
|
|
4831
5041
|
console.log("");
|
|
4832
5042
|
if (!options.simple) {
|
|
@@ -4841,12 +5051,12 @@ async function showBoard(options) {
|
|
|
4841
5051
|
const padding = boxWidth - 2 - visibleLength;
|
|
4842
5052
|
return content + " ".repeat(Math.max(0, padding));
|
|
4843
5053
|
};
|
|
4844
|
-
console.log(
|
|
4845
|
-
const headerLine =
|
|
4846
|
-
console.log(
|
|
4847
|
-
const percentageColor = completionMetrics.score >= 70 ?
|
|
5054
|
+
console.log(chalk19.dim(topBorder));
|
|
5055
|
+
const headerLine = chalk19.bold(" Project Overview");
|
|
5056
|
+
console.log(chalk19.dim("\u2551") + padLine(headerLine) + chalk19.dim("\u2551"));
|
|
5057
|
+
const percentageColor = completionMetrics.score >= 70 ? chalk19.green : completionMetrics.score >= 40 ? chalk19.yellow : chalk19.red;
|
|
4848
5058
|
const line1 = ` ${completionMetrics.totalSpecs} total \xB7 ${completionMetrics.activeSpecs} active \xB7 ${completionMetrics.completeSpecs} complete ${percentageColor("(" + completionMetrics.score + "%)")}`;
|
|
4849
|
-
console.log(
|
|
5059
|
+
console.log(chalk19.dim("\u2551") + padLine(line1) + chalk19.dim("\u2551"));
|
|
4850
5060
|
if (completionMetrics.criticalIssues.length > 0 || completionMetrics.warnings.length > 0) {
|
|
4851
5061
|
const alerts = [];
|
|
4852
5062
|
if (completionMetrics.criticalIssues.length > 0) {
|
|
@@ -4855,27 +5065,27 @@ async function showBoard(options) {
|
|
|
4855
5065
|
if (completionMetrics.warnings.length > 0) {
|
|
4856
5066
|
alerts.push(`${completionMetrics.warnings.length} specs WIP > 7 days`);
|
|
4857
5067
|
}
|
|
4858
|
-
const alertLine = ` ${
|
|
4859
|
-
console.log(
|
|
5068
|
+
const alertLine = ` ${chalk19.yellow("\u26A0\uFE0F " + alerts.join(" \xB7 "))}`;
|
|
5069
|
+
console.log(chalk19.dim("\u2551") + padLine(alertLine) + chalk19.dim("\u2551"));
|
|
4860
5070
|
}
|
|
4861
|
-
const velocityLine = ` ${
|
|
4862
|
-
console.log(
|
|
4863
|
-
console.log(
|
|
5071
|
+
const velocityLine = ` ${chalk19.cyan("\u{1F680} Velocity:")} ${velocityMetrics.cycleTime.average.toFixed(1)}d avg cycle \xB7 ${(velocityMetrics.throughput.perWeek / 7 * 7).toFixed(1)}/wk throughput`;
|
|
5072
|
+
console.log(chalk19.dim("\u2551") + padLine(velocityLine) + chalk19.dim("\u2551"));
|
|
5073
|
+
console.log(chalk19.dim(bottomBorder));
|
|
4864
5074
|
console.log("");
|
|
4865
5075
|
if (options.completionOnly) {
|
|
4866
5076
|
return;
|
|
4867
5077
|
}
|
|
4868
5078
|
}
|
|
4869
5079
|
renderColumn(STATUS_CONFIG.planned.label, STATUS_CONFIG.planned.emoji, columns.planned, true, STATUS_CONFIG.planned.colorFn);
|
|
4870
|
-
console.log(
|
|
5080
|
+
console.log(chalk19.dim("\u2501".repeat(70)));
|
|
4871
5081
|
console.log("");
|
|
4872
5082
|
renderColumn(STATUS_CONFIG["in-progress"].label, STATUS_CONFIG["in-progress"].emoji, columns["in-progress"], true, STATUS_CONFIG["in-progress"].colorFn);
|
|
4873
|
-
console.log(
|
|
5083
|
+
console.log(chalk19.dim("\u2501".repeat(70)));
|
|
4874
5084
|
console.log("");
|
|
4875
5085
|
renderColumn(STATUS_CONFIG.complete.label, STATUS_CONFIG.complete.emoji, columns.complete, options.showComplete || false, STATUS_CONFIG.complete.colorFn);
|
|
4876
5086
|
}
|
|
4877
5087
|
function renderColumn(title, emoji, specs, expanded, colorFn) {
|
|
4878
|
-
console.log(`${emoji} ${colorFn(
|
|
5088
|
+
console.log(`${emoji} ${colorFn(chalk19.bold(`${title} (${specs.length})`))}`);
|
|
4879
5089
|
console.log("");
|
|
4880
5090
|
if (expanded && specs.length > 0) {
|
|
4881
5091
|
const priorityGroups = {
|
|
@@ -4900,30 +5110,30 @@ function renderColumn(title, emoji, specs, expanded, colorFn) {
|
|
|
4900
5110
|
firstGroup = false;
|
|
4901
5111
|
const priorityLabel = priority === "none" ? "No Priority" : priority.charAt(0).toUpperCase() + priority.slice(1);
|
|
4902
5112
|
const priorityEmoji = priority === "none" ? "\u26AA" : PRIORITY_CONFIG[priority].emoji;
|
|
4903
|
-
const priorityColor = priority === "none" ?
|
|
4904
|
-
console.log(` ${priorityColor(`${priorityEmoji} ${
|
|
5113
|
+
const priorityColor = priority === "none" ? chalk19.dim : PRIORITY_CONFIG[priority].colorFn;
|
|
5114
|
+
console.log(` ${priorityColor(`${priorityEmoji} ${chalk19.bold(priorityLabel)} ${chalk19.dim(`(${groupSpecs.length})`)}`)}`);
|
|
4905
5115
|
for (const spec of groupSpecs) {
|
|
4906
5116
|
let assigneeStr = "";
|
|
4907
5117
|
if (spec.frontmatter.assignee) {
|
|
4908
|
-
assigneeStr = " " +
|
|
5118
|
+
assigneeStr = " " + chalk19.cyan(`@${sanitizeUserInput(spec.frontmatter.assignee)}`);
|
|
4909
5119
|
}
|
|
4910
5120
|
let tagsStr = "";
|
|
4911
5121
|
if (spec.frontmatter.tags?.length) {
|
|
4912
5122
|
const tags = Array.isArray(spec.frontmatter.tags) ? spec.frontmatter.tags : [];
|
|
4913
5123
|
if (tags.length > 0) {
|
|
4914
5124
|
const tagStr = tags.map((tag) => `#${sanitizeUserInput(tag)}`).join(" ");
|
|
4915
|
-
tagsStr = " " +
|
|
5125
|
+
tagsStr = " " + chalk19.dim(chalk19.magenta(tagStr));
|
|
4916
5126
|
}
|
|
4917
5127
|
}
|
|
4918
|
-
console.log(` ${
|
|
5128
|
+
console.log(` ${chalk19.cyan(sanitizeUserInput(spec.path))}${assigneeStr}${tagsStr}`);
|
|
4919
5129
|
}
|
|
4920
5130
|
}
|
|
4921
5131
|
console.log("");
|
|
4922
5132
|
} else if (!expanded && specs.length > 0) {
|
|
4923
|
-
console.log(` ${
|
|
5133
|
+
console.log(` ${chalk19.dim("(collapsed, use --complete to expand)")}`);
|
|
4924
5134
|
console.log("");
|
|
4925
5135
|
} else {
|
|
4926
|
-
console.log(` ${
|
|
5136
|
+
console.log(` ${chalk19.dim("(empty)")}`);
|
|
4927
5137
|
console.log("");
|
|
4928
5138
|
}
|
|
4929
5139
|
}
|
|
@@ -5092,26 +5302,26 @@ async function showStats(options) {
|
|
|
5092
5302
|
console.log(JSON.stringify(data, null, 2));
|
|
5093
5303
|
return;
|
|
5094
5304
|
}
|
|
5095
|
-
console.log(
|
|
5305
|
+
console.log(chalk19.bold.cyan("\u{1F4CA} Spec Stats"));
|
|
5096
5306
|
console.log("");
|
|
5097
5307
|
if (options.tag || options.assignee) {
|
|
5098
5308
|
const filterParts = [];
|
|
5099
5309
|
if (options.tag) filterParts.push(`tag=${options.tag}`);
|
|
5100
5310
|
if (options.assignee) filterParts.push(`assignee=${options.assignee}`);
|
|
5101
|
-
console.log(
|
|
5311
|
+
console.log(chalk19.dim(`Filtered by: ${filterParts.join(", ")}`));
|
|
5102
5312
|
console.log("");
|
|
5103
5313
|
}
|
|
5104
5314
|
if (showSimplified) {
|
|
5105
|
-
console.log(
|
|
5315
|
+
console.log(chalk19.bold("\u{1F4C8} Overview"));
|
|
5106
5316
|
console.log("");
|
|
5107
5317
|
const completionStatus = getCompletionStatus(completionMetrics.score);
|
|
5108
|
-
const completionColor = completionStatus.color === "green" ?
|
|
5109
|
-
console.log(` Total Specs ${
|
|
5110
|
-
console.log(` Active (Planned+WIP) ${
|
|
5111
|
-
console.log(` Complete ${
|
|
5318
|
+
const completionColor = completionStatus.color === "green" ? chalk19.green : completionStatus.color === "yellow" ? chalk19.yellow : chalk19.red;
|
|
5319
|
+
console.log(` Total Specs ${chalk19.cyan(completionMetrics.totalSpecs)}`);
|
|
5320
|
+
console.log(` Active (Planned+WIP) ${chalk19.yellow(completionMetrics.activeSpecs)}`);
|
|
5321
|
+
console.log(` Complete ${chalk19.green(completionMetrics.completeSpecs)}`);
|
|
5112
5322
|
console.log(` Completion Rate ${completionColor(`${completionMetrics.score}% ${completionStatus.emoji}`)}`);
|
|
5113
5323
|
console.log("");
|
|
5114
|
-
console.log(
|
|
5324
|
+
console.log(chalk19.bold("\u{1F4CA} Status"));
|
|
5115
5325
|
console.log("");
|
|
5116
5326
|
const labelWidth2 = 15;
|
|
5117
5327
|
const barWidth2 = 20;
|
|
@@ -5120,19 +5330,19 @@ async function showStats(options) {
|
|
|
5120
5330
|
const width = Math.round(count / maxCount * barWidth2);
|
|
5121
5331
|
const filledWidth = Math.min(width, barWidth2);
|
|
5122
5332
|
const emptyWidth = barWidth2 - filledWidth;
|
|
5123
|
-
return char.repeat(filledWidth) +
|
|
5333
|
+
return char.repeat(filledWidth) + chalk19.dim("\u2591").repeat(emptyWidth);
|
|
5124
5334
|
};
|
|
5125
|
-
console.log(` \u{1F4C5} ${"Planned".padEnd(labelWidth2)} ${
|
|
5126
|
-
console.log(` \u23F3 ${"In Progress".padEnd(labelWidth2)} ${
|
|
5127
|
-
console.log(` \u2705 ${"Complete".padEnd(labelWidth2)} ${
|
|
5335
|
+
console.log(` \u{1F4C5} ${"Planned".padEnd(labelWidth2)} ${chalk19.cyan(createBar2(statusCounts.planned, maxStatusCount))} ${chalk19.cyan(statusCounts.planned)}`);
|
|
5336
|
+
console.log(` \u23F3 ${"In Progress".padEnd(labelWidth2)} ${chalk19.yellow(createBar2(statusCounts["in-progress"], maxStatusCount))} ${chalk19.yellow(statusCounts["in-progress"])}`);
|
|
5337
|
+
console.log(` \u2705 ${"Complete".padEnd(labelWidth2)} ${chalk19.green(createBar2(statusCounts.complete, maxStatusCount))} ${chalk19.green(statusCounts.complete)}`);
|
|
5128
5338
|
if (statusCounts.archived > 0) {
|
|
5129
|
-
console.log(` \u{1F4E6} ${"Archived".padEnd(labelWidth2)} ${
|
|
5339
|
+
console.log(` \u{1F4E6} ${"Archived".padEnd(labelWidth2)} ${chalk19.dim(createBar2(statusCounts.archived, maxStatusCount))} ${chalk19.dim(statusCounts.archived)}`);
|
|
5130
5340
|
}
|
|
5131
5341
|
console.log("");
|
|
5132
5342
|
const criticalCount = priorityCounts.critical || 0;
|
|
5133
5343
|
const highCount = priorityCounts.high || 0;
|
|
5134
5344
|
if (criticalCount > 0 || highCount > 0) {
|
|
5135
|
-
console.log(
|
|
5345
|
+
console.log(chalk19.bold("\u{1F3AF} Priority Focus"));
|
|
5136
5346
|
console.log("");
|
|
5137
5347
|
if (criticalCount > 0) {
|
|
5138
5348
|
const criticalPlanned = specs.filter((s) => s.frontmatter.priority === "critical" && s.frontmatter.status === "planned").length;
|
|
@@ -5142,11 +5352,11 @@ async function showStats(options) {
|
|
|
5142
5352
|
(s) => s.frontmatter.priority === "critical" && s.frontmatter.due && dayjs3(s.frontmatter.due).isBefore(dayjs3(), "day") && s.frontmatter.status !== "complete"
|
|
5143
5353
|
).length;
|
|
5144
5354
|
const parts = [];
|
|
5145
|
-
if (criticalPlanned > 0) parts.push(
|
|
5355
|
+
if (criticalPlanned > 0) parts.push(chalk19.dim(`${criticalPlanned} planned`));
|
|
5146
5356
|
if (criticalInProgress > 0) parts.push(`${criticalInProgress} in-progress`);
|
|
5147
|
-
if (criticalComplete > 0) parts.push(
|
|
5148
|
-
if (criticalOverdue > 0) parts.push(
|
|
5149
|
-
console.log(` \u{1F534} Critical ${
|
|
5357
|
+
if (criticalComplete > 0) parts.push(chalk19.green(`${criticalComplete} complete`));
|
|
5358
|
+
if (criticalOverdue > 0) parts.push(chalk19.red(`${criticalOverdue} overdue!`));
|
|
5359
|
+
console.log(` \u{1F534} Critical ${chalk19.red(criticalCount)} specs${parts.length > 0 ? ` (${parts.join(", ")})` : ""}`);
|
|
5150
5360
|
}
|
|
5151
5361
|
if (highCount > 0) {
|
|
5152
5362
|
const highPlanned = specs.filter((s) => s.frontmatter.priority === "high" && s.frontmatter.status === "planned").length;
|
|
@@ -5156,52 +5366,52 @@ async function showStats(options) {
|
|
|
5156
5366
|
(s) => s.frontmatter.priority === "high" && s.frontmatter.due && dayjs3(s.frontmatter.due).isBefore(dayjs3(), "day") && s.frontmatter.status !== "complete"
|
|
5157
5367
|
).length;
|
|
5158
5368
|
const parts = [];
|
|
5159
|
-
if (highPlanned > 0) parts.push(
|
|
5369
|
+
if (highPlanned > 0) parts.push(chalk19.dim(`${highPlanned} planned`));
|
|
5160
5370
|
if (highInProgress > 0) parts.push(`${highInProgress} in-progress`);
|
|
5161
|
-
if (highComplete > 0) parts.push(
|
|
5162
|
-
if (highOverdue > 0) parts.push(
|
|
5163
|
-
console.log(` \u{1F7E0} High ${
|
|
5371
|
+
if (highComplete > 0) parts.push(chalk19.green(`${highComplete} complete`));
|
|
5372
|
+
if (highOverdue > 0) parts.push(chalk19.yellow(`${highOverdue} overdue`));
|
|
5373
|
+
console.log(` \u{1F7E0} High ${chalk19.hex("#FFA500")(highCount)} specs${parts.length > 0 ? ` (${parts.join(", ")})` : ""}`);
|
|
5164
5374
|
}
|
|
5165
5375
|
console.log("");
|
|
5166
5376
|
}
|
|
5167
5377
|
if (insights.length > 0) {
|
|
5168
|
-
console.log(
|
|
5378
|
+
console.log(chalk19.bold.yellow("\u26A0\uFE0F Needs Attention"));
|
|
5169
5379
|
console.log("");
|
|
5170
5380
|
for (const insight of insights) {
|
|
5171
|
-
const color = insight.severity === "critical" ?
|
|
5381
|
+
const color = insight.severity === "critical" ? chalk19.red : insight.severity === "warning" ? chalk19.yellow : chalk19.cyan;
|
|
5172
5382
|
console.log(` ${color("\u2022")} ${insight.message}`);
|
|
5173
5383
|
for (const specPath of insight.specs.slice(0, 3)) {
|
|
5174
5384
|
const spec = specs.find((s) => s.path === specPath);
|
|
5175
5385
|
const details = spec ? getSpecInsightDetails(spec) : null;
|
|
5176
|
-
console.log(` ${
|
|
5386
|
+
console.log(` ${chalk19.dim(specPath)}${details ? chalk19.dim(` (${details})`) : ""}`);
|
|
5177
5387
|
}
|
|
5178
5388
|
if (insight.specs.length > 3) {
|
|
5179
|
-
console.log(` ${
|
|
5389
|
+
console.log(` ${chalk19.dim(`...and ${insight.specs.length - 3} more`)}`);
|
|
5180
5390
|
}
|
|
5181
5391
|
}
|
|
5182
5392
|
console.log("");
|
|
5183
5393
|
} else if (completionMetrics.activeSpecs === 0 && completionMetrics.completeSpecs > 0) {
|
|
5184
|
-
console.log(
|
|
5394
|
+
console.log(chalk19.bold.green("\u{1F389} All Specs Complete!"));
|
|
5185
5395
|
console.log("");
|
|
5186
|
-
console.log(` ${
|
|
5396
|
+
console.log(` ${chalk19.dim("Great work! All active specs are complete.")}`);
|
|
5187
5397
|
console.log("");
|
|
5188
5398
|
} else if (completionMetrics.activeSpecs > 0) {
|
|
5189
|
-
console.log(
|
|
5399
|
+
console.log(chalk19.bold.green("\u2728 All Clear!"));
|
|
5190
5400
|
console.log("");
|
|
5191
|
-
console.log(` ${
|
|
5401
|
+
console.log(` ${chalk19.dim("No critical issues detected. Keep up the good work!")}`);
|
|
5192
5402
|
console.log("");
|
|
5193
5403
|
}
|
|
5194
|
-
console.log(
|
|
5404
|
+
console.log(chalk19.bold("\u{1F680} Velocity Summary"));
|
|
5195
5405
|
console.log("");
|
|
5196
|
-
const cycleTimeStatus = velocityMetrics.cycleTime.average <= 7 ?
|
|
5197
|
-
const throughputTrend = velocityMetrics.throughput.trend === "up" ?
|
|
5198
|
-
console.log(` Avg Cycle Time ${
|
|
5199
|
-
console.log(` Throughput ${
|
|
5200
|
-
console.log(` WIP ${
|
|
5406
|
+
const cycleTimeStatus = velocityMetrics.cycleTime.average <= 7 ? chalk19.green("\u2713") : chalk19.yellow("\u26A0");
|
|
5407
|
+
const throughputTrend = velocityMetrics.throughput.trend === "up" ? chalk19.green("\u2191") : velocityMetrics.throughput.trend === "down" ? chalk19.red("\u2193") : chalk19.yellow("\u2192");
|
|
5408
|
+
console.log(` Avg Cycle Time ${chalk19.cyan(velocityMetrics.cycleTime.average.toFixed(1))} days ${cycleTimeStatus}${velocityMetrics.cycleTime.average <= 7 ? chalk19.dim(" (target: 7d)") : ""}`);
|
|
5409
|
+
console.log(` Throughput ${chalk19.cyan((velocityMetrics.throughput.perWeek / 7 * 7).toFixed(1))}/week ${throughputTrend}`);
|
|
5410
|
+
console.log(` WIP ${chalk19.yellow(velocityMetrics.wip.current)} specs`);
|
|
5201
5411
|
console.log("");
|
|
5202
|
-
console.log(
|
|
5203
|
-
console.log(
|
|
5204
|
-
console.log(
|
|
5412
|
+
console.log(chalk19.dim("\u{1F4A1} Use `lean-spec stats --full` for detailed analytics"));
|
|
5413
|
+
console.log(chalk19.dim(" Use `lean-spec stats --velocity` for velocity breakdown"));
|
|
5414
|
+
console.log(chalk19.dim(" Use `lean-spec stats --timeline` for activity timeline"));
|
|
5205
5415
|
console.log("");
|
|
5206
5416
|
return;
|
|
5207
5417
|
}
|
|
@@ -5217,97 +5427,97 @@ async function showStats(options) {
|
|
|
5217
5427
|
(sum, count) => sum + count,
|
|
5218
5428
|
0
|
|
5219
5429
|
);
|
|
5220
|
-
console.log(
|
|
5430
|
+
console.log(chalk19.bold("\u{1F4C8} Overview"));
|
|
5221
5431
|
console.log("");
|
|
5222
5432
|
console.log(
|
|
5223
5433
|
` ${"Metric".padEnd(labelWidth)} ${"Value".padStart(valueWidth)}`
|
|
5224
5434
|
);
|
|
5225
5435
|
console.log(
|
|
5226
|
-
` ${
|
|
5436
|
+
` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`
|
|
5227
5437
|
);
|
|
5228
5438
|
console.log(
|
|
5229
|
-
` ${"Total Specs".padEnd(labelWidth)} ${
|
|
5439
|
+
` ${"Total Specs".padEnd(labelWidth)} ${chalk19.green(specs.length.toString().padStart(valueWidth))}`
|
|
5230
5440
|
);
|
|
5231
5441
|
console.log(
|
|
5232
|
-
` ${"With Priority".padEnd(labelWidth)} ${
|
|
5442
|
+
` ${"With Priority".padEnd(labelWidth)} ${chalk19.cyan(totalWithPriority.toString().padStart(valueWidth))}`
|
|
5233
5443
|
);
|
|
5234
5444
|
console.log(
|
|
5235
|
-
` ${"Unique Tags".padEnd(labelWidth)} ${
|
|
5445
|
+
` ${"Unique Tags".padEnd(labelWidth)} ${chalk19.magenta(Object.keys(tagCounts).length.toString().padStart(valueWidth))}`
|
|
5236
5446
|
);
|
|
5237
5447
|
console.log("");
|
|
5238
|
-
console.log(
|
|
5448
|
+
console.log(chalk19.bold("\u{1F4CA} Status Distribution"));
|
|
5239
5449
|
console.log("");
|
|
5240
5450
|
const maxStatusCount = Math.max(...Object.values(statusCounts));
|
|
5241
5451
|
const colWidth = barWidth + 3;
|
|
5242
5452
|
console.log(
|
|
5243
|
-
` ${"Status".padEnd(labelWidth)} ${
|
|
5453
|
+
` ${"Status".padEnd(labelWidth)} ${chalk19.cyan("Count".padEnd(colWidth))}`
|
|
5244
5454
|
);
|
|
5245
5455
|
console.log(
|
|
5246
|
-
` ${
|
|
5456
|
+
` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`
|
|
5247
5457
|
);
|
|
5248
5458
|
console.log(
|
|
5249
|
-
` \u{1F4CB} ${"Planned".padEnd(labelWidth - 3)} ${
|
|
5459
|
+
` \u{1F4CB} ${"Planned".padEnd(labelWidth - 3)} ${chalk19.cyan(createBar(statusCounts.planned, maxStatusCount).padEnd(barWidth))}${chalk19.cyan(statusCounts.planned.toString().padStart(3))}`
|
|
5250
5460
|
);
|
|
5251
5461
|
console.log(
|
|
5252
|
-
` \u23F3 ${"In Progress".padEnd(labelWidth - 3)} ${
|
|
5462
|
+
` \u23F3 ${"In Progress".padEnd(labelWidth - 3)} ${chalk19.yellow(createBar(statusCounts["in-progress"], maxStatusCount).padEnd(barWidth))}${chalk19.yellow(statusCounts["in-progress"].toString().padStart(3))}`
|
|
5253
5463
|
);
|
|
5254
5464
|
console.log(
|
|
5255
|
-
` \u2705 ${"Complete".padEnd(labelWidth - 3)} ${
|
|
5465
|
+
` \u2705 ${"Complete".padEnd(labelWidth - 3)} ${chalk19.green(createBar(statusCounts.complete, maxStatusCount).padEnd(barWidth))}${chalk19.green(statusCounts.complete.toString().padStart(3))}`
|
|
5256
5466
|
);
|
|
5257
5467
|
console.log(
|
|
5258
|
-
` \u{1F4E6} ${"Archived".padEnd(labelWidth - 3)} ${
|
|
5468
|
+
` \u{1F4E6} ${"Archived".padEnd(labelWidth - 3)} ${chalk19.dim(createBar(statusCounts.archived, maxStatusCount).padEnd(barWidth))}${chalk19.dim(statusCounts.archived.toString().padStart(3))}`
|
|
5259
5469
|
);
|
|
5260
5470
|
console.log("");
|
|
5261
5471
|
if (totalWithPriority > 0) {
|
|
5262
|
-
console.log(
|
|
5472
|
+
console.log(chalk19.bold("\u{1F3AF} Priority Breakdown"));
|
|
5263
5473
|
console.log("");
|
|
5264
5474
|
const maxPriorityCount = Math.max(
|
|
5265
5475
|
...Object.values(priorityCounts).filter((c) => c > 0)
|
|
5266
5476
|
);
|
|
5267
5477
|
console.log(
|
|
5268
|
-
` ${"Priority".padEnd(labelWidth)} ${
|
|
5478
|
+
` ${"Priority".padEnd(labelWidth)} ${chalk19.cyan("Count".padEnd(colWidth))}`
|
|
5269
5479
|
);
|
|
5270
5480
|
console.log(
|
|
5271
|
-
` ${
|
|
5481
|
+
` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`
|
|
5272
5482
|
);
|
|
5273
5483
|
if (priorityCounts.critical > 0) {
|
|
5274
5484
|
console.log(
|
|
5275
|
-
` \u{1F534} ${"Critical".padEnd(labelWidth - 3)} ${
|
|
5485
|
+
` \u{1F534} ${"Critical".padEnd(labelWidth - 3)} ${chalk19.red(createBar(priorityCounts.critical, maxPriorityCount).padEnd(barWidth))}${chalk19.red(priorityCounts.critical.toString().padStart(3))}`
|
|
5276
5486
|
);
|
|
5277
5487
|
}
|
|
5278
5488
|
if (priorityCounts.high > 0) {
|
|
5279
5489
|
console.log(
|
|
5280
|
-
` \u{1F7E0} ${"High".padEnd(labelWidth - 3)} ${
|
|
5490
|
+
` \u{1F7E0} ${"High".padEnd(labelWidth - 3)} ${chalk19.hex("#FFA500")(createBar(priorityCounts.high, maxPriorityCount).padEnd(barWidth))}${chalk19.hex("#FFA500")(priorityCounts.high.toString().padStart(3))}`
|
|
5281
5491
|
);
|
|
5282
5492
|
}
|
|
5283
5493
|
if (priorityCounts.medium > 0) {
|
|
5284
5494
|
console.log(
|
|
5285
|
-
` \u{1F7E1} ${"Medium".padEnd(labelWidth - 3)} ${
|
|
5495
|
+
` \u{1F7E1} ${"Medium".padEnd(labelWidth - 3)} ${chalk19.yellow(createBar(priorityCounts.medium, maxPriorityCount).padEnd(barWidth))}${chalk19.yellow(priorityCounts.medium.toString().padStart(3))}`
|
|
5286
5496
|
);
|
|
5287
5497
|
}
|
|
5288
5498
|
if (priorityCounts.low > 0) {
|
|
5289
5499
|
console.log(
|
|
5290
|
-
` \u{1F7E2} ${"Low".padEnd(labelWidth - 3)} ${
|
|
5500
|
+
` \u{1F7E2} ${"Low".padEnd(labelWidth - 3)} ${chalk19.green(createBar(priorityCounts.low, maxPriorityCount).padEnd(barWidth))}${chalk19.green(priorityCounts.low.toString().padStart(3))}`
|
|
5291
5501
|
);
|
|
5292
5502
|
}
|
|
5293
5503
|
console.log("");
|
|
5294
5504
|
}
|
|
5295
5505
|
const topTags = Object.entries(tagCounts).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
5296
5506
|
if (topTags.length > 0) {
|
|
5297
|
-
console.log(
|
|
5507
|
+
console.log(chalk19.bold("\u{1F3F7}\uFE0F Popular Tags"));
|
|
5298
5508
|
console.log("");
|
|
5299
5509
|
const maxTagCount = Math.max(...topTags.map(([, count]) => count));
|
|
5300
5510
|
console.log(
|
|
5301
|
-
` ${"Tag".padEnd(labelWidth)} ${
|
|
5511
|
+
` ${"Tag".padEnd(labelWidth)} ${chalk19.magenta("Count".padEnd(colWidth))}`
|
|
5302
5512
|
);
|
|
5303
5513
|
console.log(
|
|
5304
|
-
` ${
|
|
5514
|
+
` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`
|
|
5305
5515
|
);
|
|
5306
5516
|
for (const [tag, count] of topTags) {
|
|
5307
5517
|
const truncatedTag = tag.length > labelWidth ? tag.substring(0, labelWidth - 1) + "\u2026" : tag;
|
|
5308
5518
|
const bar = createBar(count, maxTagCount);
|
|
5309
5519
|
console.log(
|
|
5310
|
-
` ${truncatedTag.padEnd(labelWidth)} ${
|
|
5520
|
+
` ${truncatedTag.padEnd(labelWidth)} ${chalk19.magenta(bar.padEnd(barWidth))}${chalk19.magenta(count.toString().padStart(3))}`
|
|
5311
5521
|
);
|
|
5312
5522
|
}
|
|
5313
5523
|
console.log("");
|
|
@@ -5339,14 +5549,14 @@ async function showStats(options) {
|
|
|
5339
5549
|
]);
|
|
5340
5550
|
const sortedDates = Array.from(allDates).sort();
|
|
5341
5551
|
if (sortedDates.length > 0) {
|
|
5342
|
-
console.log(
|
|
5552
|
+
console.log(chalk19.bold(`\u{1F4C5} Activity (Last ${days} Days)`));
|
|
5343
5553
|
console.log("");
|
|
5344
5554
|
const colWidth = barWidth + 3;
|
|
5345
5555
|
console.log(
|
|
5346
|
-
` ${"Date".padEnd(15)} ${
|
|
5556
|
+
` ${"Date".padEnd(15)} ${chalk19.cyan("Created".padEnd(colWidth))} ${chalk19.green("Completed".padEnd(colWidth))}`
|
|
5347
5557
|
);
|
|
5348
5558
|
console.log(
|
|
5349
|
-
` ${
|
|
5559
|
+
` ${chalk19.dim("\u2500".repeat(15))} ${chalk19.dim("\u2500".repeat(colWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`
|
|
5350
5560
|
);
|
|
5351
5561
|
const maxCount = Math.max(
|
|
5352
5562
|
...Object.values(createdByDate),
|
|
@@ -5360,85 +5570,85 @@ async function showStats(options) {
|
|
|
5360
5570
|
const createdCol = `${createdBar.padEnd(barWidth)}${created.toString().padStart(3)}`;
|
|
5361
5571
|
const completedCol = `${completedBar.padEnd(barWidth)}${completed.toString().padStart(3)}`;
|
|
5362
5572
|
console.log(
|
|
5363
|
-
` ${
|
|
5573
|
+
` ${chalk19.dim(date.padEnd(15))} ${chalk19.cyan(createdCol)} ${chalk19.green(completedCol)}`
|
|
5364
5574
|
);
|
|
5365
5575
|
}
|
|
5366
5576
|
console.log("");
|
|
5367
5577
|
}
|
|
5368
5578
|
}
|
|
5369
5579
|
if (showVelocity) {
|
|
5370
|
-
console.log(
|
|
5580
|
+
console.log(chalk19.bold("\u{1F680} Velocity Metrics"));
|
|
5371
5581
|
console.log("");
|
|
5372
|
-
console.log(
|
|
5582
|
+
console.log(chalk19.bold("\u23F1\uFE0F Cycle Time (Created \u2192 Completed)"));
|
|
5373
5583
|
console.log("");
|
|
5374
5584
|
console.log(
|
|
5375
5585
|
` ${"Metric".padEnd(labelWidth)} ${"Days".padStart(valueWidth)}`
|
|
5376
5586
|
);
|
|
5377
5587
|
console.log(
|
|
5378
|
-
` ${
|
|
5588
|
+
` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`
|
|
5379
5589
|
);
|
|
5380
5590
|
console.log(
|
|
5381
|
-
` ${"Average".padEnd(labelWidth)} ${
|
|
5591
|
+
` ${"Average".padEnd(labelWidth)} ${chalk19.cyan(velocityMetrics.cycleTime.average.toFixed(1).padStart(valueWidth))}`
|
|
5382
5592
|
);
|
|
5383
5593
|
console.log(
|
|
5384
|
-
` ${"Median".padEnd(labelWidth)} ${
|
|
5594
|
+
` ${"Median".padEnd(labelWidth)} ${chalk19.cyan(velocityMetrics.cycleTime.median.toFixed(1).padStart(valueWidth))}`
|
|
5385
5595
|
);
|
|
5386
5596
|
console.log(
|
|
5387
|
-
` ${"90th Percentile".padEnd(labelWidth)} ${
|
|
5597
|
+
` ${"90th Percentile".padEnd(labelWidth)} ${chalk19.yellow(velocityMetrics.cycleTime.p90.toFixed(1).padStart(valueWidth))}`
|
|
5388
5598
|
);
|
|
5389
5599
|
console.log("");
|
|
5390
|
-
console.log(
|
|
5600
|
+
console.log(chalk19.bold("\u{1F4E6} Throughput"));
|
|
5391
5601
|
console.log("");
|
|
5392
5602
|
console.log(
|
|
5393
5603
|
` ${"Period".padEnd(labelWidth)} ${"Specs".padStart(valueWidth)}`
|
|
5394
5604
|
);
|
|
5395
5605
|
console.log(
|
|
5396
|
-
` ${
|
|
5606
|
+
` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`
|
|
5397
5607
|
);
|
|
5398
5608
|
console.log(
|
|
5399
|
-
` ${"Last 7 days".padEnd(labelWidth)} ${
|
|
5609
|
+
` ${"Last 7 days".padEnd(labelWidth)} ${chalk19.green(velocityMetrics.throughput.perWeek.toString().padStart(valueWidth))}`
|
|
5400
5610
|
);
|
|
5401
5611
|
console.log(
|
|
5402
|
-
` ${"Last 30 days".padEnd(labelWidth)} ${
|
|
5612
|
+
` ${"Last 30 days".padEnd(labelWidth)} ${chalk19.green(velocityMetrics.throughput.perMonth.toString().padStart(valueWidth))}`
|
|
5403
5613
|
);
|
|
5404
|
-
const trendColor = velocityMetrics.throughput.trend === "up" ?
|
|
5614
|
+
const trendColor = velocityMetrics.throughput.trend === "up" ? chalk19.green : velocityMetrics.throughput.trend === "down" ? chalk19.red : chalk19.yellow;
|
|
5405
5615
|
const trendSymbol = velocityMetrics.throughput.trend === "up" ? "\u2191" : velocityMetrics.throughput.trend === "down" ? "\u2193" : "\u2192";
|
|
5406
5616
|
console.log(
|
|
5407
5617
|
` ${"Trend".padEnd(labelWidth)} ${trendColor(trendSymbol + " " + velocityMetrics.throughput.trend.padStart(valueWidth - 2))}`
|
|
5408
5618
|
);
|
|
5409
5619
|
console.log("");
|
|
5410
|
-
console.log(
|
|
5620
|
+
console.log(chalk19.bold("\u{1F504} Work In Progress"));
|
|
5411
5621
|
console.log("");
|
|
5412
5622
|
console.log(
|
|
5413
5623
|
` ${"Metric".padEnd(labelWidth)} ${"Specs".padStart(valueWidth)}`
|
|
5414
5624
|
);
|
|
5415
5625
|
console.log(
|
|
5416
|
-
` ${
|
|
5626
|
+
` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`
|
|
5417
5627
|
);
|
|
5418
5628
|
console.log(
|
|
5419
|
-
` ${"Current".padEnd(labelWidth)} ${
|
|
5629
|
+
` ${"Current".padEnd(labelWidth)} ${chalk19.yellow(velocityMetrics.wip.current.toString().padStart(valueWidth))}`
|
|
5420
5630
|
);
|
|
5421
5631
|
console.log(
|
|
5422
|
-
` ${"30-day Average".padEnd(labelWidth)} ${
|
|
5632
|
+
` ${"30-day Average".padEnd(labelWidth)} ${chalk19.cyan(velocityMetrics.wip.average.toFixed(1).padStart(valueWidth))}`
|
|
5423
5633
|
);
|
|
5424
5634
|
console.log("");
|
|
5425
5635
|
if (velocityMetrics.leadTime.plannedToInProgress > 0 || velocityMetrics.leadTime.inProgressToComplete > 0) {
|
|
5426
|
-
console.log(
|
|
5636
|
+
console.log(chalk19.bold("\u{1F500} Lead Time by Stage"));
|
|
5427
5637
|
console.log("");
|
|
5428
5638
|
console.log(
|
|
5429
5639
|
` ${"Stage".padEnd(labelWidth)} ${"Days".padStart(valueWidth)}`
|
|
5430
5640
|
);
|
|
5431
5641
|
console.log(
|
|
5432
|
-
` ${
|
|
5642
|
+
` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`
|
|
5433
5643
|
);
|
|
5434
5644
|
if (velocityMetrics.leadTime.plannedToInProgress > 0) {
|
|
5435
5645
|
console.log(
|
|
5436
|
-
` ${"Planned \u2192 In Progress".padEnd(labelWidth)} ${
|
|
5646
|
+
` ${"Planned \u2192 In Progress".padEnd(labelWidth)} ${chalk19.cyan(velocityMetrics.leadTime.plannedToInProgress.toFixed(1).padStart(valueWidth))}`
|
|
5437
5647
|
);
|
|
5438
5648
|
}
|
|
5439
5649
|
if (velocityMetrics.leadTime.inProgressToComplete > 0) {
|
|
5440
5650
|
console.log(
|
|
5441
|
-
` ${"In Progress \u2192 Complete".padEnd(labelWidth)} ${
|
|
5651
|
+
` ${"In Progress \u2192 Complete".padEnd(labelWidth)} ${chalk19.green(velocityMetrics.leadTime.inProgressToComplete.toFixed(1).padStart(valueWidth))}`
|
|
5442
5652
|
);
|
|
5443
5653
|
}
|
|
5444
5654
|
console.log("");
|
|
@@ -5494,34 +5704,34 @@ async function performSearch(query, options) {
|
|
|
5494
5704
|
const { results, metadata } = searchResult;
|
|
5495
5705
|
if (results.length === 0) {
|
|
5496
5706
|
console.log("");
|
|
5497
|
-
console.log(
|
|
5707
|
+
console.log(chalk19.yellow(`\u{1F50D} No specs found matching "${sanitizeUserInput(query)}"`));
|
|
5498
5708
|
if (Object.keys(filter).length > 0) {
|
|
5499
5709
|
const filters = [];
|
|
5500
5710
|
if (options.status) filters.push(`status=${sanitizeUserInput(options.status)}`);
|
|
5501
5711
|
if (options.tag) filters.push(`tag=${sanitizeUserInput(options.tag)}`);
|
|
5502
5712
|
if (options.priority) filters.push(`priority=${sanitizeUserInput(options.priority)}`);
|
|
5503
5713
|
if (options.assignee) filters.push(`assignee=${sanitizeUserInput(options.assignee)}`);
|
|
5504
|
-
console.log(
|
|
5714
|
+
console.log(chalk19.gray(`With filters: ${filters.join(", ")}`));
|
|
5505
5715
|
}
|
|
5506
5716
|
console.log("");
|
|
5507
5717
|
return;
|
|
5508
5718
|
}
|
|
5509
5719
|
console.log("");
|
|
5510
|
-
console.log(
|
|
5511
|
-
console.log(
|
|
5720
|
+
console.log(chalk19.green(`\u{1F50D} Found ${results.length} spec${results.length === 1 ? "" : "s"} matching "${sanitizeUserInput(query)}"`));
|
|
5721
|
+
console.log(chalk19.gray(` Searched ${metadata.specsSearched} specs in ${metadata.searchTime}ms`));
|
|
5512
5722
|
if (Object.keys(filter).length > 0) {
|
|
5513
5723
|
const filters = [];
|
|
5514
5724
|
if (options.status) filters.push(`status=${sanitizeUserInput(options.status)}`);
|
|
5515
5725
|
if (options.tag) filters.push(`tag=${sanitizeUserInput(options.tag)}`);
|
|
5516
5726
|
if (options.priority) filters.push(`priority=${sanitizeUserInput(options.priority)}`);
|
|
5517
5727
|
if (options.assignee) filters.push(`assignee=${sanitizeUserInput(options.assignee)}`);
|
|
5518
|
-
console.log(
|
|
5728
|
+
console.log(chalk19.gray(` With filters: ${filters.join(", ")}`));
|
|
5519
5729
|
}
|
|
5520
5730
|
console.log("");
|
|
5521
5731
|
for (const result of results) {
|
|
5522
5732
|
const { spec, matches, score, totalMatches } = result;
|
|
5523
5733
|
const statusEmoji = spec.status === "in-progress" ? "\u{1F528}" : spec.status === "complete" ? "\u2705" : "\u{1F4C5}";
|
|
5524
|
-
console.log(
|
|
5734
|
+
console.log(chalk19.cyan(`${statusEmoji} ${sanitizeUserInput(spec.path)} ${chalk19.gray(`(${score}% match)`)}`));
|
|
5525
5735
|
const meta = [];
|
|
5526
5736
|
if (spec.priority) {
|
|
5527
5737
|
const priorityEmoji = spec.priority === "critical" ? "\u{1F534}" : spec.priority === "high" ? "\u{1F7E1}" : spec.priority === "medium" ? "\u{1F7E0}" : "\u{1F7E2}";
|
|
@@ -5531,30 +5741,30 @@ async function performSearch(query, options) {
|
|
|
5531
5741
|
meta.push(`[${spec.tags.map((tag) => sanitizeUserInput(tag)).join(", ")}]`);
|
|
5532
5742
|
}
|
|
5533
5743
|
if (meta.length > 0) {
|
|
5534
|
-
console.log(
|
|
5744
|
+
console.log(chalk19.gray(` ${meta.join(" \u2022 ")}`));
|
|
5535
5745
|
}
|
|
5536
5746
|
const titleMatch = matches.find((m) => m.field === "title");
|
|
5537
5747
|
if (titleMatch) {
|
|
5538
|
-
console.log(` ${
|
|
5748
|
+
console.log(` ${chalk19.bold("Title:")} ${highlightMatches(titleMatch.text, titleMatch.highlights)}`);
|
|
5539
5749
|
}
|
|
5540
5750
|
const descMatch = matches.find((m) => m.field === "description");
|
|
5541
5751
|
if (descMatch) {
|
|
5542
|
-
console.log(` ${
|
|
5752
|
+
console.log(` ${chalk19.bold("Description:")} ${highlightMatches(descMatch.text, descMatch.highlights)}`);
|
|
5543
5753
|
}
|
|
5544
5754
|
const tagMatches = matches.filter((m) => m.field === "tags");
|
|
5545
5755
|
if (tagMatches.length > 0) {
|
|
5546
|
-
console.log(` ${
|
|
5756
|
+
console.log(` ${chalk19.bold("Tags:")} ${tagMatches.map((m) => highlightMatches(m.text, m.highlights)).join(", ")}`);
|
|
5547
5757
|
}
|
|
5548
5758
|
const contentMatches = matches.filter((m) => m.field === "content");
|
|
5549
5759
|
if (contentMatches.length > 0) {
|
|
5550
|
-
console.log(` ${
|
|
5760
|
+
console.log(` ${chalk19.bold("Content matches:")}`);
|
|
5551
5761
|
for (const match of contentMatches) {
|
|
5552
|
-
const lineInfo = match.lineNumber ?
|
|
5762
|
+
const lineInfo = match.lineNumber ? chalk19.gray(`[L${match.lineNumber}]`) : "";
|
|
5553
5763
|
console.log(` ${lineInfo} ${highlightMatches(match.text, match.highlights)}`);
|
|
5554
5764
|
}
|
|
5555
5765
|
}
|
|
5556
5766
|
if (totalMatches > matches.length) {
|
|
5557
|
-
console.log(
|
|
5767
|
+
console.log(chalk19.gray(` ... and ${totalMatches - matches.length} more match${totalMatches - matches.length === 1 ? "" : "es"}`));
|
|
5558
5768
|
}
|
|
5559
5769
|
console.log("");
|
|
5560
5770
|
}
|
|
@@ -5565,7 +5775,7 @@ function highlightMatches(text, highlights) {
|
|
|
5565
5775
|
let lastEnd = 0;
|
|
5566
5776
|
for (const [start, end] of highlights) {
|
|
5567
5777
|
result += text.substring(lastEnd, start);
|
|
5568
|
-
result +=
|
|
5778
|
+
result += chalk19.yellow(text.substring(start, end));
|
|
5569
5779
|
lastEnd = end;
|
|
5570
5780
|
}
|
|
5571
5781
|
result += text.substring(lastEnd);
|
|
@@ -5583,7 +5793,7 @@ async function showDeps(specPath, options = {}) {
|
|
|
5583
5793
|
await autoCheckIfEnabled();
|
|
5584
5794
|
const config = await loadConfig();
|
|
5585
5795
|
const cwd = process.cwd();
|
|
5586
|
-
const specsDir =
|
|
5796
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
5587
5797
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
5588
5798
|
if (!resolvedPath) {
|
|
5589
5799
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
@@ -5647,17 +5857,17 @@ async function showDeps(specPath, options = {}) {
|
|
|
5647
5857
|
return;
|
|
5648
5858
|
}
|
|
5649
5859
|
console.log("");
|
|
5650
|
-
console.log(
|
|
5860
|
+
console.log(chalk19.green(`\u{1F4E6} Dependencies for ${chalk19.cyan(sanitizeUserInput(spec.path))}`));
|
|
5651
5861
|
console.log("");
|
|
5652
5862
|
const hasAnyRelationships = dependsOn.length > 0 || requiredBy.length > 0 || related.length > 0;
|
|
5653
5863
|
if (!hasAnyRelationships) {
|
|
5654
|
-
console.log(
|
|
5864
|
+
console.log(chalk19.gray(" No dependencies or relationships"));
|
|
5655
5865
|
console.log("");
|
|
5656
5866
|
return;
|
|
5657
5867
|
}
|
|
5658
5868
|
if ((mode === "complete" || mode === "upstream" || mode === "impact") && dependsOn.length > 0) {
|
|
5659
5869
|
const label = mode === "complete" ? "Depends On" : mode === "upstream" ? "Upstream Dependencies" : "Upstream (Impact)";
|
|
5660
|
-
console.log(
|
|
5870
|
+
console.log(chalk19.bold(`${label}:`));
|
|
5661
5871
|
for (const dep of dependsOn) {
|
|
5662
5872
|
const status = getStatusIndicator(dep.frontmatter.status);
|
|
5663
5873
|
console.log(` \u2192 ${sanitizeUserInput(dep.path)} ${status}`);
|
|
@@ -5666,7 +5876,7 @@ async function showDeps(specPath, options = {}) {
|
|
|
5666
5876
|
}
|
|
5667
5877
|
if ((mode === "complete" || mode === "downstream" || mode === "impact") && requiredBy.length > 0) {
|
|
5668
5878
|
const label = mode === "complete" ? "Required By" : mode === "downstream" ? "Downstream Dependents" : "Downstream (Impact)";
|
|
5669
|
-
console.log(
|
|
5879
|
+
console.log(chalk19.bold(`${label}:`));
|
|
5670
5880
|
for (const blocked of requiredBy) {
|
|
5671
5881
|
const status = getStatusIndicator(blocked.frontmatter.status);
|
|
5672
5882
|
console.log(` \u2190 ${sanitizeUserInput(blocked.path)} ${status}`);
|
|
@@ -5674,7 +5884,7 @@ async function showDeps(specPath, options = {}) {
|
|
|
5674
5884
|
console.log("");
|
|
5675
5885
|
}
|
|
5676
5886
|
if ((mode === "complete" || mode === "impact") && related.length > 0) {
|
|
5677
|
-
console.log(
|
|
5887
|
+
console.log(chalk19.bold("Related Specs:"));
|
|
5678
5888
|
for (const rel of related) {
|
|
5679
5889
|
const status = getStatusIndicator(rel.frontmatter.status);
|
|
5680
5890
|
console.log(` \u27F7 ${sanitizeUserInput(rel.path)} ${status}`);
|
|
@@ -5682,15 +5892,15 @@ async function showDeps(specPath, options = {}) {
|
|
|
5682
5892
|
console.log("");
|
|
5683
5893
|
}
|
|
5684
5894
|
if (mode === "complete" && (options.graph || dependsOn.length > 0)) {
|
|
5685
|
-
console.log(
|
|
5895
|
+
console.log(chalk19.bold("Dependency Chain:"));
|
|
5686
5896
|
const chain = buildDependencyChain(spec, specMap, options.depth || 3);
|
|
5687
5897
|
displayChain(chain, 0);
|
|
5688
5898
|
console.log("");
|
|
5689
5899
|
}
|
|
5690
5900
|
if (mode === "impact") {
|
|
5691
5901
|
const total = dependsOn.length + requiredBy.length + related.length;
|
|
5692
|
-
console.log(
|
|
5693
|
-
console.log(` Changing this spec affects ${
|
|
5902
|
+
console.log(chalk19.bold(`Impact Summary:`));
|
|
5903
|
+
console.log(` Changing this spec affects ${chalk19.yellow(total)} specs total`);
|
|
5694
5904
|
console.log(` Upstream: ${dependsOn.length} | Downstream: ${requiredBy.length} | Related: ${related.length}`);
|
|
5695
5905
|
console.log("");
|
|
5696
5906
|
}
|
|
@@ -5720,7 +5930,7 @@ function buildDependencyChain(spec, specMap, maxDepth, currentDepth = 0, visited
|
|
|
5720
5930
|
function displayChain(node, level) {
|
|
5721
5931
|
const indent = " ".repeat(level);
|
|
5722
5932
|
const status = getStatusIndicator(node.spec.frontmatter.status);
|
|
5723
|
-
const name = level === 0 ?
|
|
5933
|
+
const name = level === 0 ? chalk19.cyan(node.spec.path) : node.spec.path;
|
|
5724
5934
|
console.log(`${indent}${name} ${status}`);
|
|
5725
5935
|
for (const dep of node.dependencies) {
|
|
5726
5936
|
const prefix = " ".repeat(level) + "\u2514\u2500 ";
|
|
@@ -5771,19 +5981,19 @@ async function showTimeline(options) {
|
|
|
5771
5981
|
}
|
|
5772
5982
|
}
|
|
5773
5983
|
}
|
|
5774
|
-
console.log(
|
|
5984
|
+
console.log(chalk19.bold.cyan("\u{1F4C8} Spec Timeline"));
|
|
5775
5985
|
console.log("");
|
|
5776
5986
|
const allDates = /* @__PURE__ */ new Set([...Object.keys(createdByDate), ...Object.keys(completedByDate)]);
|
|
5777
5987
|
const sortedDates = Array.from(allDates).sort();
|
|
5778
5988
|
if (sortedDates.length > 0) {
|
|
5779
|
-
console.log(
|
|
5989
|
+
console.log(chalk19.bold(`\u{1F4C5} Activity (Last ${days} Days)`));
|
|
5780
5990
|
console.log("");
|
|
5781
5991
|
const labelWidth2 = 15;
|
|
5782
5992
|
const barWidth = 20;
|
|
5783
5993
|
const specsWidth = 3;
|
|
5784
5994
|
const colWidth = barWidth + specsWidth;
|
|
5785
|
-
console.log(` ${"Date".padEnd(labelWidth2)} ${
|
|
5786
|
-
console.log(` ${
|
|
5995
|
+
console.log(` ${"Date".padEnd(labelWidth2)} ${chalk19.cyan("Created".padEnd(colWidth))} ${chalk19.green("Completed".padEnd(colWidth))}`);
|
|
5996
|
+
console.log(` ${chalk19.dim("\u2500".repeat(labelWidth2))} ${chalk19.dim("\u2500".repeat(colWidth))} ${chalk19.dim("\u2500".repeat(colWidth))}`);
|
|
5787
5997
|
const maxCount = Math.max(...Object.values(createdByDate), ...Object.values(completedByDate));
|
|
5788
5998
|
for (const date of sortedDates) {
|
|
5789
5999
|
const created = createdByDate[date] || 0;
|
|
@@ -5792,7 +6002,7 @@ async function showTimeline(options) {
|
|
|
5792
6002
|
const completedBar = createBar(completed, maxCount, barWidth);
|
|
5793
6003
|
const createdCol = `${createdBar.padEnd(barWidth)}${created.toString().padStart(specsWidth)}`;
|
|
5794
6004
|
const completedCol = `${completedBar.padEnd(barWidth)}${completed.toString().padStart(specsWidth)}`;
|
|
5795
|
-
console.log(` ${
|
|
6005
|
+
console.log(` ${chalk19.dim(date.padEnd(labelWidth2))} ${chalk19.cyan(createdCol)} ${chalk19.green(completedCol)}`);
|
|
5796
6006
|
}
|
|
5797
6007
|
console.log("");
|
|
5798
6008
|
}
|
|
@@ -5802,18 +6012,18 @@ async function showTimeline(options) {
|
|
|
5802
6012
|
return dateB.diff(dateA);
|
|
5803
6013
|
}).slice(0, 6);
|
|
5804
6014
|
if (sortedMonths.length > 0) {
|
|
5805
|
-
console.log(
|
|
6015
|
+
console.log(chalk19.bold("\u{1F4CA} Monthly Overview"));
|
|
5806
6016
|
console.log("");
|
|
5807
6017
|
const labelWidth2 = 15;
|
|
5808
6018
|
const barWidth = 20;
|
|
5809
6019
|
const specsWidth = 3;
|
|
5810
6020
|
const colWidth = barWidth + specsWidth;
|
|
5811
|
-
console.log(` ${"Month".padEnd(labelWidth2)} ${
|
|
5812
|
-
console.log(` ${
|
|
6021
|
+
console.log(` ${"Month".padEnd(labelWidth2)} ${chalk19.magenta("Specs".padEnd(colWidth))}`);
|
|
6022
|
+
console.log(` ${chalk19.dim("\u2500".repeat(labelWidth2))} ${chalk19.dim("\u2500".repeat(colWidth))}`);
|
|
5813
6023
|
const maxCount = Math.max(...sortedMonths.map(([, count]) => count));
|
|
5814
6024
|
for (const [month, count] of sortedMonths) {
|
|
5815
6025
|
const bar = createBar(count, maxCount, barWidth);
|
|
5816
|
-
console.log(` ${month.padEnd(labelWidth2)} ${
|
|
6026
|
+
console.log(` ${month.padEnd(labelWidth2)} ${chalk19.magenta(bar.padEnd(barWidth))}${chalk19.magenta(count.toString().padStart(specsWidth))}`);
|
|
5817
6027
|
}
|
|
5818
6028
|
console.log("");
|
|
5819
6029
|
}
|
|
@@ -5827,14 +6037,14 @@ async function showTimeline(options) {
|
|
|
5827
6037
|
const completed = dayjs3(s.frontmatter.completed);
|
|
5828
6038
|
return completed.isAfter(today.subtract(30, "day"));
|
|
5829
6039
|
}).length;
|
|
5830
|
-
console.log(
|
|
6040
|
+
console.log(chalk19.bold("\u2705 Completion Rate"));
|
|
5831
6041
|
console.log("");
|
|
5832
6042
|
const labelWidth = 15;
|
|
5833
6043
|
const valueWidth = 5;
|
|
5834
6044
|
console.log(` ${"Period".padEnd(labelWidth)} ${"Specs".padStart(valueWidth)}`);
|
|
5835
|
-
console.log(` ${
|
|
5836
|
-
console.log(` ${"Last 7 days".padEnd(labelWidth)} ${
|
|
5837
|
-
console.log(` ${"Last 30 days".padEnd(labelWidth)} ${
|
|
6045
|
+
console.log(` ${chalk19.dim("\u2500".repeat(labelWidth))} ${chalk19.dim("\u2500".repeat(valueWidth))}`);
|
|
6046
|
+
console.log(` ${"Last 7 days".padEnd(labelWidth)} ${chalk19.green(last7Days.toString().padStart(valueWidth))}`);
|
|
6047
|
+
console.log(` ${"Last 30 days".padEnd(labelWidth)} ${chalk19.green(last30Days.toString().padStart(valueWidth))}`);
|
|
5838
6048
|
console.log("");
|
|
5839
6049
|
if (options.byTag) {
|
|
5840
6050
|
const tagStats = {};
|
|
@@ -5856,9 +6066,9 @@ async function showTimeline(options) {
|
|
|
5856
6066
|
}
|
|
5857
6067
|
const sortedTags = Object.entries(tagStats).sort((a, b) => b[1].created - a[1].created).slice(0, 10);
|
|
5858
6068
|
if (sortedTags.length > 0) {
|
|
5859
|
-
console.log(
|
|
6069
|
+
console.log(chalk19.bold("\u{1F3F7}\uFE0F By Tag"));
|
|
5860
6070
|
for (const [tag, stats] of sortedTags) {
|
|
5861
|
-
console.log(` ${
|
|
6071
|
+
console.log(` ${chalk19.dim("#")}${tag.padEnd(20)} ${chalk19.cyan(stats.created)} created \xB7 ${chalk19.green(stats.completed)} completed`);
|
|
5862
6072
|
}
|
|
5863
6073
|
console.log("");
|
|
5864
6074
|
}
|
|
@@ -5883,9 +6093,9 @@ async function showTimeline(options) {
|
|
|
5883
6093
|
}
|
|
5884
6094
|
const sortedAssignees = Object.entries(assigneeStats).sort((a, b) => b[1].created - a[1].created);
|
|
5885
6095
|
if (sortedAssignees.length > 0) {
|
|
5886
|
-
console.log(
|
|
6096
|
+
console.log(chalk19.bold("\u{1F464} By Assignee"));
|
|
5887
6097
|
for (const [assignee, stats] of sortedAssignees) {
|
|
5888
|
-
console.log(` ${
|
|
6098
|
+
console.log(` ${chalk19.dim("@")}${assignee.padEnd(20)} ${chalk19.cyan(stats.created)} created \xB7 ${chalk19.green(stats.completed)} completed`);
|
|
5889
6099
|
}
|
|
5890
6100
|
console.log("");
|
|
5891
6101
|
}
|
|
@@ -5903,10 +6113,10 @@ var STATUS_CONFIG2 = {
|
|
|
5903
6113
|
archived: { emoji: "\u{1F4E6}", color: "gray" }
|
|
5904
6114
|
};
|
|
5905
6115
|
var PRIORITY_CONFIG3 = {
|
|
5906
|
-
critical: { emoji: "\u{1F534}", label: "CRITICAL", colorFn:
|
|
5907
|
-
high: { emoji: "\u{1F7E0}", label: "HIGH", colorFn:
|
|
5908
|
-
medium: { emoji: "\u{1F7E1}", label: "MEDIUM", colorFn:
|
|
5909
|
-
low: { emoji: "\u{1F7E2}", label: "LOW", colorFn:
|
|
6116
|
+
critical: { emoji: "\u{1F534}", label: "CRITICAL", colorFn: chalk19.red },
|
|
6117
|
+
high: { emoji: "\u{1F7E0}", label: "HIGH", colorFn: chalk19.hex("#FFA500") },
|
|
6118
|
+
medium: { emoji: "\u{1F7E1}", label: "MEDIUM", colorFn: chalk19.yellow },
|
|
6119
|
+
low: { emoji: "\u{1F7E2}", label: "LOW", colorFn: chalk19.green }
|
|
5910
6120
|
};
|
|
5911
6121
|
function ganttCommand() {
|
|
5912
6122
|
return new Command("gantt").description("Show timeline with dependencies").option("--weeks <n>", "Show N weeks (default: 4)", parseInt).option("--show-complete", "Include completed specs").option("--critical-path", "Highlight critical path").action(async (options) => {
|
|
@@ -5934,8 +6144,8 @@ async function showGantt(options) {
|
|
|
5934
6144
|
return spec.frontmatter.status !== "archived";
|
|
5935
6145
|
});
|
|
5936
6146
|
if (relevantSpecs.length === 0) {
|
|
5937
|
-
console.log(
|
|
5938
|
-
console.log(
|
|
6147
|
+
console.log(chalk19.dim("No active specs found."));
|
|
6148
|
+
console.log(chalk19.dim("Tip: Use --show-complete to include completed specs."));
|
|
5939
6149
|
return;
|
|
5940
6150
|
}
|
|
5941
6151
|
const groupedSpecs = {
|
|
@@ -5971,7 +6181,7 @@ async function showGantt(options) {
|
|
|
5971
6181
|
const overdue = relevantSpecs.filter(
|
|
5972
6182
|
(s) => s.frontmatter.due && dayjs3(s.frontmatter.due).isBefore(today) && s.frontmatter.status !== "complete"
|
|
5973
6183
|
).length;
|
|
5974
|
-
console.log(
|
|
6184
|
+
console.log(chalk19.bold.cyan(`\u{1F4C5} Gantt Chart (${weeks} weeks from ${startDate.format("MMM D, YYYY")})`));
|
|
5975
6185
|
console.log("");
|
|
5976
6186
|
const specHeader = "Spec".padEnd(SPEC_COLUMN_WIDTH);
|
|
5977
6187
|
const timelineHeader = "Timeline";
|
|
@@ -5983,17 +6193,17 @@ async function showGantt(options) {
|
|
|
5983
6193
|
calendarDates.push(dateStr);
|
|
5984
6194
|
}
|
|
5985
6195
|
const dateRow = " ".repeat(SPEC_COLUMN_WIDTH) + COLUMN_SEPARATOR + calendarDates.join("");
|
|
5986
|
-
console.log(
|
|
6196
|
+
console.log(chalk19.dim(dateRow));
|
|
5987
6197
|
const specSeparator = "\u2500".repeat(SPEC_COLUMN_WIDTH);
|
|
5988
6198
|
const timelineSeparator = "\u2500".repeat(timelineColumnWidth);
|
|
5989
|
-
console.log(
|
|
6199
|
+
console.log(chalk19.dim(specSeparator + COLUMN_SEPARATOR + timelineSeparator));
|
|
5990
6200
|
const todayWeekOffset = today.diff(startDate, "week");
|
|
5991
6201
|
const todayMarkerPos = todayWeekOffset * 8;
|
|
5992
6202
|
let todayMarker = " ".repeat(SPEC_COLUMN_WIDTH) + COLUMN_SEPARATOR;
|
|
5993
6203
|
if (todayMarkerPos >= 0 && todayMarkerPos < timelineColumnWidth) {
|
|
5994
6204
|
todayMarker += " ".repeat(todayMarkerPos) + "\u2502 Today";
|
|
5995
6205
|
}
|
|
5996
|
-
console.log(
|
|
6206
|
+
console.log(chalk19.dim(todayMarker));
|
|
5997
6207
|
console.log("");
|
|
5998
6208
|
const priorities = ["critical", "high", "medium", "low"];
|
|
5999
6209
|
for (const priority of priorities) {
|
|
@@ -6011,9 +6221,9 @@ async function showGantt(options) {
|
|
|
6011
6221
|
const summaryParts = [];
|
|
6012
6222
|
if (inProgress > 0) summaryParts.push(`${inProgress} in-progress`);
|
|
6013
6223
|
if (planned > 0) summaryParts.push(`${planned} planned`);
|
|
6014
|
-
if (overdue > 0) summaryParts.push(
|
|
6015
|
-
console.log(
|
|
6016
|
-
console.log(
|
|
6224
|
+
if (overdue > 0) summaryParts.push(chalk19.red(`${overdue} overdue`));
|
|
6225
|
+
console.log(chalk19.bold("Summary: ") + summaryParts.join(" \xB7 "));
|
|
6226
|
+
console.log(chalk19.dim('\u{1F4A1} Tip: Add "due: YYYY-MM-DD" to frontmatter for timeline planning'));
|
|
6017
6227
|
}
|
|
6018
6228
|
function renderSpecRow(spec, startDate, endDate, weeks, today) {
|
|
6019
6229
|
const statusConfig = STATUS_CONFIG2[spec.frontmatter.status];
|
|
@@ -6026,7 +6236,7 @@ function renderSpecRow(spec, startDate, endDate, weeks, today) {
|
|
|
6026
6236
|
const specColumn = `${SPEC_INDENT}${emoji} ${specName}`.padEnd(SPEC_COLUMN_WIDTH);
|
|
6027
6237
|
let timelineColumn;
|
|
6028
6238
|
if (!spec.frontmatter.due) {
|
|
6029
|
-
timelineColumn =
|
|
6239
|
+
timelineColumn = chalk19.dim("(no due date set)");
|
|
6030
6240
|
} else {
|
|
6031
6241
|
timelineColumn = renderTimelineBar(spec, startDate, endDate, weeks, today);
|
|
6032
6242
|
}
|
|
@@ -6049,13 +6259,13 @@ function renderTimelineBar(spec, startDate, endDate, weeks, today) {
|
|
|
6049
6259
|
result += " ".repeat(barStart);
|
|
6050
6260
|
}
|
|
6051
6261
|
if (spec.frontmatter.status === "complete") {
|
|
6052
|
-
result +=
|
|
6262
|
+
result += chalk19.green(FILLED_BAR_CHAR.repeat(barLength));
|
|
6053
6263
|
} else if (spec.frontmatter.status === "in-progress") {
|
|
6054
6264
|
const halfLength = Math.floor(barLength / 2);
|
|
6055
|
-
result +=
|
|
6056
|
-
result +=
|
|
6265
|
+
result += chalk19.yellow(FILLED_BAR_CHAR.repeat(halfLength));
|
|
6266
|
+
result += chalk19.dim(EMPTY_BAR_CHAR.repeat(barLength - halfLength));
|
|
6057
6267
|
} else {
|
|
6058
|
-
result +=
|
|
6268
|
+
result += chalk19.dim(EMPTY_BAR_CHAR.repeat(barLength));
|
|
6059
6269
|
}
|
|
6060
6270
|
const trailingSpace = totalChars - barEnd;
|
|
6061
6271
|
if (trailingSpace > 0) {
|
|
@@ -6081,12 +6291,12 @@ async function countSpecTokens(specPath, options = {}) {
|
|
|
6081
6291
|
try {
|
|
6082
6292
|
const config = await loadConfig();
|
|
6083
6293
|
const cwd = process.cwd();
|
|
6084
|
-
const specsDir =
|
|
6294
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
6085
6295
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
6086
6296
|
if (!resolvedPath) {
|
|
6087
6297
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
6088
6298
|
}
|
|
6089
|
-
const specName =
|
|
6299
|
+
const specName = path15.basename(resolvedPath);
|
|
6090
6300
|
const result = await counter.countSpec(resolvedPath, {
|
|
6091
6301
|
detailed: options.detailed,
|
|
6092
6302
|
includeSubSpecs: options.includeSubSpecs
|
|
@@ -6099,42 +6309,42 @@ async function countSpecTokens(specPath, options = {}) {
|
|
|
6099
6309
|
}, null, 2));
|
|
6100
6310
|
return;
|
|
6101
6311
|
}
|
|
6102
|
-
console.log(
|
|
6312
|
+
console.log(chalk19.bold.cyan(`\u{1F4CA} Token Count: ${specName}`));
|
|
6103
6313
|
console.log("");
|
|
6104
6314
|
const indicators = counter.getPerformanceIndicators(result.total);
|
|
6105
6315
|
const levelEmoji = indicators.level === "excellent" ? "\u2705" : indicators.level === "good" ? "\u{1F44D}" : indicators.level === "warning" ? "\u26A0\uFE0F" : "\u{1F534}";
|
|
6106
|
-
console.log(` Total: ${
|
|
6316
|
+
console.log(` Total: ${chalk19.cyan(result.total.toLocaleString())} tokens ${levelEmoji}`);
|
|
6107
6317
|
console.log("");
|
|
6108
6318
|
if (result.files.length > 1 || options.detailed) {
|
|
6109
|
-
console.log(
|
|
6319
|
+
console.log(chalk19.bold("Files:"));
|
|
6110
6320
|
console.log("");
|
|
6111
6321
|
for (const file of result.files) {
|
|
6112
|
-
const lineInfo = file.lines ?
|
|
6113
|
-
console.log(` ${file.path.padEnd(25)} ${
|
|
6322
|
+
const lineInfo = file.lines ? chalk19.dim(` (${file.lines} lines)`) : "";
|
|
6323
|
+
console.log(` ${file.path.padEnd(25)} ${chalk19.cyan(file.tokens.toLocaleString().padStart(6))} tokens${lineInfo}`);
|
|
6114
6324
|
}
|
|
6115
6325
|
console.log("");
|
|
6116
6326
|
}
|
|
6117
6327
|
if (options.detailed && result.breakdown) {
|
|
6118
6328
|
const b = result.breakdown;
|
|
6119
6329
|
const total = b.code + b.prose + b.tables + b.frontmatter;
|
|
6120
|
-
console.log(
|
|
6330
|
+
console.log(chalk19.bold("Content Breakdown:"));
|
|
6121
6331
|
console.log("");
|
|
6122
|
-
console.log(` Prose ${
|
|
6123
|
-
console.log(` Code ${
|
|
6124
|
-
console.log(` Tables ${
|
|
6125
|
-
console.log(` Frontmatter ${
|
|
6332
|
+
console.log(` Prose ${chalk19.cyan(b.prose.toLocaleString().padStart(6))} tokens ${chalk19.dim(`(${Math.round(b.prose / total * 100)}%)`)}`);
|
|
6333
|
+
console.log(` Code ${chalk19.cyan(b.code.toLocaleString().padStart(6))} tokens ${chalk19.dim(`(${Math.round(b.code / total * 100)}%)`)}`);
|
|
6334
|
+
console.log(` Tables ${chalk19.cyan(b.tables.toLocaleString().padStart(6))} tokens ${chalk19.dim(`(${Math.round(b.tables / total * 100)}%)`)}`);
|
|
6335
|
+
console.log(` Frontmatter ${chalk19.cyan(b.frontmatter.toLocaleString().padStart(6))} tokens ${chalk19.dim(`(${Math.round(b.frontmatter / total * 100)}%)`)}`);
|
|
6126
6336
|
console.log("");
|
|
6127
6337
|
}
|
|
6128
|
-
console.log(
|
|
6338
|
+
console.log(chalk19.bold("Performance Indicators:"));
|
|
6129
6339
|
console.log("");
|
|
6130
|
-
const costColor = indicators.costMultiplier < 2 ?
|
|
6131
|
-
const effectivenessColor = indicators.effectiveness >= 95 ?
|
|
6132
|
-
console.log(` Cost multiplier: ${costColor(`${indicators.costMultiplier}x`)} ${
|
|
6133
|
-
console.log(` AI effectiveness: ${effectivenessColor(`~${indicators.effectiveness}%`)} ${
|
|
6340
|
+
const costColor = indicators.costMultiplier < 2 ? chalk19.green : indicators.costMultiplier < 4 ? chalk19.yellow : chalk19.red;
|
|
6341
|
+
const effectivenessColor = indicators.effectiveness >= 95 ? chalk19.green : indicators.effectiveness >= 85 ? chalk19.yellow : chalk19.red;
|
|
6342
|
+
console.log(` Cost multiplier: ${costColor(`${indicators.costMultiplier}x`)} ${chalk19.dim("vs 1,200 token baseline")}`);
|
|
6343
|
+
console.log(` AI effectiveness: ${effectivenessColor(`~${indicators.effectiveness}%`)} ${chalk19.dim("(hypothesis)")}`);
|
|
6134
6344
|
console.log(` Context Economy: ${levelEmoji} ${indicators.recommendation}`);
|
|
6135
6345
|
console.log("");
|
|
6136
6346
|
if (!options.includeSubSpecs && result.files.length === 1) {
|
|
6137
|
-
console.log(
|
|
6347
|
+
console.log(chalk19.dim("\u{1F4A1} Use `--include-sub-specs` to count all sub-spec files"));
|
|
6138
6348
|
}
|
|
6139
6349
|
} finally {
|
|
6140
6350
|
counter.dispose();
|
|
@@ -6180,46 +6390,46 @@ async function tokensAllCommand(options = {}) {
|
|
|
6180
6390
|
console.log(JSON.stringify(results, null, 2));
|
|
6181
6391
|
return;
|
|
6182
6392
|
}
|
|
6183
|
-
console.log(
|
|
6393
|
+
console.log(chalk19.bold.cyan("\u{1F4CA} Token Counts"));
|
|
6184
6394
|
console.log("");
|
|
6185
|
-
console.log(
|
|
6395
|
+
console.log(chalk19.dim(`Sorted by: ${sortBy}`));
|
|
6186
6396
|
console.log("");
|
|
6187
6397
|
const totalTokens = results.reduce((sum, r) => sum + r.tokens, 0);
|
|
6188
6398
|
const avgTokens = Math.round(totalTokens / results.length);
|
|
6189
6399
|
const warningCount = results.filter((r) => r.level === "warning" || r.level === "problem").length;
|
|
6190
|
-
console.log(
|
|
6400
|
+
console.log(chalk19.bold("Summary:"));
|
|
6191
6401
|
console.log("");
|
|
6192
|
-
console.log(` Total specs: ${
|
|
6193
|
-
console.log(` Total tokens: ${
|
|
6194
|
-
console.log(` Average tokens: ${
|
|
6402
|
+
console.log(` Total specs: ${chalk19.cyan(results.length)}`);
|
|
6403
|
+
console.log(` Total tokens: ${chalk19.cyan(totalTokens.toLocaleString())}`);
|
|
6404
|
+
console.log(` Average tokens: ${chalk19.cyan(avgTokens.toLocaleString())}`);
|
|
6195
6405
|
if (warningCount > 0) {
|
|
6196
|
-
console.log(` Needs review: ${
|
|
6406
|
+
console.log(` Needs review: ${chalk19.yellow(warningCount)} specs ${chalk19.dim("(\u26A0\uFE0F or \u{1F534})")}`);
|
|
6197
6407
|
}
|
|
6198
6408
|
console.log("");
|
|
6199
6409
|
const nameCol = 35;
|
|
6200
6410
|
const tokensCol = 10;
|
|
6201
6411
|
const linesCol = 8;
|
|
6202
|
-
console.log(
|
|
6412
|
+
console.log(chalk19.bold(
|
|
6203
6413
|
"Spec".padEnd(nameCol) + "Tokens".padStart(tokensCol) + "Lines".padStart(linesCol) + " Status"
|
|
6204
6414
|
));
|
|
6205
|
-
console.log(
|
|
6415
|
+
console.log(chalk19.dim("\u2500".repeat(nameCol + tokensCol + linesCol + 10)));
|
|
6206
6416
|
const displayCount = options.all ? results.length : Math.min(20, results.length);
|
|
6207
6417
|
for (let i = 0; i < displayCount; i++) {
|
|
6208
6418
|
const r = results[i];
|
|
6209
6419
|
const emoji = r.level === "excellent" ? "\u2705" : r.level === "good" ? "\u{1F44D}" : r.level === "warning" ? "\u26A0\uFE0F" : "\u{1F534}";
|
|
6210
|
-
const tokensColor = r.level === "excellent" || r.level === "good" ?
|
|
6420
|
+
const tokensColor = r.level === "excellent" || r.level === "good" ? chalk19.cyan : r.level === "warning" ? chalk19.yellow : chalk19.red;
|
|
6211
6421
|
const name = r.name.length > nameCol - 2 ? r.name.substring(0, nameCol - 3) + "\u2026" : r.name;
|
|
6212
6422
|
console.log(
|
|
6213
|
-
name.padEnd(nameCol) + tokensColor(r.tokens.toLocaleString().padStart(tokensCol)) +
|
|
6423
|
+
name.padEnd(nameCol) + tokensColor(r.tokens.toLocaleString().padStart(tokensCol)) + chalk19.dim(r.lines.toString().padStart(linesCol)) + ` ${emoji}`
|
|
6214
6424
|
);
|
|
6215
6425
|
}
|
|
6216
6426
|
if (results.length > displayCount) {
|
|
6217
6427
|
console.log("");
|
|
6218
|
-
console.log(
|
|
6219
|
-
console.log(
|
|
6428
|
+
console.log(chalk19.dim(`... and ${results.length - displayCount} more specs`));
|
|
6429
|
+
console.log(chalk19.dim(`Use --all to show all specs`));
|
|
6220
6430
|
}
|
|
6221
6431
|
console.log("");
|
|
6222
|
-
console.log(
|
|
6432
|
+
console.log(chalk19.dim("Legend: \u2705 excellent (<2K) | \u{1F44D} good (<3.5K) | \u26A0\uFE0F warning (<5K) | \u{1F534} problem (>5K)"));
|
|
6223
6433
|
console.log("");
|
|
6224
6434
|
}
|
|
6225
6435
|
function analyzeCommand() {
|
|
@@ -6233,13 +6443,13 @@ async function analyzeSpec(specPath, options = {}) {
|
|
|
6233
6443
|
try {
|
|
6234
6444
|
const config = await loadConfig();
|
|
6235
6445
|
const cwd = process.cwd();
|
|
6236
|
-
const specsDir =
|
|
6446
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
6237
6447
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
6238
6448
|
if (!resolvedPath) {
|
|
6239
6449
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
6240
6450
|
}
|
|
6241
|
-
const specName =
|
|
6242
|
-
const readmePath =
|
|
6451
|
+
const specName = path15.basename(resolvedPath);
|
|
6452
|
+
const readmePath = path15.join(resolvedPath, "README.md");
|
|
6243
6453
|
const content = await readFile(readmePath, "utf-8");
|
|
6244
6454
|
const structure = analyzeMarkdownStructure(content);
|
|
6245
6455
|
const tokenResult = await counter.countSpec(resolvedPath, {
|
|
@@ -6342,44 +6552,44 @@ function getThresholdLimit(level) {
|
|
|
6342
6552
|
}
|
|
6343
6553
|
}
|
|
6344
6554
|
function displayAnalysis(result, verbose) {
|
|
6345
|
-
console.log(
|
|
6555
|
+
console.log(chalk19.bold.cyan(`\u{1F4CA} Spec Analysis: ${result.spec}`));
|
|
6346
6556
|
console.log("");
|
|
6347
6557
|
const statusEmoji = result.threshold.status === "excellent" ? "\u2705" : result.threshold.status === "good" ? "\u{1F44D}" : result.threshold.status === "warning" ? "\u26A0\uFE0F" : "\u{1F534}";
|
|
6348
|
-
const tokenColor = result.threshold.status === "excellent" || result.threshold.status === "good" ?
|
|
6349
|
-
console.log(
|
|
6350
|
-
console.log(
|
|
6351
|
-
console.log(
|
|
6558
|
+
const tokenColor = result.threshold.status === "excellent" || result.threshold.status === "good" ? chalk19.cyan : result.threshold.status === "warning" ? chalk19.yellow : chalk19.red;
|
|
6559
|
+
console.log(chalk19.bold("Token Count:"), tokenColor(result.metrics.tokens.toLocaleString()), "tokens", statusEmoji);
|
|
6560
|
+
console.log(chalk19.dim(` Threshold: ${result.threshold.limit.toLocaleString()} tokens`));
|
|
6561
|
+
console.log(chalk19.dim(` Status: ${result.threshold.message}`));
|
|
6352
6562
|
console.log("");
|
|
6353
|
-
console.log(
|
|
6354
|
-
console.log(` Lines: ${
|
|
6355
|
-
console.log(` Sections: ${
|
|
6356
|
-
console.log(` Code blocks: ${
|
|
6357
|
-
console.log(` Max nesting: ${
|
|
6563
|
+
console.log(chalk19.bold("Structure:"));
|
|
6564
|
+
console.log(` Lines: ${chalk19.cyan(result.metrics.lines.toLocaleString())}`);
|
|
6565
|
+
console.log(` Sections: ${chalk19.cyan(result.metrics.sections.total)} (H1:${result.metrics.sections.h1}, H2:${result.metrics.sections.h2}, H3:${result.metrics.sections.h3}, H4:${result.metrics.sections.h4})`);
|
|
6566
|
+
console.log(` Code blocks: ${chalk19.cyan(result.metrics.codeBlocks)}`);
|
|
6567
|
+
console.log(` Max nesting: ${chalk19.cyan(result.metrics.maxNesting)} levels`);
|
|
6358
6568
|
console.log("");
|
|
6359
6569
|
if (verbose && result.structure.length > 0) {
|
|
6360
6570
|
const topSections = result.structure.filter((s) => s.level <= 2).sort((a, b) => b.tokens - a.tokens).slice(0, 5);
|
|
6361
|
-
console.log(
|
|
6571
|
+
console.log(chalk19.bold("Top Sections by Size:"));
|
|
6362
6572
|
console.log("");
|
|
6363
6573
|
for (let i = 0; i < topSections.length; i++) {
|
|
6364
6574
|
const s = topSections[i];
|
|
6365
6575
|
const percentage = Math.round(s.tokens / result.metrics.tokens * 100);
|
|
6366
6576
|
const indent = " ".repeat(s.level - 1);
|
|
6367
6577
|
console.log(` ${i + 1}. ${indent}${s.section}`);
|
|
6368
|
-
console.log(` ${
|
|
6369
|
-
console.log(
|
|
6578
|
+
console.log(` ${chalk19.cyan(s.tokens.toLocaleString())} tokens / ${s.lineRange[1] - s.lineRange[0] + 1} lines ${chalk19.dim(`(${percentage}%)`)}`);
|
|
6579
|
+
console.log(chalk19.dim(` Lines ${s.lineRange[0]}-${s.lineRange[1]}`));
|
|
6370
6580
|
}
|
|
6371
6581
|
console.log("");
|
|
6372
6582
|
}
|
|
6373
|
-
const actionColor = result.recommendation.action === "none" ?
|
|
6374
|
-
console.log(
|
|
6375
|
-
console.log(
|
|
6376
|
-
console.log(
|
|
6583
|
+
const actionColor = result.recommendation.action === "none" ? chalk19.green : result.recommendation.action === "compact" ? chalk19.yellow : result.recommendation.action === "split" ? chalk19.red : chalk19.blue;
|
|
6584
|
+
console.log(chalk19.bold("Recommendation:"), actionColor(result.recommendation.action.toUpperCase()));
|
|
6585
|
+
console.log(chalk19.dim(` ${result.recommendation.reason}`));
|
|
6586
|
+
console.log(chalk19.dim(` Confidence: ${result.recommendation.confidence}`));
|
|
6377
6587
|
console.log("");
|
|
6378
6588
|
if (result.recommendation.action === "split") {
|
|
6379
|
-
console.log(
|
|
6380
|
-
console.log(
|
|
6589
|
+
console.log(chalk19.dim("\u{1F4A1} Use `lean-spec split` to partition into sub-specs"));
|
|
6590
|
+
console.log(chalk19.dim("\u{1F4A1} Consider splitting by H2 sections (concerns)"));
|
|
6381
6591
|
} else if (result.recommendation.action === "compact") {
|
|
6382
|
-
console.log(
|
|
6592
|
+
console.log(chalk19.dim("\u{1F4A1} Use `lean-spec compact` to remove redundancy"));
|
|
6383
6593
|
}
|
|
6384
6594
|
console.log("");
|
|
6385
6595
|
}
|
|
@@ -6411,13 +6621,13 @@ async function splitSpec(specPath, options) {
|
|
|
6411
6621
|
}
|
|
6412
6622
|
const config = await loadConfig();
|
|
6413
6623
|
const cwd = process.cwd();
|
|
6414
|
-
const specsDir =
|
|
6624
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
6415
6625
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
6416
6626
|
if (!resolvedPath) {
|
|
6417
6627
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
6418
6628
|
}
|
|
6419
|
-
const specName =
|
|
6420
|
-
const readmePath =
|
|
6629
|
+
const specName = path15.basename(resolvedPath);
|
|
6630
|
+
const readmePath = path15.join(resolvedPath, "README.md");
|
|
6421
6631
|
const content = await readFile(readmePath, "utf-8");
|
|
6422
6632
|
const parsedOutputs = parseOutputSpecs(options.outputs);
|
|
6423
6633
|
validateNoOverlaps(parsedOutputs);
|
|
@@ -6440,7 +6650,7 @@ async function splitSpec(specPath, options) {
|
|
|
6440
6650
|
await executeSplit(resolvedPath, specName, content, extractions, options);
|
|
6441
6651
|
} catch (error) {
|
|
6442
6652
|
if (error instanceof Error) {
|
|
6443
|
-
console.error(
|
|
6653
|
+
console.error(chalk19.red(`Error: ${error.message}`));
|
|
6444
6654
|
}
|
|
6445
6655
|
throw error;
|
|
6446
6656
|
}
|
|
@@ -6478,30 +6688,30 @@ function validateNoOverlaps(outputs) {
|
|
|
6478
6688
|
}
|
|
6479
6689
|
}
|
|
6480
6690
|
async function displayDryRun(specName, extractions) {
|
|
6481
|
-
console.log(
|
|
6691
|
+
console.log(chalk19.bold.cyan(`\u{1F4CB} Split Preview: ${specName}`));
|
|
6482
6692
|
console.log("");
|
|
6483
|
-
console.log(
|
|
6693
|
+
console.log(chalk19.bold("Would create:"));
|
|
6484
6694
|
console.log("");
|
|
6485
6695
|
for (const ext of extractions) {
|
|
6486
|
-
console.log(` ${
|
|
6696
|
+
console.log(` ${chalk19.cyan(ext.file)}`);
|
|
6487
6697
|
console.log(` Lines: ${ext.lines}`);
|
|
6488
6698
|
const previewLines = ext.content.split("\n").slice(0, 3);
|
|
6489
|
-
console.log(
|
|
6699
|
+
console.log(chalk19.dim(" Preview:"));
|
|
6490
6700
|
for (const line of previewLines) {
|
|
6491
|
-
console.log(
|
|
6701
|
+
console.log(chalk19.dim(` ${line.substring(0, 60)}${line.length > 60 ? "..." : ""}`));
|
|
6492
6702
|
}
|
|
6493
6703
|
console.log("");
|
|
6494
6704
|
}
|
|
6495
|
-
console.log(
|
|
6496
|
-
console.log(
|
|
6705
|
+
console.log(chalk19.dim("No files modified (dry run)"));
|
|
6706
|
+
console.log(chalk19.dim("Run without --dry-run to apply changes"));
|
|
6497
6707
|
console.log("");
|
|
6498
6708
|
}
|
|
6499
6709
|
async function executeSplit(specPath, specName, originalContent, extractions, options) {
|
|
6500
|
-
console.log(
|
|
6710
|
+
console.log(chalk19.bold.cyan(`\u2702\uFE0F Splitting: ${specName}`));
|
|
6501
6711
|
console.log("");
|
|
6502
6712
|
const frontmatter = parseFrontmatterFromString(originalContent);
|
|
6503
6713
|
for (const ext of extractions) {
|
|
6504
|
-
const outputPath =
|
|
6714
|
+
const outputPath = path15.join(specPath, ext.file);
|
|
6505
6715
|
let finalContent = ext.content;
|
|
6506
6716
|
if (ext.file === "README.md" && frontmatter) {
|
|
6507
6717
|
const { content: contentWithFrontmatter } = createUpdatedFrontmatter(
|
|
@@ -6511,21 +6721,21 @@ async function executeSplit(specPath, specName, originalContent, extractions, op
|
|
|
6511
6721
|
finalContent = contentWithFrontmatter;
|
|
6512
6722
|
}
|
|
6513
6723
|
await writeFile(outputPath, finalContent, "utf-8");
|
|
6514
|
-
console.log(
|
|
6724
|
+
console.log(chalk19.green(`\u2713 Created ${ext.file} (${ext.lines} lines)`));
|
|
6515
6725
|
}
|
|
6516
6726
|
if (options.updateRefs) {
|
|
6517
|
-
const readmePath =
|
|
6727
|
+
const readmePath = path15.join(specPath, "README.md");
|
|
6518
6728
|
const readmeContent = await readFile(readmePath, "utf-8");
|
|
6519
6729
|
const updatedReadme = await addSubSpecLinks(
|
|
6520
6730
|
readmeContent,
|
|
6521
6731
|
extractions.map((e) => e.file).filter((f) => f !== "README.md")
|
|
6522
6732
|
);
|
|
6523
6733
|
await writeFile(readmePath, updatedReadme, "utf-8");
|
|
6524
|
-
console.log(
|
|
6734
|
+
console.log(chalk19.green(`\u2713 Updated README.md with sub-spec links`));
|
|
6525
6735
|
}
|
|
6526
6736
|
console.log("");
|
|
6527
|
-
console.log(
|
|
6528
|
-
console.log(
|
|
6737
|
+
console.log(chalk19.bold.green("Split complete!"));
|
|
6738
|
+
console.log(chalk19.dim(`Created ${extractions.length} files in ${specName}`));
|
|
6529
6739
|
console.log("");
|
|
6530
6740
|
}
|
|
6531
6741
|
async function addSubSpecLinks(content, subSpecs) {
|
|
@@ -6593,13 +6803,13 @@ async function compactSpec(specPath, options) {
|
|
|
6593
6803
|
}
|
|
6594
6804
|
const config = await loadConfig();
|
|
6595
6805
|
const cwd = process.cwd();
|
|
6596
|
-
const specsDir =
|
|
6806
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
6597
6807
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
6598
6808
|
if (!resolvedPath) {
|
|
6599
6809
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
6600
6810
|
}
|
|
6601
|
-
const specName =
|
|
6602
|
-
const readmePath =
|
|
6811
|
+
const specName = path15.basename(resolvedPath);
|
|
6812
|
+
const readmePath = path15.join(resolvedPath, "README.md");
|
|
6603
6813
|
const content = await readFile(readmePath, "utf-8");
|
|
6604
6814
|
const parsedRemoves = parseRemoveSpecs(options.removes);
|
|
6605
6815
|
validateNoOverlaps2(parsedRemoves);
|
|
@@ -6610,7 +6820,7 @@ async function compactSpec(specPath, options) {
|
|
|
6610
6820
|
await executeCompact(readmePath, specName, content, parsedRemoves);
|
|
6611
6821
|
} catch (error) {
|
|
6612
6822
|
if (error instanceof Error) {
|
|
6613
|
-
console.error(
|
|
6823
|
+
console.error(chalk19.red(`Error: ${error.message}`));
|
|
6614
6824
|
}
|
|
6615
6825
|
throw error;
|
|
6616
6826
|
}
|
|
@@ -6649,9 +6859,9 @@ function validateNoOverlaps2(removes) {
|
|
|
6649
6859
|
}
|
|
6650
6860
|
}
|
|
6651
6861
|
async function displayDryRun2(specName, content, removes) {
|
|
6652
|
-
console.log(
|
|
6862
|
+
console.log(chalk19.bold.cyan(`\u{1F4CB} Compact Preview: ${specName}`));
|
|
6653
6863
|
console.log("");
|
|
6654
|
-
console.log(
|
|
6864
|
+
console.log(chalk19.bold("Would remove:"));
|
|
6655
6865
|
console.log("");
|
|
6656
6866
|
let totalLines = 0;
|
|
6657
6867
|
for (const remove of removes) {
|
|
@@ -6660,29 +6870,29 @@ async function displayDryRun2(specName, content, removes) {
|
|
|
6660
6870
|
const removedContent = extractLines(content, remove.startLine, remove.endLine);
|
|
6661
6871
|
const previewLines = removedContent.split("\n").slice(0, 3);
|
|
6662
6872
|
console.log(` Lines ${remove.startLine}-${remove.endLine} (${lineCount} lines)`);
|
|
6663
|
-
console.log(
|
|
6873
|
+
console.log(chalk19.dim(" Preview:"));
|
|
6664
6874
|
for (const line of previewLines) {
|
|
6665
|
-
console.log(
|
|
6875
|
+
console.log(chalk19.dim(` ${line.substring(0, 60)}${line.length > 60 ? "..." : ""}`));
|
|
6666
6876
|
}
|
|
6667
6877
|
if (removedContent.split("\n").length > 3) {
|
|
6668
|
-
console.log(
|
|
6878
|
+
console.log(chalk19.dim(` ... (${removedContent.split("\n").length - 3} more lines)`));
|
|
6669
6879
|
}
|
|
6670
6880
|
console.log("");
|
|
6671
6881
|
}
|
|
6672
6882
|
const originalLines = countLines(content);
|
|
6673
6883
|
const remainingLines = originalLines - totalLines;
|
|
6674
6884
|
const percentage = Math.round(totalLines / originalLines * 100);
|
|
6675
|
-
console.log(
|
|
6676
|
-
console.log(` Original lines: ${
|
|
6677
|
-
console.log(` Removing: ${
|
|
6678
|
-
console.log(` Remaining lines: ${
|
|
6885
|
+
console.log(chalk19.bold("Summary:"));
|
|
6886
|
+
console.log(` Original lines: ${chalk19.cyan(originalLines)}`);
|
|
6887
|
+
console.log(` Removing: ${chalk19.yellow(totalLines)} lines (${percentage}%)`);
|
|
6888
|
+
console.log(` Remaining lines: ${chalk19.cyan(remainingLines)}`);
|
|
6679
6889
|
console.log("");
|
|
6680
|
-
console.log(
|
|
6681
|
-
console.log(
|
|
6890
|
+
console.log(chalk19.dim("No files modified (dry run)"));
|
|
6891
|
+
console.log(chalk19.dim("Run without --dry-run to apply changes"));
|
|
6682
6892
|
console.log("");
|
|
6683
6893
|
}
|
|
6684
6894
|
async function executeCompact(readmePath, specName, content, removes) {
|
|
6685
|
-
console.log(
|
|
6895
|
+
console.log(chalk19.bold.cyan(`\u{1F5DC}\uFE0F Compacting: ${specName}`));
|
|
6686
6896
|
console.log("");
|
|
6687
6897
|
const sorted = [...removes].sort((a, b) => b.startLine - a.startLine);
|
|
6688
6898
|
let updatedContent = content;
|
|
@@ -6691,22 +6901,22 @@ async function executeCompact(readmePath, specName, content, removes) {
|
|
|
6691
6901
|
const lineCount = remove.endLine - remove.startLine + 1;
|
|
6692
6902
|
updatedContent = removeLines(updatedContent, remove.startLine, remove.endLine);
|
|
6693
6903
|
totalRemoved += lineCount;
|
|
6694
|
-
console.log(
|
|
6904
|
+
console.log(chalk19.green(`\u2713 Removed lines ${remove.startLine}-${remove.endLine} (${lineCount} lines)`));
|
|
6695
6905
|
}
|
|
6696
6906
|
await writeFile(readmePath, updatedContent, "utf-8");
|
|
6697
6907
|
const originalLines = countLines(content);
|
|
6698
6908
|
const finalLines = countLines(updatedContent);
|
|
6699
6909
|
const percentage = Math.round(totalRemoved / originalLines * 100);
|
|
6700
6910
|
console.log("");
|
|
6701
|
-
console.log(
|
|
6702
|
-
console.log(
|
|
6703
|
-
console.log(
|
|
6911
|
+
console.log(chalk19.bold.green("Compaction complete!"));
|
|
6912
|
+
console.log(chalk19.dim(`Removed ${totalRemoved} lines (${percentage}%)`));
|
|
6913
|
+
console.log(chalk19.dim(`${originalLines} \u2192 ${finalLines} lines`));
|
|
6704
6914
|
console.log("");
|
|
6705
6915
|
}
|
|
6706
6916
|
marked.use(markedTerminal());
|
|
6707
6917
|
async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
6708
6918
|
const config = await loadConfig(cwd);
|
|
6709
|
-
const specsDir =
|
|
6919
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
6710
6920
|
let resolvedPath = null;
|
|
6711
6921
|
let targetFile = null;
|
|
6712
6922
|
const pathParts = specPath.split("/").filter((p) => p);
|
|
@@ -6715,7 +6925,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
6715
6925
|
const filePart = pathParts[pathParts.length - 1];
|
|
6716
6926
|
resolvedPath = await resolveSpecPath(specPart, cwd, specsDir);
|
|
6717
6927
|
if (resolvedPath) {
|
|
6718
|
-
targetFile =
|
|
6928
|
+
targetFile = path15.join(resolvedPath, filePart);
|
|
6719
6929
|
try {
|
|
6720
6930
|
await fs9.access(targetFile);
|
|
6721
6931
|
} catch {
|
|
@@ -6737,7 +6947,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
6737
6947
|
return null;
|
|
6738
6948
|
}
|
|
6739
6949
|
const rawContent = await fs9.readFile(targetFile, "utf-8");
|
|
6740
|
-
const fileName =
|
|
6950
|
+
const fileName = path15.basename(targetFile);
|
|
6741
6951
|
const isSubSpec = fileName !== config.structure.defaultFile;
|
|
6742
6952
|
let frontmatter = null;
|
|
6743
6953
|
if (!isSubSpec) {
|
|
@@ -6766,7 +6976,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
6766
6976
|
}
|
|
6767
6977
|
}
|
|
6768
6978
|
const content = lines.slice(contentStartIndex).join("\n").trim();
|
|
6769
|
-
const specName =
|
|
6979
|
+
const specName = path15.basename(resolvedPath);
|
|
6770
6980
|
const displayName = isSubSpec ? `${specName}/${fileName}` : specName;
|
|
6771
6981
|
return {
|
|
6772
6982
|
frontmatter,
|
|
@@ -6789,7 +6999,7 @@ function formatFrontmatter(frontmatter) {
|
|
|
6789
6999
|
archived: "\u{1F4E6}"
|
|
6790
7000
|
};
|
|
6791
7001
|
const statusEmoji = statusEmojis[frontmatter.status] || "\u{1F4C4}";
|
|
6792
|
-
lines.push(
|
|
7002
|
+
lines.push(chalk19.bold(`${statusEmoji} Status: `) + chalk19.cyan(frontmatter.status));
|
|
6793
7003
|
if (frontmatter.priority) {
|
|
6794
7004
|
const priorityEmojis = {
|
|
6795
7005
|
low: "\u{1F7E2}",
|
|
@@ -6798,25 +7008,25 @@ function formatFrontmatter(frontmatter) {
|
|
|
6798
7008
|
critical: "\u{1F534}"
|
|
6799
7009
|
};
|
|
6800
7010
|
const priorityEmoji = priorityEmojis[frontmatter.priority] || "";
|
|
6801
|
-
lines.push(
|
|
7011
|
+
lines.push(chalk19.bold(`${priorityEmoji} Priority: `) + chalk19.yellow(frontmatter.priority));
|
|
6802
7012
|
}
|
|
6803
7013
|
if (frontmatter.created) {
|
|
6804
|
-
lines.push(
|
|
7014
|
+
lines.push(chalk19.bold("\u{1F4C6} Created: ") + chalk19.gray(String(frontmatter.created)));
|
|
6805
7015
|
}
|
|
6806
7016
|
if (frontmatter.tags && frontmatter.tags.length > 0) {
|
|
6807
|
-
const tagStr = frontmatter.tags.map((tag) =>
|
|
6808
|
-
lines.push(
|
|
7017
|
+
const tagStr = frontmatter.tags.map((tag) => chalk19.blue(`#${tag}`)).join(" ");
|
|
7018
|
+
lines.push(chalk19.bold("\u{1F3F7}\uFE0F Tags: ") + tagStr);
|
|
6809
7019
|
}
|
|
6810
7020
|
if (frontmatter.assignee) {
|
|
6811
|
-
lines.push(
|
|
7021
|
+
lines.push(chalk19.bold("\u{1F464} Assignee: ") + chalk19.green(frontmatter.assignee));
|
|
6812
7022
|
}
|
|
6813
7023
|
const standardFields = ["status", "priority", "created", "tags", "assignee"];
|
|
6814
7024
|
const customFields = Object.entries(frontmatter).filter(([key]) => !standardFields.includes(key)).filter(([_, value]) => value !== void 0 && value !== null);
|
|
6815
7025
|
if (customFields.length > 0) {
|
|
6816
7026
|
lines.push("");
|
|
6817
|
-
lines.push(
|
|
7027
|
+
lines.push(chalk19.bold("Custom Fields:"));
|
|
6818
7028
|
for (const [key, value] of customFields) {
|
|
6819
|
-
lines.push(` ${
|
|
7029
|
+
lines.push(` ${chalk19.gray(key)}: ${chalk19.white(String(value))}`);
|
|
6820
7030
|
}
|
|
6821
7031
|
}
|
|
6822
7032
|
return lines.join("\n");
|
|
@@ -6824,11 +7034,11 @@ function formatFrontmatter(frontmatter) {
|
|
|
6824
7034
|
function displayFormattedSpec(spec) {
|
|
6825
7035
|
const output = [];
|
|
6826
7036
|
output.push("");
|
|
6827
|
-
output.push(
|
|
7037
|
+
output.push(chalk19.bold.cyan(`\u2501\u2501\u2501 ${spec.name} \u2501\u2501\u2501`));
|
|
6828
7038
|
output.push("");
|
|
6829
7039
|
output.push(formatFrontmatter(spec.frontmatter));
|
|
6830
7040
|
output.push("");
|
|
6831
|
-
output.push(
|
|
7041
|
+
output.push(chalk19.gray("\u2500".repeat(60)));
|
|
6832
7042
|
output.push("");
|
|
6833
7043
|
return output.join("\n");
|
|
6834
7044
|
}
|
|
@@ -6890,7 +7100,7 @@ function openCommand(specPath, options = {}) {
|
|
|
6890
7100
|
async function openSpec(specPath, options = {}) {
|
|
6891
7101
|
const cwd = process.cwd();
|
|
6892
7102
|
const config = await loadConfig(cwd);
|
|
6893
|
-
const specsDir =
|
|
7103
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
6894
7104
|
let resolvedPath = null;
|
|
6895
7105
|
let targetFile = null;
|
|
6896
7106
|
const pathParts = specPath.split("/").filter((p) => p);
|
|
@@ -6899,7 +7109,7 @@ async function openSpec(specPath, options = {}) {
|
|
|
6899
7109
|
const filePart = pathParts[pathParts.length - 1];
|
|
6900
7110
|
resolvedPath = await resolveSpecPath(specPart, cwd, specsDir);
|
|
6901
7111
|
if (resolvedPath) {
|
|
6902
|
-
targetFile =
|
|
7112
|
+
targetFile = path15.join(resolvedPath, filePart);
|
|
6903
7113
|
try {
|
|
6904
7114
|
await fs9.access(targetFile);
|
|
6905
7115
|
} catch {
|
|
@@ -6933,7 +7143,7 @@ async function openSpec(specPath, options = {}) {
|
|
|
6933
7143
|
editor = "xdg-open";
|
|
6934
7144
|
}
|
|
6935
7145
|
}
|
|
6936
|
-
console.log(
|
|
7146
|
+
console.log(chalk19.gray(`Opening ${targetFile} with ${editor}...`));
|
|
6937
7147
|
const child = spawn(editor, [targetFile], {
|
|
6938
7148
|
stdio: "inherit",
|
|
6939
7149
|
shell: true
|
|
@@ -6979,7 +7189,7 @@ async function startMcpServer() {
|
|
|
6979
7189
|
process.exit(1);
|
|
6980
7190
|
}
|
|
6981
7191
|
}
|
|
6982
|
-
function
|
|
7192
|
+
function detectPackageManager2(baseDir = process.cwd()) {
|
|
6983
7193
|
const userAgent = process.env.npm_config_user_agent || "";
|
|
6984
7194
|
if (userAgent.includes("pnpm")) {
|
|
6985
7195
|
return "pnpm";
|
|
@@ -7015,8 +7225,8 @@ function uiCommand() {
|
|
|
7015
7225
|
async function startUi(options) {
|
|
7016
7226
|
const portNum = parseInt(options.port, 10);
|
|
7017
7227
|
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
|
|
7018
|
-
console.error(
|
|
7019
|
-
console.log(
|
|
7228
|
+
console.error(chalk19.red(`\u2717 Invalid port number: ${options.port}`));
|
|
7229
|
+
console.log(chalk19.dim("Port must be between 1 and 65535"));
|
|
7020
7230
|
throw new Error(`Invalid port: ${options.port}`);
|
|
7021
7231
|
}
|
|
7022
7232
|
const cwd = process.cwd();
|
|
@@ -7030,24 +7240,24 @@ async function startUi(options) {
|
|
|
7030
7240
|
specsDir = join(cwd, config.specsDir);
|
|
7031
7241
|
}
|
|
7032
7242
|
if (!existsSync(specsDir)) {
|
|
7033
|
-
console.error(
|
|
7034
|
-
console.log(
|
|
7243
|
+
console.error(chalk19.red(`\u2717 Specs directory not found: ${specsDir}`));
|
|
7244
|
+
console.log(chalk19.dim("\nRun `lean-spec init` to initialize LeanSpec in this directory."));
|
|
7035
7245
|
throw new Error(`Specs directory not found: ${specsDir}`);
|
|
7036
7246
|
}
|
|
7037
7247
|
} else {
|
|
7038
|
-
console.log(
|
|
7248
|
+
console.log(chalk19.cyan("\u2192 Multi-project mode enabled"));
|
|
7039
7249
|
if (options.addProject) {
|
|
7040
|
-
console.log(
|
|
7250
|
+
console.log(chalk19.dim(` Adding project: ${options.addProject}`));
|
|
7041
7251
|
}
|
|
7042
7252
|
if (options.discover) {
|
|
7043
|
-
console.log(
|
|
7253
|
+
console.log(chalk19.dim(` Will discover projects in: ${options.discover}`));
|
|
7044
7254
|
}
|
|
7045
7255
|
}
|
|
7046
7256
|
if (options.dev) {
|
|
7047
7257
|
const isLeanSpecMonorepo = checkIsLeanSpecMonorepo(cwd);
|
|
7048
7258
|
if (!isLeanSpecMonorepo) {
|
|
7049
|
-
console.error(
|
|
7050
|
-
console.log(
|
|
7259
|
+
console.error(chalk19.red(`\u2717 Development mode only works in the LeanSpec monorepo`));
|
|
7260
|
+
console.log(chalk19.dim("Remove --dev flag to use production mode"));
|
|
7051
7261
|
throw new Error("Not in LeanSpec monorepo");
|
|
7052
7262
|
}
|
|
7053
7263
|
const localUiDir = join(cwd, "packages/ui");
|
|
@@ -7069,15 +7279,15 @@ function checkIsLeanSpecMonorepo(cwd) {
|
|
|
7069
7279
|
}
|
|
7070
7280
|
}
|
|
7071
7281
|
async function runLocalWeb(uiDir, specsDir, specsMode, options) {
|
|
7072
|
-
console.log(
|
|
7282
|
+
console.log(chalk19.dim("\u2192 Detected LeanSpec monorepo, using local ui package\n"));
|
|
7073
7283
|
const repoRoot = resolve(uiDir, "..", "..");
|
|
7074
|
-
const packageManager =
|
|
7284
|
+
const packageManager = detectPackageManager2(repoRoot);
|
|
7075
7285
|
if (options.dryRun) {
|
|
7076
|
-
console.log(
|
|
7077
|
-
console.log(
|
|
7078
|
-
console.log(
|
|
7286
|
+
console.log(chalk19.cyan("Would run:"));
|
|
7287
|
+
console.log(chalk19.dim(` cd ${uiDir}`));
|
|
7288
|
+
console.log(chalk19.dim(` SPECS_MODE=${specsMode} SPECS_DIR=${specsDir} PORT=${options.port} ${packageManager} run dev`));
|
|
7079
7289
|
if (options.open) {
|
|
7080
|
-
console.log(
|
|
7290
|
+
console.log(chalk19.dim(` open http://localhost:${options.port}`));
|
|
7081
7291
|
}
|
|
7082
7292
|
return;
|
|
7083
7293
|
}
|
|
@@ -7096,11 +7306,11 @@ async function runLocalWeb(uiDir, specsDir, specsMode, options) {
|
|
|
7096
7306
|
});
|
|
7097
7307
|
const readyTimeout = setTimeout(async () => {
|
|
7098
7308
|
spinner.succeed("Web UI running");
|
|
7099
|
-
console.log(
|
|
7309
|
+
console.log(chalk19.green(`
|
|
7100
7310
|
\u2728 LeanSpec UI: http://localhost:${options.port}
|
|
7101
7311
|
`));
|
|
7102
7312
|
if (options.multiProject) {
|
|
7103
|
-
console.log(
|
|
7313
|
+
console.log(chalk19.cyan("Multi-project mode is active"));
|
|
7104
7314
|
if (options.addProject) {
|
|
7105
7315
|
try {
|
|
7106
7316
|
const res = await fetch(`http://localhost:${options.port}/api/projects`, {
|
|
@@ -7109,16 +7319,16 @@ async function runLocalWeb(uiDir, specsDir, specsMode, options) {
|
|
|
7109
7319
|
body: JSON.stringify({ path: options.addProject })
|
|
7110
7320
|
});
|
|
7111
7321
|
if (res.ok) {
|
|
7112
|
-
console.log(
|
|
7322
|
+
console.log(chalk19.green(` \u2713 Added project: ${options.addProject}`));
|
|
7113
7323
|
} else {
|
|
7114
|
-
console.error(
|
|
7324
|
+
console.error(chalk19.red(` \u2717 Failed to add project: ${options.addProject}`));
|
|
7115
7325
|
}
|
|
7116
7326
|
} catch (e) {
|
|
7117
|
-
console.error(
|
|
7327
|
+
console.error(chalk19.red(` \u2717 Failed to connect to server for adding project`));
|
|
7118
7328
|
}
|
|
7119
7329
|
}
|
|
7120
7330
|
if (options.discover) {
|
|
7121
|
-
console.log(
|
|
7331
|
+
console.log(chalk19.dim(` Discovering projects in: ${options.discover}...`));
|
|
7122
7332
|
try {
|
|
7123
7333
|
const res = await fetch(`http://localhost:${options.port}/api/local-projects/discover`, {
|
|
7124
7334
|
method: "POST",
|
|
@@ -7128,7 +7338,7 @@ async function runLocalWeb(uiDir, specsDir, specsMode, options) {
|
|
|
7128
7338
|
if (res.ok) {
|
|
7129
7339
|
const data = await res.json();
|
|
7130
7340
|
const discovered = data.discovered || [];
|
|
7131
|
-
console.log(
|
|
7341
|
+
console.log(chalk19.green(` \u2713 Found ${discovered.length} projects`));
|
|
7132
7342
|
for (const p of discovered) {
|
|
7133
7343
|
const addRes = await fetch(`http://localhost:${options.port}/api/projects`, {
|
|
7134
7344
|
method: "POST",
|
|
@@ -7136,27 +7346,27 @@ async function runLocalWeb(uiDir, specsDir, specsMode, options) {
|
|
|
7136
7346
|
body: JSON.stringify({ path: p.path })
|
|
7137
7347
|
});
|
|
7138
7348
|
if (addRes.ok) {
|
|
7139
|
-
console.log(
|
|
7349
|
+
console.log(chalk19.dim(` + Added: ${p.name} (${p.path})`));
|
|
7140
7350
|
}
|
|
7141
7351
|
}
|
|
7142
7352
|
} else {
|
|
7143
|
-
console.error(
|
|
7353
|
+
console.error(chalk19.red(` \u2717 Failed to discover projects`));
|
|
7144
7354
|
}
|
|
7145
7355
|
} catch (e) {
|
|
7146
|
-
console.error(
|
|
7356
|
+
console.error(chalk19.red(` \u2717 Failed to connect to server for discovery`));
|
|
7147
7357
|
}
|
|
7148
7358
|
}
|
|
7149
7359
|
}
|
|
7150
|
-
console.log(
|
|
7360
|
+
console.log(chalk19.dim("\nPress Ctrl+C to stop\n"));
|
|
7151
7361
|
if (options.open) {
|
|
7152
7362
|
try {
|
|
7153
7363
|
const openModule = await import('open');
|
|
7154
7364
|
const open = openModule.default;
|
|
7155
7365
|
await open(`http://localhost:${options.port}`);
|
|
7156
7366
|
} catch (error) {
|
|
7157
|
-
console.log(
|
|
7158
|
-
console.log(
|
|
7159
|
-
console.error(
|
|
7367
|
+
console.log(chalk19.yellow("\u26A0 Could not open browser automatically"));
|
|
7368
|
+
console.log(chalk19.dim("Please visit the URL above manually\n"));
|
|
7369
|
+
console.error(chalk19.dim(`Debug: ${error instanceof Error ? error.message : String(error)}`));
|
|
7160
7370
|
}
|
|
7161
7371
|
}
|
|
7162
7372
|
}, 3e3);
|
|
@@ -7173,7 +7383,7 @@ async function runLocalWeb(uiDir, specsDir, specsMode, options) {
|
|
|
7173
7383
|
}
|
|
7174
7384
|
} catch (err) {
|
|
7175
7385
|
}
|
|
7176
|
-
console.log(
|
|
7386
|
+
console.log(chalk19.dim("\n\u2713 Web UI stopped"));
|
|
7177
7387
|
process.exit(0);
|
|
7178
7388
|
};
|
|
7179
7389
|
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
@@ -7187,7 +7397,7 @@ async function runLocalWeb(uiDir, specsDir, specsMode, options) {
|
|
|
7187
7397
|
spinner.stop();
|
|
7188
7398
|
if (code !== 0 && code !== null) {
|
|
7189
7399
|
spinner.fail("Web UI failed to start");
|
|
7190
|
-
console.error(
|
|
7400
|
+
console.error(chalk19.red(`
|
|
7191
7401
|
Process exited with code ${code}`));
|
|
7192
7402
|
process.exit(code);
|
|
7193
7403
|
}
|
|
@@ -7195,12 +7405,12 @@ Process exited with code ${code}`));
|
|
|
7195
7405
|
});
|
|
7196
7406
|
}
|
|
7197
7407
|
async function runPublishedUI(cwd, specsDir, specsMode, options) {
|
|
7198
|
-
console.log(
|
|
7199
|
-
const packageManager =
|
|
7408
|
+
console.log(chalk19.dim("\u2192 Using published @leanspec/ui package\n"));
|
|
7409
|
+
const packageManager = detectPackageManager2(cwd);
|
|
7200
7410
|
const { command, args, preview } = buildUiRunner(packageManager, specsDir, specsMode, options.port, options.open, options.multiProject);
|
|
7201
7411
|
if (options.dryRun) {
|
|
7202
|
-
console.log(
|
|
7203
|
-
console.log(
|
|
7412
|
+
console.log(chalk19.cyan("Would run:"));
|
|
7413
|
+
console.log(chalk19.dim(` ${preview}`));
|
|
7204
7414
|
return;
|
|
7205
7415
|
}
|
|
7206
7416
|
const child = spawn(command, args, {
|
|
@@ -7219,7 +7429,7 @@ async function runPublishedUI(cwd, specsDir, specsMode, options) {
|
|
|
7219
7429
|
}
|
|
7220
7430
|
} catch (err) {
|
|
7221
7431
|
}
|
|
7222
|
-
console.log(
|
|
7432
|
+
console.log(chalk19.dim("\n\u2713 Web UI stopped"));
|
|
7223
7433
|
process.exit(0);
|
|
7224
7434
|
};
|
|
7225
7435
|
process.once("SIGINT", () => shutdownPublished("SIGINT"));
|
|
@@ -7233,14 +7443,14 @@ async function runPublishedUI(cwd, specsDir, specsMode, options) {
|
|
|
7233
7443
|
process.exit(0);
|
|
7234
7444
|
return;
|
|
7235
7445
|
}
|
|
7236
|
-
console.error(
|
|
7446
|
+
console.error(chalk19.red(`
|
|
7237
7447
|
@leanspec/ui exited with code ${code}`));
|
|
7238
|
-
console.log(
|
|
7448
|
+
console.log(chalk19.dim("Make sure npm can download @leanspec/ui (https://www.npmjs.com/package/@leanspec/ui)."));
|
|
7239
7449
|
process.exit(code);
|
|
7240
7450
|
});
|
|
7241
7451
|
child.on("error", (error) => {
|
|
7242
|
-
console.error(
|
|
7243
|
-
console.log(
|
|
7452
|
+
console.error(chalk19.red(`Failed to launch @leanspec/ui: ${error instanceof Error ? error.message : String(error)}`));
|
|
7453
|
+
console.log(chalk19.dim("You can also run it manually with `npx @leanspec/ui --specs <dir>`"));
|
|
7244
7454
|
process.exit(1);
|
|
7245
7455
|
});
|
|
7246
7456
|
}
|
|
@@ -7583,7 +7793,7 @@ function createTool() {
|
|
|
7583
7793
|
async function getDepsData(specPath, mode = "complete") {
|
|
7584
7794
|
const config = await loadConfig();
|
|
7585
7795
|
const cwd = process.cwd();
|
|
7586
|
-
const specsDir =
|
|
7796
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
7587
7797
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
7588
7798
|
if (!resolvedPath) {
|
|
7589
7799
|
throw new Error(`Spec not found: ${specPath}`);
|
|
@@ -8031,7 +8241,7 @@ function tokensTool() {
|
|
|
8031
8241
|
try {
|
|
8032
8242
|
const config = await loadConfig();
|
|
8033
8243
|
const cwd = process.cwd();
|
|
8034
|
-
const specsDir =
|
|
8244
|
+
const specsDir = path15.join(cwd, config.specsDir);
|
|
8035
8245
|
const resolvedPath = await resolveSpecPath(input.specPath, cwd, specsDir);
|
|
8036
8246
|
if (!resolvedPath) {
|
|
8037
8247
|
return {
|
|
@@ -8042,7 +8252,7 @@ function tokensTool() {
|
|
|
8042
8252
|
isError: true
|
|
8043
8253
|
};
|
|
8044
8254
|
}
|
|
8045
|
-
const specName =
|
|
8255
|
+
const specName = path15.basename(resolvedPath);
|
|
8046
8256
|
const result = await counter.countSpec(resolvedPath, {
|
|
8047
8257
|
detailed: input.detailed,
|
|
8048
8258
|
includeSubSpecs: input.includeSubSpecs
|
|
@@ -8581,6 +8791,6 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
8581
8791
|
main().catch(console.error);
|
|
8582
8792
|
}
|
|
8583
8793
|
|
|
8584
|
-
export { analyzeCommand, archiveCommand, backfillCommand, boardCommand, checkCommand, compactCommand, createCommand, createMcpServer, depsCommand, filesCommand, ganttCommand, initCommand, linkCommand, listCommand, mcpCommand, migrateCommand, openCommand, searchCommand, splitCommand, statsCommand, templatesCommand, timelineCommand, tokensCommand, uiCommand, unlinkCommand, updateCommand, validateCommand, viewCommand };
|
|
8585
|
-
//# sourceMappingURL=chunk-
|
|
8586
|
-
//# sourceMappingURL=chunk-
|
|
8794
|
+
export { analyzeCommand, archiveCommand, backfillCommand, boardCommand, checkCommand, compactCommand, createCommand, createMcpServer, depsCommand, examplesCommand, filesCommand, ganttCommand, initCommand, linkCommand, listCommand, mcpCommand, migrateCommand, openCommand, searchCommand, splitCommand, statsCommand, templatesCommand, timelineCommand, tokensCommand, uiCommand, unlinkCommand, updateCommand, validateCommand, viewCommand };
|
|
8795
|
+
//# sourceMappingURL=chunk-6FKLWECL.js.map
|
|
8796
|
+
//# sourceMappingURL=chunk-6FKLWECL.js.map
|