lean-spec 0.2.7-dev.20251127024801 → 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-7D64YGN6.js → chunk-2DAPKDV5.js} +506 -119
- 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-7D64YGN6.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';
|
|
@@ -16,7 +16,7 @@ import stripAnsi from 'strip-ansi';
|
|
|
16
16
|
import matter from 'gray-matter';
|
|
17
17
|
import yaml from 'js-yaml';
|
|
18
18
|
import dayjs3 from 'dayjs';
|
|
19
|
-
import { select, checkbox } from '@inquirer/prompts';
|
|
19
|
+
import { select, checkbox, confirm } from '@inquirer/prompts';
|
|
20
20
|
import { marked } from 'marked';
|
|
21
21
|
import { markedTerminal } from 'marked-terminal';
|
|
22
22
|
|
|
@@ -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 = {};
|
|
@@ -4494,6 +4494,11 @@ var AI_TOOL_CONFIGS = {
|
|
|
4494
4494
|
detection: {
|
|
4495
4495
|
commands: ["aider"],
|
|
4496
4496
|
configDirs: [".aider"]
|
|
4497
|
+
},
|
|
4498
|
+
cli: {
|
|
4499
|
+
command: "aider",
|
|
4500
|
+
promptFlag: "--message"
|
|
4501
|
+
// Aider doesn't have a simple allow-all flag, uses different interaction model
|
|
4497
4502
|
}
|
|
4498
4503
|
},
|
|
4499
4504
|
claude: {
|
|
@@ -4505,6 +4510,15 @@ var AI_TOOL_CONFIGS = {
|
|
|
4505
4510
|
commands: ["claude"],
|
|
4506
4511
|
configDirs: [".claude"],
|
|
4507
4512
|
envVars: ["ANTHROPIC_API_KEY"]
|
|
4513
|
+
},
|
|
4514
|
+
cli: {
|
|
4515
|
+
command: "claude",
|
|
4516
|
+
promptFlag: "-p",
|
|
4517
|
+
// -p is the print/non-interactive flag
|
|
4518
|
+
promptIsPositional: true,
|
|
4519
|
+
// Prompt is positional argument
|
|
4520
|
+
allowToolsFlag: "--permission-mode acceptEdits"
|
|
4521
|
+
// Auto-accept edit operations
|
|
4508
4522
|
}
|
|
4509
4523
|
},
|
|
4510
4524
|
codex: {
|
|
@@ -4527,6 +4541,11 @@ var AI_TOOL_CONFIGS = {
|
|
|
4527
4541
|
detection: {
|
|
4528
4542
|
commands: ["copilot"],
|
|
4529
4543
|
envVars: ["GITHUB_TOKEN"]
|
|
4544
|
+
},
|
|
4545
|
+
cli: {
|
|
4546
|
+
command: "copilot",
|
|
4547
|
+
promptFlag: "-p",
|
|
4548
|
+
allowToolsFlag: "--allow-all-tools"
|
|
4530
4549
|
}
|
|
4531
4550
|
},
|
|
4532
4551
|
cursor: {
|
|
@@ -4557,6 +4576,13 @@ var AI_TOOL_CONFIGS = {
|
|
|
4557
4576
|
commands: ["gemini"],
|
|
4558
4577
|
configDirs: [".gemini"],
|
|
4559
4578
|
envVars: ["GOOGLE_API_KEY", "GEMINI_API_KEY"]
|
|
4579
|
+
},
|
|
4580
|
+
cli: {
|
|
4581
|
+
command: "gemini",
|
|
4582
|
+
promptFlag: "-p",
|
|
4583
|
+
// Note: deprecated but still works
|
|
4584
|
+
allowToolsFlag: "-y"
|
|
4585
|
+
// YOLO mode
|
|
4560
4586
|
}
|
|
4561
4587
|
},
|
|
4562
4588
|
opencode: {
|
|
@@ -4593,7 +4619,7 @@ async function configDirExists(dirName) {
|
|
|
4593
4619
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
4594
4620
|
if (!homeDir) return false;
|
|
4595
4621
|
try {
|
|
4596
|
-
await
|
|
4622
|
+
await fs12.access(path17.join(homeDir, dirName));
|
|
4597
4623
|
return true;
|
|
4598
4624
|
} catch {
|
|
4599
4625
|
return false;
|
|
@@ -4612,7 +4638,7 @@ async function extensionInstalled(extensionId) {
|
|
|
4612
4638
|
];
|
|
4613
4639
|
for (const extDir of extensionDirs) {
|
|
4614
4640
|
try {
|
|
4615
|
-
const entries = await
|
|
4641
|
+
const entries = await fs12.readdir(extDir);
|
|
4616
4642
|
if (entries.some((e) => e.toLowerCase().startsWith(extensionId.toLowerCase()))) {
|
|
4617
4643
|
return true;
|
|
4618
4644
|
}
|
|
@@ -4695,7 +4721,7 @@ async function createAgentToolSymlinks(cwd, selectedTools) {
|
|
|
4695
4721
|
const targetPath = path17.join(cwd, file);
|
|
4696
4722
|
try {
|
|
4697
4723
|
try {
|
|
4698
|
-
await
|
|
4724
|
+
await fs12.access(targetPath);
|
|
4699
4725
|
results.push({ file, skipped: true });
|
|
4700
4726
|
continue;
|
|
4701
4727
|
} catch {
|
|
@@ -4710,10 +4736,10 @@ async function createAgentToolSymlinks(cwd, selectedTools) {
|
|
|
4710
4736
|
|
|
4711
4737
|
See AGENTS.md for the full LeanSpec AI agent instructions.
|
|
4712
4738
|
`;
|
|
4713
|
-
await
|
|
4739
|
+
await fs12.writeFile(targetPath, windowsContent, "utf-8");
|
|
4714
4740
|
results.push({ file, created: true, error: "created as copy (Windows)" });
|
|
4715
4741
|
} else {
|
|
4716
|
-
await
|
|
4742
|
+
await fs12.symlink("AGENTS.md", targetPath);
|
|
4717
4743
|
results.push({ file, created: true });
|
|
4718
4744
|
}
|
|
4719
4745
|
} catch (error) {
|
|
@@ -4734,7 +4760,7 @@ async function detectExistingSystemPrompts(cwd) {
|
|
|
4734
4760
|
const found = [];
|
|
4735
4761
|
for (const file of commonFiles) {
|
|
4736
4762
|
try {
|
|
4737
|
-
await
|
|
4763
|
+
await fs12.access(path17.join(cwd, file));
|
|
4738
4764
|
found.push(file);
|
|
4739
4765
|
} catch {
|
|
4740
4766
|
}
|
|
@@ -4746,13 +4772,13 @@ async function handleExistingFiles(action, existingFiles, templateDir, cwd, vari
|
|
|
4746
4772
|
const filePath = path17.join(cwd, file);
|
|
4747
4773
|
const templateFilePath = path17.join(templateDir, file);
|
|
4748
4774
|
try {
|
|
4749
|
-
await
|
|
4775
|
+
await fs12.access(templateFilePath);
|
|
4750
4776
|
} catch {
|
|
4751
4777
|
continue;
|
|
4752
4778
|
}
|
|
4753
4779
|
if (action === "merge-ai" && file === "AGENTS.md") {
|
|
4754
|
-
const existing = await
|
|
4755
|
-
let template = await
|
|
4780
|
+
const existing = await fs12.readFile(filePath, "utf-8");
|
|
4781
|
+
let template = await fs12.readFile(templateFilePath, "utf-8");
|
|
4756
4782
|
for (const [key, value] of Object.entries(variables)) {
|
|
4757
4783
|
template = template.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
4758
4784
|
}
|
|
@@ -4788,8 +4814,8 @@ Create a single consolidated AGENTS.md that:
|
|
|
4788
4814
|
- Maintains clear structure and readability
|
|
4789
4815
|
- Removes any duplicate or conflicting guidance
|
|
4790
4816
|
`;
|
|
4791
|
-
await
|
|
4792
|
-
await
|
|
4817
|
+
await fs12.mkdir(path17.dirname(promptPath), { recursive: true });
|
|
4818
|
+
await fs12.writeFile(promptPath, aiPrompt, "utf-8");
|
|
4793
4819
|
console.log(chalk20.green(`\u2713 Created AI consolidation prompt`));
|
|
4794
4820
|
console.log(chalk20.cyan(` \u2192 ${promptPath}`));
|
|
4795
4821
|
console.log("");
|
|
@@ -4800,8 +4826,8 @@ Create a single consolidated AGENTS.md that:
|
|
|
4800
4826
|
console.log(chalk20.gray(" 4. Review and commit the result"));
|
|
4801
4827
|
console.log("");
|
|
4802
4828
|
} else if (action === "merge-append" && file === "AGENTS.md") {
|
|
4803
|
-
const existing = await
|
|
4804
|
-
let template = await
|
|
4829
|
+
const existing = await fs12.readFile(filePath, "utf-8");
|
|
4830
|
+
let template = await fs12.readFile(templateFilePath, "utf-8");
|
|
4805
4831
|
for (const [key, value] of Object.entries(variables)) {
|
|
4806
4832
|
template = template.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
4807
4833
|
}
|
|
@@ -4812,26 +4838,26 @@ Create a single consolidated AGENTS.md that:
|
|
|
4812
4838
|
## LeanSpec Integration
|
|
4813
4839
|
|
|
4814
4840
|
${template.split("\n").slice(1).join("\n")}`;
|
|
4815
|
-
await
|
|
4841
|
+
await fs12.writeFile(filePath, merged, "utf-8");
|
|
4816
4842
|
console.log(chalk20.green(`\u2713 Appended LeanSpec section to ${file}`));
|
|
4817
4843
|
console.log(chalk20.yellow(" \u26A0 Note: May be verbose. Consider consolidating later."));
|
|
4818
4844
|
} else if (action === "overwrite") {
|
|
4819
4845
|
const backupPath = `${filePath}.backup`;
|
|
4820
|
-
await
|
|
4846
|
+
await fs12.rename(filePath, backupPath);
|
|
4821
4847
|
console.log(chalk20.yellow(`\u2713 Backed up ${file} \u2192 ${file}.backup`));
|
|
4822
|
-
let content = await
|
|
4848
|
+
let content = await fs12.readFile(templateFilePath, "utf-8");
|
|
4823
4849
|
for (const [key, value] of Object.entries(variables)) {
|
|
4824
4850
|
content = content.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
4825
4851
|
}
|
|
4826
|
-
await
|
|
4852
|
+
await fs12.writeFile(filePath, content, "utf-8");
|
|
4827
4853
|
console.log(chalk20.green(`\u2713 Created new ${file}`));
|
|
4828
4854
|
console.log(chalk20.gray(` \u{1F4A1} Your original content is preserved in ${file}.backup`));
|
|
4829
4855
|
}
|
|
4830
4856
|
}
|
|
4831
4857
|
}
|
|
4832
4858
|
async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
|
|
4833
|
-
await
|
|
4834
|
-
const entries = await
|
|
4859
|
+
await fs12.mkdir(dest, { recursive: true });
|
|
4860
|
+
const entries = await fs12.readdir(src, { withFileTypes: true });
|
|
4835
4861
|
for (const entry of entries) {
|
|
4836
4862
|
const srcPath = path17.join(src, entry.name);
|
|
4837
4863
|
const destPath = path17.join(dest, entry.name);
|
|
@@ -4842,13 +4868,13 @@ async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
|
|
|
4842
4868
|
await copyDirectory(srcPath, destPath, skipFiles, variables);
|
|
4843
4869
|
} else {
|
|
4844
4870
|
try {
|
|
4845
|
-
await
|
|
4871
|
+
await fs12.access(destPath);
|
|
4846
4872
|
} catch {
|
|
4847
|
-
let content = await
|
|
4873
|
+
let content = await fs12.readFile(srcPath, "utf-8");
|
|
4848
4874
|
for (const [key, value] of Object.entries(variables)) {
|
|
4849
4875
|
content = content.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
|
4850
4876
|
}
|
|
4851
|
-
await
|
|
4877
|
+
await fs12.writeFile(destPath, content, "utf-8");
|
|
4852
4878
|
}
|
|
4853
4879
|
}
|
|
4854
4880
|
}
|
|
@@ -4856,7 +4882,7 @@ async function copyDirectory(src, dest, skipFiles = [], variables = {}) {
|
|
|
4856
4882
|
async function getProjectName2(cwd) {
|
|
4857
4883
|
try {
|
|
4858
4884
|
const packageJsonPath = path17.join(cwd, "package.json");
|
|
4859
|
-
const content = await
|
|
4885
|
+
const content = await fs12.readFile(packageJsonPath, "utf-8");
|
|
4860
4886
|
const pkg = JSON.parse(content);
|
|
4861
4887
|
if (pkg.name) {
|
|
4862
4888
|
return pkg.name;
|
|
@@ -4865,6 +4891,122 @@ async function getProjectName2(cwd) {
|
|
|
4865
4891
|
}
|
|
4866
4892
|
return path17.basename(cwd);
|
|
4867
4893
|
}
|
|
4894
|
+
function buildMergeCommand(cli, promptPath) {
|
|
4895
|
+
const prompt = `Follow the instructions in ${promptPath} to consolidate AGENTS.md. Read the prompt file, then edit AGENTS.md with the merged content.`;
|
|
4896
|
+
const args = [];
|
|
4897
|
+
if (cli.promptIsPositional) {
|
|
4898
|
+
args.push(prompt);
|
|
4899
|
+
if (cli.promptFlag) {
|
|
4900
|
+
args.push(cli.promptFlag);
|
|
4901
|
+
}
|
|
4902
|
+
} else {
|
|
4903
|
+
args.push(cli.promptFlag);
|
|
4904
|
+
args.push(prompt);
|
|
4905
|
+
}
|
|
4906
|
+
if (cli.allowToolsFlag) {
|
|
4907
|
+
const flagParts = cli.allowToolsFlag.split(" ");
|
|
4908
|
+
args.push(...flagParts);
|
|
4909
|
+
}
|
|
4910
|
+
return { command: cli.command, args };
|
|
4911
|
+
}
|
|
4912
|
+
async function executeMergeWithAI(cwd, promptPath, tool, timeoutMs = 12e4) {
|
|
4913
|
+
const config = AI_TOOL_CONFIGS[tool];
|
|
4914
|
+
if (!config.cli) {
|
|
4915
|
+
return {
|
|
4916
|
+
success: false,
|
|
4917
|
+
error: `Tool ${tool} does not have CLI configuration`
|
|
4918
|
+
};
|
|
4919
|
+
}
|
|
4920
|
+
const { command, args } = buildMergeCommand(config.cli, promptPath);
|
|
4921
|
+
const quotedArgs = args.map((arg) => {
|
|
4922
|
+
if (arg.includes(" ") || arg.includes('"') || arg.includes("'")) {
|
|
4923
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
4924
|
+
}
|
|
4925
|
+
return arg;
|
|
4926
|
+
});
|
|
4927
|
+
const fullCommand = `${command} ${quotedArgs.join(" ")}`;
|
|
4928
|
+
return new Promise((resolve3) => {
|
|
4929
|
+
let output = "";
|
|
4930
|
+
let errorOutput = "";
|
|
4931
|
+
let timedOut = false;
|
|
4932
|
+
const child = spawn(fullCommand, [], {
|
|
4933
|
+
cwd,
|
|
4934
|
+
shell: true,
|
|
4935
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
4936
|
+
});
|
|
4937
|
+
const timeout = setTimeout(() => {
|
|
4938
|
+
timedOut = true;
|
|
4939
|
+
child.kill("SIGTERM");
|
|
4940
|
+
}, timeoutMs);
|
|
4941
|
+
child.stdout?.on("data", (data) => {
|
|
4942
|
+
output += data.toString();
|
|
4943
|
+
process.stdout.write(data);
|
|
4944
|
+
});
|
|
4945
|
+
child.stderr?.on("data", (data) => {
|
|
4946
|
+
errorOutput += data.toString();
|
|
4947
|
+
process.stderr.write(data);
|
|
4948
|
+
});
|
|
4949
|
+
child.on("close", (code) => {
|
|
4950
|
+
clearTimeout(timeout);
|
|
4951
|
+
if (timedOut) {
|
|
4952
|
+
resolve3({
|
|
4953
|
+
success: false,
|
|
4954
|
+
output,
|
|
4955
|
+
error: "Command timed out",
|
|
4956
|
+
timedOut: true
|
|
4957
|
+
});
|
|
4958
|
+
} else if (code === 0) {
|
|
4959
|
+
resolve3({
|
|
4960
|
+
success: true,
|
|
4961
|
+
output
|
|
4962
|
+
});
|
|
4963
|
+
} else {
|
|
4964
|
+
resolve3({
|
|
4965
|
+
success: false,
|
|
4966
|
+
output,
|
|
4967
|
+
error: errorOutput || `Process exited with code ${code}`
|
|
4968
|
+
});
|
|
4969
|
+
}
|
|
4970
|
+
});
|
|
4971
|
+
child.on("error", (err) => {
|
|
4972
|
+
clearTimeout(timeout);
|
|
4973
|
+
resolve3({
|
|
4974
|
+
success: false,
|
|
4975
|
+
error: err.message
|
|
4976
|
+
});
|
|
4977
|
+
});
|
|
4978
|
+
});
|
|
4979
|
+
}
|
|
4980
|
+
async function getCliCapableDetectedTools() {
|
|
4981
|
+
const detectionResults = await detectInstalledAITools();
|
|
4982
|
+
const priorityOrder = ["copilot", "gemini", "claude", "aider", "codex"];
|
|
4983
|
+
const detected = detectionResults.filter((r) => r.detected && AI_TOOL_CONFIGS[r.tool].cli).map((r) => ({
|
|
4984
|
+
tool: r.tool,
|
|
4985
|
+
config: AI_TOOL_CONFIGS[r.tool],
|
|
4986
|
+
reasons: r.reasons
|
|
4987
|
+
}));
|
|
4988
|
+
detected.sort((a, b) => {
|
|
4989
|
+
const aIndex = priorityOrder.indexOf(a.tool);
|
|
4990
|
+
const bIndex = priorityOrder.indexOf(b.tool);
|
|
4991
|
+
const aPriority = aIndex === -1 ? 999 : aIndex;
|
|
4992
|
+
const bPriority = bIndex === -1 ? 999 : bIndex;
|
|
4993
|
+
return aPriority - bPriority;
|
|
4994
|
+
});
|
|
4995
|
+
return detected;
|
|
4996
|
+
}
|
|
4997
|
+
function getDisplayCommand(tool, promptPath) {
|
|
4998
|
+
const config = AI_TOOL_CONFIGS[tool];
|
|
4999
|
+
if (!config.cli) return "";
|
|
5000
|
+
const { command, args } = buildMergeCommand(config.cli, promptPath);
|
|
5001
|
+
const displayArgs = args.map((arg, index) => {
|
|
5002
|
+
const isPromptArg = config.cli.promptIsPositional ? index === 0 : index === 1;
|
|
5003
|
+
if (isPromptArg && arg.includes(" ")) {
|
|
5004
|
+
return `"${arg}"`;
|
|
5005
|
+
}
|
|
5006
|
+
return arg;
|
|
5007
|
+
});
|
|
5008
|
+
return `${command} ${displayArgs.join(" ")}`;
|
|
5009
|
+
}
|
|
4868
5010
|
|
|
4869
5011
|
// src/utils/examples.ts
|
|
4870
5012
|
var EXAMPLES = {
|
|
@@ -4916,8 +5058,213 @@ function exampleExists(name) {
|
|
|
4916
5058
|
var __dirname = path17.dirname(fileURLToPath(import.meta.url));
|
|
4917
5059
|
var TEMPLATES_DIR = path17.join(__dirname, "..", "templates");
|
|
4918
5060
|
var EXAMPLES_DIR = path17.join(TEMPLATES_DIR, "examples");
|
|
5061
|
+
async function attemptAutoMerge(cwd, promptPath, autoExecute) {
|
|
5062
|
+
const cliTools = await getCliCapableDetectedTools();
|
|
5063
|
+
if (cliTools.length === 0) {
|
|
5064
|
+
return false;
|
|
5065
|
+
}
|
|
5066
|
+
const tool = cliTools[0];
|
|
5067
|
+
const displayCmd = getDisplayCommand(tool.tool, promptPath);
|
|
5068
|
+
console.log("");
|
|
5069
|
+
console.log(chalk20.cyan(`\u{1F50D} Detected AI CLI: ${tool.config.description}`));
|
|
5070
|
+
for (const reason of tool.reasons) {
|
|
5071
|
+
console.log(chalk20.gray(` \u2514\u2500 ${reason}`));
|
|
5072
|
+
}
|
|
5073
|
+
console.log("");
|
|
5074
|
+
console.log(chalk20.gray(`Command: ${displayCmd}`));
|
|
5075
|
+
console.log("");
|
|
5076
|
+
let shouldExecute = autoExecute;
|
|
5077
|
+
if (!autoExecute) {
|
|
5078
|
+
shouldExecute = await confirm({
|
|
5079
|
+
message: "Run merge automatically using detected AI CLI?",
|
|
5080
|
+
default: true
|
|
5081
|
+
});
|
|
5082
|
+
}
|
|
5083
|
+
if (!shouldExecute) {
|
|
5084
|
+
console.log(chalk20.gray("Skipping auto-merge. Run the command above manually to merge."));
|
|
5085
|
+
return false;
|
|
5086
|
+
}
|
|
5087
|
+
console.log("");
|
|
5088
|
+
console.log(chalk20.cyan("\u{1F916} Running AI-assisted merge..."));
|
|
5089
|
+
console.log(chalk20.gray(" (This may take a moment)"));
|
|
5090
|
+
console.log("");
|
|
5091
|
+
const result = await executeMergeWithAI(cwd, promptPath, tool.tool);
|
|
5092
|
+
if (result.success) {
|
|
5093
|
+
console.log("");
|
|
5094
|
+
console.log(chalk20.green("\u2713 AGENTS.md merged successfully!"));
|
|
5095
|
+
console.log(chalk20.gray(" Review changes: git diff AGENTS.md"));
|
|
5096
|
+
return true;
|
|
5097
|
+
} else if (result.timedOut) {
|
|
5098
|
+
console.log("");
|
|
5099
|
+
console.log(chalk20.yellow("\u26A0 Merge timed out. Try running the command manually:"));
|
|
5100
|
+
console.log(chalk20.gray(` ${displayCmd}`));
|
|
5101
|
+
return false;
|
|
5102
|
+
} else {
|
|
5103
|
+
console.log("");
|
|
5104
|
+
console.log(chalk20.yellow(`\u26A0 Auto-merge encountered an issue: ${result.error}`));
|
|
5105
|
+
console.log(chalk20.gray(" Try running the command manually:"));
|
|
5106
|
+
console.log(chalk20.gray(` ${displayCmd}`));
|
|
5107
|
+
return false;
|
|
5108
|
+
}
|
|
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
|
+
}
|
|
4919
5266
|
function initCommand() {
|
|
4920
|
-
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) => {
|
|
4921
5268
|
if (options.list) {
|
|
4922
5269
|
await listExamples();
|
|
4923
5270
|
return;
|
|
@@ -4926,18 +5273,32 @@ function initCommand() {
|
|
|
4926
5273
|
await scaffoldExample(options.example, options.name);
|
|
4927
5274
|
return;
|
|
4928
5275
|
}
|
|
4929
|
-
await initProject(options.yes, options.template, options.agentTools);
|
|
5276
|
+
await initProject(options.yes, options.template, options.agentTools, options.force);
|
|
4930
5277
|
});
|
|
4931
5278
|
}
|
|
4932
|
-
async function initProject(skipPrompts = false, templateOption, agentToolsOption) {
|
|
5279
|
+
async function initProject(skipPrompts = false, templateOption, agentToolsOption, forceReinit = false) {
|
|
4933
5280
|
const cwd = process.cwd();
|
|
5281
|
+
const configPath = path17.join(cwd, ".lean-spec", "config.json");
|
|
5282
|
+
let isAlreadyInitialized = false;
|
|
4934
5283
|
try {
|
|
4935
|
-
await
|
|
4936
|
-
|
|
4937
|
-
console.log(chalk20.gray("To reinitialize, delete .lean-spec/ directory first."));
|
|
4938
|
-
return;
|
|
5284
|
+
await fs12.access(configPath);
|
|
5285
|
+
isAlreadyInitialized = true;
|
|
4939
5286
|
} catch {
|
|
4940
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
|
+
}
|
|
4941
5302
|
console.log("");
|
|
4942
5303
|
console.log(chalk20.green("Welcome to LeanSpec!"));
|
|
4943
5304
|
console.log("");
|
|
@@ -5010,7 +5371,7 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5010
5371
|
const templateConfigPath = path17.join(templateDir, "config.json");
|
|
5011
5372
|
let templateConfig;
|
|
5012
5373
|
try {
|
|
5013
|
-
const content = await
|
|
5374
|
+
const content = await fs12.readFile(templateConfigPath, "utf-8");
|
|
5014
5375
|
templateConfig = JSON.parse(content).config;
|
|
5015
5376
|
} catch {
|
|
5016
5377
|
console.error(chalk20.red(`Error: Template not found: ${templateName}`));
|
|
@@ -5079,30 +5440,39 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5079
5440
|
}
|
|
5080
5441
|
console.log("");
|
|
5081
5442
|
}
|
|
5082
|
-
const
|
|
5443
|
+
const symlinkTools = Object.entries(AI_TOOL_CONFIGS).filter(([, config]) => config.usesSymlink).map(([key, config]) => ({
|
|
5083
5444
|
name: config.description,
|
|
5084
5445
|
value: key,
|
|
5085
5446
|
checked: detectedDefaults.includes(key)
|
|
5086
5447
|
}));
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
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
|
+
}
|
|
5091
5461
|
}
|
|
5092
5462
|
const templatesDir = path17.join(cwd, ".lean-spec", "templates");
|
|
5093
5463
|
try {
|
|
5094
|
-
await
|
|
5464
|
+
await fs12.mkdir(templatesDir, { recursive: true });
|
|
5095
5465
|
} catch (error) {
|
|
5096
5466
|
console.error(chalk20.red("Error creating templates directory:"), error);
|
|
5097
5467
|
process.exit(1);
|
|
5098
5468
|
}
|
|
5099
5469
|
const templateFilesDir = path17.join(templateDir, "files");
|
|
5100
5470
|
try {
|
|
5101
|
-
const files = await
|
|
5471
|
+
const files = await fs12.readdir(templateFilesDir);
|
|
5102
5472
|
if (templateName === "standard") {
|
|
5103
5473
|
const readmePath = path17.join(templateFilesDir, "README.md");
|
|
5104
5474
|
const targetSpecPath = path17.join(templatesDir, "spec-template.md");
|
|
5105
|
-
await
|
|
5475
|
+
await fs12.copyFile(readmePath, targetSpecPath);
|
|
5106
5476
|
console.log(chalk20.green("\u2713 Created .lean-spec/templates/spec-template.md"));
|
|
5107
5477
|
templateConfig.template = "spec-template.md";
|
|
5108
5478
|
templateConfig.templates = {
|
|
@@ -5112,7 +5482,7 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5112
5482
|
for (const file of files) {
|
|
5113
5483
|
const srcPath = path17.join(templateFilesDir, file);
|
|
5114
5484
|
const destPath = path17.join(templatesDir, file);
|
|
5115
|
-
await
|
|
5485
|
+
await fs12.copyFile(srcPath, destPath);
|
|
5116
5486
|
}
|
|
5117
5487
|
console.log(chalk20.green(`\u2713 Created .lean-spec/templates/ with ${files.length} files`));
|
|
5118
5488
|
console.log(chalk20.gray(` Files: ${files.join(", ")}`));
|
|
@@ -5129,6 +5499,7 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5129
5499
|
console.log(chalk20.green("\u2713 Created .lean-spec/config.json"));
|
|
5130
5500
|
const existingFiles = await detectExistingSystemPrompts(cwd);
|
|
5131
5501
|
let skipFiles = [];
|
|
5502
|
+
let mergeCompleted = false;
|
|
5132
5503
|
if (existingFiles.length > 0) {
|
|
5133
5504
|
console.log("");
|
|
5134
5505
|
console.log(chalk20.yellow(`Found existing: ${existingFiles.join(", ")}`));
|
|
@@ -5136,6 +5507,13 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5136
5507
|
console.log(chalk20.gray("Using AI-Assisted Merge for existing AGENTS.md"));
|
|
5137
5508
|
const projectName2 = await getProjectName2(cwd);
|
|
5138
5509
|
await handleExistingFiles("merge-ai", existingFiles, templateDir, cwd, { project_name: projectName2 });
|
|
5510
|
+
const promptPath = path17.join(cwd, ".lean-spec", "MERGE-AGENTS-PROMPT.md");
|
|
5511
|
+
mergeCompleted = await attemptAutoMerge(
|
|
5512
|
+
cwd,
|
|
5513
|
+
promptPath,
|
|
5514
|
+
true
|
|
5515
|
+
/* skipPrompts */
|
|
5516
|
+
);
|
|
5139
5517
|
} else {
|
|
5140
5518
|
const action = await select({
|
|
5141
5519
|
message: "How would you like to handle existing AGENTS.md?",
|
|
@@ -5166,17 +5544,25 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5166
5544
|
await handleExistingFiles(action, existingFiles, templateDir, cwd, { project_name: projectName2 });
|
|
5167
5545
|
if (action === "skip") {
|
|
5168
5546
|
skipFiles = existingFiles;
|
|
5547
|
+
} else if (action === "merge-ai") {
|
|
5548
|
+
const promptPath = path17.join(cwd, ".lean-spec", "MERGE-AGENTS-PROMPT.md");
|
|
5549
|
+
mergeCompleted = await attemptAutoMerge(
|
|
5550
|
+
cwd,
|
|
5551
|
+
promptPath,
|
|
5552
|
+
false
|
|
5553
|
+
/* skipPrompts */
|
|
5554
|
+
);
|
|
5169
5555
|
}
|
|
5170
5556
|
}
|
|
5171
5557
|
}
|
|
5172
5558
|
const projectName = await getProjectName2(cwd);
|
|
5173
|
-
if (!skipFiles.includes("AGENTS.md")) {
|
|
5559
|
+
if (!skipFiles.includes("AGENTS.md") && !mergeCompleted) {
|
|
5174
5560
|
const agentsSourcePath = path17.join(templateDir, "AGENTS.md");
|
|
5175
5561
|
const agentsTargetPath = path17.join(cwd, "AGENTS.md");
|
|
5176
5562
|
try {
|
|
5177
|
-
let agentsContent = await
|
|
5563
|
+
let agentsContent = await fs12.readFile(agentsSourcePath, "utf-8");
|
|
5178
5564
|
agentsContent = agentsContent.replace(/\{project_name\}/g, projectName);
|
|
5179
|
-
await
|
|
5565
|
+
await fs12.writeFile(agentsTargetPath, agentsContent, "utf-8");
|
|
5180
5566
|
console.log(chalk20.green("\u2713 Created AGENTS.md"));
|
|
5181
5567
|
} catch (error) {
|
|
5182
5568
|
console.error(chalk20.red("Error copying AGENTS.md:"), error);
|
|
@@ -5197,7 +5583,7 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5197
5583
|
}
|
|
5198
5584
|
const filesDir = path17.join(templateDir, "files");
|
|
5199
5585
|
try {
|
|
5200
|
-
const filesToCopy = await
|
|
5586
|
+
const filesToCopy = await fs12.readdir(filesDir);
|
|
5201
5587
|
const hasOtherFiles = filesToCopy.some((f) => !f.match(/\.(md)$/i) || !["README.md", "DESIGN.md", "PLAN.md", "TEST.md"].includes(f));
|
|
5202
5588
|
if (hasOtherFiles) {
|
|
5203
5589
|
await copyDirectory(filesDir, cwd, [...skipFiles, "README.md", "DESIGN.md", "PLAN.md", "TEST.md"], { project_name: projectName });
|
|
@@ -5209,7 +5595,7 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5209
5595
|
}
|
|
5210
5596
|
const specsDir = path17.join(cwd, "specs");
|
|
5211
5597
|
try {
|
|
5212
|
-
await
|
|
5598
|
+
await fs12.mkdir(specsDir, { recursive: true });
|
|
5213
5599
|
console.log(chalk20.green("\u2713 Created specs/ directory"));
|
|
5214
5600
|
} catch (error) {
|
|
5215
5601
|
console.error(chalk20.red("Error creating specs directory:"), error);
|
|
@@ -5218,9 +5604,10 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
|
|
|
5218
5604
|
console.log("");
|
|
5219
5605
|
console.log(chalk20.green("\u2713 LeanSpec initialized!"));
|
|
5220
5606
|
console.log("");
|
|
5221
|
-
console.log("
|
|
5222
|
-
console.log(
|
|
5223
|
-
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"));
|
|
5224
5611
|
console.log("");
|
|
5225
5612
|
}
|
|
5226
5613
|
async function listExamples() {
|
|
@@ -5260,7 +5647,7 @@ async function scaffoldExample(exampleName, customName) {
|
|
|
5260
5647
|
const targetDirName = customName || exampleName;
|
|
5261
5648
|
const targetPath = path17.join(process.cwd(), targetDirName);
|
|
5262
5649
|
try {
|
|
5263
|
-
const files = await
|
|
5650
|
+
const files = await fs12.readdir(targetPath);
|
|
5264
5651
|
const nonGitFiles = files.filter((f) => f !== ".git");
|
|
5265
5652
|
if (nonGitFiles.length > 0) {
|
|
5266
5653
|
console.error(chalk20.red(`Error: Directory "${targetDirName}" already exists and is not empty.`));
|
|
@@ -5273,7 +5660,7 @@ async function scaffoldExample(exampleName, customName) {
|
|
|
5273
5660
|
console.log(chalk20.green(`Setting up example: ${example.title}`));
|
|
5274
5661
|
console.log(chalk20.gray(example.description));
|
|
5275
5662
|
console.log("");
|
|
5276
|
-
await
|
|
5663
|
+
await fs12.mkdir(targetPath, { recursive: true });
|
|
5277
5664
|
console.log(chalk20.green(`\u2713 Created directory: ${targetDirName}/`));
|
|
5278
5665
|
const examplePath = path17.join(EXAMPLES_DIR, exampleName);
|
|
5279
5666
|
await copyDirectoryRecursive(examplePath, targetPath);
|
|
@@ -5333,27 +5720,27 @@ async function selectExample() {
|
|
|
5333
5720
|
return choice;
|
|
5334
5721
|
}
|
|
5335
5722
|
async function copyDirectoryRecursive(src, dest) {
|
|
5336
|
-
const entries = await
|
|
5723
|
+
const entries = await fs12.readdir(src, { withFileTypes: true });
|
|
5337
5724
|
for (const entry of entries) {
|
|
5338
5725
|
const srcPath = path17.join(src, entry.name);
|
|
5339
5726
|
const destPath = path17.join(dest, entry.name);
|
|
5340
5727
|
if (entry.isDirectory()) {
|
|
5341
|
-
await
|
|
5728
|
+
await fs12.mkdir(destPath, { recursive: true });
|
|
5342
5729
|
await copyDirectoryRecursive(srcPath, destPath);
|
|
5343
5730
|
} else {
|
|
5344
|
-
await
|
|
5731
|
+
await fs12.copyFile(srcPath, destPath);
|
|
5345
5732
|
}
|
|
5346
5733
|
}
|
|
5347
5734
|
}
|
|
5348
5735
|
async function detectPackageManager() {
|
|
5349
5736
|
const cwd = process.cwd();
|
|
5350
5737
|
try {
|
|
5351
|
-
await
|
|
5738
|
+
await fs12.access(path17.join(cwd, "..", "pnpm-lock.yaml"));
|
|
5352
5739
|
return "pnpm";
|
|
5353
5740
|
} catch {
|
|
5354
5741
|
}
|
|
5355
5742
|
try {
|
|
5356
|
-
await
|
|
5743
|
+
await fs12.access(path17.join(cwd, "..", "yarn.lock"));
|
|
5357
5744
|
return "yarn";
|
|
5358
5745
|
} catch {
|
|
5359
5746
|
}
|
|
@@ -5382,8 +5769,8 @@ async function showFiles(specPath, options = {}) {
|
|
|
5382
5769
|
}
|
|
5383
5770
|
const subFiles = await loadSubFiles(spec.fullPath);
|
|
5384
5771
|
if (options.json) {
|
|
5385
|
-
const readmeStat2 = await
|
|
5386
|
-
const readmeContent2 = await
|
|
5772
|
+
const readmeStat2 = await fs12.stat(spec.filePath);
|
|
5773
|
+
const readmeContent2 = await fs12.readFile(spec.filePath, "utf-8");
|
|
5387
5774
|
const readmeTokens2 = await countTokens({ content: readmeContent2 });
|
|
5388
5775
|
const jsonOutput = {
|
|
5389
5776
|
spec: spec.name,
|
|
@@ -5410,9 +5797,9 @@ async function showFiles(specPath, options = {}) {
|
|
|
5410
5797
|
console.log(chalk20.cyan(`\u{1F4C4} Files in ${sanitizeUserInput(spec.name)}`));
|
|
5411
5798
|
console.log("");
|
|
5412
5799
|
console.log(chalk20.green("Required:"));
|
|
5413
|
-
const readmeStat = await
|
|
5800
|
+
const readmeStat = await fs12.stat(spec.filePath);
|
|
5414
5801
|
const readmeSize = formatSize(readmeStat.size);
|
|
5415
|
-
const readmeContent = await
|
|
5802
|
+
const readmeContent = await fs12.readFile(spec.filePath, "utf-8");
|
|
5416
5803
|
const readmeTokens = await countTokens({ content: readmeContent });
|
|
5417
5804
|
console.log(chalk20.green(` \u2713 README.md (${readmeSize}, ~${readmeTokens.total.toLocaleString()} tokens) Main spec`));
|
|
5418
5805
|
console.log("");
|
|
@@ -5433,7 +5820,7 @@ async function showFiles(specPath, options = {}) {
|
|
|
5433
5820
|
console.log(chalk20.cyan("Documents:"));
|
|
5434
5821
|
for (const file of documents) {
|
|
5435
5822
|
const size = formatSize(file.size);
|
|
5436
|
-
const content = await
|
|
5823
|
+
const content = await fs12.readFile(file.path, "utf-8");
|
|
5437
5824
|
const tokenCount = await countTokens({ content });
|
|
5438
5825
|
console.log(chalk20.cyan(` \u2713 ${sanitizeUserInput(file.name).padEnd(20)} (${size}, ~${tokenCount.total.toLocaleString()} tokens)`));
|
|
5439
5826
|
}
|
|
@@ -6502,7 +6889,7 @@ async function validateSpecs(options = {}) {
|
|
|
6502
6889
|
for (const spec of specs) {
|
|
6503
6890
|
let content;
|
|
6504
6891
|
try {
|
|
6505
|
-
content = await
|
|
6892
|
+
content = await fs12.readFile(spec.filePath, "utf-8");
|
|
6506
6893
|
} catch (error) {
|
|
6507
6894
|
console.error(chalk20.red(`Error reading ${spec.filePath}:`), error);
|
|
6508
6895
|
continue;
|
|
@@ -6556,7 +6943,7 @@ function migrateCommand(inputPath, options = {}) {
|
|
|
6556
6943
|
async function migrateSpecs(inputPath, options = {}) {
|
|
6557
6944
|
const config = await loadConfig();
|
|
6558
6945
|
try {
|
|
6559
|
-
const stats = await
|
|
6946
|
+
const stats = await fs12.stat(inputPath);
|
|
6560
6947
|
if (!stats.isDirectory()) {
|
|
6561
6948
|
console.error("\x1B[31m\u274C Error:\x1B[0m Input path must be a directory");
|
|
6562
6949
|
process.exit(1);
|
|
@@ -6584,7 +6971,7 @@ async function migrateSpecs(inputPath, options = {}) {
|
|
|
6584
6971
|
async function scanDocuments(dirPath) {
|
|
6585
6972
|
const documents = [];
|
|
6586
6973
|
async function scanRecursive(currentPath) {
|
|
6587
|
-
const entries = await
|
|
6974
|
+
const entries = await fs12.readdir(currentPath, { withFileTypes: true });
|
|
6588
6975
|
for (const entry of entries) {
|
|
6589
6976
|
const fullPath = path17.join(currentPath, entry.name);
|
|
6590
6977
|
if (entry.isDirectory()) {
|
|
@@ -6593,7 +6980,7 @@ async function scanDocuments(dirPath) {
|
|
|
6593
6980
|
}
|
|
6594
6981
|
} else if (entry.isFile()) {
|
|
6595
6982
|
if (entry.name.endsWith(".md") || entry.name.endsWith(".markdown")) {
|
|
6596
|
-
const stats = await
|
|
6983
|
+
const stats = await fs12.stat(fullPath);
|
|
6597
6984
|
documents.push({
|
|
6598
6985
|
path: fullPath,
|
|
6599
6986
|
name: entry.name,
|
|
@@ -8957,7 +9344,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
8957
9344
|
if (resolvedPath) {
|
|
8958
9345
|
targetFile = path17.join(resolvedPath, filePart);
|
|
8959
9346
|
try {
|
|
8960
|
-
await
|
|
9347
|
+
await fs12.access(targetFile);
|
|
8961
9348
|
} catch {
|
|
8962
9349
|
return null;
|
|
8963
9350
|
}
|
|
@@ -8976,7 +9363,7 @@ async function readSpecContent(specPath, cwd = process.cwd()) {
|
|
|
8976
9363
|
if (!targetFile) {
|
|
8977
9364
|
return null;
|
|
8978
9365
|
}
|
|
8979
|
-
const rawContent = await
|
|
9366
|
+
const rawContent = await fs12.readFile(targetFile, "utf-8");
|
|
8980
9367
|
const fileName = path17.basename(targetFile);
|
|
8981
9368
|
const isSubSpec = fileName !== config.structure.defaultFile;
|
|
8982
9369
|
let frontmatter = null;
|
|
@@ -9141,7 +9528,7 @@ async function openSpec(specPath, options = {}) {
|
|
|
9141
9528
|
if (resolvedPath) {
|
|
9142
9529
|
targetFile = path17.join(resolvedPath, filePart);
|
|
9143
9530
|
try {
|
|
9144
|
-
await
|
|
9531
|
+
await fs12.access(targetFile);
|
|
9145
9532
|
} catch {
|
|
9146
9533
|
targetFile = null;
|
|
9147
9534
|
}
|
|
@@ -10997,5 +11384,5 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
10997
11384
|
}
|
|
10998
11385
|
|
|
10999
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 };
|
|
11000
|
-
//# sourceMappingURL=chunk-
|
|
11001
|
-
//# sourceMappingURL=chunk-
|
|
11387
|
+
//# sourceMappingURL=chunk-2DAPKDV5.js.map
|
|
11388
|
+
//# sourceMappingURL=chunk-2DAPKDV5.js.map
|