speclore 0.1.4 → 0.1.7

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/index.js CHANGED
@@ -1,12 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __esm = (fn, res, err) => function __init() {
4
- if (err) throw err[0];
5
- try {
6
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
- } catch (e) {
8
- throw err = [e], e;
9
- }
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
5
  };
11
6
  var __export = (target, all) => {
12
7
  for (var name in all)
@@ -323,7 +318,7 @@ function detectAITools(projectRoot2) {
323
318
  detected: existsSync2(join2(projectRoot2, ".cursor")),
324
319
  configFiles: cursorConfigs
325
320
  });
326
- const claudeFiles = [".claude/", ".mcp.json", "CLAUDE.md"];
321
+ const claudeFiles = [".claude/", "CLAUDE.md"];
327
322
  const claudeConfigs = claudeFiles.filter((f) => existsSync2(join2(projectRoot2, f)));
328
323
  tools.push({
329
324
  tool: "claude",
@@ -759,7 +754,7 @@ var init_graph_builder = __esm({
759
754
  });
760
755
 
761
756
  // src/core/context-engine/context-writer.ts
762
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, statSync as statSync2, mkdirSync as mkdirSync4 } from "fs";
757
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, statSync as statSync2, mkdirSync as mkdirSync5 } from "fs";
763
758
  import { join as join10 } from "path";
764
759
  import { execFileSync } from "child_process";
