lean-spec 0.1.1 → 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 +119 -0
- package/README.md +25 -24
- package/dist/{chunk-GLXYUS7F.js → chunk-S6TGAL75.js} +392 -112
- package/dist/chunk-S6TGAL75.js.map +1 -0
- package/dist/cli.js +30 -13
- package/dist/cli.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/package.json +16 -16
- 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-GLXYUS7F.js.map +0 -1
- /package/bin/{lspec.js → lean-spec.js} +0 -0
|
@@ -39,7 +39,7 @@ var DEFAULT_CONFIG = {
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
async function loadConfig(cwd = process.cwd()) {
|
|
42
|
-
const configPath = path.join(cwd, ".
|
|
42
|
+
const configPath = path.join(cwd, ".lean-spec", "config.json");
|
|
43
43
|
try {
|
|
44
44
|
const content = await fs.readFile(configPath, "utf-8");
|
|
45
45
|
const userConfig = JSON.parse(content);
|
|
@@ -51,7 +51,7 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
async function saveConfig(config, cwd = process.cwd()) {
|
|
54
|
-
const configDir = path.join(cwd, ".
|
|
54
|
+
const configDir = path.join(cwd, ".lean-spec");
|
|
55
55
|
const configPath = path.join(configDir, "config.json");
|
|
56
56
|
await fs.mkdir(configDir, { recursive: true });
|
|
57
57
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
@@ -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) {
|
|
@@ -616,14 +616,14 @@ async function checkSpecs(options = {}) {
|
|
|
616
616
|
console.log("");
|
|
617
617
|
}
|
|
618
618
|
console.log(chalk3.cyan("Tip: Use date prefix to prevent conflicts:"));
|
|
619
|
-
console.log(chalk3.gray(' Edit .
|
|
619
|
+
console.log(chalk3.gray(' Edit .lean-spec/config.json \u2192 structure.prefix: "{YYYYMMDD}-"'));
|
|
620
620
|
console.log("");
|
|
621
621
|
console.log(chalk3.cyan("Or rename folders manually to resolve."));
|
|
622
622
|
console.log("");
|
|
623
623
|
} else {
|
|
624
624
|
console.log("");
|
|
625
625
|
console.log(chalk3.yellow(`\u26A0\uFE0F Conflict warning: ${conflicts.length} sequence conflict(s) detected`));
|
|
626
|
-
console.log(chalk3.gray("Run:
|
|
626
|
+
console.log(chalk3.gray("Run: lean-spec check"));
|
|
627
627
|
console.log("");
|
|
628
628
|
}
|
|
629
629
|
}
|
|
@@ -677,7 +677,7 @@ async function createSpec(name, options = {}) {
|
|
|
677
677
|
}
|
|
678
678
|
}
|
|
679
679
|
await fs5.mkdir(specDir, { recursive: true });
|
|
680
|
-
const templatesDir = path6.join(cwd, ".
|
|
680
|
+
const templatesDir = path6.join(cwd, ".lean-spec", "templates");
|
|
681
681
|
let templateName;
|
|
682
682
|
if (options.template) {
|
|
683
683
|
if (config.templates?.[options.template]) {
|
|
@@ -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
|
}
|
|
@@ -1351,7 +1355,7 @@ import * as path11 from "path";
|
|
|
1351
1355
|
import chalk9 from "chalk";
|
|
1352
1356
|
async function listTemplates(cwd = process.cwd()) {
|
|
1353
1357
|
const config = await loadConfig(cwd);
|
|
1354
|
-
const templatesDir = path11.join(cwd, ".
|
|
1358
|
+
const templatesDir = path11.join(cwd, ".lean-spec", "templates");
|
|
1355
1359
|
console.log("");
|
|
1356
1360
|
console.log(chalk9.green("=== Project Templates ==="));
|
|
1357
1361
|
console.log("");
|
|
@@ -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()) {
|
|
@@ -1397,7 +1401,7 @@ async function showTemplate(templateName, cwd = process.cwd()) {
|
|
|
1397
1401
|
console.error(chalk9.gray(`Available: ${Object.keys(config.templates || {}).join(", ")}`));
|
|
1398
1402
|
process.exit(1);
|
|
1399
1403
|
}
|
|
1400
|
-
const templatesDir = path11.join(cwd, ".
|
|
1404
|
+
const templatesDir = path11.join(cwd, ".lean-spec", "templates");
|
|
1401
1405
|
const templateFile = config.templates[templateName];
|
|
1402
1406
|
const templatePath = path11.join(templatesDir, templateFile);
|
|
1403
1407
|
try {
|
|
@@ -1415,7 +1419,7 @@ async function showTemplate(templateName, cwd = process.cwd()) {
|
|
|
1415
1419
|
}
|
|
1416
1420
|
async function addTemplate(name, file, cwd = process.cwd()) {
|
|
1417
1421
|
const config = await loadConfig(cwd);
|
|
1418
|
-
const templatesDir = path11.join(cwd, ".
|
|
1422
|
+
const templatesDir = path11.join(cwd, ".lean-spec", "templates");
|
|
1419
1423
|
const templatePath = path11.join(templatesDir, file);
|
|
1420
1424
|
try {
|
|
1421
1425
|
await fs8.access(templatePath);
|
|
@@ -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);
|
|
@@ -1453,11 +1457,11 @@ async function removeTemplate(name, cwd = process.cwd()) {
|
|
|
1453
1457
|
delete config.templates[name];
|
|
1454
1458
|
await saveConfig(config, cwd);
|
|
1455
1459
|
console.log(chalk9.green(`\u2713 Removed template: ${name}`));
|
|
1456
|
-
console.log(chalk9.gray(` Note: Template file ${file} still exists in .
|
|
1460
|
+
console.log(chalk9.gray(` Note: Template file ${file} still exists in .lean-spec/templates/`));
|
|
1457
1461
|
}
|
|
1458
1462
|
async function copyTemplate(source, target, cwd = process.cwd()) {
|
|
1459
1463
|
const config = await loadConfig(cwd);
|
|
1460
|
-
const templatesDir = path11.join(cwd, ".
|
|
1464
|
+
const templatesDir = path11.join(cwd, ".lean-spec", "templates");
|
|
1461
1465
|
let sourceFile;
|
|
1462
1466
|
if (config.templates?.[source]) {
|
|
1463
1467
|
sourceFile = config.templates[source];
|
|
@@ -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
|
|
@@ -1523,7 +1527,56 @@ async function handleExistingFiles(action, existingFiles, templateDir, cwd, vari
|
|
|
1523
1527
|
} catch {
|
|
1524
1528
|
continue;
|
|
1525
1529
|
}
|
|
1526
|
-
if (action === "merge" && file === "AGENTS.md") {
|
|
1530
|
+
if (action === "merge-ai" && file === "AGENTS.md") {
|
|
1531
|
+
const existing = await fs9.readFile(filePath, "utf-8");
|
|
1532
|
+
let template = await fs9.readFile(templateFilePath, "utf-8");
|
|
1533
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
1534
|
+
template = template.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
1535
|
+
}
|
|
1536
|
+
const promptPath = path12.join(cwd, ".lean-spec", "MERGE-AGENTS-PROMPT.md");
|
|
1537
|
+
const aiPrompt = `# AI Prompt: Consolidate AGENTS.md
|
|
1538
|
+
|
|
1539
|
+
## Task
|
|
1540
|
+
Consolidate the existing AGENTS.md with LeanSpec instructions into a single, coherent document.
|
|
1541
|
+
|
|
1542
|
+
## Instructions
|
|
1543
|
+
1. Read both documents below
|
|
1544
|
+
2. Merge them intelligently:
|
|
1545
|
+
- Preserve ALL existing project-specific information (workflows, SOPs, architecture, conventions)
|
|
1546
|
+
- Integrate LeanSpec sections where they fit naturally
|
|
1547
|
+
- Remove redundancy and ensure coherent flow
|
|
1548
|
+
- Keep the tone and style consistent
|
|
1549
|
+
3. Replace the existing AGENTS.md with the consolidated version
|
|
1550
|
+
|
|
1551
|
+
## Existing AGENTS.md
|
|
1552
|
+
\`\`\`markdown
|
|
1553
|
+
${existing}
|
|
1554
|
+
\`\`\`
|
|
1555
|
+
|
|
1556
|
+
## LeanSpec Instructions to Integrate
|
|
1557
|
+
\`\`\`markdown
|
|
1558
|
+
${template}
|
|
1559
|
+
\`\`\`
|
|
1560
|
+
|
|
1561
|
+
## Output
|
|
1562
|
+
Create a single consolidated AGENTS.md that:
|
|
1563
|
+
- Keeps all existing project context and workflows
|
|
1564
|
+
- Adds LeanSpec commands and principles where appropriate
|
|
1565
|
+
- Maintains clear structure and readability
|
|
1566
|
+
- Removes any duplicate or conflicting guidance
|
|
1567
|
+
`;
|
|
1568
|
+
await fs9.mkdir(path12.dirname(promptPath), { recursive: true });
|
|
1569
|
+
await fs9.writeFile(promptPath, aiPrompt, "utf-8");
|
|
1570
|
+
console.log(chalk10.green(`\u2713 Created AI consolidation prompt`));
|
|
1571
|
+
console.log(chalk10.cyan(` \u2192 ${promptPath}`));
|
|
1572
|
+
console.log("");
|
|
1573
|
+
console.log(chalk10.yellow("\u{1F4DD} Next steps:"));
|
|
1574
|
+
console.log(chalk10.gray(" 1. Open .lean-spec/MERGE-AGENTS-PROMPT.md"));
|
|
1575
|
+
console.log(chalk10.gray(" 2. Send it to your AI coding assistant (GitHub Copilot, Cursor, etc.)"));
|
|
1576
|
+
console.log(chalk10.gray(" 3. Let AI create the consolidated AGENTS.md"));
|
|
1577
|
+
console.log(chalk10.gray(" 4. Review and commit the result"));
|
|
1578
|
+
console.log("");
|
|
1579
|
+
} else if (action === "merge-append" && file === "AGENTS.md") {
|
|
1527
1580
|
const existing = await fs9.readFile(filePath, "utf-8");
|
|
1528
1581
|
let template = await fs9.readFile(templateFilePath, "utf-8");
|
|
1529
1582
|
for (const [key, value] of Object.entries(variables)) {
|
|
@@ -1537,8 +1590,9 @@ async function handleExistingFiles(action, existingFiles, templateDir, cwd, vari
|
|
|
1537
1590
|
|
|
1538
1591
|
${template.split("\n").slice(1).join("\n")}`;
|
|
1539
1592
|
await fs9.writeFile(filePath, merged, "utf-8");
|
|
1540
|
-
console.log(chalk10.green(`\u2713
|
|
1541
|
-
|
|
1593
|
+
console.log(chalk10.green(`\u2713 Appended LeanSpec section to ${file}`));
|
|
1594
|
+
console.log(chalk10.yellow(" \u26A0 Note: May be verbose. Consider consolidating later."));
|
|
1595
|
+
} else if (action === "overwrite") {
|
|
1542
1596
|
const backupPath = `${filePath}.backup`;
|
|
1543
1597
|
await fs9.rename(filePath, backupPath);
|
|
1544
1598
|
console.log(chalk10.yellow(`\u2713 Backed up ${file} \u2192 ${file}.backup`));
|
|
@@ -1548,6 +1602,7 @@ ${template.split("\n").slice(1).join("\n")}`;
|
|
|
1548
1602
|
}
|
|
1549
1603
|
await fs9.writeFile(filePath, content, "utf-8");
|
|
1550
1604
|
console.log(chalk10.green(`\u2713 Created new ${file}`));
|
|
1605
|
+
console.log(chalk10.gray(` \u{1F4A1} Your original content is preserved in ${file}.backup`));
|
|
1551
1606
|
}
|
|
1552
1607
|
}
|
|
1553
1608
|
}
|
|
@@ -1594,9 +1649,9 @@ var TEMPLATES_DIR = path13.join(__dirname2, "..", "templates");
|
|
|
1594
1649
|
async function initProject() {
|
|
1595
1650
|
const cwd = process.cwd();
|
|
1596
1651
|
try {
|
|
1597
|
-
await fs10.access(path13.join(cwd, ".
|
|
1598
|
-
console.log(chalk11.yellow("LeanSpec already initialized in this directory."));
|
|
1599
|
-
console.log(chalk11.gray("To reinitialize, delete .
|
|
1652
|
+
await fs10.access(path13.join(cwd, ".lean-spec", "config.json"));
|
|
1653
|
+
console.log(chalk11.yellow("\u26A0 LeanSpec already initialized in this directory."));
|
|
1654
|
+
console.log(chalk11.gray("To reinitialize, delete .lean-spec/ directory first."));
|
|
1600
1655
|
return;
|
|
1601
1656
|
} catch {
|
|
1602
1657
|
}
|
|
@@ -1615,12 +1670,13 @@ async function initProject() {
|
|
|
1615
1670
|
name: "Choose template",
|
|
1616
1671
|
value: "template",
|
|
1617
1672
|
description: "Pick from: minimal, standard, enterprise"
|
|
1618
|
-
},
|
|
1619
|
-
{
|
|
1620
|
-
name: "Customize everything",
|
|
1621
|
-
value: "custom",
|
|
1622
|
-
description: "Full control over structure and settings"
|
|
1623
1673
|
}
|
|
1674
|
+
// TODO: Re-enable when custom setup mode is implemented
|
|
1675
|
+
// {
|
|
1676
|
+
// name: 'Customize everything',
|
|
1677
|
+
// value: 'custom',
|
|
1678
|
+
// description: 'Full control over structure and settings',
|
|
1679
|
+
// },
|
|
1624
1680
|
]
|
|
1625
1681
|
});
|
|
1626
1682
|
let templateName = "standard";
|
|
@@ -1637,8 +1693,6 @@ async function initProject() {
|
|
|
1637
1693
|
}
|
|
1638
1694
|
]
|
|
1639
1695
|
});
|
|
1640
|
-
} else if (setupMode === "custom") {
|
|
1641
|
-
console.log(chalk11.yellow("Full customization coming soon. Using standard for now."));
|
|
1642
1696
|
}
|
|
1643
1697
|
const templateDir = path13.join(TEMPLATES_DIR, templateName);
|
|
1644
1698
|
const templateConfigPath = path13.join(templateDir, "config.json");
|
|
@@ -1650,31 +1704,34 @@ async function initProject() {
|
|
|
1650
1704
|
console.error(chalk11.red(`Error: Template not found: ${templateName}`));
|
|
1651
1705
|
process.exit(1);
|
|
1652
1706
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1707
|
+
let patternChoice = "simple";
|
|
1708
|
+
if (setupMode !== "quick") {
|
|
1709
|
+
patternChoice = await select({
|
|
1710
|
+
message: "Select folder pattern:",
|
|
1711
|
+
choices: [
|
|
1712
|
+
{
|
|
1713
|
+
name: "Simple: 001-my-spec/",
|
|
1714
|
+
value: "simple",
|
|
1715
|
+
description: "Global sequential numbering (recommended)"
|
|
1716
|
+
},
|
|
1717
|
+
{
|
|
1718
|
+
name: "Date-grouped: 20251105/001-my-spec/",
|
|
1719
|
+
value: "date-grouped",
|
|
1720
|
+
description: "Group specs by creation date (good for teams)"
|
|
1721
|
+
},
|
|
1722
|
+
{
|
|
1723
|
+
name: "Flat with date: 20251105-001-my-spec/",
|
|
1724
|
+
value: "date-prefix",
|
|
1725
|
+
description: "Date prefix with global numbering"
|
|
1726
|
+
},
|
|
1727
|
+
{
|
|
1728
|
+
name: "Custom pattern",
|
|
1729
|
+
value: "custom",
|
|
1730
|
+
description: "Enter your own pattern"
|
|
1731
|
+
}
|
|
1732
|
+
]
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1678
1735
|
if (patternChoice === "simple") {
|
|
1679
1736
|
templateConfig.structure.pattern = "flat";
|
|
1680
1737
|
templateConfig.structure.prefix = "";
|
|
@@ -1688,13 +1745,13 @@ async function initProject() {
|
|
|
1688
1745
|
} else if (patternChoice === "custom") {
|
|
1689
1746
|
console.log("");
|
|
1690
1747
|
console.log(chalk11.yellow("\u26A0 Custom pattern input is not yet implemented."));
|
|
1691
|
-
console.log(chalk11.gray(" You can manually edit .
|
|
1748
|
+
console.log(chalk11.gray(" You can manually edit .lean-spec/config.json after initialization."));
|
|
1692
1749
|
console.log(chalk11.gray(" Using simple pattern for now."));
|
|
1693
1750
|
console.log("");
|
|
1694
1751
|
templateConfig.structure.pattern = "flat";
|
|
1695
1752
|
templateConfig.structure.prefix = "";
|
|
1696
1753
|
}
|
|
1697
|
-
const templatesDir = path13.join(cwd, ".
|
|
1754
|
+
const templatesDir = path13.join(cwd, ".lean-spec", "templates");
|
|
1698
1755
|
try {
|
|
1699
1756
|
await fs10.mkdir(templatesDir, { recursive: true });
|
|
1700
1757
|
} catch (error) {
|
|
@@ -1705,7 +1762,7 @@ async function initProject() {
|
|
|
1705
1762
|
const targetSpecPath = path13.join(templatesDir, "spec-template.md");
|
|
1706
1763
|
try {
|
|
1707
1764
|
await fs10.copyFile(templateSpecPath, targetSpecPath);
|
|
1708
|
-
console.log(chalk11.green("\u2713 Created .
|
|
1765
|
+
console.log(chalk11.green("\u2713 Created .lean-spec/templates/spec-template.md"));
|
|
1709
1766
|
} catch (error) {
|
|
1710
1767
|
console.error(chalk11.red("Error copying template:"), error);
|
|
1711
1768
|
process.exit(1);
|
|
@@ -1715,29 +1772,34 @@ async function initProject() {
|
|
|
1715
1772
|
default: "spec-template.md"
|
|
1716
1773
|
};
|
|
1717
1774
|
await saveConfig(templateConfig, cwd);
|
|
1718
|
-
console.log(chalk11.green("\u2713 Created .
|
|
1775
|
+
console.log(chalk11.green("\u2713 Created .lean-spec/config.json"));
|
|
1719
1776
|
const existingFiles = await detectExistingSystemPrompts(cwd);
|
|
1720
1777
|
let skipFiles = [];
|
|
1721
1778
|
if (existingFiles.length > 0) {
|
|
1722
1779
|
console.log("");
|
|
1723
1780
|
console.log(chalk11.yellow(`Found existing: ${existingFiles.join(", ")}`));
|
|
1724
1781
|
const action = await select({
|
|
1725
|
-
message: "How would you like to
|
|
1782
|
+
message: "How would you like to handle existing AGENTS.md?",
|
|
1726
1783
|
choices: [
|
|
1727
1784
|
{
|
|
1728
|
-
name: "
|
|
1729
|
-
value: "merge",
|
|
1730
|
-
description: "
|
|
1785
|
+
name: "AI-Assisted Merge (recommended)",
|
|
1786
|
+
value: "merge-ai",
|
|
1787
|
+
description: "Creates prompt for AI to intelligently consolidate both files"
|
|
1788
|
+
},
|
|
1789
|
+
{
|
|
1790
|
+
name: "Simple Append",
|
|
1791
|
+
value: "merge-append",
|
|
1792
|
+
description: "Quickly appends LeanSpec section (may be verbose)"
|
|
1731
1793
|
},
|
|
1732
1794
|
{
|
|
1733
|
-
name: "
|
|
1734
|
-
value: "
|
|
1735
|
-
description: "
|
|
1795
|
+
name: "Replace with LeanSpec",
|
|
1796
|
+
value: "overwrite",
|
|
1797
|
+
description: "Backs up existing, creates fresh AGENTS.md from template"
|
|
1736
1798
|
},
|
|
1737
1799
|
{
|
|
1738
|
-
name: "
|
|
1800
|
+
name: "Keep Existing Only",
|
|
1739
1801
|
value: "skip",
|
|
1740
|
-
description: "
|
|
1802
|
+
description: "Skips AGENTS.md, only adds .lean-spec config and specs/"
|
|
1741
1803
|
}
|
|
1742
1804
|
]
|
|
1743
1805
|
});
|
|
@@ -1762,7 +1824,7 @@ async function initProject() {
|
|
|
1762
1824
|
console.log("Next steps:");
|
|
1763
1825
|
console.log(chalk11.gray(" - Review and customize AGENTS.md"));
|
|
1764
1826
|
console.log(chalk11.gray(" - Check out example spec in specs/"));
|
|
1765
|
-
console.log(chalk11.gray(" - Create your first spec:
|
|
1827
|
+
console.log(chalk11.gray(" - Create your first spec: lean-spec create my-feature"));
|
|
1766
1828
|
console.log("");
|
|
1767
1829
|
}
|
|
1768
1830
|
|
|
@@ -2728,6 +2790,220 @@ async function validateCommand(options = {}) {
|
|
|
2728
2790
|
return !hasErrors;
|
|
2729
2791
|
}
|
|
2730
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
|
+
|
|
2731
3007
|
// src/commands/board.ts
|
|
2732
3008
|
import chalk15 from "chalk";
|
|
2733
3009
|
|
|
@@ -3035,7 +3311,7 @@ function renderColumn(title, emoji, specs, expanded, colorFn) {
|
|
|
3035
3311
|
}
|
|
3036
3312
|
console.log("");
|
|
3037
3313
|
} else if (!expanded && specs.length > 0) {
|
|
3038
|
-
console.log(` ${chalk15.dim("(collapsed, use --
|
|
3314
|
+
console.log(` ${chalk15.dim("(collapsed, use --complete to expand)")}`);
|
|
3039
3315
|
console.log("");
|
|
3040
3316
|
} else {
|
|
3041
3317
|
console.log(` ${chalk15.dim("(empty)")}`);
|
|
@@ -3316,9 +3592,9 @@ async function statsCommand(options) {
|
|
|
3316
3592
|
console.log(` Throughput ${chalk16.cyan((velocityMetrics.throughput.perWeek / 7 * 7).toFixed(1))}/week ${throughputTrend}`);
|
|
3317
3593
|
console.log(` WIP ${chalk16.yellow(velocityMetrics.wip.current)} specs`);
|
|
3318
3594
|
console.log("");
|
|
3319
|
-
console.log(chalk16.dim("\u{1F4A1} Use `
|
|
3320
|
-
console.log(chalk16.dim(" Use `
|
|
3321
|
-
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"));
|
|
3322
3598
|
console.log("");
|
|
3323
3599
|
return;
|
|
3324
3600
|
}
|
|
@@ -3665,12 +3941,12 @@ function escapeRegex(str) {
|
|
|
3665
3941
|
|
|
3666
3942
|
// src/commands/deps.ts
|
|
3667
3943
|
import chalk18 from "chalk";
|
|
3668
|
-
import * as
|
|
3944
|
+
import * as path18 from "path";
|
|
3669
3945
|
async function depsCommand(specPath, options) {
|
|
3670
3946
|
await autoCheckIfEnabled();
|
|
3671
3947
|
const config = await loadConfig();
|
|
3672
3948
|
const cwd = process.cwd();
|
|
3673
|
-
const specsDir =
|
|
3949
|
+
const specsDir = path18.join(cwd, config.specsDir);
|
|
3674
3950
|
const resolvedPath = await resolveSpecPath(specPath, cwd, specsDir);
|
|
3675
3951
|
if (!resolvedPath) {
|
|
3676
3952
|
throw new Error(`Spec not found: ${sanitizeUserInput(specPath)}`);
|
|
@@ -3701,26 +3977,28 @@ async function depsCommand(specPath, options) {
|
|
|
3701
3977
|
console.log("");
|
|
3702
3978
|
console.log(chalk18.green(`\u{1F4E6} Dependencies for ${chalk18.cyan(sanitizeUserInput(spec.path))}`));
|
|
3703
3979
|
console.log("");
|
|
3704
|
-
|
|
3980
|
+
const hasAnyRelationships = dependsOn.length > 0 || blocks.length > 0 || relatedSpecs.length > 0;
|
|
3981
|
+
if (!hasAnyRelationships) {
|
|
3982
|
+
console.log(chalk18.gray(" No dependencies or relationships"));
|
|
3983
|
+
console.log("");
|
|
3984
|
+
return;
|
|
3985
|
+
}
|
|
3705
3986
|
if (dependsOn.length > 0) {
|
|
3987
|
+
console.log(chalk18.bold("Depends On:"));
|
|
3706
3988
|
for (const dep of dependsOn) {
|
|
3707
3989
|
const status = getStatusIndicator(dep.frontmatter.status);
|
|
3708
3990
|
console.log(` \u2192 ${sanitizeUserInput(dep.path)} ${status}`);
|
|
3709
3991
|
}
|
|
3710
|
-
|
|
3711
|
-
console.log(chalk18.gray(" (none)"));
|
|
3992
|
+
console.log("");
|
|
3712
3993
|
}
|
|
3713
|
-
console.log("");
|
|
3714
|
-
console.log(chalk18.bold("Blocks:"));
|
|
3715
3994
|
if (blocks.length > 0) {
|
|
3995
|
+
console.log(chalk18.bold("Required By:"));
|
|
3716
3996
|
for (const blocked of blocks) {
|
|
3717
3997
|
const status = getStatusIndicator(blocked.frontmatter.status);
|
|
3718
3998
|
console.log(` \u2190 ${sanitizeUserInput(blocked.path)} ${status}`);
|
|
3719
3999
|
}
|
|
3720
|
-
|
|
3721
|
-
console.log(chalk18.gray(" (none)"));
|
|
4000
|
+
console.log("");
|
|
3722
4001
|
}
|
|
3723
|
-
console.log("");
|
|
3724
4002
|
if (relatedSpecs.length > 0) {
|
|
3725
4003
|
console.log(chalk18.bold("Related Specs:"));
|
|
3726
4004
|
for (const rel of relatedSpecs) {
|
|
@@ -4183,8 +4461,8 @@ function renderTimelineBar(spec, startDate, endDate, weeks, today) {
|
|
|
4183
4461
|
}
|
|
4184
4462
|
|
|
4185
4463
|
// src/commands/viewer.ts
|
|
4186
|
-
import * as
|
|
4187
|
-
import * as
|
|
4464
|
+
import * as fs14 from "fs/promises";
|
|
4465
|
+
import * as path19 from "path";
|
|
4188
4466
|
import chalk21 from "chalk";
|
|
4189
4467
|
import { marked } from "marked";
|
|
4190
4468
|
import { markedTerminal } from "marked-terminal";
|
|
@@ -4192,7 +4470,7 @@ import { spawn } from "child_process";
|
|
|
4192
4470
|
marked.use(markedTerminal());
|
|
4193
4471
|
async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
4194
4472
|
const config = await loadConfig(cwd);
|
|
4195
|
-
const specsDir =
|
|
4473
|
+
const specsDir = path19.join(cwd, config.specsDir);
|
|
4196
4474
|
let resolvedPath = null;
|
|
4197
4475
|
let targetFile = null;
|
|
4198
4476
|
const pathParts = specPath.split("/").filter((p) => p);
|
|
@@ -4201,9 +4479,9 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
4201
4479
|
const filePart = pathParts[pathParts.length - 1];
|
|
4202
4480
|
resolvedPath = await resolveSpecPath(specPart, cwd, specsDir);
|
|
4203
4481
|
if (resolvedPath) {
|
|
4204
|
-
targetFile =
|
|
4482
|
+
targetFile = path19.join(resolvedPath, filePart);
|
|
4205
4483
|
try {
|
|
4206
|
-
await
|
|
4484
|
+
await fs14.access(targetFile);
|
|
4207
4485
|
} catch {
|
|
4208
4486
|
return null;
|
|
4209
4487
|
}
|
|
@@ -4222,8 +4500,8 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
4222
4500
|
if (!targetFile) {
|
|
4223
4501
|
return null;
|
|
4224
4502
|
}
|
|
4225
|
-
const rawContent = await
|
|
4226
|
-
const fileName =
|
|
4503
|
+
const rawContent = await fs14.readFile(targetFile, "utf-8");
|
|
4504
|
+
const fileName = path19.basename(targetFile);
|
|
4227
4505
|
const isSubSpec = fileName !== config.structure.defaultFile;
|
|
4228
4506
|
let frontmatter = null;
|
|
4229
4507
|
if (!isSubSpec) {
|
|
@@ -4252,7 +4530,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
4252
4530
|
}
|
|
4253
4531
|
}
|
|
4254
4532
|
const content = lines.slice(contentStartIndex).join("\n").trim();
|
|
4255
|
-
const specName =
|
|
4533
|
+
const specName = path19.basename(resolvedPath);
|
|
4256
4534
|
const displayName = isSubSpec ? `${specName}/${fileName}` : specName;
|
|
4257
4535
|
return {
|
|
4258
4536
|
frontmatter,
|
|
@@ -4319,7 +4597,7 @@ function displayFormattedSpec(spec) {
|
|
|
4319
4597
|
async function viewCommand(specPath, options = {}) {
|
|
4320
4598
|
const spec = await readSpecContent(specPath, process.cwd());
|
|
4321
4599
|
if (!spec) {
|
|
4322
|
-
throw new Error(`Spec not found: ${specPath}. Try:
|
|
4600
|
+
throw new Error(`Spec not found: ${specPath}. Try: lean-spec list`);
|
|
4323
4601
|
}
|
|
4324
4602
|
if (options.json) {
|
|
4325
4603
|
const jsonOutput = {
|
|
@@ -4342,7 +4620,7 @@ async function viewCommand(specPath, options = {}) {
|
|
|
4342
4620
|
async function openCommand(specPath, options = {}) {
|
|
4343
4621
|
const cwd = process.cwd();
|
|
4344
4622
|
const config = await loadConfig(cwd);
|
|
4345
|
-
const specsDir =
|
|
4623
|
+
const specsDir = path19.join(cwd, config.specsDir);
|
|
4346
4624
|
let resolvedPath = null;
|
|
4347
4625
|
let targetFile = null;
|
|
4348
4626
|
const pathParts = specPath.split("/").filter((p) => p);
|
|
@@ -4351,9 +4629,9 @@ async function openCommand(specPath, options = {}) {
|
|
|
4351
4629
|
const filePart = pathParts[pathParts.length - 1];
|
|
4352
4630
|
resolvedPath = await resolveSpecPath(specPart, cwd, specsDir);
|
|
4353
4631
|
if (resolvedPath) {
|
|
4354
|
-
targetFile =
|
|
4632
|
+
targetFile = path19.join(resolvedPath, filePart);
|
|
4355
4633
|
try {
|
|
4356
|
-
await
|
|
4634
|
+
await fs14.access(targetFile);
|
|
4357
4635
|
} catch {
|
|
4358
4636
|
targetFile = null;
|
|
4359
4637
|
}
|
|
@@ -4432,7 +4710,6 @@ async function mcpCommand() {
|
|
|
4432
4710
|
}
|
|
4433
4711
|
|
|
4434
4712
|
// src/mcp-server.ts
|
|
4435
|
-
import * as path19 from "path";
|
|
4436
4713
|
function formatErrorMessage(prefix, error) {
|
|
4437
4714
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4438
4715
|
return `${prefix}: ${errorMsg}`;
|
|
@@ -4496,20 +4773,22 @@ async function searchSpecsData(query, options) {
|
|
|
4496
4773
|
return results;
|
|
4497
4774
|
}
|
|
4498
4775
|
async function readSpecData(specPath) {
|
|
4499
|
-
const config = await loadConfig();
|
|
4500
4776
|
const cwd = process.cwd();
|
|
4501
|
-
const
|
|
4502
|
-
|
|
4503
|
-
if (!resolvedPath) {
|
|
4504
|
-
throw new Error(`Spec not found: ${specPath}`);
|
|
4505
|
-
}
|
|
4506
|
-
const specInfo = await getSpec(resolvedPath);
|
|
4507
|
-
if (!specInfo) {
|
|
4777
|
+
const specContent = await readSpecContent(specPath, cwd);
|
|
4778
|
+
if (!specContent) {
|
|
4508
4779
|
throw new Error(`Spec not found: ${specPath}`);
|
|
4509
4780
|
}
|
|
4510
4781
|
return {
|
|
4511
|
-
spec:
|
|
4512
|
-
|
|
4782
|
+
spec: {
|
|
4783
|
+
name: specContent.name,
|
|
4784
|
+
path: specContent.path,
|
|
4785
|
+
status: specContent.frontmatter.status,
|
|
4786
|
+
created: String(specContent.frontmatter.created),
|
|
4787
|
+
priority: specContent.frontmatter.priority,
|
|
4788
|
+
tags: specContent.frontmatter.tags,
|
|
4789
|
+
assignee: specContent.frontmatter.assignee
|
|
4790
|
+
},
|
|
4791
|
+
content: specContent.content
|
|
4513
4792
|
};
|
|
4514
4793
|
}
|
|
4515
4794
|
async function getStatsData() {
|
|
@@ -4665,7 +4944,7 @@ async function createMcpServer() {
|
|
|
4665
4944
|
title: "View Spec",
|
|
4666
4945
|
description: "Read the complete content of a specification. Use this to understand spec details, review design decisions, or check implementation status. Returns metadata and full content.",
|
|
4667
4946
|
inputSchema: {
|
|
4668
|
-
specPath: z.string().describe('The spec to view. Can be: spec name (e.g., "unified-dashboard"), sequence number (e.g., "045" or "45"),
|
|
4947
|
+
specPath: z.string().describe('The spec to view. Can be: spec name (e.g., "unified-dashboard"), sequence number (e.g., "045" or "45"), full folder name (e.g., "045-unified-dashboard"), or sub-spec file (e.g., "045/DESIGN.md" or "unified-dashboard/TESTING.md").'),
|
|
4669
4948
|
raw: z.boolean().optional().describe("Output raw markdown instead of formatted"),
|
|
4670
4949
|
json: z.boolean().optional().describe("Output as JSON instead of formatted")
|
|
4671
4950
|
},
|
|
@@ -5354,6 +5633,7 @@ export {
|
|
|
5354
5633
|
initProject,
|
|
5355
5634
|
filesCommand,
|
|
5356
5635
|
validateCommand,
|
|
5636
|
+
migrateCommand,
|
|
5357
5637
|
boardCommand,
|
|
5358
5638
|
statsCommand,
|
|
5359
5639
|
searchCommand,
|
|
@@ -5365,4 +5645,4 @@ export {
|
|
|
5365
5645
|
createMcpServer,
|
|
5366
5646
|
mcpCommand
|
|
5367
5647
|
};
|
|
5368
|
-
//# sourceMappingURL=chunk-
|
|
5648
|
+
//# sourceMappingURL=chunk-S6TGAL75.js.map
|