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