lean-spec 0.1.2 → 0.1.3
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/CHANGELOG.md +79 -0
- package/dist/{chunk-OXTU3PN4.js → chunk-S6TGAL75.js} +253 -34
- package/dist/chunk-S6TGAL75.js.map +1 -0
- package/dist/cli.js +18 -1
- package/dist/cli.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/package.json +15 -15
- package/templates/enterprise/README.md +1 -1
- package/templates/enterprise/files/AGENTS.md +12 -12
- package/templates/minimal/README.md +1 -1
- package/templates/minimal/files/AGENTS.md +7 -7
- package/templates/standard/README.md +1 -1
- package/templates/standard/files/AGENTS.md +10 -10
- package/dist/chunk-OXTU3PN4.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,84 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.3] - 2025-11-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
**New Commands:**
|
|
15
|
+
- `lean-spec migrate` - Migrate from existing tools (ADRs, RFCs, design docs) with AI assistance
|
|
16
|
+
- `lean-spec archive` - Archive completed specs with automatic frontmatter updates
|
|
17
|
+
- `lean-spec backfill` - Backfill timestamps and metadata from git history
|
|
18
|
+
|
|
19
|
+
**Documentation Enhancements:**
|
|
20
|
+
- Complete documentation site overhaul with improved information architecture
|
|
21
|
+
- AI-assisted spec writing guide with philosophy and best practices
|
|
22
|
+
- Migration guides for teams coming from ADRs, RFCs, and other tools
|
|
23
|
+
- First principles documentation (Context Economy, Signal-to-Noise, etc.)
|
|
24
|
+
- Comprehensive core concepts guide with practical examples
|
|
25
|
+
|
|
26
|
+
**Quality & Validation:**
|
|
27
|
+
- Enhanced `lean-spec validate` with complexity analysis
|
|
28
|
+
- Spec relationship clarity with bidirectional `related` and directional `depends_on`
|
|
29
|
+
- Improved frontmatter handling and metadata management
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
**User Experience:**
|
|
34
|
+
- Unified dashboard combining board view with project health metrics
|
|
35
|
+
- Pattern-aware list grouping with visual icons and better organization
|
|
36
|
+
- Improved init flow with pattern selection
|
|
37
|
+
- Enhanced stats dashboard with actionable insights
|
|
38
|
+
- Better MCP error handling and stability
|
|
39
|
+
|
|
40
|
+
**Documentation:**
|
|
41
|
+
- Restructured docs with clearer navigation and information flow
|
|
42
|
+
- Updated README with AI-first positioning
|
|
43
|
+
- Comprehensive examples and use cases
|
|
44
|
+
- Improved CLI command documentation
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- MCP server stability issues with frontmatter parsing
|
|
48
|
+
- TypeScript type errors in migrate command
|
|
49
|
+
- Documentation accuracy issues across all guides
|
|
50
|
+
- Frontmatter handling edge cases
|
|
51
|
+
|
|
52
|
+
### Philosophy
|
|
53
|
+
|
|
54
|
+
This UAT release operationalizes LeanSpec's five first principles:
|
|
55
|
+
1. **Context Economy** - Specs fit in working memory (<400 lines)
|
|
56
|
+
2. **Signal-to-Noise** - Every word informs decisions
|
|
57
|
+
3. **Intent Over Implementation** - Capture why, not just how
|
|
58
|
+
4. **Bridge the Gap** - Both human and AI understand
|
|
59
|
+
5. **Progressive Disclosure** - Add complexity only when needed
|
|
60
|
+
|
|
61
|
+
**Notable Completed Specs in this Release:**
|
|
62
|
+
- 063: Migration from existing tools
|
|
63
|
+
- 062: Documentation information architecture v2
|
|
64
|
+
- 061: AI-assisted spec writing
|
|
65
|
+
- 060: Core concepts coherence
|
|
66
|
+
- 058: Docs overview polish
|
|
67
|
+
- 057: Docs validation comprehensive
|
|
68
|
+
- 056: Docs site accuracy audit
|
|
69
|
+
- 055: README redesign (AI-first)
|
|
70
|
+
- 054: Validate output (lint-style)
|
|
71
|
+
- 052: Branding assets
|
|
72
|
+
- 051: First principles documentation
|
|
73
|
+
- 049: LeanSpec first principles foundation
|
|
74
|
+
- 048: Spec complexity analysis
|
|
75
|
+
- 047: Git backfill timestamps
|
|
76
|
+
- 046: Stats dashboard refactor
|
|
77
|
+
- 045: Unified dashboard
|
|
78
|
+
- 044: Spec relationships clarity
|
|
79
|
+
|
|
80
|
+
**Testing:**
|
|
81
|
+
- All 261 tests passing (100% pass rate)
|
|
82
|
+
- Zero critical bugs
|
|
83
|
+
- MCP server stable
|
|
84
|
+
- Documentation site builds cleanly
|
|
85
|
+
|
|
86
|
+
**Ready for:** UAT testing before official 0.2.0 launch
|
|
87
|
+
|
|
10
88
|
## [0.1.2] - 2025-11-10
|
|
11
89
|
|
|
12
90
|
### Changed
|
|
@@ -127,6 +205,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
127
205
|
- Gray-matter for frontmatter parsing
|
|
128
206
|
- Dayjs for date handling
|
|
129
207
|
|
|
208
|
+
[0.1.3]: https://github.com/codervisor/lean-spec/releases/tag/v0.1.3
|
|
130
209
|
[0.1.2]: https://github.com/codervisor/lean-spec/releases/tag/v0.1.2
|
|
131
210
|
[0.1.1]: https://github.com/codervisor/lean-spec/releases/tag/v0.1.1
|
|
132
211
|
[0.1.0]: https://github.com/codervisor/lean-spec/releases/tag/v0.1.0
|
|
@@ -141,13 +141,13 @@ async function loadSubFiles(specDir, options = {}) {
|
|
|
141
141
|
if (entry.name === "README.md") continue;
|
|
142
142
|
if (entry.isDirectory()) continue;
|
|
143
143
|
const filePath = path2.join(specDir, entry.name);
|
|
144
|
-
const
|
|
144
|
+
const stat5 = await fs2.stat(filePath);
|
|
145
145
|
const ext = path2.extname(entry.name).toLowerCase();
|
|
146
146
|
const isDocument = ext === ".md";
|
|
147
147
|
const subFile = {
|
|
148
148
|
name: entry.name,
|
|
149
149
|
path: filePath,
|
|
150
|
-
size:
|
|
150
|
+
size: stat5.size,
|
|
151
151
|
type: isDocument ? "document" : "asset"
|
|
152
152
|
};
|
|
153
153
|
if (isDocument && options.includeContent) {
|
|
@@ -734,7 +734,7 @@ ${options.description}`
|
|
|
734
734
|
);
|
|
735
735
|
}
|
|
736
736
|
} catch (error) {
|
|
737
|
-
throw new Error(`Template not found: ${templatePath}. Run:
|
|
737
|
+
throw new Error(`Template not found: ${templatePath}. Run: lean-spec init`);
|
|
738
738
|
}
|
|
739
739
|
await fs5.writeFile(specFile, content, "utf-8");
|
|
740
740
|
console.log(chalk4.green(`\u2713 Created: ${sanitizeUserInput(specDir)}/`));
|
|
@@ -755,6 +755,10 @@ async function archiveSpec(specPath) {
|
|
|
755
755
|
if (!resolvedPath) {
|
|
756
756
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
757
757
|
}
|
|
758
|
+
const specFile = await getSpecFile(resolvedPath, config.structure.defaultFile);
|
|
759
|
+
if (specFile) {
|
|
760
|
+
await updateFrontmatter(specFile, { status: "archived" });
|
|
761
|
+
}
|
|
758
762
|
const archiveDir = path7.join(specsDir, "archived");
|
|
759
763
|
await fs6.mkdir(archiveDir, { recursive: true });
|
|
760
764
|
const specName = path7.basename(resolvedPath);
|
|
@@ -870,7 +874,7 @@ async function listSpecs(options = {}) {
|
|
|
870
874
|
await fs7.access(specsDir);
|
|
871
875
|
} catch {
|
|
872
876
|
console.log("");
|
|
873
|
-
console.log("No specs directory found. Initialize with:
|
|
877
|
+
console.log("No specs directory found. Initialize with: lean-spec init");
|
|
874
878
|
console.log("");
|
|
875
879
|
return;
|
|
876
880
|
}
|
|
@@ -1359,7 +1363,7 @@ async function listTemplates(cwd = process.cwd()) {
|
|
|
1359
1363
|
await fs8.access(templatesDir);
|
|
1360
1364
|
} catch {
|
|
1361
1365
|
console.log(chalk9.yellow("No templates directory found."));
|
|
1362
|
-
console.log(chalk9.gray("Run:
|
|
1366
|
+
console.log(chalk9.gray("Run: lean-spec init"));
|
|
1363
1367
|
console.log("");
|
|
1364
1368
|
return;
|
|
1365
1369
|
}
|
|
@@ -1382,12 +1386,12 @@ async function listTemplates(cwd = process.cwd()) {
|
|
|
1382
1386
|
console.log(chalk9.cyan("Available files:"));
|
|
1383
1387
|
for (const file of templateFiles) {
|
|
1384
1388
|
const filePath = path11.join(templatesDir, file);
|
|
1385
|
-
const
|
|
1386
|
-
const sizeKB = (
|
|
1389
|
+
const stat5 = await fs8.stat(filePath);
|
|
1390
|
+
const sizeKB = (stat5.size / 1024).toFixed(1);
|
|
1387
1391
|
console.log(` ${file} (${sizeKB} KB)`);
|
|
1388
1392
|
}
|
|
1389
1393
|
console.log("");
|
|
1390
|
-
console.log(chalk9.gray("Use templates with:
|
|
1394
|
+
console.log(chalk9.gray("Use templates with: lean-spec create <name> --template=<template-name>"));
|
|
1391
1395
|
console.log("");
|
|
1392
1396
|
}
|
|
1393
1397
|
async function showTemplate(templateName, cwd = process.cwd()) {
|
|
@@ -1423,7 +1427,7 @@ async function addTemplate(name, file, cwd = process.cwd()) {
|
|
|
1423
1427
|
console.error(chalk9.red(`Template file not found: ${file}`));
|
|
1424
1428
|
console.error(chalk9.gray(`Expected at: ${templatePath}`));
|
|
1425
1429
|
console.error(
|
|
1426
|
-
chalk9.yellow("Create the file first or use:
|
|
1430
|
+
chalk9.yellow("Create the file first or use: lean-spec templates copy <source> <target>")
|
|
1427
1431
|
);
|
|
1428
1432
|
process.exit(1);
|
|
1429
1433
|
}
|
|
@@ -1436,7 +1440,7 @@ async function addTemplate(name, file, cwd = process.cwd()) {
|
|
|
1436
1440
|
config.templates[name] = file;
|
|
1437
1441
|
await saveConfig(config, cwd);
|
|
1438
1442
|
console.log(chalk9.green(`\u2713 Added template: ${name} \u2192 ${file}`));
|
|
1439
|
-
console.log(chalk9.gray(` Use with:
|
|
1443
|
+
console.log(chalk9.gray(` Use with: lean-spec create <spec-name> --template=${name}`));
|
|
1440
1444
|
}
|
|
1441
1445
|
async function removeTemplate(name, cwd = process.cwd()) {
|
|
1442
1446
|
const config = await loadConfig(cwd);
|
|
@@ -1484,7 +1488,7 @@ async function copyTemplate(source, target, cwd = process.cwd()) {
|
|
|
1484
1488
|
await saveConfig(config, cwd);
|
|
1485
1489
|
console.log(chalk9.green(`\u2713 Registered template: ${templateName}`));
|
|
1486
1490
|
console.log(chalk9.gray(` Edit: ${targetPath}`));
|
|
1487
|
-
console.log(chalk9.gray(` Use with:
|
|
1491
|
+
console.log(chalk9.gray(` Use with: lean-spec create <spec-name> --template=${templateName}`));
|
|
1488
1492
|
}
|
|
1489
1493
|
|
|
1490
1494
|
// src/commands/init.ts
|
|
@@ -1820,7 +1824,7 @@ async function initProject() {
|
|
|
1820
1824
|
console.log("Next steps:");
|
|
1821
1825
|
console.log(chalk11.gray(" - Review and customize AGENTS.md"));
|
|
1822
1826
|
console.log(chalk11.gray(" - Check out example spec in specs/"));
|
|
1823
|
-
console.log(chalk11.gray(" - Create your first spec:
|
|
1827
|
+
console.log(chalk11.gray(" - Create your first spec: lean-spec create my-feature"));
|
|
1824
1828
|
console.log("");
|
|
1825
1829
|
}
|
|
1826
1830
|
|
|
@@ -2786,6 +2790,220 @@ async function validateCommand(options = {}) {
|
|
|
2786
2790
|
return !hasErrors;
|
|
2787
2791
|
}
|
|
2788
2792
|
|
|
2793
|
+
// src/commands/migrate.ts
|
|
2794
|
+
import * as fs13 from "fs/promises";
|
|
2795
|
+
import * as path17 from "path";
|
|
2796
|
+
async function migrateCommand(inputPath, options = {}) {
|
|
2797
|
+
const config = await loadConfig();
|
|
2798
|
+
try {
|
|
2799
|
+
const stats = await fs13.stat(inputPath);
|
|
2800
|
+
if (!stats.isDirectory()) {
|
|
2801
|
+
console.error("\x1B[31m\u274C Error:\x1B[0m Input path must be a directory");
|
|
2802
|
+
process.exit(1);
|
|
2803
|
+
}
|
|
2804
|
+
} catch (error) {
|
|
2805
|
+
console.error(`\x1B[31m\u274C Error:\x1B[0m Path not found: ${inputPath}`);
|
|
2806
|
+
process.exit(1);
|
|
2807
|
+
}
|
|
2808
|
+
console.log(`\x1B[36mScanning:\x1B[0m ${inputPath}
|
|
2809
|
+
`);
|
|
2810
|
+
const documents = await scanDocuments(inputPath);
|
|
2811
|
+
if (documents.length === 0) {
|
|
2812
|
+
console.error(`\x1B[31m\u274C Error:\x1B[0m No documents found in ${inputPath}`);
|
|
2813
|
+
console.error(" Check path and try again");
|
|
2814
|
+
process.exit(1);
|
|
2815
|
+
}
|
|
2816
|
+
console.log(`\x1B[32m\u2713\x1B[0m Found ${documents.length} document${documents.length === 1 ? "" : "s"}
|
|
2817
|
+
`);
|
|
2818
|
+
if (options.aiProvider) {
|
|
2819
|
+
await migrateWithAI(inputPath, documents, options);
|
|
2820
|
+
} else {
|
|
2821
|
+
await outputManualInstructions(inputPath, documents, config);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
async function scanDocuments(dirPath) {
|
|
2825
|
+
const documents = [];
|
|
2826
|
+
async function scanRecursive(currentPath) {
|
|
2827
|
+
const entries = await fs13.readdir(currentPath, { withFileTypes: true });
|
|
2828
|
+
for (const entry of entries) {
|
|
2829
|
+
const fullPath = path17.join(currentPath, entry.name);
|
|
2830
|
+
if (entry.isDirectory()) {
|
|
2831
|
+
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
2832
|
+
await scanRecursive(fullPath);
|
|
2833
|
+
}
|
|
2834
|
+
} else if (entry.isFile()) {
|
|
2835
|
+
if (entry.name.endsWith(".md") || entry.name.endsWith(".markdown")) {
|
|
2836
|
+
const stats = await fs13.stat(fullPath);
|
|
2837
|
+
documents.push({
|
|
2838
|
+
path: fullPath,
|
|
2839
|
+
name: entry.name,
|
|
2840
|
+
size: stats.size
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
await scanRecursive(dirPath);
|
|
2847
|
+
return documents;
|
|
2848
|
+
}
|
|
2849
|
+
async function outputManualInstructions(inputPath, documents, config) {
|
|
2850
|
+
const specsDir = config.specsDir || "specs";
|
|
2851
|
+
console.log("\u2550".repeat(70));
|
|
2852
|
+
console.log("\x1B[1m\x1B[36m\u{1F4CB} LeanSpec Migration Instructions\x1B[0m");
|
|
2853
|
+
console.log("\u2550".repeat(70));
|
|
2854
|
+
console.log();
|
|
2855
|
+
console.log("\x1B[1mSource Location:\x1B[0m");
|
|
2856
|
+
console.log(` ${inputPath} (${documents.length} documents found)`);
|
|
2857
|
+
console.log();
|
|
2858
|
+
console.log("\x1B[1mMigration Prompt:\x1B[0m");
|
|
2859
|
+
console.log(" Copy this prompt to your AI assistant (Copilot, Claude, ChatGPT, etc.):");
|
|
2860
|
+
console.log();
|
|
2861
|
+
console.log("\u2500".repeat(70));
|
|
2862
|
+
console.log();
|
|
2863
|
+
console.log("You are helping migrate specification documents to LeanSpec format.");
|
|
2864
|
+
console.log();
|
|
2865
|
+
console.log(`\x1B[1mSource:\x1B[0m ${inputPath}`);
|
|
2866
|
+
console.log();
|
|
2867
|
+
console.log("\x1B[1mYour Task:\x1B[0m");
|
|
2868
|
+
console.log("1. Analyze the source documents to understand their format and structure");
|
|
2869
|
+
console.log("2. For each document, extract:");
|
|
2870
|
+
console.log(" - Title/name");
|
|
2871
|
+
console.log(" - Status (map to: planned, in-progress, complete, archived)");
|
|
2872
|
+
console.log(" - Creation date");
|
|
2873
|
+
console.log(" - Priority (if present)");
|
|
2874
|
+
console.log(" - Main content sections");
|
|
2875
|
+
console.log(" - Relationships to other documents");
|
|
2876
|
+
console.log();
|
|
2877
|
+
console.log("3. Migrate each document by running these commands:");
|
|
2878
|
+
console.log();
|
|
2879
|
+
console.log(" # Create spec");
|
|
2880
|
+
console.log(" lean-spec create <name>");
|
|
2881
|
+
console.log();
|
|
2882
|
+
console.log(" # Set metadata (NEVER edit frontmatter manually)");
|
|
2883
|
+
console.log(" lean-spec update <name> --status <status>");
|
|
2884
|
+
console.log(" lean-spec update <name> --priority <priority>");
|
|
2885
|
+
console.log(" lean-spec update <name> --tags <tag1,tag2>");
|
|
2886
|
+
console.log();
|
|
2887
|
+
console.log(" # Edit content with your preferred tool");
|
|
2888
|
+
console.log(" # Map original sections to LeanSpec structure:");
|
|
2889
|
+
console.log(" # - Overview: Problem statement and context");
|
|
2890
|
+
console.log(" # - Design: Technical approach and decisions");
|
|
2891
|
+
console.log(" # - Plan: Implementation steps (if applicable)");
|
|
2892
|
+
console.log(" # - Test: Validation criteria (if applicable)");
|
|
2893
|
+
console.log(" # - Notes: Additional context, trade-offs, alternatives");
|
|
2894
|
+
console.log();
|
|
2895
|
+
console.log("4. After migration, run:");
|
|
2896
|
+
console.log();
|
|
2897
|
+
console.log(" lean-spec validate # Check for issues");
|
|
2898
|
+
console.log(" lean-spec board # Verify migration");
|
|
2899
|
+
console.log();
|
|
2900
|
+
console.log("\x1B[1mImportant Rules:\x1B[0m");
|
|
2901
|
+
console.log("- Preserve decision rationale and context");
|
|
2902
|
+
console.log("- Map status appropriately to LeanSpec states");
|
|
2903
|
+
console.log("- Link related specs using `related` field (manual frontmatter edit)");
|
|
2904
|
+
console.log("- Follow LeanSpec first principles: clarity over completeness");
|
|
2905
|
+
console.log("- Keep specs under 400 lines (split if needed)");
|
|
2906
|
+
console.log();
|
|
2907
|
+
console.log("\u2500".repeat(70));
|
|
2908
|
+
console.log();
|
|
2909
|
+
console.log("\x1B[36m\u2139\x1B[0m \x1B[1mTip:\x1B[0m For AI-assisted migration, use:");
|
|
2910
|
+
console.log(" \x1B[90mlean-spec migrate <path> --with copilot\x1B[0m");
|
|
2911
|
+
console.log();
|
|
2912
|
+
}
|
|
2913
|
+
async function migrateWithAI(inputPath, documents, options) {
|
|
2914
|
+
const provider = options.aiProvider;
|
|
2915
|
+
console.log(`\x1B[36m\u{1F916} AI-Assisted Migration:\x1B[0m ${provider}
|
|
2916
|
+
`);
|
|
2917
|
+
const tool = await verifyAITool(provider);
|
|
2918
|
+
if (!tool.installed) {
|
|
2919
|
+
console.error(`\x1B[31m\u274C ${tool.name} CLI not found\x1B[0m`);
|
|
2920
|
+
console.error(` Install: ${tool.installCmd}`);
|
|
2921
|
+
console.error(" Or run without --with flag for manual instructions");
|
|
2922
|
+
process.exit(1);
|
|
2923
|
+
}
|
|
2924
|
+
if (!tool.compatible) {
|
|
2925
|
+
console.error(`\x1B[31m\u274C ${tool.name} version ${tool.version} too old\x1B[0m`);
|
|
2926
|
+
console.error(` Required: >=${tool.minVersion}`);
|
|
2927
|
+
console.error(` Update: ${tool.updateCmd}`);
|
|
2928
|
+
process.exit(1);
|
|
2929
|
+
}
|
|
2930
|
+
console.log(`\x1B[32m\u2713\x1B[0m ${tool.name} CLI verified (v${tool.version})
|
|
2931
|
+
`);
|
|
2932
|
+
console.log("\x1B[33m\u26A0 AI-assisted migration is not yet fully implemented\x1B[0m");
|
|
2933
|
+
console.log(" This feature will automatically execute migration via AI CLI tools.");
|
|
2934
|
+
console.log();
|
|
2935
|
+
console.log(" For now, use manual mode (without --with flag) to get migration instructions.");
|
|
2936
|
+
console.log();
|
|
2937
|
+
}
|
|
2938
|
+
async function verifyAITool(provider) {
|
|
2939
|
+
const tools = {
|
|
2940
|
+
copilot: {
|
|
2941
|
+
name: "GitHub Copilot CLI",
|
|
2942
|
+
cliCommand: "github-copilot-cli",
|
|
2943
|
+
installCmd: "npm install -g @githubnext/github-copilot-cli",
|
|
2944
|
+
updateCmd: "npm update -g @githubnext/github-copilot-cli",
|
|
2945
|
+
versionCmd: "github-copilot-cli --version",
|
|
2946
|
+
minVersion: "0.1.0"
|
|
2947
|
+
},
|
|
2948
|
+
claude: {
|
|
2949
|
+
name: "Claude CLI",
|
|
2950
|
+
cliCommand: "claude",
|
|
2951
|
+
installCmd: "pip install claude-cli",
|
|
2952
|
+
updateCmd: "pip install --upgrade claude-cli",
|
|
2953
|
+
versionCmd: "claude --version",
|
|
2954
|
+
minVersion: "1.0.0"
|
|
2955
|
+
},
|
|
2956
|
+
gemini: {
|
|
2957
|
+
name: "Gemini CLI",
|
|
2958
|
+
cliCommand: "gemini-cli",
|
|
2959
|
+
installCmd: "npm install -g @google/gemini-cli",
|
|
2960
|
+
updateCmd: "npm update -g @google/gemini-cli",
|
|
2961
|
+
versionCmd: "gemini-cli --version",
|
|
2962
|
+
minVersion: "1.0.0"
|
|
2963
|
+
}
|
|
2964
|
+
};
|
|
2965
|
+
const toolDef = tools[provider];
|
|
2966
|
+
let installed = false;
|
|
2967
|
+
let version;
|
|
2968
|
+
try {
|
|
2969
|
+
const { execSync: execSync3 } = await import("child_process");
|
|
2970
|
+
execSync3(`which ${toolDef.cliCommand}`, { stdio: "ignore" });
|
|
2971
|
+
installed = true;
|
|
2972
|
+
try {
|
|
2973
|
+
const versionOutput = execSync3(toolDef.versionCmd, {
|
|
2974
|
+
encoding: "utf-8",
|
|
2975
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2976
|
+
});
|
|
2977
|
+
const versionMatch = versionOutput.match(/(\d+\.\d+\.\d+)/);
|
|
2978
|
+
if (versionMatch) {
|
|
2979
|
+
version = versionMatch[1];
|
|
2980
|
+
}
|
|
2981
|
+
} catch {
|
|
2982
|
+
version = "unknown";
|
|
2983
|
+
}
|
|
2984
|
+
} catch {
|
|
2985
|
+
installed = false;
|
|
2986
|
+
}
|
|
2987
|
+
const compatible = installed && (version === "unknown" || version !== void 0 && satisfiesVersion(version, toolDef.minVersion));
|
|
2988
|
+
return {
|
|
2989
|
+
...toolDef,
|
|
2990
|
+
installed,
|
|
2991
|
+
version,
|
|
2992
|
+
compatible
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
function satisfiesVersion(version, minVersion) {
|
|
2996
|
+
const vParts = version.split(".").map(Number);
|
|
2997
|
+
const minParts = minVersion.split(".").map(Number);
|
|
2998
|
+
for (let i = 0; i < 3; i++) {
|
|
2999
|
+
const v = vParts[i] || 0;
|
|
3000
|
+
const min = minParts[i] || 0;
|
|
3001
|
+
if (v > min) return true;
|
|
3002
|
+
if (v < min) return false;
|
|
3003
|
+
}
|
|
3004
|
+
return true;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
2789
3007
|
// src/commands/board.ts
|
|
2790
3008
|
import chalk15 from "chalk";
|
|
2791
3009
|
|
|
@@ -3374,9 +3592,9 @@ async function statsCommand(options) {
|
|
|
3374
3592
|
console.log(` Throughput ${chalk16.cyan((velocityMetrics.throughput.perWeek / 7 * 7).toFixed(1))}/week ${throughputTrend}`);
|
|
3375
3593
|
console.log(` WIP ${chalk16.yellow(velocityMetrics.wip.current)} specs`);
|
|
3376
3594
|
console.log("");
|
|
3377
|
-
console.log(chalk16.dim("\u{1F4A1} Use `
|
|
3378
|
-
console.log(chalk16.dim(" Use `
|
|
3379
|
-
console.log(chalk16.dim(" Use `
|
|
3595
|
+
console.log(chalk16.dim("\u{1F4A1} Use `lean-spec stats --full` for detailed analytics"));
|
|
3596
|
+
console.log(chalk16.dim(" Use `lean-spec stats --velocity` for velocity breakdown"));
|
|
3597
|
+
console.log(chalk16.dim(" Use `lean-spec stats --timeline` for activity timeline"));
|
|
3380
3598
|
console.log("");
|
|
3381
3599
|
return;
|
|
3382
3600
|
}
|
|
@@ -3723,12 +3941,12 @@ function escapeRegex(str) {
|
|
|
3723
3941
|
|
|
3724
3942
|
// src/commands/deps.ts
|
|
3725
3943
|
import chalk18 from "chalk";
|
|
3726
|
-
import * as
|
|
3944
|
+
import * as path18 from "path";
|
|
3727
3945
|
async function depsCommand(specPath, options) {
|
|
3728
3946
|
await autoCheckIfEnabled();
|
|
3729
3947
|
const config = await loadConfig();
|
|
3730
3948
|
const cwd = process.cwd();
|
|
3731
|
-
const specsDir =
|
|
3949
|
+
const specsDir = path18.join(cwd, config.specsDir);
|
|
3732
3950
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
3733
3951
|
if (!resolvedPath) {
|
|
3734
3952
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
@@ -3804,8 +4022,8 @@ function findDependencies(spec, specMap) {
|
|
|
3804
4022
|
if (dep) {
|
|
3805
4023
|
deps.push(dep);
|
|
3806
4024
|
} else {
|
|
3807
|
-
for (const [
|
|
3808
|
-
if (
|
|
4025
|
+
for (const [path20, s] of specMap.entries()) {
|
|
4026
|
+
if (path20.includes(depPath)) {
|
|
3809
4027
|
deps.push(s);
|
|
3810
4028
|
break;
|
|
3811
4029
|
}
|
|
@@ -3837,8 +4055,8 @@ function findRelated(spec, specMap) {
|
|
|
3837
4055
|
if (rel) {
|
|
3838
4056
|
related.push(rel);
|
|
3839
4057
|
} else {
|
|
3840
|
-
for (const [
|
|
3841
|
-
if (
|
|
4058
|
+
for (const [path20, s] of specMap.entries()) {
|
|
4059
|
+
if (path20.includes(relPath)) {
|
|
3842
4060
|
related.push(s);
|
|
3843
4061
|
break;
|
|
3844
4062
|
}
|
|
@@ -4243,8 +4461,8 @@ function renderTimelineBar(spec, startDate, endDate, weeks, today) {
|
|
|
4243
4461
|
}
|
|
4244
4462
|
|
|
4245
4463
|
// src/commands/viewer.ts
|
|
4246
|
-
import * as
|
|
4247
|
-
import * as
|
|
4464
|
+
import * as fs14 from "fs/promises";
|
|
4465
|
+
import * as path19 from "path";
|
|
4248
4466
|
import chalk21 from "chalk";
|
|
4249
4467
|
import { marked } from "marked";
|
|
4250
4468
|
import { markedTerminal } from "marked-terminal";
|
|
@@ -4252,7 +4470,7 @@ import { spawn } from "child_process";
|
|
|
4252
4470
|
marked.use(markedTerminal());
|
|
4253
4471
|
async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
4254
4472
|
const config = await loadConfig(cwd);
|
|
4255
|
-
const specsDir =
|
|
4473
|
+
const specsDir = path19.join(cwd, config.specsDir);
|
|
4256
4474
|
let resolvedPath = null;
|
|
4257
4475
|
let targetFile = null;
|
|
4258
4476
|
const pathParts = specPath.split("/").filter((p) => p);
|
|
@@ -4261,9 +4479,9 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
4261
4479
|
const filePart = pathParts[pathParts.length - 1];
|
|
4262
4480
|
resolvedPath = await resolveSpecPath(specPart, cwd, specsDir);
|
|
4263
4481
|
if (resolvedPath) {
|
|
4264
|
-
targetFile =
|
|
4482
|
+
targetFile = path19.join(resolvedPath, filePart);
|
|
4265
4483
|
try {
|
|
4266
|
-
await
|
|
4484
|
+
await fs14.access(targetFile);
|
|
4267
4485
|
} catch {
|
|
4268
4486
|
return null;
|
|
4269
4487
|
}
|
|
@@ -4282,8 +4500,8 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
4282
4500
|
if (!targetFile) {
|
|
4283
4501
|
return null;
|
|
4284
4502
|
}
|
|
4285
|
-
const rawContent = await
|
|
4286
|
-
const fileName =
|
|
4503
|
+
const rawContent = await fs14.readFile(targetFile, "utf-8");
|
|
4504
|
+
const fileName = path19.basename(targetFile);
|
|
4287
4505
|
const isSubSpec = fileName !== config.structure.defaultFile;
|
|
4288
4506
|
let frontmatter = null;
|
|
4289
4507
|
if (!isSubSpec) {
|
|
@@ -4312,7 +4530,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
4312
4530
|
}
|
|
4313
4531
|
}
|
|
4314
4532
|
const content = lines.slice(contentStartIndex).join("\n").trim();
|
|
4315
|
-
const specName =
|
|
4533
|
+
const specName = path19.basename(resolvedPath);
|
|
4316
4534
|
const displayName = isSubSpec ? `${specName}/${fileName}` : specName;
|
|
4317
4535
|
return {
|
|
4318
4536
|
frontmatter,
|
|
@@ -4379,7 +4597,7 @@ function displayFormattedSpec(spec) {
|
|
|
4379
4597
|
async function viewCommand(specPath, options = {}) {
|
|
4380
4598
|
const spec = await readSpecContent(specPath, process.cwd());
|
|
4381
4599
|
if (!spec) {
|
|
4382
|
-
throw new Error(`Spec not found: ${specPath}. Try:
|
|
4600
|
+
throw new Error(`Spec not found: ${specPath}. Try: lean-spec list`);
|
|
4383
4601
|
}
|
|
4384
4602
|
if (options.json) {
|
|
4385
4603
|
const jsonOutput = {
|
|
@@ -4402,7 +4620,7 @@ async function viewCommand(specPath, options = {}) {
|
|
|
4402
4620
|
async function openCommand(specPath, options = {}) {
|
|
4403
4621
|
const cwd = process.cwd();
|
|
4404
4622
|
const config = await loadConfig(cwd);
|
|
4405
|
-
const specsDir =
|
|
4623
|
+
const specsDir = path19.join(cwd, config.specsDir);
|
|
4406
4624
|
let resolvedPath = null;
|
|
4407
4625
|
let targetFile = null;
|
|
4408
4626
|
const pathParts = specPath.split("/").filter((p) => p);
|
|
@@ -4411,9 +4629,9 @@ async function openCommand(specPath, options = {}) {
|
|
|
4411
4629
|
const filePart = pathParts[pathParts.length - 1];
|
|
4412
4630
|
resolvedPath = await resolveSpecPath(specPart, cwd, specsDir);
|
|
4413
4631
|
if (resolvedPath) {
|
|
4414
|
-
targetFile =
|
|
4632
|
+
targetFile = path19.join(resolvedPath, filePart);
|
|
4415
4633
|
try {
|
|
4416
|
-
await
|
|
4634
|
+
await fs14.access(targetFile);
|
|
4417
4635
|
} catch {
|
|
4418
4636
|
targetFile = null;
|
|
4419
4637
|
}
|
|
@@ -5415,6 +5633,7 @@ export {
|
|
|
5415
5633
|
initProject,
|
|
5416
5634
|
filesCommand,
|
|
5417
5635
|
validateCommand,
|
|
5636
|
+
migrateCommand,
|
|
5418
5637
|
boardCommand,
|
|
5419
5638
|
statsCommand,
|
|
5420
5639
|
searchCommand,
|
|
@@ -5426,4 +5645,4 @@ export {
|
|
|
5426
5645
|
createMcpServer,
|
|
5427
5646
|
mcpCommand
|
|
5428
5647
|
};
|
|
5429
|
-
//# sourceMappingURL=chunk-
|
|
5648
|
+
//# sourceMappingURL=chunk-S6TGAL75.js.map
|