lean-spec 0.2.7-dev.20251127055844 → 0.2.7-dev.20251127073145
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-FTKNRIOE.js → chunk-2DAPKDV5.js} +297 -117
- package/dist/chunk-2DAPKDV5.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
- package/templates/detailed/AGENTS.md +0 -19
- package/templates/detailed/README.md +3 -3
- package/templates/standard/AGENTS.md +0 -19
- package/templates/standard/README.md +1 -2
- package/dist/chunk-FTKNRIOE.js.map +0 -1
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import * as path17 from 'path';
|
|
7
7
|
import { dirname, join, resolve } from 'path';
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
-
import * as
|
|
9
|
+
import * as fs12 from 'fs/promises';
|
|
10
10
|
import { readFile, writeFile } from 'fs/promises';
|
|
11
11
|
import chalk20 from 'chalk';
|
|
12
12
|
import { Command } from 'commander';
|
|
@@ -43,7 +43,7 @@ var DEFAULT_CONFIG = {
|
|
|
43
43
|
async function loadConfig(cwd = process.cwd()) {
|
|
44
44
|
const configPath = path17.join(cwd, ".lean-spec", "config.json");
|
|
45
45
|
try {
|
|
46
|
-
const content = await
|
|
46
|
+
const content = await fs12.readFile(configPath, "utf-8");
|
|
47
47
|
const userConfig = JSON.parse(content);
|
|
48
48
|
const merged = { ...DEFAULT_CONFIG, ...userConfig };
|
|
49
49
|
normalizeLegacyPattern(merged);
|
|
@@ -55,8 +55,8 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
55
55
|
async function saveConfig(config, cwd = process.cwd()) {
|
|
56
56
|
const configDir = path17.join(cwd, ".lean-spec");
|
|
57
57
|
const configPath = path17.join(configDir, "config.json");
|
|
58
|
-
await
|
|
59
|
-
await
|
|
58
|
+
await fs12.mkdir(configDir, { recursive: true });
|
|
59
|
+
await fs12.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
60
60
|
}
|
|
61
61
|
function getToday(format = "YYYYMMDD") {
|
|
62
62
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -136,12 +136,12 @@ function extractGroup(extractor, dateFormat = "YYYYMMDD", fields, fallback) {
|
|
|
136
136
|
async function loadSubFiles(specDir, options = {}) {
|
|
137
137
|
const subFiles = [];
|
|
138
138
|
try {
|
|
139
|
-
const entries = await
|
|
139
|
+
const entries = await fs12.readdir(specDir, { withFileTypes: true });
|
|
140
140
|
for (const entry of entries) {
|
|
141
141
|
if (entry.name === "README.md") continue;
|
|
142
142
|
if (entry.isDirectory()) continue;
|
|
143
143
|
const filePath = path17.join(specDir, entry.name);
|
|
144
|
-
const stat6 = await
|
|
144
|
+
const stat6 = await fs12.stat(filePath);
|
|
145
145
|
const ext = path17.extname(entry.name).toLowerCase();
|
|
146
146
|
const isDocument = ext === ".md";
|
|
147
147
|
const subFile = {
|
|
@@ -151,7 +151,7 @@ async function loadSubFiles(specDir, options = {}) {
|
|
|
151
151
|
type: isDocument ? "document" : "asset"
|
|
152
152
|
};
|
|
153
153
|
if (isDocument && options.includeContent) {
|
|
154
|
-
subFile.content = await
|
|
154
|
+
subFile.content = await fs12.readFile(filePath, "utf-8");
|
|
155
155
|
}
|
|
156
156
|
subFiles.push(subFile);
|
|
157
157
|
}
|
|
@@ -171,14 +171,14 @@ async function loadAllSpecs(options = {}) {
|
|
|
171
171
|
const specsDir = path17.join(cwd, config.specsDir);
|
|
172
172
|
const specs = [];
|
|
173
173
|
try {
|
|
174
|
-
await
|
|
174
|
+
await fs12.access(specsDir);
|
|
175
175
|
} catch {
|
|
176
176
|
return [];
|
|
177
177
|
}
|
|
178
178
|
const specPattern = /^(\d{2,})-/;
|
|
179
179
|
async function loadSpecsFromDir(dir, relativePath = "") {
|
|
180
180
|
try {
|
|
181
|
-
const entries = await
|
|
181
|
+
const entries = await fs12.readdir(dir, { withFileTypes: true });
|
|
182
182
|
for (const entry of entries) {
|
|
183
183
|
if (!entry.isDirectory()) continue;
|
|
184
184
|
if (entry.name === "archived" && relativePath === "") continue;
|
|
@@ -212,7 +212,7 @@ async function loadAllSpecs(options = {}) {
|
|
|
212
212
|
frontmatter
|
|
213
213
|
};
|
|
214
214
|
if (options.includeContent) {
|
|
215
|
-
specInfo.content = await
|
|
215
|
+
specInfo.content = await fs12.readFile(specFile, "utf-8");
|
|
216
216
|
}
|
|
217
217
|
if (options.includeSubFiles) {
|
|
218
218
|
specInfo.subFiles = await loadSubFiles(entryPath, {
|
|
@@ -287,7 +287,7 @@ async function getSpec(specPath) {
|
|
|
287
287
|
fullPath = path17.join(specsDir, specPath);
|
|
288
288
|
}
|
|
289
289
|
try {
|
|
290
|
-
await
|
|
290
|
+
await fs12.access(fullPath);
|
|
291
291
|
} catch {
|
|
292
292
|
return null;
|
|
293
293
|
}
|
|
@@ -295,7 +295,7 @@ async function getSpec(specPath) {
|
|
|
295
295
|
if (!specFile) return null;
|
|
296
296
|
const frontmatter = await parseFrontmatter(specFile, config);
|
|
297
297
|
if (!frontmatter) return null;
|
|
298
|
-
const content = await
|
|
298
|
+
const content = await fs12.readFile(specFile, "utf-8");
|
|
299
299
|
const relativePath = path17.relative(specsDir, fullPath);
|
|
300
300
|
const parts = relativePath.split(path17.sep);
|
|
301
301
|
const date = parts[0] === "archived" ? parts[1] : parts[0];
|
|
@@ -319,7 +319,7 @@ async function getGlobalNextSeq(specsDir, digits) {
|
|
|
319
319
|
const specPattern = createSpecDirPattern();
|
|
320
320
|
async function scanDirectory(dir) {
|
|
321
321
|
try {
|
|
322
|
-
const entries = await
|
|
322
|
+
const entries = await fs12.readdir(dir, { withFileTypes: true });
|
|
323
323
|
for (const entry of entries) {
|
|
324
324
|
if (!entry.isDirectory()) continue;
|
|
325
325
|
const match = entry.name.match(specPattern);
|
|
@@ -349,7 +349,7 @@ async function getGlobalNextSeq(specsDir, digits) {
|
|
|
349
349
|
async function resolveSpecPath(specPath, cwd, specsDir) {
|
|
350
350
|
if (path17.isAbsolute(specPath)) {
|
|
351
351
|
try {
|
|
352
|
-
await
|
|
352
|
+
await fs12.access(specPath);
|
|
353
353
|
return specPath;
|
|
354
354
|
} catch {
|
|
355
355
|
return null;
|
|
@@ -357,13 +357,13 @@ async function resolveSpecPath(specPath, cwd, specsDir) {
|
|
|
357
357
|
}
|
|
358
358
|
const cwdPath = path17.resolve(cwd, specPath);
|
|
359
359
|
try {
|
|
360
|
-
await
|
|
360
|
+
await fs12.access(cwdPath);
|
|
361
361
|
return cwdPath;
|
|
362
362
|
} catch {
|
|
363
363
|
}
|
|
364
364
|
const specsPath = path17.join(specsDir, specPath);
|
|
365
365
|
try {
|
|
366
|
-
await
|
|
366
|
+
await fs12.access(specsPath);
|
|
367
367
|
return specsPath;
|
|
368
368
|
} catch {
|
|
369
369
|
}
|
|
@@ -381,7 +381,7 @@ async function searchBySequence(specsDir, seqNum) {
|
|
|
381
381
|
const specPattern = createSpecDirPattern();
|
|
382
382
|
async function scanDirectory(dir) {
|
|
383
383
|
try {
|
|
384
|
-
const entries = await
|
|
384
|
+
const entries = await fs12.readdir(dir, { withFileTypes: true });
|
|
385
385
|
for (const entry of entries) {
|
|
386
386
|
if (!entry.isDirectory()) continue;
|
|
387
387
|
const match = entry.name.match(specPattern);
|
|
@@ -404,7 +404,7 @@ async function searchBySequence(specsDir, seqNum) {
|
|
|
404
404
|
async function searchInAllDirectories(specsDir, specName) {
|
|
405
405
|
async function scanDirectory(dir) {
|
|
406
406
|
try {
|
|
407
|
-
const entries = await
|
|
407
|
+
const entries = await fs12.readdir(dir, { withFileTypes: true });
|
|
408
408
|
for (const entry of entries) {
|
|
409
409
|
if (!entry.isDirectory()) continue;
|
|
410
410
|
if (entry.name === specName) {
|
|
@@ -754,11 +754,11 @@ async function loadSpecContent(specPath) {
|
|
|
754
754
|
const specDir = spec.fullPath;
|
|
755
755
|
let content = "";
|
|
756
756
|
try {
|
|
757
|
-
const files = await
|
|
757
|
+
const files = await fs12.readdir(specDir);
|
|
758
758
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
759
759
|
for (const file of mdFiles) {
|
|
760
760
|
const filePath = path17.join(specDir, file);
|
|
761
|
-
const fileContent = await
|
|
761
|
+
const fileContent = await fs12.readFile(filePath, "utf-8");
|
|
762
762
|
content += `
|
|
763
763
|
|
|
764
764
|
### ${file}
|
|
@@ -766,14 +766,14 @@ async function loadSpecContent(specPath) {
|
|
|
766
766
|
${fileContent}`;
|
|
767
767
|
}
|
|
768
768
|
} catch {
|
|
769
|
-
content = await
|
|
769
|
+
content = await fs12.readFile(spec.filePath, "utf-8");
|
|
770
770
|
}
|
|
771
771
|
return content;
|
|
772
772
|
}
|
|
773
773
|
async function createWorktree(specPath, specName, cwd) {
|
|
774
774
|
const worktreePath = path17.join(cwd, ".worktrees", `spec-${specName}`);
|
|
775
775
|
const branchName = `feature/${specName}`;
|
|
776
|
-
await
|
|
776
|
+
await fs12.mkdir(path17.join(cwd, ".worktrees"), { recursive: true });
|
|
777
777
|
return new Promise((resolve3, reject) => {
|
|
778
778
|
const child = spawn("git", ["worktree", "add", worktreePath, "-b", branchName], {
|
|
779
779
|
cwd,
|
|
@@ -1452,7 +1452,7 @@ var TokenCounter = class {
|
|
|
1452
1452
|
* Count tokens in a single file
|
|
1453
1453
|
*/
|
|
1454
1454
|
async countFile(filePath, options = {}) {
|
|
1455
|
-
const content = await
|
|
1455
|
+
const content = await fs12.readFile(filePath, "utf-8");
|
|
1456
1456
|
const tokens = await this.countString(content);
|
|
1457
1457
|
const lines = content.split("\n").length;
|
|
1458
1458
|
const result = {
|
|
@@ -1472,11 +1472,11 @@ var TokenCounter = class {
|
|
|
1472
1472
|
* Count tokens in a spec (including sub-specs if requested)
|
|
1473
1473
|
*/
|
|
1474
1474
|
async countSpec(specPath, options = {}) {
|
|
1475
|
-
const stats = await
|
|
1475
|
+
const stats = await fs12.stat(specPath);
|
|
1476
1476
|
if (stats.isFile()) {
|
|
1477
1477
|
return this.countFile(specPath, options);
|
|
1478
1478
|
}
|
|
1479
|
-
const files = await
|
|
1479
|
+
const files = await fs12.readdir(specPath);
|
|
1480
1480
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
1481
1481
|
const filesToCount = [];
|
|
1482
1482
|
if (mdFiles.includes("README.md")) {
|
|
@@ -1502,7 +1502,7 @@ var TokenCounter = class {
|
|
|
1502
1502
|
}
|
|
1503
1503
|
for (const file of filesToCount) {
|
|
1504
1504
|
const filePath = path17.join(specPath, file);
|
|
1505
|
-
const content = await
|
|
1505
|
+
const content = await fs12.readFile(filePath, "utf-8");
|
|
1506
1506
|
const tokens = await this.countString(content);
|
|
1507
1507
|
const lines = content.split("\n").length;
|
|
1508
1508
|
fileCounts.push({
|
|
@@ -1838,7 +1838,7 @@ var ComplexityValidator = class {
|
|
|
1838
1838
|
let subSpecCount = 0;
|
|
1839
1839
|
try {
|
|
1840
1840
|
const specDir = path17.dirname(spec.filePath);
|
|
1841
|
-
const files = await
|
|
1841
|
+
const files = await fs12.readdir(specDir);
|
|
1842
1842
|
const mdFiles = files.filter(
|
|
1843
1843
|
(f) => f.endsWith(".md") && f !== "README.md"
|
|
1844
1844
|
);
|
|
@@ -3266,7 +3266,7 @@ async function getGitInfo() {
|
|
|
3266
3266
|
async function getProjectName(cwd = process.cwd()) {
|
|
3267
3267
|
try {
|
|
3268
3268
|
const packageJsonPath = path17.join(cwd, "package.json");
|
|
3269
|
-
const content = await
|
|
3269
|
+
const content = await fs12.readFile(packageJsonPath, "utf-8");
|
|
3270
3270
|
const packageJson2 = JSON.parse(content);
|
|
3271
3271
|
return packageJson2.name || null;
|
|
3272
3272
|
} catch {
|
|
@@ -3505,7 +3505,7 @@ async function createSpec(name, options = {}) {
|
|
|
3505
3505
|
const config = await loadConfig();
|
|
3506
3506
|
const cwd = process.cwd();
|
|
3507
3507
|
const specsDir = path17.join(cwd, config.specsDir);
|
|
3508
|
-
await
|
|
3508
|
+
await fs12.mkdir(specsDir, { recursive: true });
|
|
3509
3509
|
const seq = await getGlobalNextSeq(specsDir, config.structure.sequenceDigits);
|
|
3510
3510
|
let specRelativePath;
|
|
3511
3511
|
if (config.structure.pattern === "flat") {
|
|
@@ -3528,14 +3528,14 @@ async function createSpec(name, options = {}) {
|
|
|
3528
3528
|
const specDir = path17.join(specsDir, specRelativePath);
|
|
3529
3529
|
const specFile = path17.join(specDir, config.structure.defaultFile);
|
|
3530
3530
|
try {
|
|
3531
|
-
await
|
|
3531
|
+
await fs12.access(specDir);
|
|
3532
3532
|
throw new Error(`Spec already exists: ${sanitizeUserInput(specDir)}`);
|
|
3533
3533
|
} catch (error) {
|
|
3534
3534
|
if (error.code === "ENOENT") ; else {
|
|
3535
3535
|
throw error;
|
|
3536
3536
|
}
|
|
3537
3537
|
}
|
|
3538
|
-
await
|
|
3538
|
+
await fs12.mkdir(specDir, { recursive: true });
|
|
3539
3539
|
const templatesDir = path17.join(cwd, ".lean-spec", "templates");
|
|
3540
3540
|
let templateName;
|
|
3541
3541
|
if (options.template) {
|
|
@@ -3550,17 +3550,17 @@ async function createSpec(name, options = {}) {
|
|
|
3550
3550
|
}
|
|
3551
3551
|
let templatePath = path17.join(templatesDir, templateName);
|
|
3552
3552
|
try {
|
|
3553
|
-
await
|
|
3553
|
+
await fs12.access(templatePath);
|
|
3554
3554
|
} catch {
|
|
3555
3555
|
const legacyPath = path17.join(templatesDir, "spec-template.md");
|
|
3556
3556
|
try {
|
|
3557
|
-
await
|
|
3557
|
+
await fs12.access(legacyPath);
|
|
3558
3558
|
templatePath = legacyPath;
|
|
3559
3559
|
templateName = "spec-template.md";
|
|
3560
3560
|
} catch {
|
|
3561
3561
|
const readmePath = path17.join(templatesDir, "README.md");
|
|
3562
3562
|
try {
|
|
3563
|
-
await
|
|
3563
|
+
await fs12.access(readmePath);
|
|
3564
3564
|
templatePath = readmePath;
|
|
3565
3565
|
templateName = "README.md";
|
|
3566
3566
|
} catch {
|
|
@@ -3571,7 +3571,7 @@ async function createSpec(name, options = {}) {
|
|
|
3571
3571
|
let content;
|
|
3572
3572
|
let varContext;
|
|
3573
3573
|
try {
|
|
3574
|
-
const template = await
|
|
3574
|
+
const template = await fs12.readFile(templatePath, "utf-8");
|
|
3575
3575
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3576
3576
|
const title = options.title || name;
|
|
3577
3577
|
varContext = await buildVariableContext(config, { name: title, date });
|
|
@@ -3615,9 +3615,9 @@ ${options.description}`
|
|
|
3615
3615
|
} catch (error) {
|
|
3616
3616
|
throw new Error(`Template not found: ${templatePath}. Run: lean-spec init`);
|
|
3617
3617
|
}
|
|
3618
|
-
await
|
|
3618
|
+
await fs12.writeFile(specFile, content, "utf-8");
|
|
3619
3619
|
try {
|
|
3620
|
-
const templateFiles = await
|
|
3620
|
+
const templateFiles = await fs12.readdir(templatesDir);
|
|
3621
3621
|
const additionalFiles = templateFiles.filter(
|
|
3622
3622
|
(f) => f.endsWith(".md") && f !== templateName && f !== "spec-template.md" && f !== config.structure.defaultFile
|
|
3623
3623
|
);
|
|
@@ -3625,9 +3625,9 @@ ${options.description}`
|
|
|
3625
3625
|
for (const file of additionalFiles) {
|
|
3626
3626
|
const srcPath = path17.join(templatesDir, file);
|
|
3627
3627
|
const destPath = path17.join(specDir, file);
|
|
3628
|
-
let fileContent = await
|
|
3628
|
+
let fileContent = await fs12.readFile(srcPath, "utf-8");
|
|
3629
3629
|
fileContent = resolveVariables(fileContent, varContext);
|
|
3630
|
-
await
|
|
3630
|
+
await fs12.writeFile(destPath, fileContent, "utf-8");
|
|
3631
3631
|
}
|
|
3632
3632
|
console.log(chalk20.green(`\u2713 Created: ${sanitizeUserInput(specDir)}/`));
|
|
3633
3633
|
console.log(chalk20.gray(` Files: ${config.structure.defaultFile}, ${additionalFiles.join(", ")}`));
|
|
@@ -3672,10 +3672,10 @@ async function archiveSpec(specPath) {
|
|
|
3672
3672
|
await updateFrontmatter(specFile, { status: "archived" });
|
|
3673
3673
|
}
|
|
3674
3674
|
const archiveDir = path17.join(specsDir, "archived");
|
|
3675
|
-
await
|
|
3675
|
+
await fs12.mkdir(archiveDir, { recursive: true });
|
|
3676
3676
|
const specName = path17.basename(resolvedPath);
|
|
3677
3677
|
const archivePath = path17.join(archiveDir, specName);
|
|
3678
|
-
await
|
|
3678
|
+
await fs12.rename(resolvedPath, archivePath);
|
|
3679
3679
|
console.log(chalk20.green(`\u2713 Archived: ${sanitizeUserInput(archivePath)}`));
|
|
3680
3680
|
}
|
|
3681
3681
|
|
|
@@ -3727,7 +3727,7 @@ async function listSpecs(options = {}) {
|
|
|
3727
3727
|
const cwd = process.cwd();
|
|
3728
3728
|
const specsDir = path17.join(cwd, config.specsDir);
|
|
3729
3729
|
try {
|
|
3730
|
-
await
|
|
3730
|
+
await fs12.access(specsDir);
|
|
3731
3731
|
} catch {
|
|
3732
3732
|
console.log("");
|
|
3733
3733
|
console.log("No specs directory found. Initialize with: lean-spec init");
|
|
@@ -4355,14 +4355,14 @@ async function listTemplates(cwd = process.cwd()) {
|
|
|
4355
4355
|
console.log(chalk20.green("=== Project Templates ==="));
|
|
4356
4356
|
console.log("");
|
|
4357
4357
|
try {
|
|
4358
|
-
await
|
|
4358
|
+
await fs12.access(templatesDir);
|
|
4359
4359
|
} catch {
|
|
4360
4360
|
console.log(chalk20.yellow("No templates directory found."));
|
|
4361
4361
|
console.log(chalk20.gray("Run: lean-spec init"));
|
|
4362
4362
|
console.log("");
|
|
4363
4363
|
return;
|
|
4364
4364
|
}
|
|
4365
|
-
const files = await
|
|
4365
|
+
const files = await fs12.readdir(templatesDir);
|
|
4366
4366
|
const templateFiles = files.filter((f) => f.endsWith(".md"));
|
|
4367
4367
|
if (templateFiles.length === 0) {
|
|
4368
4368
|
console.log(chalk20.yellow("No templates found."));
|
|
@@ -4381,7 +4381,7 @@ async function listTemplates(cwd = process.cwd()) {
|
|
|
4381
4381
|
console.log(chalk20.cyan("Available files:"));
|
|
4382
4382
|
for (const file of templateFiles) {
|
|
4383
4383
|
const filePath = path17.join(templatesDir, file);
|
|
4384
|
-
const stat6 = await
|
|
4384
|
+
const stat6 = await fs12.stat(filePath);
|
|
4385
4385
|
const sizeKB = (stat6.size / 1024).toFixed(1);
|
|
4386
4386
|
console.log(` ${file} (${sizeKB} KB)`);
|
|
4387
4387
|
}
|
|
@@ -4400,7 +4400,7 @@ async function showTemplate(templateName, cwd = process.cwd()) {
|
|
|
4400
4400
|
const templateFile = config.templates[templateName];
|
|
4401
4401
|
const templatePath = path17.join(templatesDir, templateFile);
|
|
4402
4402
|
try {
|
|
4403
|
-
const content = await
|
|
4403
|
+
const content = await fs12.readFile(templatePath, "utf-8");
|
|
4404
4404
|
console.log("");
|
|
4405
4405
|
console.log(chalk20.cyan(`=== Template: ${templateName} (${templateFile}) ===`));
|
|
4406
4406
|
console.log("");
|
|
@@ -4417,7 +4417,7 @@ async function addTemplate(name, file, cwd = process.cwd()) {
|
|
|
4417
4417
|
const templatesDir = path17.join(cwd, ".lean-spec", "templates");
|
|
4418
4418
|
const templatePath = path17.join(templatesDir, file);
|
|
4419
4419
|
try {
|
|
4420
|
-
await
|
|
4420
|
+
await fs12.access(templatePath);
|
|
4421
4421
|
} catch {
|
|
4422
4422
|
console.error(chalk20.red(`Template file not found: ${file}`));
|
|
4423
4423
|
console.error(chalk20.gray(`Expected at: ${templatePath}`));
|
|
@@ -4465,7 +4465,7 @@ async function copyTemplate(source, target, cwd = process.cwd()) {
|
|
|
4465
4465
|
}
|
|
4466
4466
|
const sourcePath = path17.join(templatesDir, sourceFile);
|
|
4467
4467
|
try {
|
|
4468
|
-
await
|
|
4468
|
+
await fs12.access(sourcePath);
|
|
4469
4469
|
} catch {
|
|
4470
4470
|
console.error(chalk20.red(`Source template not found: ${source}`));
|
|
4471
4471
|
console.error(chalk20.gray(`Expected at: ${sourcePath}`));
|
|
@@ -4473,7 +4473,7 @@ async function copyTemplate(source, target, cwd = process.cwd()) {
|
|
|
4473
4473
|
}
|
|
4474
4474
|
const targetFile = target.endsWith(".md") ? target : `${target}.md`;
|
|
4475
4475
|
const targetPath = path17.join(templatesDir, targetFile);
|
|
4476
|
-
await
|
|
4476
|
+
await fs12.copyFile(sourcePath, targetPath);
|
|
4477
4477
|
console.log(chalk20.green(`\u2713 Copied: ${sourceFile} \u2192 ${targetFile}`));
|
|
4478
4478
|
if (!config.templates) {
|
|
4479
4479
|
config.templates = {};
|
|
@@ -4619,7 +4619,7 @@ async function configDirExists(dirName) {
|
|
|
4619
4619
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
4620
4620
|
if (!homeDir) return false;
|
|
4621
4621
|
try {
|
|
4622
|
-
await
|
|
4622
|
+
await fs12.access(path17.join(homeDir, dirName));
|
|
4623
4623
|
return true;
|
|
4624
4624
|
} catch {
|
|
4625
4625
|
return false;
|
|
@@ -4638,7 +4638,7 @@ async function extensionInstalled(extensionId) {
|
|
|
4638
4638
|
];
|
|
4639
4639
|
for (const extDir of extensionDirs) {
|
|
4640
4640
|
try {
|
|
4641
|
-
const entries = await
|
|
4641
|
+
const entries = await fs12.readdir(extDir);
|
|
4642
4642
|
if (entries.some((e) => e.toLowerCase().startsWith(extensionId.toLowerCase()))) {
|
|
4643
4643
|
return true;
|
|
4644
4644
|
}
|
|
@@ -4721,7 +4721,7 @@ async function createAgentToolSymlinks(cwd, selectedTools) {
|
|
|
4721
4721
|
const targetPath = path17.join(cwd, file);
|
|
4722
4722
|
try {
|
|
4723
4723
|
try {
|
|
4724
|
-
await
|
|
4724
|
+
await fs12.access(targetPath);
|
|
4725
4725
|
results.push({ file, skipped: true });
|
|
4726
4726
|
continue;
|
|
4727
4727
|
} catch {
|
|
@@ -4736,10 +4736,10 @@ async function createAgentToolSymlinks(cwd, selectedTools) {
|
|
|
4736
4736
|
|
|
4737
4737
|
See AGENTS.md for the full LeanSpec AI agent instructions.
|
|
4738
4738
|
`;
|
|
4739
|
-
await
|
|
4739
|
+
await fs12.writeFile(targetPath, windowsContent, "utf-8");
|
|
4740
4740
|
results.push({ file, created: true, error: "created as copy (Windows)" });
|
|
4741
4741
|
} else {
|
|
4742
|
-
await
|
|
4742
|
+
await fs12.symlink("AGENTS.md", targetPath);
|
|
4743
4743
|
results.push({ file, created: true });
|
|
4744
4744
|
}
|
|
4745
4745
|
} catch (error) {
|
|
@@ -4760,7 +4760,7 @@ async function detectExistingSystemPrompts(cwd) {
|
|
|
4760
4760
|
const found = [];
|
|
4761
4761
|
for (const file of commonFiles) {
|
|
4762
4762
|
try {
|
|
4763
|
-
await
|
|
4763
|
+
await fs12.access(path17.join(cwd, file));
|
|
4764
4764
|
found.push(file);
|
|
4765
4765
|
} catch {
|
|
4766
4766
|
}
|
|
@@ -4772,13 +4772,13 @@ async function handleExistingFiles(action, existingFiles, templateDir, cwd, vari
|
|
|
4772
4772
|
const filePath = path17.join(cwd, file);
|
|
4773
4773
|
const templateFilePath = path17.join(templateDir, file);
|
|
4774
4774
|
try {
|
|
4775
|
-
await
|
|
4775
|
+
await fs12.access(templateFilePath);
|
|
4776
4776
|
} catch {
|
|
4777
4777
|
continue;
|
|
4778
4778
|
}
|
|
4779
4779
|
if (action === "merge-ai" && file === "AGENTS.md") {
|
|
4780
|
-
const existing = await
|
|
4781
|
-
let template = await
|
|
4780
|
+
const existing = await fs12.readFile(filePath, "utf-8");
|
|
4781
|
+
let template = await fs12.readFile(templateFilePath, "utf-8");
|
|
4782
4782
|
for (const [key, value] of Object.entries(variables)) {
|
|
4783
4783
|
template = template.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
4784
4784
|
}
|
|
@@ -4814,8 +4814,8 @@ Create a single consolidated AGENTS.md that:
|
|
|
4814
4814
|
- Maintains clear structure and readability
|
|
4815
4815
|
- Removes any duplicate or conflicting guidance
|
|
4816
4816
|
`;
|
|
4817
|
-
await
|
|
4818
|
-
await
|
|
4817
|
+
await fs12.mkdir(path17.dirname(promptPath), { recursive: true });
|
|
4818
|
+
await fs12.writeFile(promptPath, aiPrompt, "utf-8");
|
|
4819
4819
|
console.log(chalk20.green(`\u2713 Created AI consolidation prompt`));
|
|
4820
4820
|
console.log(chalk20.cyan(` \u2192 ${promptPath}`));
|
|
4821
4821
|
console.log("");
|
|
@@ -4826,8 +4826,8 @@ Create a single consolidated AGENTS.md that:
|
|
|
4826
4826
|
console.log(chalk20.gray(" 4. Review and commit the result"));
|
|
4827
4827
|
console.log("");
|
|
4828
4828
|
} else if (action === "merge-append" && file === "AGENTS.md") {
|
|
4829
|
-
const existing = await
|
|
4830
|
-
let template = await
|
|
4829
|
+
const existing = await fs12.readFile(filePath, "utf-8");
|
|
4830
|
+
let template = await fs12.readFile(templateFilePath, "utf-8");
|
|
4831
4831
|
for (const [key, value] of Object.entries(variables)) {
|
|
4832
4832
|
template = template.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
4833
4833
|
}
|
|
@@ -4838,26 +4838,26 @@ Create a single consolidated AGENTS.md that:
|
|
|
4838
4838
|
## LeanSpec Integration
|
|
4839
4839
|
|
|
4840
4840
|
${template.split("\n").slice(1).join("\n")}`;
|
|
4841
|
-
await
|
|
4841
|
+
await fs12.writeFile(filePath, merged, "utf-8");
|
|
4842
4842
|
console.log(chalk20.green(`\u2713 Appended LeanSpec section to ${file}`));
|
|
4843
4843
|
console.log(chalk20.yellow(" \u26A0 Note: May be verbose. Consider consolidating later."));
|
|
4844
4844
|
} else if (action === "overwrite") {
|
|
4845
4845
|
const backupPath = `${filePath}.backup`;
|
|
4846
|
-
await
|
|
4846
|
+
await fs12.rename(filePath, backupPath);
|
|
4847
4847
|
console.log(chalk20.yellow(`\u2713 Backed up ${file} \u2192 ${file}.backup`));
|
|
4848
|
-
let content = await
|
|
4848
|
+
let content = await fs12.readFile(templateFilePath, "utf-8");
|
|
4849
4849
|
for (const [key, value] of Object.entries(variables)) {
|
|
4850
4850
|
content = content.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
4851
4851
|
}
|
|
4852
|
-
await
|
|
4852
|
+
await fs12.writeFile(filePath, content, "utf-8");
|
|
4853
4853
|
console.log(chalk20.green(`\u2713 Created new ${file}`));
|
|
4854
4854
|
console.log(chalk20.gray(` \u{1F4A1} Your original content is preserved in ${file}.backup`));
|
|
4855
4855
|
}
|
|
4856
4856
|
}
|
|
4857
4857
|
}
|
|
4858
4858
|
async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
|
|
4859
|
-
await
|
|
4860
|
-
const entries = await
|
|
4859
|
+
await fs12.mkdir(dest, { recursive: true });
|
|
4860
|
+
const entries = await fs12.readdir(src, { withFileTypes: true });
|
|
4861
4861
|
for (const entry of entries) {
|
|
4862
4862
|
const srcPath = path17.join(src, entry.name);
|
|
4863
4863
|
const destPath = path17.join(dest, entry.name);
|
|
@@ -4868,13 +4868,13 @@ async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
|
|
|
4868
4868
|
await copyDirectory(srcPath, destPath, skipFiles, variables);
|
|
4869
4869
|
} else {
|
|
4870
4870
|
try {
|
|
4871
|
-
await
|
|
4871
|
+
await fs12.access(destPath);
|
|
4872
4872
|
} catch {
|
|
4873
|
-
let content = await
|
|
4873
|
+
let content = await fs12.readFile(srcPath, "utf-8");
|
|
4874
4874
|
for (const [key, value] of Object.entries(variables)) {
|
|
4875
4875
|
content = content.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
4876
4876
|
}
|
|
4877
|
-
await
|
|
4877
|
+
await fs12.writeFile(destPath, content, "utf-8");
|
|
4878
4878
|
}
|
|
4879
4879
|
}
|
|
4880
4880
|
}
|
|
@@ -4882,7 +4882,7 @@ async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
|
|
|
4882
4882
|
async function getProjectName2(cwd) {
|
|
4883
4883
|
try {
|
|
4884
4884
|
const packageJsonPath = path17.join(cwd, "package.json");
|
|
4885
|
-
const content = await
|
|
4885
|
+
const content = await fs12.readFile(packageJsonPath, "utf-8");
|
|
4886
4886
|
const pkg = JSON.parse(content);
|
|
4887
4887
|
if (pkg.name) {
|
|
4888
4888
|
return pkg.name;
|
|
@@ -5107,8 +5107,164 @@ async function attemptAutoMerge(cwd, promptPath, autoExecute) {
|
|
|
5107
5107
|
return false;
|
|
5108
5108
|
}
|
|
5109
5109
|
}
|
|
5110
|
+
async function handleReinitialize(cwd, skipPrompts, forceReinit) {
|
|
5111
|
+
const specsDir = path17.join(cwd, "specs");
|
|
5112
|
+
let specCount = 0;
|
|
5113
|
+
try {
|
|
5114
|
+
const entries = await fs12.readdir(specsDir, { withFileTypes: true });
|
|
5115
|
+
specCount = entries.filter((e) => e.isDirectory()).length;
|
|
5116
|
+
} catch {
|
|
5117
|
+
}
|
|
5118
|
+
console.log("");
|
|
5119
|
+
console.log(chalk20.yellow("\u26A0 LeanSpec is already initialized in this directory."));
|
|
5120
|
+
if (specCount > 0) {
|
|
5121
|
+
console.log(chalk20.cyan(` Found ${specCount} spec${specCount > 1 ? "s" : ""} in specs/`));
|
|
5122
|
+
}
|
|
5123
|
+
console.log("");
|
|
5124
|
+
if (forceReinit) {
|
|
5125
|
+
console.log(chalk20.gray("Force flag detected. Resetting configuration..."));
|
|
5126
|
+
return "reset-config";
|
|
5127
|
+
}
|
|
5128
|
+
if (skipPrompts) {
|
|
5129
|
+
console.log(chalk20.gray("Using safe upgrade (preserving all existing files)"));
|
|
5130
|
+
return "upgrade";
|
|
5131
|
+
}
|
|
5132
|
+
const strategy = await select({
|
|
5133
|
+
message: "What would you like to do?",
|
|
5134
|
+
choices: [
|
|
5135
|
+
{
|
|
5136
|
+
name: "Upgrade configuration (recommended)",
|
|
5137
|
+
value: "upgrade",
|
|
5138
|
+
description: "Update config to latest version. Keeps specs and AGENTS.md untouched."
|
|
5139
|
+
},
|
|
5140
|
+
{
|
|
5141
|
+
name: "Reset configuration",
|
|
5142
|
+
value: "reset-config",
|
|
5143
|
+
description: "Fresh config from template. Keeps specs/ directory."
|
|
5144
|
+
},
|
|
5145
|
+
{
|
|
5146
|
+
name: "Full reset",
|
|
5147
|
+
value: "full-reset",
|
|
5148
|
+
description: "Remove .lean-spec/, specs/, and AGENTS.md. Start completely fresh."
|
|
5149
|
+
},
|
|
5150
|
+
{
|
|
5151
|
+
name: "Cancel",
|
|
5152
|
+
value: "cancel",
|
|
5153
|
+
description: "Exit without changes."
|
|
5154
|
+
}
|
|
5155
|
+
]
|
|
5156
|
+
});
|
|
5157
|
+
if (strategy === "full-reset") {
|
|
5158
|
+
const warnings = [];
|
|
5159
|
+
if (specCount > 0) {
|
|
5160
|
+
warnings.push(`${specCount} spec${specCount > 1 ? "s" : ""} in specs/`);
|
|
5161
|
+
}
|
|
5162
|
+
try {
|
|
5163
|
+
await fs12.access(path17.join(cwd, "AGENTS.md"));
|
|
5164
|
+
warnings.push("AGENTS.md");
|
|
5165
|
+
} catch {
|
|
5166
|
+
}
|
|
5167
|
+
if (warnings.length > 0) {
|
|
5168
|
+
console.log("");
|
|
5169
|
+
console.log(chalk20.red("\u26A0 This will permanently delete:"));
|
|
5170
|
+
for (const warning of warnings) {
|
|
5171
|
+
console.log(chalk20.red(` - ${warning}`));
|
|
5172
|
+
}
|
|
5173
|
+
console.log("");
|
|
5174
|
+
const confirmed = await confirm({
|
|
5175
|
+
message: "Are you sure you want to continue?",
|
|
5176
|
+
default: false
|
|
5177
|
+
});
|
|
5178
|
+
if (!confirmed) {
|
|
5179
|
+
console.log(chalk20.gray("Cancelled."));
|
|
5180
|
+
return "cancel";
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
console.log(chalk20.gray("Performing full reset..."));
|
|
5184
|
+
await fs12.rm(path17.join(cwd, ".lean-spec"), { recursive: true, force: true });
|
|
5185
|
+
console.log(chalk20.gray(" Removed .lean-spec/"));
|
|
5186
|
+
try {
|
|
5187
|
+
await fs12.rm(specsDir, { recursive: true, force: true });
|
|
5188
|
+
console.log(chalk20.gray(" Removed specs/"));
|
|
5189
|
+
} catch {
|
|
5190
|
+
}
|
|
5191
|
+
for (const file of ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]) {
|
|
5192
|
+
try {
|
|
5193
|
+
await fs12.rm(path17.join(cwd, file), { force: true });
|
|
5194
|
+
console.log(chalk20.gray(` Removed ${file}`));
|
|
5195
|
+
} catch {
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
return strategy;
|
|
5200
|
+
}
|
|
5201
|
+
async function upgradeConfig(cwd) {
|
|
5202
|
+
const configPath = path17.join(cwd, ".lean-spec", "config.json");
|
|
5203
|
+
let existingConfig;
|
|
5204
|
+
try {
|
|
5205
|
+
const content = await fs12.readFile(configPath, "utf-8");
|
|
5206
|
+
existingConfig = JSON.parse(content);
|
|
5207
|
+
} catch {
|
|
5208
|
+
console.error(chalk20.red("Error reading existing config"));
|
|
5209
|
+
process.exit(1);
|
|
5210
|
+
}
|
|
5211
|
+
const templateConfigPath = path17.join(TEMPLATES_DIR, "standard", "config.json");
|
|
5212
|
+
let templateConfig;
|
|
5213
|
+
try {
|
|
5214
|
+
const content = await fs12.readFile(templateConfigPath, "utf-8");
|
|
5215
|
+
templateConfig = JSON.parse(content).config;
|
|
5216
|
+
} catch {
|
|
5217
|
+
console.error(chalk20.red("Error reading template config"));
|
|
5218
|
+
process.exit(1);
|
|
5219
|
+
}
|
|
5220
|
+
const upgradedConfig = {
|
|
5221
|
+
...templateConfig,
|
|
5222
|
+
...existingConfig,
|
|
5223
|
+
// Deep merge structure
|
|
5224
|
+
structure: {
|
|
5225
|
+
...templateConfig.structure,
|
|
5226
|
+
...existingConfig.structure
|
|
5227
|
+
}
|
|
5228
|
+
};
|
|
5229
|
+
const templatesDir = path17.join(cwd, ".lean-spec", "templates");
|
|
5230
|
+
try {
|
|
5231
|
+
await fs12.mkdir(templatesDir, { recursive: true });
|
|
5232
|
+
} catch {
|
|
5233
|
+
}
|
|
5234
|
+
const templateFiles = ["spec-template.md"];
|
|
5235
|
+
let templatesUpdated = false;
|
|
5236
|
+
for (const file of templateFiles) {
|
|
5237
|
+
const destPath = path17.join(templatesDir, file);
|
|
5238
|
+
try {
|
|
5239
|
+
await fs12.access(destPath);
|
|
5240
|
+
} catch {
|
|
5241
|
+
const srcPath = path17.join(TEMPLATES_DIR, "standard", "files", "README.md");
|
|
5242
|
+
try {
|
|
5243
|
+
await fs12.copyFile(srcPath, destPath);
|
|
5244
|
+
templatesUpdated = true;
|
|
5245
|
+
console.log(chalk20.green(`\u2713 Added missing template: ${file}`));
|
|
5246
|
+
} catch {
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
await saveConfig(upgradedConfig, cwd);
|
|
5251
|
+
console.log("");
|
|
5252
|
+
console.log(chalk20.green("\u2713 Configuration upgraded!"));
|
|
5253
|
+
console.log("");
|
|
5254
|
+
console.log(chalk20.gray("What was updated:"));
|
|
5255
|
+
console.log(chalk20.gray(" - Config merged with latest defaults"));
|
|
5256
|
+
if (templatesUpdated) {
|
|
5257
|
+
console.log(chalk20.gray(" - Missing templates added"));
|
|
5258
|
+
}
|
|
5259
|
+
console.log("");
|
|
5260
|
+
console.log(chalk20.gray("What was preserved:"));
|
|
5261
|
+
console.log(chalk20.gray(" - Your specs/ directory"));
|
|
5262
|
+
console.log(chalk20.gray(" - Your AGENTS.md"));
|
|
5263
|
+
console.log(chalk20.gray(" - Your custom settings"));
|
|
5264
|
+
console.log("");
|
|
5265
|
+
}
|
|
5110
5266
|
function initCommand() {
|
|
5111
|
-
return new Command("init").description("Initialize LeanSpec in current directory").option("-y, --yes", "Skip prompts and use defaults (quick start with standard template)").option("--template <name>", "Use specific template (standard or detailed)").option("--example [name]", "Scaffold an example project for tutorials (interactive if no name provided)").option("--name <dirname>", "Custom directory name for example project").option("--list", "List available example projects").option("--agent-tools <tools>", 'AI tools to create symlinks for (comma-separated: claude,gemini,copilot or "all" or "none")').action(async (options) => {
|
|
5267
|
+
return new Command("init").description("Initialize LeanSpec in current directory").option("-y, --yes", "Skip prompts and use defaults (quick start with standard template)").option("-f, --force", "Force re-initialization (resets config, keeps specs)").option("--template <name>", "Use specific template (standard or detailed)").option("--example [name]", "Scaffold an example project for tutorials (interactive if no name provided)").option("--name <dirname>", "Custom directory name for example project").option("--list", "List available example projects").option("--agent-tools <tools>", 'AI tools to create symlinks for (comma-separated: claude,gemini,copilot or "all" or "none")').action(async (options) => {
|
|
5112
5268
|
if (options.list) {
|
|
5113
5269
|
await listExamples();
|
|
5114
5270
|
return;
|
|
@@ -5117,18 +5273,32 @@ function initCommand() {
|
|
|
5117
5273
|
await scaffoldExample(options.example, options.name);
|
|
5118
5274
|
return;
|
|
5119
5275
|
}
|
|
5120
|
-
await initProject(options.yes, options.template, options.agentTools);
|
|
5276
|
+
await initProject(options.yes, options.template, options.agentTools, options.force);
|
|
5121
5277
|
});
|
|
5122
5278
|
}
|
|
5123
|
-
async function initProject(skipPrompts = false, templateOption, agentToolsOption) {
|
|
5279
|
+
async function initProject(skipPrompts = false, templateOption, agentToolsOption, forceReinit = false) {
|
|
5124
5280
|
const cwd = process.cwd();
|
|
5281
|
+
const configPath = path17.join(cwd, ".lean-spec", "config.json");
|
|
5282
|
+
let isAlreadyInitialized = false;
|
|
5125
5283
|
try {
|
|
5126
|
-
await
|
|
5127
|
-
|
|
5128
|
-
console.log(chalk20.gray("To reinitialize, delete .lean-spec/ directory first."));
|
|
5129
|
-
return;
|
|
5284
|
+
await fs12.access(configPath);
|
|
5285
|
+
isAlreadyInitialized = true;
|
|
5130
5286
|
} catch {
|
|
5131
5287
|
}
|
|
5288
|
+
if (isAlreadyInitialized) {
|
|
5289
|
+
const strategy = await handleReinitialize(cwd, skipPrompts, forceReinit);
|
|
5290
|
+
if (strategy === "cancel") {
|
|
5291
|
+
return;
|
|
5292
|
+
}
|
|
5293
|
+
if (strategy === "upgrade") {
|
|
5294
|
+
await upgradeConfig(cwd);
|
|
5295
|
+
return;
|
|
5296
|
+
}
|
|
5297
|
+
if (strategy === "reset-config") {
|
|
5298
|
+
await fs12.rm(path17.join(cwd, ".lean-spec"), { recursive: true, force: true });
|
|
5299
|
+
console.log(chalk20.gray("Removed .lean-spec/ configuration"));
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5132
5302
|
console.log("");
|
|
5133
5303
|
console.log(chalk20.green("Welcome to LeanSpec!"));
|
|
5134
5304
|
console.log("");
|
|
@@ -5201,7 +5371,7 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5201
5371
|
const templateConfigPath = path17.join(templateDir, "config.json");
|
|
5202
5372
|
let templateConfig;
|
|
5203
5373
|
try {
|
|
5204
|
-
const content = await
|
|
5374
|
+
const content = await fs12.readFile(templateConfigPath, "utf-8");
|
|
5205
5375
|
templateConfig = JSON.parse(content).config;
|
|
5206
5376
|
} catch {
|
|
5207
5377
|
console.error(chalk20.red(`Error: Template not found: ${templateName}`));
|
|
@@ -5270,30 +5440,39 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5270
5440
|
}
|
|
5271
5441
|
console.log("");
|
|
5272
5442
|
}
|
|
5273
|
-
const
|
|
5443
|
+
const symlinkTools = Object.entries(AI_TOOL_CONFIGS).filter(([, config]) => config.usesSymlink).map(([key, config]) => ({
|
|
5274
5444
|
name: config.description,
|
|
5275
5445
|
value: key,
|
|
5276
5446
|
checked: detectedDefaults.includes(key)
|
|
5277
5447
|
}));
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5448
|
+
if (symlinkTools.length > 0) {
|
|
5449
|
+
console.log("");
|
|
5450
|
+
console.log(chalk20.gray("AGENTS.md will be created as the primary instruction file."));
|
|
5451
|
+
console.log(chalk20.gray("Some AI tools (Claude Code, Gemini CLI) use their own filenames."));
|
|
5452
|
+
console.log("");
|
|
5453
|
+
const symlinkSelection = await checkbox({
|
|
5454
|
+
message: "Create symlinks for additional AI tools?",
|
|
5455
|
+
choices: symlinkTools
|
|
5456
|
+
});
|
|
5457
|
+
selectedAgentTools = symlinkSelection;
|
|
5458
|
+
} else {
|
|
5459
|
+
selectedAgentTools = [];
|
|
5460
|
+
}
|
|
5282
5461
|
}
|
|
5283
5462
|
const templatesDir = path17.join(cwd, ".lean-spec", "templates");
|
|
5284
5463
|
try {
|
|
5285
|
-
await
|
|
5464
|
+
await fs12.mkdir(templatesDir, { recursive: true });
|
|
5286
5465
|
} catch (error) {
|
|
5287
5466
|
console.error(chalk20.red("Error creating templates directory:"), error);
|
|
5288
5467
|
process.exit(1);
|
|
5289
5468
|
}
|
|
5290
5469
|
const templateFilesDir = path17.join(templateDir, "files");
|
|
5291
5470
|
try {
|
|
5292
|
-
const files = await
|
|
5471
|
+
const files = await fs12.readdir(templateFilesDir);
|
|
5293
5472
|
if (templateName === "standard") {
|
|
5294
5473
|
const readmePath = path17.join(templateFilesDir, "README.md");
|
|
5295
5474
|
const targetSpecPath = path17.join(templatesDir, "spec-template.md");
|
|
5296
|
-
await
|
|
5475
|
+
await fs12.copyFile(readmePath, targetSpecPath);
|
|
5297
5476
|
console.log(chalk20.green("\u2713 Created .lean-spec/templates/spec-template.md"));
|
|
5298
5477
|
templateConfig.template = "spec-template.md";
|
|
5299
5478
|
templateConfig.templates = {
|
|
@@ -5303,7 +5482,7 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5303
5482
|
for (const file of files) {
|
|
5304
5483
|
const srcPath = path17.join(templateFilesDir, file);
|
|
5305
5484
|
const destPath = path17.join(templatesDir, file);
|
|
5306
|
-
await
|
|
5485
|
+
await fs12.copyFile(srcPath, destPath);
|
|
5307
5486
|
}
|
|
5308
5487
|
console.log(chalk20.green(`\u2713 Created .lean-spec/templates/ with ${files.length} files`));
|
|
5309
5488
|
console.log(chalk20.gray(` Files: ${files.join(", ")}`));
|
|
@@ -5381,9 +5560,9 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5381
5560
|
const agentsSourcePath = path17.join(templateDir, "AGENTS.md");
|
|
5382
5561
|
const agentsTargetPath = path17.join(cwd, "AGENTS.md");
|
|
5383
5562
|
try {
|
|
5384
|
-
let agentsContent = await
|
|
5563
|
+
let agentsContent = await fs12.readFile(agentsSourcePath, "utf-8");
|
|
5385
5564
|
agentsContent = agentsContent.replace(/\{project_name\}/g, projectName);
|
|
5386
|
-
await
|
|
5565
|
+
await fs12.writeFile(agentsTargetPath, agentsContent, "utf-8");
|
|
5387
5566
|
console.log(chalk20.green("\u2713 Created AGENTS.md"));
|
|
5388
5567
|
} catch (error) {
|
|
5389
5568
|
console.error(chalk20.red("Error copying AGENTS.md:"), error);
|
|
@@ -5404,7 +5583,7 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5404
5583
|
}
|
|
5405
5584
|
const filesDir = path17.join(templateDir, "files");
|
|
5406
5585
|
try {
|
|
5407
|
-
const filesToCopy = await
|
|
5586
|
+
const filesToCopy = await fs12.readdir(filesDir);
|
|
5408
5587
|
const hasOtherFiles = filesToCopy.some((f) => !f.match(/\.(md)$/i) || !["README.md", "DESIGN.md", "PLAN.md", "TEST.md"].includes(f));
|
|
5409
5588
|
if (hasOtherFiles) {
|
|
5410
5589
|
await copyDirectory(filesDir, cwd, [...skipFiles, "README.md", "DESIGN.md", "PLAN.md", "TEST.md"], { project_name: projectName });
|
|
@@ -5416,7 +5595,7 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5416
5595
|
}
|
|
5417
5596
|
const specsDir = path17.join(cwd, "specs");
|
|
5418
5597
|
try {
|
|
5419
|
-
await
|
|
5598
|
+
await fs12.mkdir(specsDir, { recursive: true });
|
|
5420
5599
|
console.log(chalk20.green("\u2713 Created specs/ directory"));
|
|
5421
5600
|
} catch (error) {
|
|
5422
5601
|
console.error(chalk20.red("Error creating specs directory:"), error);
|
|
@@ -5425,9 +5604,10 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5425
5604
|
console.log("");
|
|
5426
5605
|
console.log(chalk20.green("\u2713 LeanSpec initialized!"));
|
|
5427
5606
|
console.log("");
|
|
5428
|
-
console.log("
|
|
5429
|
-
console.log(
|
|
5430
|
-
console.log(chalk20.
|
|
5607
|
+
console.log(chalk20.cyan("You're ready to go!") + chalk20.gray(" Ask your AI to create a spec for your next feature."));
|
|
5608
|
+
console.log("");
|
|
5609
|
+
console.log(chalk20.gray('Example: "Create a spec for user authentication"'));
|
|
5610
|
+
console.log(chalk20.gray("Learn more: https://lean-spec.dev/docs/guide/getting-started"));
|
|
5431
5611
|
console.log("");
|
|
5432
5612
|
}
|
|
5433
5613
|
async function listExamples() {
|
|
@@ -5467,7 +5647,7 @@ async function scaffoldExample(exampleName, customName) {
|
|
|
5467
5647
|
const targetDirName = customName || exampleName;
|
|
5468
5648
|
const targetPath = path17.join(process.cwd(), targetDirName);
|
|
5469
5649
|
try {
|
|
5470
|
-
const files = await
|
|
5650
|
+
const files = await fs12.readdir(targetPath);
|
|
5471
5651
|
const nonGitFiles = files.filter((f) => f !== ".git");
|
|
5472
5652
|
if (nonGitFiles.length > 0) {
|
|
5473
5653
|
console.error(chalk20.red(`Error: Directory "${targetDirName}" already exists and is not empty.`));
|
|
@@ -5480,7 +5660,7 @@ async function scaffoldExample(exampleName, customName) {
|
|
|
5480
5660
|
console.log(chalk20.green(`Setting up example: ${example.title}`));
|
|
5481
5661
|
console.log(chalk20.gray(example.description));
|
|
5482
5662
|
console.log("");
|
|
5483
|
-
await
|
|
5663
|
+
await fs12.mkdir(targetPath, { recursive: true });
|
|
5484
5664
|
console.log(chalk20.green(`\u2713 Created directory: ${targetDirName}/`));
|
|
5485
5665
|
const examplePath = path17.join(EXAMPLES_DIR, exampleName);
|
|
5486
5666
|
await copyDirectoryRecursive(examplePath, targetPath);
|
|
@@ -5540,27 +5720,27 @@ async function selectExample() {
|
|
|
5540
5720
|
return choice;
|
|
5541
5721
|
}
|
|
5542
5722
|
async function copyDirectoryRecursive(src, dest) {
|
|
5543
|
-
const entries = await
|
|
5723
|
+
const entries = await fs12.readdir(src, { withFileTypes: true });
|
|
5544
5724
|
for (const entry of entries) {
|
|
5545
5725
|
const srcPath = path17.join(src, entry.name);
|
|
5546
5726
|
const destPath = path17.join(dest, entry.name);
|
|
5547
5727
|
if (entry.isDirectory()) {
|
|
5548
|
-
await
|
|
5728
|
+
await fs12.mkdir(destPath, { recursive: true });
|
|
5549
5729
|
await copyDirectoryRecursive(srcPath, destPath);
|
|
5550
5730
|
} else {
|
|
5551
|
-
await
|
|
5731
|
+
await fs12.copyFile(srcPath, destPath);
|
|
5552
5732
|
}
|
|
5553
5733
|
}
|
|
5554
5734
|
}
|
|
5555
5735
|
async function detectPackageManager() {
|
|
5556
5736
|
const cwd = process.cwd();
|
|
5557
5737
|
try {
|
|
5558
|
-
await
|
|
5738
|
+
await fs12.access(path17.join(cwd, "..", "pnpm-lock.yaml"));
|
|
5559
5739
|
return "pnpm";
|
|
5560
5740
|
} catch {
|
|
5561
5741
|
}
|
|
5562
5742
|
try {
|
|
5563
|
-
await
|
|
5743
|
+
await fs12.access(path17.join(cwd, "..", "yarn.lock"));
|
|
5564
5744
|
return "yarn";
|
|
5565
5745
|
} catch {
|
|
5566
5746
|
}
|
|
@@ -5589,8 +5769,8 @@ async function showFiles(specPath, options = {}) {
|
|
|
5589
5769
|
}
|
|
5590
5770
|
const subFiles = await loadSubFiles(spec.fullPath);
|
|
5591
5771
|
if (options.json) {
|
|
5592
|
-
const readmeStat2 = await
|
|
5593
|
-
const readmeContent2 = await
|
|
5772
|
+
const readmeStat2 = await fs12.stat(spec.filePath);
|
|
5773
|
+
const readmeContent2 = await fs12.readFile(spec.filePath, "utf-8");
|
|
5594
5774
|
const readmeTokens2 = await countTokens({ content: readmeContent2 });
|
|
5595
5775
|
const jsonOutput = {
|
|
5596
5776
|
spec: spec.name,
|
|
@@ -5617,9 +5797,9 @@ async function showFiles(specPath, options = {}) {
|
|
|
5617
5797
|
console.log(chalk20.cyan(`\u{1F4C4} Files in ${sanitizeUserInput(spec.name)}`));
|
|
5618
5798
|
console.log("");
|
|
5619
5799
|
console.log(chalk20.green("Required:"));
|
|
5620
|
-
const readmeStat = await
|
|
5800
|
+
const readmeStat = await fs12.stat(spec.filePath);
|
|
5621
5801
|
const readmeSize = formatSize(readmeStat.size);
|
|
5622
|
-
const readmeContent = await
|
|
5802
|
+
const readmeContent = await fs12.readFile(spec.filePath, "utf-8");
|
|
5623
5803
|
const readmeTokens = await countTokens({ content: readmeContent });
|
|
5624
5804
|
console.log(chalk20.green(` \u2713 README.md (${readmeSize}, ~${readmeTokens.total.toLocaleString()} tokens) Main spec`));
|
|
5625
5805
|
console.log("");
|
|
@@ -5640,7 +5820,7 @@ async function showFiles(specPath, options = {}) {
|
|
|
5640
5820
|
console.log(chalk20.cyan("Documents:"));
|
|
5641
5821
|
for (const file of documents) {
|
|
5642
5822
|
const size = formatSize(file.size);
|
|
5643
|
-
const content = await
|
|
5823
|
+
const content = await fs12.readFile(file.path, "utf-8");
|
|
5644
5824
|
const tokenCount = await countTokens({ content });
|
|
5645
5825
|
console.log(chalk20.cyan(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size}, ~${tokenCount.total.toLocaleString()} tokens)`));
|
|
5646
5826
|
}
|
|
@@ -6709,7 +6889,7 @@ async function validateSpecs(options = {}) {
|
|
|
6709
6889
|
for (const spec of specs) {
|
|
6710
6890
|
let content;
|
|
6711
6891
|
try {
|
|
6712
|
-
content = await
|
|
6892
|
+
content = await fs12.readFile(spec.filePath, "utf-8");
|
|
6713
6893
|
} catch (error) {
|
|
6714
6894
|
console.error(chalk20.red(`Error reading ${spec.filePath}:`), error);
|
|
6715
6895
|
continue;
|
|
@@ -6763,7 +6943,7 @@ function migrateCommand(inputPath, options = {}) {
|
|
|
6763
6943
|
async function migrateSpecs(inputPath, options = {}) {
|
|
6764
6944
|
const config = await loadConfig();
|
|
6765
6945
|
try {
|
|
6766
|
-
const stats = await
|
|
6946
|
+
const stats = await fs12.stat(inputPath);
|
|
6767
6947
|
if (!stats.isDirectory()) {
|
|
6768
6948
|
console.error("\x1B[31m\u274C Error:\x1B[0m Input path must be a directory");
|
|
6769
6949
|
process.exit(1);
|
|
@@ -6791,7 +6971,7 @@ async function migrateSpecs(inputPath, options = {}) {
|
|
|
6791
6971
|
async function scanDocuments(dirPath) {
|
|
6792
6972
|
const documents = [];
|
|
6793
6973
|
async function scanRecursive(currentPath) {
|
|
6794
|
-
const entries = await
|
|
6974
|
+
const entries = await fs12.readdir(currentPath, { withFileTypes: true });
|
|
6795
6975
|
for (const entry of entries) {
|
|
6796
6976
|
const fullPath = path17.join(currentPath, entry.name);
|
|
6797
6977
|
if (entry.isDirectory()) {
|
|
@@ -6800,7 +6980,7 @@ async function scanDocuments(dirPath) {
|
|
|
6800
6980
|
}
|
|
6801
6981
|
} else if (entry.isFile()) {
|
|
6802
6982
|
if (entry.name.endsWith(".md") || entry.name.endsWith(".markdown")) {
|
|
6803
|
-
const stats = await
|
|
6983
|
+
const stats = await fs12.stat(fullPath);
|
|
6804
6984
|
documents.push({
|
|
6805
6985
|
path: fullPath,
|
|
6806
6986
|
name: entry.name,
|
|
@@ -9164,7 +9344,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
9164
9344
|
if (resolvedPath) {
|
|
9165
9345
|
targetFile = path17.join(resolvedPath, filePart);
|
|
9166
9346
|
try {
|
|
9167
|
-
await
|
|
9347
|
+
await fs12.access(targetFile);
|
|
9168
9348
|
} catch {
|
|
9169
9349
|
return null;
|
|
9170
9350
|
}
|
|
@@ -9183,7 +9363,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
9183
9363
|
if (!targetFile) {
|
|
9184
9364
|
return null;
|
|
9185
9365
|
}
|
|
9186
|
-
const rawContent = await
|
|
9366
|
+
const rawContent = await fs12.readFile(targetFile, "utf-8");
|
|
9187
9367
|
const fileName = path17.basename(targetFile);
|
|
9188
9368
|
const isSubSpec = fileName !== config.structure.defaultFile;
|
|
9189
9369
|
let frontmatter = null;
|
|
@@ -9348,7 +9528,7 @@ async function openSpec(specPath, options = {}) {
|
|
|
9348
9528
|
if (resolvedPath) {
|
|
9349
9529
|
targetFile = path17.join(resolvedPath, filePart);
|
|
9350
9530
|
try {
|
|
9351
|
-
await
|
|
9531
|
+
await fs12.access(targetFile);
|
|
9352
9532
|
} catch {
|
|
9353
9533
|
targetFile = null;
|
|
9354
9534
|
}
|
|
@@ -11204,5 +11384,5 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
11204
11384
|
}
|
|
11205
11385
|
|
|
11206
11386
|
export { agentCommand, 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 };
|
|
11207
|
-
//# sourceMappingURL=chunk-
|
|
11208
|
-
//# sourceMappingURL=chunk-
|
|
11387
|
+
//# sourceMappingURL=chunk-2DAPKDV5.js.map
|
|
11388
|
+
//# sourceMappingURL=chunk-2DAPKDV5.js.map
|