765
760
  function buildContext(projectRoot2, config) {
@@ -830,7 +825,7 @@ function loadContext(specLoreDir) {
830
825
  }
831
826
  function writeContextFile(specLoreDir, context) {
832
827
  if (!existsSync7(specLoreDir)) {
833
- mkdirSync4(specLoreDir, { recursive: true });
828
+ mkdirSync5(specLoreDir, { recursive: true });
834
829
  }
835
830
  const contextPath = join10(specLoreDir, CONTEXT_FILENAME);
836
831
  writeFileSync5(contextPath, JSON.stringify(context, null, 2), "utf-8");
@@ -1021,7 +1016,7 @@ var init_cost_tracker = __esm({
1021
1016
  });
1022
1017
 
1023
1018
  // src/core/state-manager/index.ts
1024
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
1019
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
1025
1020
  import { join as join12 } from "path";
1026
1021
  import yaml2 from "js-yaml";
1027
1022
  import { globSync as globSync2 } from "glob";
@@ -1070,7 +1065,7 @@ var init_state_manager = __esm({
1070
1065
  save(state) {
1071
1066
  const dir = join12(this.projectRoot, ".speclore");
1072
1067
  if (!existsSync8(dir)) {
1073
- mkdirSync5(dir, { recursive: true });
1068
+ mkdirSync6(dir, { recursive: true });
1074
1069
  }
1075
1070
  writeFileSync6(this.statePath, yaml2.dump(state, { lineWidth: 120 }), "utf-8");
1076
1071
  }
@@ -1411,6 +1406,13 @@ function formatCellValue(value) {
1411
1406
  if (typeof value === "string") return value;
1412
1407
  if (typeof value === "number" || typeof value === "boolean") return String(value);
1413
1408
  if (value instanceof Date) return value.toISOString();
1409
+ if (typeof value === "object" && value !== null && "v" in value) {
1410
+ const cell = value;
1411
+ if (cell.w != null) return cell.w;
1412
+ if (cell.v == null) return "";
1413
+ if (typeof cell.v === "string" || typeof cell.v === "number" || typeof cell.v === "boolean") return String(cell.v);
1414
+ return JSON.stringify(cell.v);
1415
+ }
1414
1416
  return "";
1415
1417
  }
1416
1418
  var init_excel_utils = __esm({
@@ -1420,7 +1422,43 @@ var init_excel_utils = __esm({
1420
1422
  });
1421
1423
 
1422
1424
  // src/plugins/builtin/xlsx-reader.ts
1423
- import ExcelJS from "exceljs";
1425
+ import { readFile, read, utils } from "xlsx";
1426
+ function parseWorkbook(workbook) {
1427
+ const requirements = [];
1428
+ for (const sheetName of workbook.SheetNames) {
1429
+ const sheet = workbook.Sheets[sheetName];
1430
+ if (!sheet) continue;
1431
+ const allRows = utils.sheet_to_json(sheet, { header: 1, defval: "" });
1432
+ if (allRows.length === 0) continue;
1433
+ const headers = allRows[0].map((cell) => formatCellValue(cell));
1434
+ const dataRows = allRows.slice(1);
1435
+ const rows = dataRows.map((row) => {
1436
+ const obj = {};
1437
+ row.forEach((cell, colNumber) => {
1438
+ const key = headers[colNumber] ?? `Col${colNumber + 1}`;
1439
+ obj[key] = formatCellValue(cell);
1440
+ });
1441
+ return obj;
1442
+ });
1443
+ for (let i = 0; i < rows.length; i++) {
1444
+ const row = rows[i];
1445
+ const title = row["Title"] ?? row["title"] ?? row["Name"] ?? row["name"] ?? row["ID"] ?? `Row ${i + 1}`;
1446
+ const description = row["Description"] ?? row["description"] ?? row["Desc"] ?? "";
1447
+ const ac = row["Acceptance Criteria"] ?? row["acceptance"] ?? row["AC"] ?? "";
1448
+ if (description || title) {
1449
+ requirements.push({
1450
+ id: `${sheetName}/${i + 1}`,
1451
+ title: String(title),
1452
+ description: String(description),
1453
+ acceptanceCriteria: ac ? String(ac).split("\n").filter(Boolean) : void 0,
1454
+ rawContent: JSON.stringify(row),
1455
+ confidence: 0.7
1456
+ });
1457
+ }
1458
+ }
1459
+ }
1460
+ return requirements;
1461
+ }
1424
1462
  var XlsxReader;
1425
1463
  var init_xlsx_reader = __esm({
1426
1464
  "src/plugins/builtin/xlsx-reader.ts"() {
@@ -1432,46 +1470,9 @@ var init_xlsx_reader = __esm({
1432
1470
  canRead(source) {
1433
1471
  return /\.xlsx$/i.test(source) || /\.xls$/i.test(source);
1434
1472
  }
1435
- async read(source) {
1436
- const workbook = new ExcelJS.Workbook();
1437
- await workbook.xlsx.readFile(source);
1438
- const requirements = [];
1439
- for (const sheet of workbook.worksheets) {
1440
- const sheetName = sheet.name;
1441
- const headers = [];
1442
- const rows = [];
1443
- sheet.eachRow((row, rowNumber) => {
1444
- if (rowNumber === 1) {
1445
- row.eachCell((cell, colNumber) => {
1446
- headers[colNumber - 1] = formatCellValue(cell.value);
1447
- });
1448
- } else {
1449
- const obj = {};
1450
- row.eachCell((cell, colNumber) => {
1451
- const key = headers[colNumber - 1] ?? `Col${colNumber}`;
1452
- obj[key] = formatCellValue(cell.value);
1453
- });
1454
- rows.push(obj);
1455
- }
1456
- });
1457
- for (let i = 0; i < rows.length; i++) {
1458
- const row = rows[i];
1459
- const title = row["Title"] ?? row["title"] ?? row["Name"] ?? row["name"] ?? row["ID"] ?? `Row ${i + 1}`;
1460
- const description = row["Description"] ?? row["description"] ?? row["Desc"] ?? "";
1461
- const ac = row["Acceptance Criteria"] ?? row["acceptance"] ?? row["AC"] ?? "";
1462
- if (description || title) {
1463
- requirements.push({
1464
- id: `${sheetName}/${i + 1}`,
1465
- title: String(title),
1466
- description: String(description),
1467
- acceptanceCriteria: ac ? String(ac).split("\n").filter(Boolean) : void 0,
1468
- rawContent: JSON.stringify(row),
1469
- confidence: 0.7
1470
- });
1471
- }
1472
- }
1473
- }
1474
- return requirements;
1473
+ read(source) {
1474
+ const workbook = readFile(source);
1475
+ return Promise.resolve(parseWorkbook(workbook));
1475
1476
  }
1476
1477
  };
1477
1478
  }
@@ -1489,21 +1490,35 @@ var init_pdf_reader = __esm({
1489
1490
  return /\.pdf$/i.test(source);
1490
1491
  }
1491
1492
  async read(source) {
1492
- let pdfParse;
1493
+ let pdfjsLib;
1493
1494
  try {
1494
- const mod = await import("pdf-parse");
1495
- pdfParse = mod.default ?? mod;
1495
+ pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
1496
1496
  } catch {
1497
- throw new Error("pdf-parse package is required for PDF support. Install: npm i pdf-parse");
1497
+ throw new Error("pdfjs-dist package is required for PDF support. Install: npm i pdfjs-dist");
1498
1498
  }
1499
- const { readFileSync: readFileSync22 } = await import("fs");
1500
- const buffer = readFileSync22(source);
1501
- const data = await pdfParse(buffer);
1499
+ const { readFileSync: readFileSync24 } = await import("fs");
1500
+ const buffer = readFileSync24(source);
1501
+ const uint8 = new Uint8Array(buffer);
1502
+ const doc = await pdfjsLib.getDocument({
1503
+ data: uint8,
1504
+ useWorkerFetch: false,
1505
+ isEvalSupported: false
1506
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1507
+ }).promise;
1508
+ const textParts = [];
1509
+ for (let i = 1; i <= doc.numPages; i++) {
1510
+ const page = await doc.getPage(i);
1511
+ const textContent = await page.getTextContent();
1512
+ const pageText = textContent.items.map((item) => item.str).join("");
1513
+ if (pageText) textParts.push(pageText);
1514
+ }
1515
+ doc.destroy();
1516
+ const text = textParts.join("\n");
1502
1517
  return [{
1503
1518
  id: source.replace(/\\/g, "/").replace(/\.pdf$/i, ""),
1504
1519
  title: source.split("/").pop()?.replace(/\.pdf$/i, "") ?? "Untitled",
1505
- description: data.text,
1506
- rawContent: data.text,
1520
+ description: text,
1521
+ rawContent: text,
1507
1522
  confidence: 0.75
1508
1523
  }];
1509
1524
  }
@@ -2099,13 +2114,13 @@ var init_image_reader = __esm({
2099
2114
  return /\.(png|jpe?g|webp)$/i.test(source);
2100
2115
  }
2101
2116
  async read(source) {
2102
- const { existsSync: existsSync30, readFileSync: readFileSync22 } = await import("fs");
2103
- if (!existsSync30(source)) {
2117
+ const { existsSync: existsSync31, readFileSync: readFileSync24 } = await import("fs");
2118
+ if (!existsSync31(source)) {
2104
2119
  throw new Error(`Image file not found: ${source}`);
2105
2120
  }
2106
2121
  const ext = source.split(".").pop()?.toLowerCase() ?? "png";
2107
2122
  const mimeType = ext === "jpg" ? "image/jpeg" : `image/${ext}`;
2108
- const buffer = readFileSync22(source);
2123
+ const buffer = readFileSync24(source);
2109
2124
  let text;
2110
2125
  try {
2111
2126
  const { createProvider: createProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
@@ -2134,7 +2149,7 @@ var init_image_reader = __esm({
2134
2149
  });
2135
2150
 
2136
2151
  // src/plugins/builtin/cursor-writer.ts
2137
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, rmSync } from "fs";
2152
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync11, rmSync } from "fs";
2138
2153
  import { join as join14 } from "path";
2139
2154
  var CursorWriter;
2140
2155
  var init_cursor_writer = __esm({
@@ -2150,7 +2165,7 @@ var init_cursor_writer = __esm({
2150
2165
  write(constraints) {
2151
2166
  this.projectRoot = constraints.projectRoot;
2152
2167
  const rulesDir = join14(constraints.projectRoot, ".cursor", "rules");
2153
- mkdirSync6(rulesDir, { recursive: true });
2168
+ mkdirSync7(rulesDir, { recursive: true });
2154
2169
  const frontmatter = [
2155
2170
  "---",
2156
2171
  "description: SpecLore coding constraints \u2014 auto-generated",
@@ -2229,7 +2244,7 @@ var init_cursor_writer = __esm({
2229
2244
  });
2230
2245
 
2231
2246
  // src/plugins/builtin/claude-writer.ts
2232
- import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync12, rmSync as rmSync2 } from "fs";
2247
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync12, rmSync as rmSync2 } from "fs";
2233
2248
  import { join as join15 } from "path";
2234
2249
  var ClaudeWriter;
2235
2250
  var init_claude_writer = __esm({
@@ -2245,7 +2260,7 @@ var init_claude_writer = __esm({
2245
2260
  write(constraints) {
2246
2261
  this.projectRoot = constraints.projectRoot;
2247
2262
  const rulesDir = join15(constraints.projectRoot, ".claude", "rules");
2248
- mkdirSync7(rulesDir, { recursive: true });
2263
+ mkdirSync8(rulesDir, { recursive: true });
2249
2264
  const content = this.buildMarkdown(constraints);
2250
2265
  writeFileSync8(join15(rulesDir, "speclore.md"), content, "utf-8");
2251
2266
  return Promise.resolve();
@@ -2311,7 +2326,7 @@ var init_claude_writer = __esm({
2311
2326
  });
2312
2327
 
2313
2328
  // src/plugins/builtin/qoder-writer.ts
2314
- import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, existsSync as existsSync13, rmSync as rmSync3 } from "fs";
2329
+ import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, existsSync as existsSync13, rmSync as rmSync3 } from "fs";
2315
2330
  import { join as join16 } from "path";
2316
2331
  var QoderWriter;
2317
2332
  var init_qoder_writer = __esm({
@@ -2327,7 +2342,7 @@ var init_qoder_writer = __esm({
2327
2342
  write(constraints) {
2328
2343
  this.projectRoot = constraints.projectRoot;
2329
2344
  const rulesDir = join16(constraints.projectRoot, ".qoder", "rules");
2330
- mkdirSync8(rulesDir, { recursive: true });
2345
+ mkdirSync9(rulesDir, { recursive: true });
2331
2346
  const content = this.buildMarkdown(constraints);
2332
2347
  writeFileSync9(join16(rulesDir, "speclore.md"), content, "utf-8");
2333
2348
  return Promise.resolve();
@@ -2760,11 +2775,12 @@ var init_markdown_reader = __esm({
2760
2775
 
2761
2776
  // src/core/requirement-reader/docx-reader.ts
2762
2777
  import { basename as basename3, extname as extname3 } from "path";
2778
+ import { readFileSync as readFileSync10 } from "fs";
2763
2779
  import mammoth from "mammoth";
2764
- async function readDocxFile(filePath) {
2765
- const result = await mammoth.extractRawText({ path: filePath });
2780
+ async function parseDocxBuffer(buffer, idHint = "document") {
2781
+ const result = await mammoth.extractRawText({ buffer });
2766
2782
  const content = result.value;
2767
- const id = basename3(filePath, extname3(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
2783
+ const id = idHint.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
2768
2784
  const lines = content.split("\n").filter((l) => l.trim());
2769
2785
  const title = lines[0]?.trim() ?? id;
2770
2786
  return {
@@ -2775,6 +2791,11 @@ async function readDocxFile(filePath) {
2775
2791
  confidence: 0.9
2776
2792
  };
2777
2793
  }
2794
+ async function readDocxFile(filePath) {
2795
+ const buffer = readFileSync10(filePath);
2796
+ const name = basename3(filePath, extname3(filePath));
2797
+ return parseDocxBuffer(buffer, name);
2798
+ }
2778
2799
  var init_docx_reader2 = __esm({
2779
2800
  "src/core/requirement-reader/docx-reader.ts"() {
2780
2801
  "use strict";
@@ -2783,44 +2804,55 @@ var init_docx_reader2 = __esm({
2783
2804
 
2784
2805
  // src/core/requirement-reader/xlsx-reader.ts
2785
2806
  import { basename as basename4, extname as extname4 } from "path";
2786
- import ExcelJS2 from "exceljs";
2787
- async function readXlsxFile(filePath) {
2788
- const workbook = new ExcelJS2.Workbook();
2789
- await workbook.xlsx.readFile(filePath);
2790
- const id = basename4(filePath, extname4(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
2791
- const sheet = workbook.worksheets[0];
2807
+ import { readFile as readFile2, read as read2, utils as utils2 } from "xlsx";
2808
+ function parseWorkbook2(workbook, idHint) {
2809
+ const id = idHint.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
2810
+ const firstSheetName = workbook.SheetNames[0];
2811
+ if (!firstSheetName) {
2812
+ return Promise.reject(new Error("No sheets found in workbook"));
2813
+ }
2814
+ const sheet = workbook.Sheets[firstSheetName];
2792
2815
  if (!sheet) {
2793
- throw new Error(`No sheets found in ${filePath}`);
2794
- }
2795
- const title = sheet.name;
2796
- const headers = [];
2797
- const rows = [];
2798
- sheet.eachRow((row, rowNumber) => {
2799
- if (rowNumber === 1) {
2800
- row.eachCell((cell, colNumber) => {
2801
- headers[colNumber - 1] = formatCellValue(cell.value);
2802
- });
2803
- } else {
2804
- const obj = {};
2805
- row.eachCell((cell, colNumber) => {
2806
- const key = headers[colNumber - 1] ?? `Col${colNumber}`;
2807
- obj[key] = formatCellValue(cell.value);
2808
- });
2809
- rows.push(obj);
2810
- }
2816
+ return Promise.reject(new Error(`Sheet "${firstSheetName}" not found`));
2817
+ }
2818
+ const title = firstSheetName;
2819
+ const rows = utils2.sheet_to_json(sheet, { header: 1, defval: "" });
2820
+ if (rows.length === 0) {
2821
+ return Promise.resolve({
2822
+ id,
2823
+ title,
2824
+ description: "",
2825
+ rawContent: "",
2826
+ confidence: 0.85
2827
+ });
2828
+ }
2829
+ const headers = rows[0].map((cell) => formatCellValue(cell));
2830
+ const dataRows = rows.slice(1);
2831
+ const objects = dataRows.map((row) => {
2832
+ const obj = {};
2833
+ row.forEach((cell, colNumber) => {
2834
+ const key = headers[colNumber] ?? `Col${colNumber + 1}`;
2835
+ obj[key] = formatCellValue(cell);
2836
+ });
2837
+ return obj;
2811
2838
  });
2812
- const textRows = rows.map((row) => {
2839
+ const textRows = objects.map((row) => {
2813
2840
  const cells = Object.entries(row).map(([key, value]) => `${key}: ${value}`).join(" | ");
2814
2841
  return cells;
2815
2842
  });
2816
2843
  const content = textRows.join("\n");
2817
- return {
2844
+ return Promise.resolve({
2818
2845
  id,
2819
2846
  title,
2820
2847
  description: content,
2821
2848
  rawContent: content,
2822
2849
  confidence: 0.85
2823
- };
2850
+ });
2851
+ }
2852
+ function readXlsxFile(filePath) {
2853
+ const workbook = readFile2(filePath);
2854
+ const idHint = basename4(filePath, extname4(filePath));
2855
+ return parseWorkbook2(workbook, idHint);
2824
2856
  }
2825
2857
  var init_xlsx_reader2 = __esm({
2826
2858
  "src/core/requirement-reader/xlsx-reader.ts"() {
@@ -2831,22 +2863,43 @@ var init_xlsx_reader2 = __esm({
2831
2863
 
2832
2864
  // src/core/requirement-reader/pdf-reader.ts
2833
2865
  import { basename as basename5, extname as extname5 } from "path";
2834
- import { readFile } from "fs/promises";
2835
- async function readPdfFile(filePath) {
2836
- const pdfParse = (await import("pdf-parse")).default;
2837
- const dataBuffer = await readFile(filePath);
2838
- const data = await pdfParse(dataBuffer);
2839
- const id = basename5(filePath, extname5(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
2840
- const lines = data.text.split("\n").filter((l) => l.trim());
2866
+ import { readFile as readFile3 } from "fs/promises";
2867
+ async function extractPdfText(dataBuffer) {
2868
+ const pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
2869
+ const uint8 = new Uint8Array(dataBuffer);
2870
+ const doc = await pdfjsLib.getDocument({
2871
+ data: uint8,
2872
+ useWorkerFetch: false,
2873
+ isEvalSupported: false
2874
+ }).promise;
2875
+ const textParts = [];
2876
+ for (let i = 1; i <= doc.numPages; i++) {
2877
+ const page = await doc.getPage(i);
2878
+ const textContent = await page.getTextContent();
2879
+ const pageText = textContent.items.map((item) => item.str).join("");
2880
+ if (pageText) textParts.push(pageText);
2881
+ }
2882
+ doc.destroy();
2883
+ return textParts.join("\n");
2884
+ }
2885
+ async function parsePdfBuffer(dataBuffer, idHint = "document") {
2886
+ const text = await extractPdfText(dataBuffer);
2887
+ const id = idHint.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
2888
+ const lines = text.split("\n").filter((l) => l.trim());
2841
2889
  const title = lines[0]?.trim() ?? id;
2842
2890
  return {
2843
2891
  id,
2844
2892
  title,
2845
- description: data.text,
2846
- rawContent: data.text,
2893
+ description: text,
2894
+ rawContent: text,
2847
2895
  confidence: 0.8
2848
2896
  };
2849
2897
  }
2898
+ async function readPdfFile(filePath) {
2899
+ const dataBuffer = await readFile3(filePath);
2900
+ const name = basename5(filePath, extname5(filePath));
2901
+ return parsePdfBuffer(dataBuffer, name);
2902
+ }
2850
2903
  var init_pdf_reader2 = __esm({
2851
2904
  "src/core/requirement-reader/pdf-reader.ts"() {
2852
2905
  "use strict";
@@ -2855,14 +2908,14 @@ var init_pdf_reader2 = __esm({
2855
2908
 
2856
2909
  // src/core/requirement-reader/image-reader.ts
2857
2910
  import { basename as basename6, extname as extname6 } from "path";
2858
- import { readFileSync as readFileSync10 } from "fs";
2859
- async function readImageFile(filePath) {
2911
+ import { readFileSync as readFileSync11 } from "fs";
2912
+ async function readImageFile(filePath, providerOverride) {
2860
2913
  const id = basename6(filePath, extname6(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
2861
2914
  logger.info(`Reading image via AI Vision: ${filePath}`);
2862
2915
  const ext = extname6(filePath).toLowerCase();
2863
2916
  const mimeType = MIME_MAP[ext] ?? "image/png";
2864
- const buffer = readFileSync10(filePath);
2865
- const provider = await createProvider();
2917
+ const buffer = readFileSync11(filePath);
2918
+ const provider = providerOverride ?? await createProvider();
2866
2919
  const prompt = `Please extract all text content from this image. Return the text as-is, preserving structure and formatting. If the image contains a table, convert it to a structured text format.`;
2867
2920
  if (!provider.generateWithImage) {
2868
2921
  throw new Error(`AI provider '${provider.name}' does not support image/vision input. Use a vision-capable model.`);
@@ -3307,11 +3360,11 @@ var init_prompt_builder = __esm({
3307
3360
  });
3308
3361
 
3309
3362
  // src/core/feature-generator/generator.ts
3310
- import { writeFileSync as writeFileSync10, mkdirSync as mkdirSync9, existsSync as existsSync15 } from "fs";
3363
+ import { writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, existsSync as existsSync15 } from "fs";
3311
3364
  import { join as join17, dirname as dirname2 } from "path";
3312
3365
  import { Parser as Parser2, GherkinClassicTokenMatcher as GherkinClassicTokenMatcher2, AstBuilder as AstBuilder2 } from "@cucumber/gherkin";
3313
3366
  import { IdGenerator as IdGenerator2 } from "@cucumber/messages";
3314
- async function generateFeature(requirement, context, config, projectRoot2) {
3367
+ async function generateFeature(requirement, context, config, projectRoot2, providerOverride) {
3315
3368
  logger.info(`Generating feature for: ${requirement.title}`);
3316
3369
  const registry = getRegistry();
3317
3370
  await registry.invokeLifecycle("beforeSpec", requirement);
@@ -3319,7 +3372,7 @@ async function generateFeature(requirement, context, config, projectRoot2) {
3319
3372
  const aiConfig = config.ai;
3320
3373
  const fallbackConfigs = aiConfig?.fallbackProviders ?? [];
3321
3374
  const providerConfigs = aiConfig ? [aiConfig, ...fallbackConfigs] : [];
3322
- const provider = providerConfigs.length > 1 ? await createProviderChain(providerConfigs) : await createProvider(aiConfig);
3375
+ const provider = providerOverride ?? (providerConfigs.length > 1 ? await createProviderChain(providerConfigs) : await createProvider(aiConfig));
3323
3376
  if (!provider.isAvailable()) {
3324
3377
  throw new Error("AI provider not available. Set API key in environment or config.yaml.");
3325
3378
  }
@@ -3376,7 +3429,7 @@ Please fix these issues and regenerate the complete Feature file.`;
3376
3429
  const moduleDir = inferModule(requirement, context);
3377
3430
  const filePath = join17(outputDir, moduleDir, `${requirement.id}.feature`);
3378
3431
  if (!existsSync15(dirname2(filePath))) {
3379
- mkdirSync9(dirname2(filePath), { recursive: true });
3432
+ mkdirSync10(dirname2(filePath), { recursive: true });
3380
3433
  }
3381
3434
  writeFileSync10(filePath, featureContent, "utf-8");
3382
3435
  logger.info(`Feature written: ${toPosixPath(filePath)}`);
@@ -3542,9 +3595,9 @@ function detectAITools2(projectRoot2) {
3542
3595
  tools.push("cursor");
3543
3596
  logger.debug("Detected Cursor (.cursor/)");
3544
3597
  }
3545
- if (existsSync16(join19(projectRoot2, ".claude")) || existsSync16(join19(projectRoot2, "CLAUDE.md")) || existsSync16(join19(projectRoot2, ".mcp.json"))) {
3598
+ if (existsSync16(join19(projectRoot2, ".claude")) || existsSync16(join19(projectRoot2, "CLAUDE.md"))) {
3546
3599
  tools.push("claude");
3547
- logger.debug("Detected Claude Code (.claude/ or CLAUDE.md or .mcp.json)");
3600
+ logger.debug("Detected Claude Code (.claude/ or CLAUDE.md)");
3548
3601
  }
3549
3602
  if (existsSync16(join19(projectRoot2, ".qoder"))) {
3550
3603
  tools.push("qoder");
@@ -3561,7 +3614,7 @@ var init_ai_tool_detector = __esm({
3561
3614
  });
3562
3615
 
3563
3616
  // src/core/constraint-coder/constraint-writer.ts
3564
- import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync10, existsSync as existsSync17 } from "fs";
3617
+ import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, existsSync as existsSync17 } from "fs";
3565
3618
  import { join as join20 } from "path";
3566
3619
  function writeConstraints(projectRoot2, tools, content) {
3567
3620
  const writtenFiles = [];
@@ -3635,7 +3688,7 @@ function buildConstraintText(content) {
3635
3688
  }
3636
3689
  function writeCursorRule2(projectRoot2, text) {
3637
3690
  const dir = join20(projectRoot2, ".cursor", "rules");
3638
- if (!existsSync17(dir)) mkdirSync10(dir, { recursive: true });
3691
+ if (!existsSync17(dir)) mkdirSync11(dir, { recursive: true });
3639
3692
  const filePath = join20(dir, "speclore.mdc");
3640
3693
  const frontmatter = [
3641
3694
  "---",
@@ -3650,14 +3703,14 @@ function writeCursorRule2(projectRoot2, text) {
3650
3703
  }
3651
3704
  function writeClaudeRule2(projectRoot2, text) {
3652
3705
  const dir = join20(projectRoot2, ".claude", "rules");
3653
- if (!existsSync17(dir)) mkdirSync10(dir, { recursive: true });
3706
+ if (!existsSync17(dir)) mkdirSync11(dir, { recursive: true });
3654
3707
  const filePath = join20(dir, "speclore.md");
3655
3708
  writeFileSync11(filePath, text, "utf-8");
3656
3709
  return filePath;
3657
3710
  }
3658
3711
  function writeQoderRule2(projectRoot2, text) {
3659
3712
  const dir = join20(projectRoot2, ".qoder", "rules");
3660
- if (!existsSync17(dir)) mkdirSync10(dir, { recursive: true });
3713
+ if (!existsSync17(dir)) mkdirSync11(dir, { recursive: true });
3661
3714
  const filePath = join20(dir, "speclore.md");
3662
3715
  writeFileSync11(filePath, text, "utf-8");
3663
3716
  return filePath;
@@ -3737,14 +3790,14 @@ var init_constraint_coder = __esm({
3737
3790
  init_logger();
3738
3791
  init_ai_tool_detector();
3739
3792
  init_constraint_writer();
3740
- MAPPING_INSTRUCTIONS = `\u4E3A\u6BCF\u4E2A\u6D4B\u8BD5\u6587\u4EF6\u751F\u6210\u5BF9\u5E94\u7684\u6620\u5C04\u6587\u4EF6\u5230 .speclore/mappings/{module}/{feature-name}.json\u3002
3741
- \u683C\u5F0F\uFF1A{ "feature": "specs/...", "scenarios": { "Scenario\u540D\u79F0": { "testFile": "...", "testMethod": "..." } } }
3742
- \u6BCF\u6B21\u4FEE\u6539\u6D4B\u8BD5\u65F6\u540C\u6B65\u66F4\u65B0\u6620\u5C04\u6587\u4EF6\u3002`;
3793
+ MAPPING_INSTRUCTIONS = `Generate a mapping file for each test file at .speclore/mappings/{module}/{feature-name}.json.
3794
+ Format: { "feature": "specs/...", "scenarios": { "Scenario name": { "testFile": "...", "testMethod": "..." } } }
3795
+ Keep mapping files in sync whenever tests are modified.`;
3743
3796
  }
3744
3797
  });
3745
3798
 
3746
3799
  // src/core/test-scaffolder/framework-detector.ts
3747
- import { readFileSync as readFileSync11, existsSync as existsSync18 } from "fs";
3800
+ import { readFileSync as readFileSync12, existsSync as existsSync18 } from "fs";
3748
3801
  import { join as join21 } from "path";
3749
3802
  function detectTestFramework(projectRoot2) {
3750
3803
  const pkgPath = join21(projectRoot2, "package.json");
@@ -3752,7 +3805,7 @@ function detectTestFramework(projectRoot2) {
3752
3805
  return "vitest";
3753
3806
  }
3754
3807
  try {
3755
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
3808
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
3756
3809
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3757
3810
  if ("vitest" in deps) return "vitest";
3758
3811
  if ("jest" in deps || "@jest/core" in deps) return "jest";
@@ -3768,7 +3821,7 @@ var init_framework_detector = __esm({
3768
3821
  });
3769
3822
 
3770
3823
  // src/core/test-scaffolder/index.ts
3771
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync12, existsSync as existsSync19, mkdirSync as mkdirSync11 } from "fs";
3824
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync12, existsSync as existsSync19, mkdirSync as mkdirSync12 } from "fs";
3772
3825
  import { join as join22, relative as relative3, dirname as dirname3, basename as basename8 } from "path";
3773
3826
  function generateTestScaffolding(projectRoot2, features, config) {
3774
3827
  const framework = detectTestFramework(projectRoot2);
@@ -3791,7 +3844,7 @@ function generateTestScaffolding(projectRoot2, features, config) {
3791
3844
  const content = generateTestFileContent(feature.featureName, scenarios, framework);
3792
3845
  const dir = dirname3(absTestPath);
3793
3846
  if (!existsSync19(dir)) {
3794
- mkdirSync11(dir, { recursive: true });
3847
+ mkdirSync12(dir, { recursive: true });
3795
3848
  }
3796
3849
  writeFileSync12(absTestPath, content, "utf-8");
3797
3850
  results.push({ testFile: testFilePath, framework, scenarios: scenarios.length });
@@ -3864,7 +3917,7 @@ function generateTestFileContent(featureName, scenarios, framework) {
3864
3917
  return lines.join("\n");
3865
3918
  }
3866
3919
  function appendMissingScenarios(testFilePath, scenarios, _framework) {
3867
- const content = readFileSync12(testFilePath, "utf-8");
3920
+ const content = readFileSync13(testFilePath, "utf-8");
3868
3921
  const missing = [];
3869
3922
  for (const scenario of scenarios) {
3870
3923
  if (!content.includes(`Scenario: ${scenario.name}`)) {
@@ -3905,7 +3958,7 @@ var init_test_scaffolder = __esm({
3905
3958
  });
3906
3959
 
3907
3960
  // src/core/verifier/mapping-resolver.ts
3908
- import { readFileSync as readFileSync14, existsSync as existsSync21, readdirSync as readdirSync2 } from "fs";
3961
+ import { readFileSync as readFileSync15, existsSync as existsSync21, readdirSync as readdirSync2 } from "fs";
3909
3962
  import { join as join24 } from "path";
3910
3963
  function resolveMappings(projectRoot2, features, testOutput) {
3911
3964
  const results = [];
@@ -3935,7 +3988,7 @@ function resolveFromMappingFile(projectRoot2, _feature, scenario) {
3935
3988
  const files = findMappingFiles(mappingsDir);
3936
3989
  for (const file of files) {
3937
3990
  try {
3938
- const content = readFileSync14(file, "utf-8");
3991
+ const content = readFileSync15(file, "utf-8");
3939
3992
  const mapping = JSON.parse(content);
3940
3993
  if (mapping.scenarios && scenario.name in mapping.scenarios) {
3941
3994
  const entry = mapping.scenarios[scenario.name];
@@ -3965,7 +4018,7 @@ function resolveFromTag(projectRoot2, _feature, scenario) {
3965
4018
  const files = findTestFiles(testsDir);
3966
4019
  for (const file of files) {
3967
4020
  try {
3968
- const content = readFileSync14(file, "utf-8");
4021
+ const content = readFileSync15(file, "utf-8");
3969
4022
  const tagRegex = /@speclore-scenario:\s*(.+)/g;
3970
4023
  let match;
3971
4024
  while ((match = tagRegex.exec(content)) !== null) {
@@ -4145,13 +4198,13 @@ var report_generator_exports = {};
4145
4198
  __export(report_generator_exports, {
4146
4199
  generateReport: () => generateReport
4147
4200
  });
4148
- import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync12, existsSync as existsSync22, readFileSync as readFileSync15 } from "fs";
4201
+ import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync13, existsSync as existsSync22, readFileSync as readFileSync16 } from "fs";
4149
4202
  import { join as join25, dirname as dirname4 } from "path";
4150
4203
  import { fileURLToPath as fileURLToPath2 } from "url";
4151
4204
  function generateReport(report, projectRoot2, config) {
4152
4205
  const reportsDir = join25(projectRoot2, ".speclore", "reports");
4153
4206
  if (!existsSync22(reportsDir)) {
4154
- mkdirSync12(reportsDir, { recursive: true });
4207
+ mkdirSync13(reportsDir, { recursive: true });
4155
4208
  }
4156
4209
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
4157
4210
  const writtenFiles = [];
@@ -4208,7 +4261,7 @@ function renderHtmlReport(report) {
4208
4261
  const templatePath = join25(dirname4(fileURLToPath2(import.meta.url)), "..", "..", "cli", "templates", "report.html");
4209
4262
  let template;
4210
4263
  try {
4211
- template = readFileSync15(templatePath, "utf-8");
4264
+ template = readFileSync16(templatePath, "utf-8");
4212
4265
  } catch {
4213
4266
  template = getInlineTemplate();
4214
4267
  }
@@ -4287,7 +4340,7 @@ var init_verifier = __esm({
4287
4340
  });
4288
4341
 
4289
4342
  // src/core/analyzer/rdg-builder.ts
4290
- import { readFileSync as readFileSync16, existsSync as existsSync23, readdirSync as readdirSync3 } from "fs";
4343
+ import { readFileSync as readFileSync17, existsSync as existsSync23, readdirSync as readdirSync3 } from "fs";
4291
4344
  import { join as join26 } from "path";
4292
4345
  var init_rdg_builder = __esm({
4293
4346
  "src/core/analyzer/rdg-builder.ts"() {
@@ -4317,6 +4370,9 @@ import { execFileSync as execFileSync3 } from "child_process";
4317
4370
  import { globSync as globSync5 } from "glob";
4318
4371
  function analyzeImpact(projectRoot2, context, config) {
4319
4372
  const changedFiles = getChangedFiles(projectRoot2);
4373
+ return analyzeImpactWithChanges(changedFiles, context, config, projectRoot2);
4374
+ }
4375
+ function analyzeImpactWithChanges(changedFiles, context, config, projectRoot2) {
4320
4376
  const affectedModules = determineAffectedModules(changedFiles, context);
4321
4377
  const affectedFeatures = determineAffectedFeatures(affectedModules, projectRoot2, config.spec.outputDir);
4322
4378
  logger.info(
@@ -4392,11 +4448,11 @@ var init_analyzer = __esm({
4392
4448
  });
4393
4449
 
4394
4450
  // src/infra/file-lock.ts
4395
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, unlinkSync, existsSync as existsSync27, statSync as statSync3 } from "fs";
4396
- import { join as join30 } from "path";
4451
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync16, unlinkSync, existsSync as existsSync28, statSync as statSync3 } from "fs";
4452
+ import { join as join31 } from "path";
4397
4453
  function acquireLock(specLoreDir) {
4398
- const lockPath = join30(specLoreDir, LOCK_FILENAME);
4399
- if (existsSync27(lockPath)) {
4454
+ const lockPath = join31(specLoreDir, LOCK_FILENAME);
4455
+ if (existsSync28(lockPath)) {
4400
4456
  const existing = readLockInfo(lockPath);
4401
4457
  if (existing && !isLockExpired(existing)) {
4402
4458
  logger.debug(`Lock already held by PID ${existing.pid}, not expired`);
@@ -4410,7 +4466,7 @@ function acquireLock(specLoreDir) {
4410
4466
  timestamp: Date.now()
4411
4467
  };
4412
4468
  try {
4413
- writeFileSync15(lockPath, JSON.stringify(lockInfo), { flag: "wx", encoding: "utf-8" });
4469
+ writeFileSync16(lockPath, JSON.stringify(lockInfo), { flag: "wx", encoding: "utf-8" });
4414
4470
  } catch {
4415
4471
  logger.debug("Lock write failed \u2014 another process may have acquired it");
4416
4472
  return false;
@@ -4419,8 +4475,8 @@ function acquireLock(specLoreDir) {
4419
4475
  return true;
4420
4476
  }
4421
4477
  function releaseLock(specLoreDir) {
4422
- const lockPath = join30(specLoreDir, LOCK_FILENAME);
4423
- if (!existsSync27(lockPath)) {
4478
+ const lockPath = join31(specLoreDir, LOCK_FILENAME);
4479
+ if (!existsSync28(lockPath)) {
4424
4480
  return;
4425
4481
  }
4426
4482
  const existing = readLockInfo(lockPath);
@@ -4431,7 +4487,7 @@ function releaseLock(specLoreDir) {
4431
4487
  }
4432
4488
  function readLockInfo(lockPath) {
4433
4489
  try {
4434
- const content = readFileSync19(lockPath, "utf-8");
4490
+ const content = readFileSync21(lockPath, "utf-8");
4435
4491
  const parsed = JSON.parse(content);
4436
4492
  if (typeof parsed.pid === "number" && typeof parsed.timestamp === "number") {
4437
4493
  return parsed;
@@ -4501,8 +4557,8 @@ var init_schemas = __esm({
4501
4557
  });
4502
4558
 
4503
4559
  // src/mcp/tools.ts
4504
- import { join as join31 } from "path";
4505
- import { readFileSync as readFileSync20, existsSync as existsSync28, mkdirSync as mkdirSync13, writeFileSync as writeFileSync16 } from "fs";
4560
+ import { join as join32 } from "path";
4561
+ import { readFileSync as readFileSync22, existsSync as existsSync29, mkdirSync as mkdirSync14, writeFileSync as writeFileSync17 } from "fs";
4506
4562
  import { globSync as globSync7 } from "glob";
4507
4563
  import { toJSONSchema } from "zod";
4508
4564
  async function executeSpecTool(args, projectRoot2) {
@@ -4513,7 +4569,7 @@ async function executeSpecTool(args, projectRoot2) {
4513
4569
  const config = loadConfig(projectRoot2);
4514
4570
  logger.info(`Reading requirement from: ${args.source.slice(0, 80)}...`);
4515
4571
  const req = await readRequirement(args.source);
4516
- const specLoreDir = join31(projectRoot2, ".speclore");
4572
+ const specLoreDir = join32(projectRoot2, ".speclore");
4517
4573
  const context = loadContext(specLoreDir) ?? buildContext(projectRoot2, config);
4518
4574
  const feature = await generateFeature(req, context, config, projectRoot2);
4519
4575
  const createdFiles = [feature.path];
@@ -4545,7 +4601,7 @@ async function executeCodeTool(args, projectRoot2) {
4545
4601
  }
4546
4602
  ensureProjectReady(projectRoot2);
4547
4603
  const config = loadConfig(projectRoot2);
4548
- const specLoreDir = join31(projectRoot2, ".speclore");
4604
+ const specLoreDir = join32(projectRoot2, ".speclore");
4549
4605
  const context = loadContext(specLoreDir) ?? buildContext(projectRoot2, config);
4550
4606
  const featureFiles = resolveFeatureFiles(args.features, projectRoot2, config);
4551
4607
  if (featureFiles.length === 0) {
@@ -4598,7 +4654,7 @@ async function executeCodeTool(args, projectRoot2) {
4598
4654
  async function executeVerifyTool(args, projectRoot2) {
4599
4655
  ensureProjectReady(projectRoot2);
4600
4656
  const config = loadConfig(projectRoot2);
4601
- const specLoreDir = join31(projectRoot2, ".speclore");
4657
+ const specLoreDir = join32(projectRoot2, ".speclore");
4602
4658
  const context = loadContext(specLoreDir) ?? buildContext(projectRoot2, config);
4603
4659
  const featureFiles = resolveFeatureFiles(args.features, projectRoot2, config);
4604
4660
  const stateManager = new StateManager(projectRoot2);
@@ -4660,22 +4716,22 @@ async function executeVerifyTool(args, projectRoot2) {
4660
4716
  };
4661
4717
  }
4662
4718
  function ensureProjectReady(projectRoot2) {
4663
- const specLoreDir = join31(projectRoot2, ".speclore");
4664
- const configPath = join31(specLoreDir, "config.yaml");
4719
+ const specLoreDir = join32(projectRoot2, ".speclore");
4720
+ const configPath = join32(specLoreDir, "config.yaml");
4665
4721
  let configCreated = false;
4666
- if (!existsSync28(specLoreDir)) {
4667
- mkdirSync13(specLoreDir, { recursive: true });
4722
+ if (!existsSync29(specLoreDir)) {
4723
+ mkdirSync14(specLoreDir, { recursive: true });
4668
4724
  }
4669
- if (!existsSync28(configPath)) {
4725
+ if (!existsSync29(configPath)) {
4670
4726
  const projectName = projectRoot2.split(/[/\\]/).pop() ?? "my-project";
4671
- writeFileSync16(configPath, generateDefaultConfigYaml(projectName), "utf-8");
4727
+ writeFileSync17(configPath, generateDefaultConfigYaml(projectName), "utf-8");
4672
4728
  configCreated = true;
4673
4729
  logger.info(`Auto-created default config: ${configPath}`);
4674
4730
  }
4675
4731
  const stateManager = new StateManager(projectRoot2);
4676
4732
  stateManager.ensureInitialized();
4677
4733
  const config = loadConfig(projectRoot2);
4678
- const specsDir = join31(projectRoot2, config.spec.outputDir);
4734
+ const specsDir = join32(projectRoot2, config.spec.outputDir);
4679
4735
  const migrated = stateManager.migrateFeatures(specsDir);
4680
4736
  if (migrated > 0) {
4681
4737
  logger.info(`Migrated ${migrated} existing .feature file(s) into state tracking.`);
@@ -4712,14 +4768,14 @@ function resolveNextStep(currentState, _summary) {
4712
4768
  }
4713
4769
  }
4714
4770
  function resolveFeatureFiles(patterns, projectRoot2, config) {
4715
- const specsDir = join31(projectRoot2, config.spec.outputDir);
4771
+ const specsDir = join32(projectRoot2, config.spec.outputDir);
4716
4772
  const searchPatterns = patterns && patterns.length > 0 ? patterns : [`${specsDir}/**/*.feature`];
4717
4773
  const files = [];
4718
4774
  for (const pattern of searchPatterns) {
4719
4775
  const matches = globSync7(pattern, { cwd: projectRoot2, absolute: true });
4720
4776
  for (const filePath of matches) {
4721
- if (existsSync28(filePath)) {
4722
- const content = readFileSync20(filePath, "utf-8");
4777
+ if (existsSync29(filePath)) {
4778
+ const content = readFileSync22(filePath, "utf-8");
4723
4779
  files.push(parseFeatureFile(filePath, content));
4724
4780
  }
4725
4781
  }
@@ -4827,8 +4883,8 @@ var init_tools = __esm({
4827
4883
  });
4828
4884
 
4829
4885
  // src/mcp/status.ts
4830
- import { readFileSync as readFileSync21, existsSync as existsSync29, mkdirSync as mkdirSync14, writeFileSync as writeFileSync17 } from "fs";
4831
- import { join as join32 } from "path";
4886
+ import { readFileSync as readFileSync23, existsSync as existsSync30, mkdirSync as mkdirSync15, writeFileSync as writeFileSync18 } from "fs";
4887
+ import { join as join33 } from "path";
4832
4888
  import { globSync as globSync8 } from "glob";
4833
4889
  function executeStatusTool(args, projectRoot2) {
4834
4890
  const { configCreated } = ensureProjectReadyForStatus(projectRoot2);
@@ -4844,9 +4900,9 @@ function executeStatusTool(args, projectRoot2) {
4844
4900
  continue;
4845
4901
  }
4846
4902
  let scenarioCount = 0;
4847
- if (existsSync29(path)) {
4903
+ if (existsSync30(path)) {
4848
4904
  try {
4849
- const content = readFileSync21(path, "utf-8");
4905
+ const content = readFileSync23(path, "utf-8");
4850
4906
  const matches = content.match(/Scenario(?: Outline)?:/g);
4851
4907
  scenarioCount = matches?.length ?? 0;
4852
4908
  } catch {
@@ -4861,7 +4917,7 @@ function executeStatusTool(args, projectRoot2) {
4861
4917
  lastVerify: entry.lastVerify ? { passed: entry.lastVerify.passed, failed: entry.lastVerify.failed, timestamp: entry.lastVerify.timestamp } : void 0
4862
4918
  });
4863
4919
  }
4864
- const specsDir = join32(projectRoot2, config.spec.outputDir);
4920
+ const specsDir = join33(projectRoot2, config.spec.outputDir);
4865
4921
  const allFeatureFiles = globSync8(`${specsDir}/**/*.feature`, { cwd: projectRoot2, absolute: true });
4866
4922
  const trackedPaths = new Set(featureEntries.map((f) => f.path));
4867
4923
  for (const filePath of allFeatureFiles) {
@@ -4869,7 +4925,7 @@ function executeStatusTool(args, projectRoot2) {
4869
4925
  if (args.feature && !filePath.includes(args.feature)) continue;
4870
4926
  let scenarioCount = 0;
4871
4927
  try {
4872
- const content = readFileSync21(filePath, "utf-8");
4928
+ const content = readFileSync23(filePath, "utf-8");
4873
4929
  const matches = content.match(/Scenario(?: Outline)?:/g);
4874
4930
  scenarioCount = matches?.length ?? 0;
4875
4931
  } catch {
@@ -4926,22 +4982,22 @@ function buildRecommendedActions(features, summary, testCommand) {
4926
4982
  return actions;
4927
4983
  }
4928
4984
  function ensureProjectReadyForStatus(projectRoot2) {
4929
- const specLoreDir = join32(projectRoot2, ".speclore");
4930
- const configPath = join32(specLoreDir, "config.yaml");
4985
+ const specLoreDir = join33(projectRoot2, ".speclore");
4986
+ const configPath = join33(specLoreDir, "config.yaml");
4931
4987
  let configCreated = false;
4932
- if (!existsSync29(specLoreDir)) {
4933
- mkdirSync14(specLoreDir, { recursive: true });
4988
+ if (!existsSync30(specLoreDir)) {
4989
+ mkdirSync15(specLoreDir, { recursive: true });
4934
4990
  }
4935
- if (!existsSync29(configPath)) {
4991
+ if (!existsSync30(configPath)) {
4936
4992
  const projectName = projectRoot2.split(/[/\\]/).pop() ?? "my-project";
4937
- writeFileSync17(configPath, generateDefaultConfigYaml(projectName), "utf-8");
4993
+ writeFileSync18(configPath, generateDefaultConfigYaml(projectName), "utf-8");
4938
4994
  configCreated = true;
4939
4995
  logger.info(`Auto-created default config: ${configPath}`);
4940
4996
  }
4941
4997
  const stateManager = new StateManager(projectRoot2);
4942
4998
  stateManager.ensureInitialized();
4943
4999
  const config = loadConfig(projectRoot2);
4944
- const specsDir = join32(projectRoot2, config.spec.outputDir);
5000
+ const specsDir = join33(projectRoot2, config.spec.outputDir);
4945
5001
  const migrated = stateManager.migrateFeatures(specsDir);
4946
5002
  if (migrated > 0) {
4947
5003
  logger.info(`Migrated ${migrated} existing .feature file(s) into state tracking.`);
@@ -4965,7 +5021,7 @@ __export(server_exports, {
4965
5021
  });
4966
5022
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4967
5023
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4968
- import { mkdirSync as mkdirSync15 } from "fs";
5024
+ import { mkdirSync as mkdirSync16 } from "fs";
4969
5025
  function getProjectRoot() {
4970
5026
  return projectRoot;
4971
5027
  }
@@ -4973,7 +5029,7 @@ async function startMcpServer() {
4973
5029
  projectRoot = process.env.SPECLORE_PROJECT_ROOT ?? process.cwd();
4974
5030
  const specLoreDir = `${projectRoot}/.speclore`;
4975
5031
  try {
4976
- mkdirSync15(specLoreDir, { recursive: true });
5032
+ mkdirSync16(specLoreDir, { recursive: true });
4977
5033
  const locked = acquireLock(specLoreDir);
4978
5034
  if (!locked) {
4979
5035
  logger.warn("Could not acquire lock \u2014 another SpecLore instance may be running.");
@@ -5086,11 +5142,11 @@ import { Command } from "commander";
5086
5142
  init_logger();
5087
5143
  init_config2();
5088
5144
  init_detector();
5089
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
5145
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
5090
5146
  import { join as join7 } from "path";
5091
5147
 
5092
5148
  // src/setup/config-writer.ts
5093
- import { writeFileSync as writeFileSync2, existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
5149
+ import { writeFileSync as writeFileSync2, existsSync as existsSync4, readFileSync as readFileSync4, mkdirSync as mkdirSync2 } from "fs";
5094
5150
  import { join as join5 } from "path";
5095
5151
 
5096
5152
  // src/infra/install-detector.ts
@@ -5141,13 +5197,42 @@ function detectCloneInstall(projectRoot2) {
5141
5197
 
5142
5198
  // src/setup/config-writer.ts
5143
5199
  init_logger();
5144
- function writeMcpConfig(projectRoot2, _globalMode) {
5200
+ function writeMcpConfig(projectRoot2, tools, _globalMode) {
5145
5201
  const installInfo = detectInstallMethod(projectRoot2);
5146
5202
  const serverCommand = getServerCommand(installInfo);
5147
- writeCursorMcp(projectRoot2, serverCommand);
5148
- writeClaudeMcp(projectRoot2, serverCommand);
5149
- writeQoderMcp(projectRoot2, serverCommand);
5150
- logger.info("MCP configuration written for all detected AI clients.");
5203
+ const detectedToolNames = tools.map((t) => t.tool);
5204
+ if (detectedToolNames.includes("cursor")) {
5205
+ writeCursorMcp(projectRoot2, serverCommand);
5206
+ }
5207
+ if (detectedToolNames.includes("claude")) {
5208
+ writeClaudeMcp(projectRoot2, serverCommand);
5209
+ }
5210
+ if (detectedToolNames.includes("qoder")) {
5211
+ writeQoderMcp(projectRoot2, serverCommand);
5212
+ }
5213
+ if (detectedToolNames.length > 0) {
5214
+ logger.info(`MCP configuration written for: ${detectedToolNames.join(", ")}`);
5215
+ }
5216
+ }
5217
+ function writeMcpForClient(projectRoot2, client) {
5218
+ const installInfo = detectInstallMethod(projectRoot2);
5219
+ const serverCommand = getServerCommand(installInfo);
5220
+ switch (client) {
5221
+ case "cursor":
5222
+ mkdirSync2(join5(projectRoot2, ".cursor"), { recursive: true });
5223
+ writeCursorMcp(projectRoot2, serverCommand);
5224
+ break;
5225
+ case "claude":
5226
+ if (!existsSync4(join5(projectRoot2, ".claude"))) {
5227
+ mkdirSync2(join5(projectRoot2, ".claude"), { recursive: true });
5228
+ }
5229
+ writeClaudeMcp(projectRoot2, serverCommand);
5230
+ break;
5231
+ case "qoder":
5232
+ mkdirSync2(join5(projectRoot2, ".qoder"), { recursive: true });
5233
+ writeQoderMcp(projectRoot2, serverCommand);
5234
+ break;
5235
+ }
5151
5236
  }
5152
5237
  function getServerCommand(installInfo) {
5153
5238
  if (installInfo.mode === "npm") {
@@ -5171,6 +5256,9 @@ function writeCursorMcp(projectRoot2, serverCmd) {
5171
5256
  logger.info(` Cursor: ${mcpPath}`);
5172
5257
  }
5173
5258
  function writeClaudeMcp(projectRoot2, serverCmd) {
5259
+ const hasClaudeDir = existsSync4(join5(projectRoot2, ".claude"));
5260
+ const hasClaudeMd = existsSync4(join5(projectRoot2, "CLAUDE.md"));
5261
+ if (!hasClaudeDir && !hasClaudeMd) return;
5174
5262
  const mcpPath = join5(projectRoot2, ".mcp.json");
5175
5263
  const existing = readExistingJson(mcpPath);
5176
5264
  if (!existing.mcpServers) existing.mcpServers = {};
@@ -5209,7 +5297,7 @@ function readExistingJson(filePath) {
5209
5297
 
5210
5298
  // src/setup/rule-writer.ts
5211
5299
  init_logger();
5212
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
5300
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
5213
5301
  import { join as join6 } from "path";
5214
5302
  function writeRuleFiles(projectRoot2, tools) {
5215
5303
  for (const tool of tools) {
@@ -5228,7 +5316,7 @@ function writeRuleFiles(projectRoot2, tools) {
5228
5316
  }
5229
5317
  function writeCursorRule(projectRoot2) {
5230
5318
  const rulesDir = join6(projectRoot2, ".cursor", "rules");
5231
- mkdirSync2(rulesDir, { recursive: true });
5319
+ mkdirSync3(rulesDir, { recursive: true });
5232
5320
  const content = `---
5233
5321
  description: SpecLore \u2014 AI coding constraints (setup placeholder)
5234
5322
  globs:
@@ -5250,7 +5338,7 @@ SpecLore is configured for this project. When the \`speclore.spec\` MCP tool is
5250
5338
  }
5251
5339
  function writeClaudeRule(projectRoot2) {
5252
5340
  const rulesDir = join6(projectRoot2, ".claude", "rules");
5253
- mkdirSync2(rulesDir, { recursive: true });
5341
+ mkdirSync3(rulesDir, { recursive: true });
5254
5342
  const content = `# SpecLore
5255
5343
 
5256
5344
  SpecLore is configured for this project. When the \`speclore.spec\` MCP tool is available:
@@ -5266,7 +5354,7 @@ SpecLore is configured for this project. When the \`speclore.spec\` MCP tool is
5266
5354
  }
5267
5355
  function writeQoderRule(projectRoot2) {
5268
5356
  const rulesDir = join6(projectRoot2, ".qoder", "rules");
5269
- mkdirSync2(rulesDir, { recursive: true });
5357
+ mkdirSync3(rulesDir, { recursive: true });
5270
5358
  const content = `# SpecLore
5271
5359
 
5272
5360
  SpecLore is configured for this project. When the \`speclore.spec\` MCP tool is available:
@@ -5290,13 +5378,20 @@ function registerSetupCommand(program) {
5290
5378
  logger.info("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
5291
5379
  const tools = detectAITools(projectRoot2);
5292
5380
  if (tools.length === 0) {
5293
- logger.warn("No supported AI tools detected. Continuing anyway...");
5381
+ logger.warn("No supported AI tools detected.");
5382
+ logger.info("");
5383
+ logger.info(" To enable MCP integration, open your AI client in this project first,");
5384
+ logger.info(" then re-run setup. Or use manual configuration:");
5385
+ logger.info(" speclore mcp add cursor \u2014 configure for Cursor");
5386
+ logger.info(" speclore mcp add claude \u2014 configure for Claude Code");
5387
+ logger.info(" speclore mcp add qoder \u2014 configure for Qoder");
5388
+ logger.info("");
5294
5389
  } else {
5295
5390
  logger.info(`Detected AI tools: ${tools.map((t) => t.tool).join(", ")}`);
5296
5391
  }
5297
5392
  const specLoreDir = opts.global ? join7(process.env.HOME ?? process.env.USERPROFILE ?? "~", ".speclore") : join7(projectRoot2, ".speclore");
5298
5393
  if (!existsSync5(specLoreDir)) {
5299
- mkdirSync3(specLoreDir, { recursive: true });
5394
+ mkdirSync4(specLoreDir, { recursive: true });
5300
5395
  }
5301
5396
  const configPath = join7(specLoreDir, "config.yaml");
5302
5397
  if (!existsSync5(configPath)) {
@@ -5304,12 +5399,23 @@ function registerSetupCommand(program) {
5304
5399
  writeFileSync4(configPath, generateDefaultConfigYaml(projectName), "utf-8");
5305
5400
  logger.info(`Created config: ${configPath}`);
5306
5401
  }
5307
- writeMcpConfig(projectRoot2, opts.global ?? false);
5402
+ writeMcpConfig(projectRoot2, tools, opts.global ?? false);
5308
5403
  writeRuleFiles(projectRoot2, tools);
5309
5404
  logger.info("");
5310
- logger.info("Setup complete! You can now:");
5311
- logger.info(" \u2022 Run `speclore init` to initialize project context");
5312
- logger.info(" \u2022 Or just start talking to your AI client about requirements");
5405
+ logger.info("Setup complete! Next steps:");
5406
+ logger.info("");
5407
+ logger.info(" Option A \u2014 CLI workflow (terminal users):");
5408
+ logger.info(' speclore spec "your requirement" \u2192 generate .feature');
5409
+ logger.info(" speclore code \u2192 generate constraints + tests");
5410
+ logger.info(" speclore verify \u2192 run acceptance");
5411
+ logger.info("");
5412
+ logger.info(" Option B \u2014 AI client workflow (recommended):");
5413
+ logger.info(" Open Cursor / Qoder / Claude Code and start chatting.");
5414
+ logger.info(" MCP is already configured \u2014 AI handles the full pipeline.");
5415
+ logger.info("");
5416
+ logger.info(" Optional \u2014 Pre-scan project context:");
5417
+ logger.info(" speclore init \u2192 scan modules/entities/APIs for better AI context");
5418
+ logger.info(" (Not required \u2014 context is auto-built on first spec/code call)");
5313
5419
  });
5314
5420
  }
5315
5421
 
@@ -5319,7 +5425,7 @@ init_logger();
5319
5425
  init_context_engine();
5320
5426
  import { join as join11 } from "path";
5321
5427
  function registerInitCommand(program) {
5322
- program.command("init").description("Initialize project: scan structure, detect modules, generate context.json").option("-v, --verbose", "Enable debug logging").action((opts) => {
5428
+ program.command("init").description("Scan project structure and generate context.json (optional \u2014 auto-runs on first spec/code call)").option("-v, --verbose", "Enable debug logging").action((opts) => {
5323
5429
  if (opts.verbose) logger.setLevel("debug");
5324
5430
  const projectRoot2 = process.cwd();
5325
5431
  logger.info("SpecLore Init");
@@ -5383,7 +5489,7 @@ init_test_scaffolder();
5383
5489
  init_context_engine();
5384
5490
  init_state_manager();
5385
5491
  import { join as join23 } from "path";
5386
- import { readFileSync as readFileSync13, existsSync as existsSync20 } from "fs";
5492
+ import { readFileSync as readFileSync14, existsSync as existsSync20 } from "fs";
5387
5493
  import { globSync as globSync4 } from "glob";
5388
5494
  function registerCodeCommand(program) {
5389
5495
  program.command("code").description("Generate AI coding constraint files from .feature specs").argument("[features...]", "Feature file paths or glob patterns (all if omitted)").option("-v, --verbose", "Enable debug logging").action(async (features, opts) => {
@@ -5439,7 +5545,7 @@ function resolveFeatures(patterns, projectRoot2, config) {
5439
5545
  const matches = globSync4(pattern, { cwd: projectRoot2, absolute: true });
5440
5546
  for (const filePath of matches) {
5441
5547
  if (existsSync20(filePath)) {
5442
- const content = readFileSync13(filePath, "utf-8");
5548
+ const content = readFileSync14(filePath, "utf-8");
5443
5549
  const featureMatch = content.match(/Feature:\s*(.+)/);
5444
5550
  files.push({
5445
5551
  path: filePath,
@@ -5463,7 +5569,7 @@ init_context_engine();
5463
5569
  init_analyzer();
5464
5570
  init_state_manager();
5465
5571
  import { join as join27 } from "path";
5466
- import { readFileSync as readFileSync17, existsSync as existsSync24 } from "fs";
5572
+ import { readFileSync as readFileSync18, existsSync as existsSync24 } from "fs";
5467
5573
  import { globSync as globSync6 } from "glob";
5468
5574
  function registerVerifyCommand(program) {
5469
5575
  program.command("verify").description("Run tests and map results to .feature scenarios").argument("[features...]", "Feature file paths or glob patterns (all if omitted)").option("--impact", "Enable change impact analysis").option("--watch", "Watch for .feature file changes and re-run").option("--timeout <minutes>", "Watch timeout in minutes (default: 30)", "30").option("-v, --verbose", "Enable debug logging").action(async (features, opts) => {
@@ -5550,7 +5656,7 @@ function resolveFeatures2(patterns, projectRoot2, config) {
5550
5656
  const matches = globSync6(pattern, { cwd: projectRoot2, absolute: true });
5551
5657
  for (const filePath of matches) {
5552
5658
  if (existsSync24(filePath)) {
5553
- const content = readFileSync17(filePath, "utf-8");
5659
+ const content = readFileSync18(filePath, "utf-8");
5554
5660
  const featureMatch = content.match(/Feature:\s*(.+)/);
5555
5661
  files.push({
5556
5662
  path: filePath,
@@ -5571,7 +5677,7 @@ init_logger();
5571
5677
 
5572
5678
  // src/setup/cleanup.ts
5573
5679
  init_logger();
5574
- import { rmSync as rmSync4, existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync14 } from "fs";
5680
+ import { rmSync as rmSync4, existsSync as existsSync25, readFileSync as readFileSync19, writeFileSync as writeFileSync14 } from "fs";
5575
5681
  import { join as join28 } from "path";
5576
5682
  function runTeardown(projectRoot2, globalMode) {
5577
5683
  if (globalMode) {
@@ -5600,7 +5706,7 @@ function runTeardown(projectRoot2, globalMode) {
5600
5706
  function removeMcpEntry(mcpPath, serverName) {
5601
5707
  if (!existsSync25(mcpPath)) return;
5602
5708
  try {
5603
- const config = JSON.parse(readFileSync18(mcpPath, "utf-8"));
5709
+ const config = JSON.parse(readFileSync19(mcpPath, "utf-8"));
5604
5710
  if (config.mcpServers?.[serverName]) {
5605
5711
  delete config.mcpServers[serverName];
5606
5712
  writeFileSync14(mcpPath, JSON.stringify(config, null, 2), "utf-8");
@@ -5696,6 +5802,114 @@ function registerMigrateCommand(program) {
5696
5802
  });
5697
5803
  }
5698
5804
 
5805
+ // src/cli/commands/mcp-config.ts
5806
+ init_logger();
5807
+ import { existsSync as existsSync27, readFileSync as readFileSync20, writeFileSync as writeFileSync15, rmSync as rmSync5 } from "fs";
5808
+ import { join as join30 } from "path";
5809
+ var VALID_CLIENTS = ["cursor", "claude", "qoder"];
5810
+ function getMcpPath(projectRoot2, client) {
5811
+ switch (client) {
5812
+ case "cursor":
5813
+ return join30(projectRoot2, ".cursor", "mcp.json");
5814
+ case "claude":
5815
+ return join30(projectRoot2, ".mcp.json");
5816
+ case "qoder":
5817
+ return join30(projectRoot2, ".qoder", "mcp.json");
5818
+ }
5819
+ }
5820
+ function clientLabel(client) {
5821
+ switch (client) {
5822
+ case "cursor":
5823
+ return "Cursor";
5824
+ case "claude":
5825
+ return "Claude Code";
5826
+ case "qoder":
5827
+ return "Qoder";
5828
+ }
5829
+ }
5830
+ function registerMcpConfigCommands(program) {
5831
+ const mcpCmd = program.commands.find((c) => c.name() === "mcp");
5832
+ if (!mcpCmd) return;
5833
+ mcpCmd.command("add <client>").description("Manually write SpecLore MCP config for a specific AI client (cursor | claude | qoder)").action((clientArg) => {
5834
+ const projectRoot2 = process.cwd();
5835
+ const client = validateClient(clientArg);
5836
+ if (!client) return;
5837
+ logger.info(`Configuring MCP for ${clientLabel(client)}...`);
5838
+ writeMcpForClient(projectRoot2, client);
5839
+ logger.info(`\u2713 ${clientLabel(client)} MCP configured at ${getMcpPath(projectRoot2, client)}`);
5840
+ });
5841
+ mcpCmd.command("remove <client>").description("Remove SpecLore MCP config from a specific AI client (cursor | claude | qoder)").action((clientArg) => {
5842
+ const projectRoot2 = process.cwd();
5843
+ const client = validateClient(clientArg);
5844
+ if (!client) return;
5845
+ const mcpPath = getMcpPath(projectRoot2, client);
5846
+ if (!existsSync27(mcpPath)) {
5847
+ logger.info(`${clientLabel(client)}: no MCP config file found at ${mcpPath}`);
5848
+ return;
5849
+ }
5850
+ const removed = removeMcpServerEntry(mcpPath, "speclore");
5851
+ if (removed) {
5852
+ logger.info(`\u2713 Removed speclore from ${clientLabel(client)}: ${mcpPath}`);
5853
+ } else {
5854
+ logger.info(`${clientLabel(client)}: speclore entry not found in ${mcpPath}`);
5855
+ }
5856
+ });
5857
+ mcpCmd.command("list").description("Show current SpecLore MCP configuration status for all clients").action(() => {
5858
+ const projectRoot2 = process.cwd();
5859
+ logger.info("SpecLore MCP Configuration Status");
5860
+ logger.info("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
5861
+ let anyFound = false;
5862
+ for (const client of VALID_CLIENTS) {
5863
+ const mcpPath = getMcpPath(projectRoot2, client);
5864
+ if (!existsSync27(mcpPath)) {
5865
+ logger.info(` \u25CB ${clientLabel(client)}: no config file (${mcpPath})`);
5866
+ continue;
5867
+ }
5868
+ try {
5869
+ const config = JSON.parse(readFileSync20(mcpPath, "utf-8"));
5870
+ if (config.mcpServers?.["speclore"]) {
5871
+ logger.info(` \u2713 ${clientLabel(client)}: configured \u2192 ${mcpPath}`);
5872
+ anyFound = true;
5873
+ } else {
5874
+ logger.info(` \u25CB ${clientLabel(client)}: config file exists but speclore not registered \u2192 ${mcpPath}`);
5875
+ }
5876
+ } catch {
5877
+ logger.info(` \u2717 ${clientLabel(client)}: failed to parse ${mcpPath}`);
5878
+ }
5879
+ }
5880
+ if (!anyFound) {
5881
+ logger.info("");
5882
+ logger.info(" No SpecLore MCP configurations found.");
5883
+ logger.info(" Run `speclore setup` or `speclore mcp add <client>` to configure.");
5884
+ }
5885
+ });
5886
+ }
5887
+ function validateClient(arg) {
5888
+ const normalised = arg.toLowerCase().trim();
5889
+ if (!VALID_CLIENTS.includes(normalised)) {
5890
+ logger.error(`Unknown client "${arg}". Supported: ${VALID_CLIENTS.join(", ")}`);
5891
+ return null;
5892
+ }
5893
+ return normalised;
5894
+ }
5895
+ function removeMcpServerEntry(mcpPath, serverName) {
5896
+ if (!existsSync27(mcpPath)) return false;
5897
+ try {
5898
+ const config = JSON.parse(readFileSync20(mcpPath, "utf-8"));
5899
+ if (!config.mcpServers?.[serverName]) return false;
5900
+ delete config.mcpServers[serverName];
5901
+ const remainingServers = Object.keys(config.mcpServers ?? {});
5902
+ if (remainingServers.length === 0) {
5903
+ rmSync5(mcpPath);
5904
+ } else {
5905
+ writeFileSync15(mcpPath, JSON.stringify(config, null, 2), "utf-8");
5906
+ }
5907
+ return true;
5908
+ } catch {
5909
+ return false;
5910
+ }
5911
+ }
5912
+
5699
5913
  // src/cli/index.ts
5700
5914
  init_version();
5701
5915
  function createProgram() {
@@ -5715,6 +5929,7 @@ function createProgram() {
5715
5929
  const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5716
5930
  await startMcpServer2();
5717
5931
  });
5932
+ registerMcpConfigCommands(program);
5718
5933
  program.argument("[text...]", "Quick requirement text \u2192 .feature").action(async (text) => {
5719
5934
  if (text.length > 0) {
5720
5935
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));