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.
@@ -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 fs11 from 'fs/promises';
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 fs11.readFile(configPath, "utf-8");
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 fs11.mkdir(configDir, { recursive: true });
59
- await fs11.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
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 fs11.readdir(specDir, { withFileTypes: true });
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 fs11.stat(filePath);
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 fs11.readFile(filePath, "utf-8");
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 fs11.access(specsDir);
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 fs11.readdir(dir, { withFileTypes: true });
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 fs11.readFile(specFile, "utf-8");
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 fs11.access(fullPath);
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 fs11.readFile(specFile, "utf-8");
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 fs11.readdir(dir, { withFileTypes: true });
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 fs11.access(specPath);
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 fs11.access(cwdPath);
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 fs11.access(specsPath);
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 fs11.readdir(dir, { withFileTypes: true });
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 fs11.readdir(dir, { withFileTypes: true });
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 fs11.readdir(specDir);
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 fs11.readFile(filePath, "utf-8");
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 fs11.readFile(spec.filePath, "utf-8");
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 fs11.mkdir(path17.join(cwd, ".worktrees"), { recursive: true });
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 fs11.readFile(filePath, "utf-8");
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 fs11.stat(specPath);
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 fs11.readdir(specPath);
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 fs11.readFile(filePath, "utf-8");
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 fs11.readdir(specDir);
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 fs11.readFile(packageJsonPath, "utf-8");
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 fs11.mkdir(specsDir, { recursive: true });
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 fs11.access(specDir);
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 fs11.mkdir(specDir, { recursive: true });
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 fs11.access(templatePath);
3553
+ await fs12.access(templatePath);
3554
3554
  } catch {
3555
3555
  const legacyPath = path17.join(templatesDir, "spec-template.md");
3556
3556
  try {
3557
- await fs11.access(legacyPath);
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 fs11.access(readmePath);
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 fs11.readFile(templatePath, "utf-8");
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 fs11.writeFile(specFile, content, "utf-8");
3618
+ await fs12.writeFile(specFile, content, "utf-8");
3619
3619
  try {
3620
- const templateFiles = await fs11.readdir(templatesDir);
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 fs11.readFile(srcPath, "utf-8");
3628
+ let fileContent = await fs12.readFile(srcPath, "utf-8");
3629
3629
  fileContent = resolveVariables(fileContent, varContext);
3630
- await fs11.writeFile(destPath, fileContent, "utf-8");
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 fs11.mkdir(archiveDir, { recursive: true });
3675
+ await fs12.mkdir(archiveDir, { recursive: true });
3676
3676
  const specName = path17.basename(resolvedPath);
3677
3677
  const archivePath = path17.join(archiveDir, specName);
3678
- await fs11.rename(resolvedPath, archivePath);
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 fs11.access(specsDir);
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 fs11.access(templatesDir);
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 fs11.readdir(templatesDir);
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 fs11.stat(filePath);
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 fs11.readFile(templatePath, "utf-8");
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 fs11.access(templatePath);
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 fs11.access(sourcePath);
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 fs11.copyFile(sourcePath, targetPath);
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 fs11.access(path17.join(homeDir, dirName));
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 fs11.readdir(extDir);
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 fs11.access(targetPath);
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 fs11.writeFile(targetPath, windowsContent, "utf-8");
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 fs11.symlink("AGENTS.md", targetPath);
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 fs11.access(path17.join(cwd, file));
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 fs11.access(templateFilePath);
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 fs11.readFile(filePath, "utf-8");
4755
- let template = await fs11.readFile(templateFilePath, "utf-8");
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 fs11.mkdir(path17.dirname(promptPath), { recursive: true });
4792
- await fs11.writeFile(promptPath, aiPrompt, "utf-8");
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 fs11.readFile(filePath, "utf-8");
4804
- let template = await fs11.readFile(templateFilePath, "utf-8");
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 fs11.writeFile(filePath, merged, "utf-8");
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 fs11.rename(filePath, backupPath);
4846
+ await fs12.rename(filePath, backupPath);
4821
4847
  console.log(chalk20.yellow(`\u2713 Backed up ${file} \u2192 ${file}.backup`));
4822
- let content = await fs11.readFile(templateFilePath, "utf-8");
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 fs11.writeFile(filePath, content, "utf-8");
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 fs11.mkdir(dest, { recursive: true });
4834
- const entries = await fs11.readdir(src, { withFileTypes: true });
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 fs11.access(destPath);
4871
+ await fs12.access(destPath);
4846
4872
  } catch {
4847
- let content = await fs11.readFile(srcPath, "utf-8");
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 fs11.writeFile(destPath, content, "utf-8");
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 fs11.readFile(packageJsonPath, "utf-8");
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 fs11.access(path17.join(cwd, ".lean-spec", "config.json"));
4936
- console.log(chalk20.yellow("\u26A0 LeanSpec already initialized in this directory."));
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 fs11.readFile(templateConfigPath, "utf-8");
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 toolChoices = Object.entries(AI_TOOL_CONFIGS).map(([key, config]) => ({
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
- selectedAgentTools = await checkbox({
5088
- message: "Which AI tools do you use? (creates symlinks for tool-specific instruction files)",
5089
- choices: toolChoices
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 fs11.mkdir(templatesDir, { recursive: true });
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 fs11.readdir(templateFilesDir);
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 fs11.copyFile(readmePath, targetSpecPath);
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 fs11.copyFile(srcPath, destPath);
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 fs11.readFile(agentsSourcePath, "utf-8");
5563
+ let agentsContent = await fs12.readFile(agentsSourcePath, "utf-8");
5178
5564
  agentsContent = agentsContent.replace(/\{project_name\}/g, projectName);
5179
- await fs11.writeFile(agentsTargetPath, agentsContent, "utf-8");
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 fs11.readdir(filesDir);
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 fs11.mkdir(specsDir, { recursive: true });
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("Next steps:");
5222
- console.log(chalk20.cyan(" 1. Edit AGENTS.md") + chalk20.gray(' \u2192 Fill in the "\u{1F4CB} Project Context" section'));
5223
- console.log(chalk20.cyan(" 2. Create your first spec:") + chalk20.gray(" lean-spec create my-feature"));
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 fs11.readdir(targetPath);
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 fs11.mkdir(targetPath, { recursive: true });
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 fs11.readdir(src, { withFileTypes: true });
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 fs11.mkdir(destPath, { recursive: true });
5728
+ await fs12.mkdir(destPath, { recursive: true });
5342
5729
  await copyDirectoryRecursive(srcPath, destPath);
5343
5730
  } else {
5344
- await fs11.copyFile(srcPath, destPath);
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 fs11.access(path17.join(cwd, "..", "pnpm-lock.yaml"));
5738
+ await fs12.access(path17.join(cwd, "..", "pnpm-lock.yaml"));
5352
5739
  return "pnpm";
5353
5740
  } catch {
5354
5741
  }
5355
5742
  try {
5356
- await fs11.access(path17.join(cwd, "..", "yarn.lock"));
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 fs11.stat(spec.filePath);
5386
- const readmeContent2 = await fs11.readFile(spec.filePath, "utf-8");
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 fs11.stat(spec.filePath);
5800
+ const readmeStat = await fs12.stat(spec.filePath);
5414
5801
  const readmeSize = formatSize(readmeStat.size);
5415
- const readmeContent = await fs11.readFile(spec.filePath, "utf-8");
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 fs11.readFile(file.path, "utf-8");
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 fs11.readFile(spec.filePath, "utf-8");
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 fs11.stat(inputPath);
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 fs11.readdir(currentPath, { withFileTypes: true });
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 fs11.stat(fullPath);
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 fs11.access(targetFile);
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 fs11.readFile(targetFile, "utf-8");
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 fs11.access(targetFile);
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-7D64YGN6.js.map
11001
- //# sourceMappingURL=chunk-7D64YGN6.js.map
11387
+ //# sourceMappingURL=chunk-2DAPKDV5.js.map
11388
+ //# sourceMappingURL=chunk-2DAPKDV5.js.map