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.
@@ -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)
@@ -950,7 +945,7 @@ var DocxReader = class {
950
945
  };
951
946
 
952
947
  // src/plugins/builtin/xlsx-reader.ts
953
- import ExcelJS from "exceljs";
948
+ import { readFile, read, utils } from "xlsx";
954
949
 
955
950
  // src/infra/excel-utils.ts
956
951
  function formatCellValue(value) {
@@ -958,56 +953,62 @@ function formatCellValue(value) {
958
953
  if (typeof value === "string") return value;
959
954
  if (typeof value === "number" || typeof value === "boolean") return String(value);
960
955
  if (value instanceof Date) return value.toISOString();
956
+ if (typeof value === "object" && value !== null && "v" in value) {
957
+ const cell = value;
958
+ if (cell.w != null) return cell.w;
959
+ if (cell.v == null) return "";
960
+ if (typeof cell.v === "string" || typeof cell.v === "number" || typeof cell.v === "boolean") return String(cell.v);
961
+ return JSON.stringify(cell.v);
962
+ }
961
963
  return "";
962
964
  }
963
965
 
964
966
  // src/plugins/builtin/xlsx-reader.ts
967
+ function parseWorkbook(workbook) {
968
+ const requirements = [];
969
+ for (const sheetName of workbook.SheetNames) {
970
+ const sheet = workbook.Sheets[sheetName];
971
+ if (!sheet) continue;
972
+ const allRows = utils.sheet_to_json(sheet, { header: 1, defval: "" });
973
+ if (allRows.length === 0) continue;
974
+ const headers = allRows[0].map((cell) => formatCellValue(cell));
975
+ const dataRows = allRows.slice(1);
976
+ const rows = dataRows.map((row) => {
977
+ const obj = {};
978
+ row.forEach((cell, colNumber) => {
979
+ const key = headers[colNumber] ?? `Col${colNumber + 1}`;
980
+ obj[key] = formatCellValue(cell);
981
+ });
982
+ return obj;
983
+ });
984
+ for (let i = 0; i < rows.length; i++) {
985
+ const row = rows[i];
986
+ const title = row["Title"] ?? row["title"] ?? row["Name"] ?? row["name"] ?? row["ID"] ?? `Row ${i + 1}`;
987
+ const description = row["Description"] ?? row["description"] ?? row["Desc"] ?? "";
988
+ const ac = row["Acceptance Criteria"] ?? row["acceptance"] ?? row["AC"] ?? "";
989
+ if (description || title) {
990
+ requirements.push({
991
+ id: `${sheetName}/${i + 1}`,
992
+ title: String(title),
993
+ description: String(description),
994
+ acceptanceCriteria: ac ? String(ac).split("\n").filter(Boolean) : void 0,
995
+ rawContent: JSON.stringify(row),
996
+ confidence: 0.7
997
+ });
998
+ }
999
+ }
1000
+ }
1001
+ return requirements;
1002
+ }
965
1003
  var XlsxReader = class {
966
1004
  name = "xlsx-reader";
967
1005
  supportedFormats = [".xlsx", ".xls"];
968
1006
  canRead(source) {
969
1007
  return /\.xlsx$/i.test(source) || /\.xls$/i.test(source);
970
1008
  }
971
- async read(source) {
972
- const workbook = new ExcelJS.Workbook();
973
- await workbook.xlsx.readFile(source);
974
- const requirements = [];
975
- for (const sheet of workbook.worksheets) {
976
- const sheetName = sheet.name;
977
- const headers = [];
978
- const rows = [];
979
- sheet.eachRow((row, rowNumber) => {
980
- if (rowNumber === 1) {
981
- row.eachCell((cell, colNumber) => {
982
- headers[colNumber - 1] = formatCellValue(cell.value);
983
- });
984
- } else {
985
- const obj = {};
986
- row.eachCell((cell, colNumber) => {
987
- const key = headers[colNumber - 1] ?? `Col${colNumber}`;
988
- obj[key] = formatCellValue(cell.value);
989
- });
990
- rows.push(obj);
991
- }
992
- });
993
- for (let i = 0; i < rows.length; i++) {
994
- const row = rows[i];
995
- const title = row["Title"] ?? row["title"] ?? row["Name"] ?? row["name"] ?? row["ID"] ?? `Row ${i + 1}`;
996
- const description = row["Description"] ?? row["description"] ?? row["Desc"] ?? "";
997
- const ac = row["Acceptance Criteria"] ?? row["acceptance"] ?? row["AC"] ?? "";
998
- if (description || title) {
999
- requirements.push({
1000
- id: `${sheetName}/${i + 1}`,
1001
- title: String(title),
1002
- description: String(description),
1003
- acceptanceCriteria: ac ? String(ac).split("\n").filter(Boolean) : void 0,
1004
- rawContent: JSON.stringify(row),
1005
- confidence: 0.7
1006
- });
1007
- }
1008
- }
1009
- }
1010
- return requirements;
1009
+ read(source) {
1010
+ const workbook = readFile(source);
1011
+ return Promise.resolve(parseWorkbook(workbook));
1011
1012
  }
1012
1013
  };
1013
1014
 
@@ -1019,21 +1020,35 @@ var PdfReader = class {
1019
1020
  return /\.pdf$/i.test(source);
1020
1021
  }
1021
1022
  async read(source) {
1022
- let pdfParse;
1023
+ let pdfjsLib;
1023
1024
  try {
1024
- const mod = await import("pdf-parse");
1025
- pdfParse = mod.default ?? mod;
1025
+ pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
1026
1026
  } catch {
1027
- throw new Error("pdf-parse package is required for PDF support. Install: npm i pdf-parse");
1028
- }
1029
- const { readFileSync: readFileSync17 } = await import("fs");
1030
- const buffer = readFileSync17(source);
1031
- const data = await pdfParse(buffer);
1027
+ throw new Error("pdfjs-dist package is required for PDF support. Install: npm i pdfjs-dist");
1028
+ }
1029
+ const { readFileSync: readFileSync18 } = await import("fs");
1030
+ const buffer = readFileSync18(source);
1031
+ const uint8 = new Uint8Array(buffer);
1032
+ const doc = await pdfjsLib.getDocument({
1033
+ data: uint8,
1034
+ useWorkerFetch: false,
1035
+ isEvalSupported: false
1036
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1037
+ }).promise;
1038
+ const textParts = [];
1039
+ for (let i = 1; i <= doc.numPages; i++) {
1040
+ const page = await doc.getPage(i);
1041
+ const textContent = await page.getTextContent();
1042
+ const pageText = textContent.items.map((item) => item.str).join("");
1043
+ if (pageText) textParts.push(pageText);
1044
+ }
1045
+ doc.destroy();
1046
+ const text = textParts.join("\n");
1032
1047
  return [{
1033
1048
  id: source.replace(/\\/g, "/").replace(/\.pdf$/i, ""),
1034
1049
  title: source.split("/").pop()?.replace(/\.pdf$/i, "") ?? "Untitled",
1035
- description: data.text,
1036
- rawContent: data.text,
1050
+ description: text,
1051
+ rawContent: text,
1037
1052
  confidence: 0.75
1038
1053
  }];
1039
1054
  }
@@ -1047,13 +1062,13 @@ var ImageReader = class {
1047
1062
  return /\.(png|jpe?g|webp)$/i.test(source);
1048
1063
  }
1049
1064
  async read(source) {
1050
- const { existsSync: existsSync22, readFileSync: readFileSync17 } = await import("fs");
1065
+ const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
1051
1066
  if (!existsSync22(source)) {
1052
1067
  throw new Error(`Image file not found: ${source}`);
1053
1068
  }
1054
1069
  const ext = source.split(".").pop()?.toLowerCase() ?? "png";
1055
1070
  const mimeType = ext === "jpg" ? "image/jpeg" : `image/${ext}`;
1056
- const buffer = readFileSync17(source);
1071
+ const buffer = readFileSync18(source);
1057
1072
  let text;
1058
1073
  try {
1059
1074
  const { createProvider: createProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
@@ -1647,11 +1662,12 @@ function extractDependencies(content) {
1647
1662
 
1648
1663
  // src/core/requirement-reader/docx-reader.ts
1649
1664
  import { basename as basename2, extname as extname2 } from "path";
1665
+ import { readFileSync as readFileSync4 } from "fs";
1650
1666
  import mammoth from "mammoth";
1651
- async function readDocxFile(filePath) {
1652
- const result = await mammoth.extractRawText({ path: filePath });
1667
+ async function parseDocxBuffer(buffer, idHint = "document") {
1668
+ const result = await mammoth.extractRawText({ buffer });
1653
1669
  const content = result.value;
1654
- const id = basename2(filePath, extname2(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1670
+ const id = idHint.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1655
1671
  const lines = content.split("\n").filter((l) => l.trim());
1656
1672
  const title = lines[0]?.trim() ?? id;
1657
1673
  return {
@@ -1662,86 +1678,123 @@ async function readDocxFile(filePath) {
1662
1678
  confidence: 0.9
1663
1679
  };
1664
1680
  }
1681
+ async function readDocxFile(filePath) {
1682
+ const buffer = readFileSync4(filePath);
1683
+ const name = basename2(filePath, extname2(filePath));
1684
+ return parseDocxBuffer(buffer, name);
1685
+ }
1665
1686
 
1666
1687
  // src/core/requirement-reader/xlsx-reader.ts
1667
1688
  import { basename as basename3, extname as extname3 } from "path";
1668
- import ExcelJS2 from "exceljs";
1669
- async function readXlsxFile(filePath) {
1670
- const workbook = new ExcelJS2.Workbook();
1671
- await workbook.xlsx.readFile(filePath);
1672
- const id = basename3(filePath, extname3(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1673
- const sheet = workbook.worksheets[0];
1689
+ import { readFile as readFile2, read as read2, utils as utils2 } from "xlsx";
1690
+ function parseWorkbook2(workbook, idHint) {
1691
+ const id = idHint.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1692
+ const firstSheetName = workbook.SheetNames[0];
1693
+ if (!firstSheetName) {
1694
+ return Promise.reject(new Error("No sheets found in workbook"));
1695
+ }
1696
+ const sheet = workbook.Sheets[firstSheetName];
1674
1697
  if (!sheet) {
1675
- throw new Error(`No sheets found in ${filePath}`);
1676
- }
1677
- const title = sheet.name;
1678
- const headers = [];
1679
- const rows = [];
1680
- sheet.eachRow((row, rowNumber) => {
1681
- if (rowNumber === 1) {
1682
- row.eachCell((cell, colNumber) => {
1683
- headers[colNumber - 1] = formatCellValue(cell.value);
1684
- });
1685
- } else {
1686
- const obj = {};
1687
- row.eachCell((cell, colNumber) => {
1688
- const key = headers[colNumber - 1] ?? `Col${colNumber}`;
1689
- obj[key] = formatCellValue(cell.value);
1690
- });
1691
- rows.push(obj);
1692
- }
1698
+ return Promise.reject(new Error(`Sheet "${firstSheetName}" not found`));
1699
+ }
1700
+ const title = firstSheetName;
1701
+ const rows = utils2.sheet_to_json(sheet, { header: 1, defval: "" });
1702
+ if (rows.length === 0) {
1703
+ return Promise.resolve({
1704
+ id,
1705
+ title,
1706
+ description: "",
1707
+ rawContent: "",
1708
+ confidence: 0.85
1709
+ });
1710
+ }
1711
+ const headers = rows[0].map((cell) => formatCellValue(cell));
1712
+ const dataRows = rows.slice(1);
1713
+ const objects = dataRows.map((row) => {
1714
+ const obj = {};
1715
+ row.forEach((cell, colNumber) => {
1716
+ const key = headers[colNumber] ?? `Col${colNumber + 1}`;
1717
+ obj[key] = formatCellValue(cell);
1718
+ });
1719
+ return obj;
1693
1720
  });
1694
- const textRows = rows.map((row) => {
1721
+ const textRows = objects.map((row) => {
1695
1722
  const cells = Object.entries(row).map(([key, value]) => `${key}: ${value}`).join(" | ");
1696
1723
  return cells;
1697
1724
  });
1698
1725
  const content = textRows.join("\n");
1699
- return {
1726
+ return Promise.resolve({
1700
1727
  id,
1701
1728
  title,
1702
1729
  description: content,
1703
1730
  rawContent: content,
1704
1731
  confidence: 0.85
1705
- };
1732
+ });
1733
+ }
1734
+ function readXlsxFile(filePath) {
1735
+ const workbook = readFile2(filePath);
1736
+ const idHint = basename3(filePath, extname3(filePath));
1737
+ return parseWorkbook2(workbook, idHint);
1706
1738
  }
1707
1739
 
1708
1740
  // src/core/requirement-reader/pdf-reader.ts
1709
1741
  import { basename as basename4, extname as extname4 } from "path";
1710
- import { readFile } from "fs/promises";
1711
- async function readPdfFile(filePath) {
1712
- const pdfParse = (await import("pdf-parse")).default;
1713
- const dataBuffer = await readFile(filePath);
1714
- const data = await pdfParse(dataBuffer);
1715
- const id = basename4(filePath, extname4(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1716
- const lines = data.text.split("\n").filter((l) => l.trim());
1742
+ import { readFile as readFile3 } from "fs/promises";
1743
+ async function extractPdfText(dataBuffer) {
1744
+ const pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
1745
+ const uint8 = new Uint8Array(dataBuffer);
1746
+ const doc = await pdfjsLib.getDocument({
1747
+ data: uint8,
1748
+ useWorkerFetch: false,
1749
+ isEvalSupported: false
1750
+ }).promise;
1751
+ const textParts = [];
1752
+ for (let i = 1; i <= doc.numPages; i++) {
1753
+ const page = await doc.getPage(i);
1754
+ const textContent = await page.getTextContent();
1755
+ const pageText = textContent.items.map((item) => item.str).join("");
1756
+ if (pageText) textParts.push(pageText);
1757
+ }
1758
+ doc.destroy();
1759
+ return textParts.join("\n");
1760
+ }
1761
+ async function parsePdfBuffer(dataBuffer, idHint = "document") {
1762
+ const text = await extractPdfText(dataBuffer);
1763
+ const id = idHint.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1764
+ const lines = text.split("\n").filter((l) => l.trim());
1717
1765
  const title = lines[0]?.trim() ?? id;
1718
1766
  return {
1719
1767
  id,
1720
1768
  title,
1721
- description: data.text,
1722
- rawContent: data.text,
1769
+ description: text,
1770
+ rawContent: text,
1723
1771
  confidence: 0.8
1724
1772
  };
1725
1773
  }
1774
+ async function readPdfFile(filePath) {
1775
+ const dataBuffer = await readFile3(filePath);
1776
+ const name = basename4(filePath, extname4(filePath));
1777
+ return parsePdfBuffer(dataBuffer, name);
1778
+ }
1726
1779
 
1727
1780
  // src/core/requirement-reader/image-reader.ts
1728
1781
  init_provider();
1729
1782
  init_logger();
1730
1783
  import { basename as basename5, extname as extname5 } from "path";
1731
- import { readFileSync as readFileSync4 } from "fs";
1784
+ import { readFileSync as readFileSync5 } from "fs";
1732
1785
  var MIME_MAP = {
1733
1786
  ".png": "image/png",
1734
1787
  ".jpg": "image/jpeg",
1735
1788
  ".jpeg": "image/jpeg",
1736
1789
  ".webp": "image/webp"
1737
1790
  };
1738
- async function readImageFile(filePath) {
1791
+ async function readImageFile(filePath, providerOverride) {
1739
1792
  const id = basename5(filePath, extname5(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1740
1793
  logger.info(`Reading image via AI Vision: ${filePath}`);
1741
1794
  const ext = extname5(filePath).toLowerCase();
1742
1795
  const mimeType = MIME_MAP[ext] ?? "image/png";
1743
- const buffer = readFileSync4(filePath);
1744
- const provider = await createProvider();
1796
+ const buffer = readFileSync5(filePath);
1797
+ const provider = providerOverride ?? await createProvider();
1745
1798
  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.`;
1746
1799
  if (!provider.generateWithImage) {
1747
1800
  throw new Error(`AI provider '${provider.name}' does not support image/vision input. Use a vision-capable model.`);
@@ -2145,7 +2198,7 @@ function toPosixPath(p) {
2145
2198
 
2146
2199
  // src/core/feature-generator/generator.ts
2147
2200
  var MAX_VALIDATION_RETRIES = 2;
2148
- async function generateFeature(requirement, context, config, projectRoot2) {
2201
+ async function generateFeature(requirement, context, config, projectRoot2, providerOverride) {
2149
2202
  logger.info(`Generating feature for: ${requirement.title}`);
2150
2203
  const registry = getRegistry();
2151
2204
  await registry.invokeLifecycle("beforeSpec", requirement);
@@ -2153,7 +2206,7 @@ async function generateFeature(requirement, context, config, projectRoot2) {
2153
2206
  const aiConfig = config.ai;
2154
2207
  const fallbackConfigs = aiConfig?.fallbackProviders ?? [];
2155
2208
  const providerConfigs = aiConfig ? [aiConfig, ...fallbackConfigs] : [];
2156
- const provider = providerConfigs.length > 1 ? await createProviderChain(providerConfigs) : await createProvider(aiConfig);
2209
+ const provider = providerOverride ?? (providerConfigs.length > 1 ? await createProviderChain(providerConfigs) : await createProvider(aiConfig));
2157
2210
  if (!provider.isAvailable()) {
2158
2211
  throw new Error("AI provider not available. Set API key in environment or config.yaml.");
2159
2212
  }
@@ -2348,9 +2401,9 @@ function detectAITools(projectRoot2) {
2348
2401
  tools.push("cursor");
2349
2402
  logger.debug("Detected Cursor (.cursor/)");
2350
2403
  }
2351
- if (existsSync8(join7(projectRoot2, ".claude")) || existsSync8(join7(projectRoot2, "CLAUDE.md")) || existsSync8(join7(projectRoot2, ".mcp.json"))) {
2404
+ if (existsSync8(join7(projectRoot2, ".claude")) || existsSync8(join7(projectRoot2, "CLAUDE.md"))) {
2352
2405
  tools.push("claude");
2353
- logger.debug("Detected Claude Code (.claude/ or CLAUDE.md or .mcp.json)");
2406
+ logger.debug("Detected Claude Code (.claude/ or CLAUDE.md)");
2354
2407
  }
2355
2408
  if (existsSync8(join7(projectRoot2, ".qoder"))) {
2356
2409
  tools.push("qoder");
@@ -2483,9 +2536,9 @@ function readModuleConfig(config) {
2483
2536
 
2484
2537
  // src/core/constraint-coder/index.ts
2485
2538
  init_logger();
2486
- var 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
2487
- \u683C\u5F0F\uFF1A{ "feature": "specs/...", "scenarios": { "Scenario\u540D\u79F0": { "testFile": "...", "testMethod": "..." } } }
2488
- \u6BCF\u6B21\u4FEE\u6539\u6D4B\u8BD5\u65F6\u540C\u6B65\u66F4\u65B0\u6620\u5C04\u6587\u4EF6\u3002`;
2539
+ var MAPPING_INSTRUCTIONS = `Generate a mapping file for each test file at .speclore/mappings/{module}/{feature-name}.json.
2540
+ Format: { "feature": "specs/...", "scenarios": { "Scenario name": { "testFile": "...", "testMethod": "..." } } }
2541
+ Keep mapping files in sync whenever tests are modified.`;
2489
2542
  async function generateConstraints(projectRoot2, features, _context, config) {
2490
2543
  const tools = detectAITools(projectRoot2);
2491
2544
  if (tools.length === 0) {
@@ -2547,7 +2600,7 @@ import { execFileSync } from "child_process";
2547
2600
 
2548
2601
  // src/core/verifier/mapping-resolver.ts
2549
2602
  init_logger();
2550
- import { readFileSync as readFileSync5, existsSync as existsSync10, readdirSync } from "fs";
2603
+ import { readFileSync as readFileSync6, existsSync as existsSync10, readdirSync } from "fs";
2551
2604
  import { join as join9 } from "path";
2552
2605
  function resolveMappings(projectRoot2, features, testOutput) {
2553
2606
  const results = [];
@@ -2577,7 +2630,7 @@ function resolveFromMappingFile(projectRoot2, _feature, scenario) {
2577
2630
  const files = findMappingFiles(mappingsDir);
2578
2631
  for (const file of files) {
2579
2632
  try {
2580
- const content = readFileSync5(file, "utf-8");
2633
+ const content = readFileSync6(file, "utf-8");
2581
2634
  const mapping = JSON.parse(content);
2582
2635
  if (mapping.scenarios && scenario.name in mapping.scenarios) {
2583
2636
  const entry = mapping.scenarios[scenario.name];
@@ -2607,7 +2660,7 @@ function resolveFromTag(projectRoot2, _feature, scenario) {
2607
2660
  const files = findTestFiles(testsDir);
2608
2661
  for (const file of files) {
2609
2662
  try {
2610
- const content = readFileSync5(file, "utf-8");
2663
+ const content = readFileSync6(file, "utf-8");
2611
2664
  const tagRegex = /@speclore-scenario:\s*(.+)/g;
2612
2665
  let match;
2613
2666
  while ((match = tagRegex.exec(content)) !== null) {
@@ -2770,19 +2823,19 @@ function executeTestCommand(projectRoot2, config) {
2770
2823
 
2771
2824
  // src/core/verifier/report-generator.ts
2772
2825
  init_logger();
2773
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
2826
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
2774
2827
  import { join as join10, dirname as dirname2 } from "path";
2775
2828
  import { fileURLToPath } from "url";
2776
2829
 
2777
2830
  // src/core/context-engine/context-writer.ts
2778
2831
  init_logger();
2779
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as existsSync13, statSync as statSync3, mkdirSync as mkdirSync7 } from "fs";
2832
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync13, statSync as statSync3, mkdirSync as mkdirSync7 } from "fs";
2780
2833
  import { join as join13 } from "path";
2781
2834
  import { execFileSync as execFileSync2 } from "child_process";
2782
2835
 
2783
2836
  // src/core/context-engine/graph-builder.ts
2784
2837
  init_logger();
2785
- import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync7 } from "fs";
2838
+ import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync8 } from "fs";
2786
2839
  import { join as join11, basename as basename7, extname as extname7, relative as relative2 } from "path";
2787
2840
  function detectProjectInfo(projectRoot2) {
2788
2841
  const info = {
@@ -2796,7 +2849,7 @@ function detectProjectInfo(projectRoot2) {
2796
2849
  info.language = "typescript";
2797
2850
  info.buildTool = "npm";
2798
2851
  try {
2799
- const pkg = JSON.parse(readFileSync7(join11(projectRoot2, "package.json"), "utf-8"));
2852
+ const pkg = JSON.parse(readFileSync8(join11(projectRoot2, "package.json"), "utf-8"));
2800
2853
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2801
2854
  if ("next" in deps) info.framework = "next.js";
2802
2855
  else if ("@nestjs/core" in deps) info.framework = "nestjs";
@@ -2813,7 +2866,7 @@ function detectProjectInfo(projectRoot2) {
2813
2866
  info.buildTool = "maven";
2814
2867
  info.testFramework = "junit";
2815
2868
  try {
2816
- const pom = readFileSync7(join11(projectRoot2, "pom.xml"), "utf-8");
2869
+ const pom = readFileSync8(join11(projectRoot2, "pom.xml"), "utf-8");
2817
2870
  if (pom.includes("spring-boot")) info.framework = "spring-boot";
2818
2871
  } catch {
2819
2872
  }
@@ -2834,7 +2887,7 @@ function detectProjectInfo(projectRoot2) {
2834
2887
  info.buildTool = "pip";
2835
2888
  info.testFramework = "pytest";
2836
2889
  try {
2837
- const reqs = existsSync12(join11(projectRoot2, "requirements.txt")) ? readFileSync7(join11(projectRoot2, "requirements.txt"), "utf-8") : readFileSync7(join11(projectRoot2, "pyproject.toml"), "utf-8");
2890
+ const reqs = existsSync12(join11(projectRoot2, "requirements.txt")) ? readFileSync8(join11(projectRoot2, "requirements.txt"), "utf-8") : readFileSync8(join11(projectRoot2, "pyproject.toml"), "utf-8");
2838
2891
  if (reqs.includes("django")) info.framework = "django";
2839
2892
  else if (reqs.includes("flask")) info.framework = "flask";
2840
2893
  else if (reqs.includes("fastapi")) info.framework = "fastapi";
@@ -2921,7 +2974,7 @@ function scanImports(dir) {
2921
2974
  const ext = extname7(entry);
2922
2975
  if (![".ts", ".tsx", ".js", ".jsx", ".java", ".py"].includes(ext)) continue;
2923
2976
  try {
2924
- const content = readFileSync7(fullPath, "utf-8");
2977
+ const content = readFileSync8(fullPath, "utf-8");
2925
2978
  const importRegex = /(?:import\s+.*?from\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
2926
2979
  let match;
2927
2980
  while ((match = importRegex.exec(content)) !== null) {
@@ -2958,7 +3011,7 @@ function analyzeFileForEntities(filePath, modName, modPath, modRelativePath) {
2958
3011
  const name = basename7(filePath, ext);
2959
3012
  if (!ANALYZABLE_EXTENSIONS.has(ext)) return entities;
2960
3013
  try {
2961
- const content = readFileSync7(filePath, "utf-8");
3014
+ const content = readFileSync8(filePath, "utf-8");
2962
3015
  const relFile = toPosixPath(join11(modRelativePath, relative2(modPath, filePath)));
2963
3016
  if (/(?:Entity|Model|Domain|Schema)$/i.test(name)) {
2964
3017
  entities.push({ name, module: modName, file: relFile });
@@ -3011,7 +3064,7 @@ function analyzeFileForApis(filePath, modName, modPath, modRelativePath) {
3011
3064
  const name = basename7(filePath, ext);
3012
3065
  if (!ANALYZABLE_EXTENSIONS.has(ext)) return apis;
3013
3066
  try {
3014
- const content = readFileSync7(filePath, "utf-8");
3067
+ const content = readFileSync8(filePath, "utf-8");
3015
3068
  const relFile = toPosixPath(join11(modRelativePath, relative2(modPath, filePath)));
3016
3069
  if (/Controller|Resource|Handler|Route$/i.test(name)) {
3017
3070
  apis.push({
@@ -3128,7 +3181,7 @@ function extractApis(projectRoot2, modules) {
3128
3181
  }
3129
3182
 
3130
3183
  // src/version.ts
3131
- import { readFileSync as readFileSync8 } from "fs";
3184
+ import { readFileSync as readFileSync9 } from "fs";
3132
3185
  import { fileURLToPath as fileURLToPath2 } from "url";
3133
3186
  import { join as join12, dirname as dirname3 } from "path";
3134
3187
  function readVersion() {
@@ -3136,7 +3189,7 @@ function readVersion() {
3136
3189
  for (let depth = 0; depth < 4; depth++) {
3137
3190
  try {
3138
3191
  const pkgPath = join12(currentDir, "package.json");
3139
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
3192
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
3140
3193
  if (typeof pkg.version === "string") return pkg.version;
3141
3194
  } catch {
3142
3195
  }
@@ -3207,7 +3260,7 @@ function loadContext(specLoreDir) {
3207
3260
  return null;
3208
3261
  }
3209
3262
  try {
3210
- const content = readFileSync9(contextPath, "utf-8");
3263
+ const content = readFileSync10(contextPath, "utf-8");
3211
3264
  const context = JSON.parse(content);
3212
3265
  logger.debug("Loaded cached context.json");
3213
3266
  return context;
@@ -3226,7 +3279,7 @@ function hasGitHeadChanged(specLoreDir) {
3226
3279
  }).trim();
3227
3280
  const headFile = join13(specLoreDir, ".git-head");
3228
3281
  if (!existsSync13(headFile)) return true;
3229
- const savedHead = readFileSync9(headFile, "utf-8").trim();
3282
+ const savedHead = readFileSync10(headFile, "utf-8").trim();
3230
3283
  if (savedHead !== head) {
3231
3284
  writeFileSync8(headFile, head, "utf-8");
3232
3285
  return true;
@@ -3245,7 +3298,7 @@ function truncateTo(text, maxLines) {
3245
3298
 
3246
3299
  // src/core/analyzer/rdg-builder.ts
3247
3300
  init_logger();
3248
- import { readFileSync as readFileSync10, existsSync as existsSync14, readdirSync as readdirSync3 } from "fs";
3301
+ import { readFileSync as readFileSync11, existsSync as existsSync14, readdirSync as readdirSync3 } from "fs";
3249
3302
  import { join as join14 } from "path";
3250
3303
 
3251
3304
  // src/core/analyzer/cdg-builder.ts
@@ -3260,6 +3313,9 @@ import { execFileSync as execFileSync3 } from "child_process";
3260
3313
  import { globSync as globSync2 } from "glob";
3261
3314
  function analyzeImpact(projectRoot2, context, config) {
3262
3315
  const changedFiles = getChangedFiles(projectRoot2);
3316
+ return analyzeImpactWithChanges(changedFiles, context, config, projectRoot2);
3317
+ }
3318
+ function analyzeImpactWithChanges(changedFiles, context, config, projectRoot2) {
3263
3319
  const affectedModules = determineAffectedModules(changedFiles, context);
3264
3320
  const affectedFeatures = determineAffectedFeatures(affectedModules, projectRoot2, config.spec.outputDir);
3265
3321
  logger.info(
@@ -3317,7 +3373,7 @@ function determineAffectedFeatures(affectedModules, projectRoot2, outputDir) {
3317
3373
  }
3318
3374
 
3319
3375
  // src/core/state-manager/index.ts
3320
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync15, mkdirSync as mkdirSync8 } from "fs";
3376
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync15, mkdirSync as mkdirSync8 } from "fs";
3321
3377
  import { join as join15 } from "path";
3322
3378
  import yaml from "js-yaml";
3323
3379
  import { globSync as globSync3 } from "glob";
@@ -3349,7 +3405,7 @@ var StateManager = class {
3349
3405
  return createDefaultState();
3350
3406
  }
3351
3407
  try {
3352
- const content = readFileSync11(this.statePath, "utf-8");
3408
+ const content = readFileSync12(this.statePath, "utf-8");
3353
3409
  const parsed = yaml.load(content, { schema: yaml.JSON_SCHEMA });
3354
3410
  if (parsed && typeof parsed === "object") {
3355
3411
  return parsed;
@@ -3511,11 +3567,11 @@ var StateManager = class {
3511
3567
  };
3512
3568
 
3513
3569
  // src/core/test-scaffolder/index.ts
3514
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
3570
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
3515
3571
  import { join as join17, relative as relative3, dirname as dirname4, basename as basename8 } from "path";
3516
3572
 
3517
3573
  // src/core/test-scaffolder/framework-detector.ts
3518
- import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
3574
+ import { readFileSync as readFileSync13, existsSync as existsSync16 } from "fs";
3519
3575
  import { join as join16 } from "path";
3520
3576
  function detectTestFramework(projectRoot2) {
3521
3577
  const pkgPath = join16(projectRoot2, "package.json");
@@ -3523,7 +3579,7 @@ function detectTestFramework(projectRoot2) {
3523
3579
  return "vitest";
3524
3580
  }
3525
3581
  try {
3526
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
3582
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
3527
3583
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3528
3584
  if ("vitest" in deps) return "vitest";
3529
3585
  if ("jest" in deps || "@jest/core" in deps) return "jest";
@@ -3629,7 +3685,7 @@ function generateTestFileContent(featureName, scenarios, framework) {
3629
3685
  return lines.join("\n");
3630
3686
  }
3631
3687
  function appendMissingScenarios(testFilePath, scenarios, _framework) {
3632
- const content = readFileSync13(testFilePath, "utf-8");
3688
+ const content = readFileSync14(testFilePath, "utf-8");
3633
3689
  const missing = [];
3634
3690
  for (const scenario of scenarios) {
3635
3691
  if (!content.includes(`Scenario: ${scenario.name}`)) {
@@ -3663,7 +3719,7 @@ function appendMissingScenarios(testFilePath, scenarios, _framework) {
3663
3719
  }
3664
3720
 
3665
3721
  // src/infra/config.ts
3666
- import { readFileSync as readFileSync14, existsSync as existsSync18 } from "fs";
3722
+ import { readFileSync as readFileSync15, existsSync as existsSync18 } from "fs";
3667
3723
  import { join as join18 } from "path";
3668
3724
  import { homedir } from "os";
3669
3725
  import yaml2 from "js-yaml";
@@ -3726,7 +3782,7 @@ function readYamlIfExists(filePath) {
3726
3782
  return null;
3727
3783
  }
3728
3784
  try {
3729
- const content = readFileSync14(filePath, "utf-8");
3785
+ const content = readFileSync15(filePath, "utf-8");
3730
3786
  const parsed = yaml2.load(content, { schema: yaml2.JSON_SCHEMA });
3731
3787
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3732
3788
  return parsed;
@@ -3850,7 +3906,7 @@ verify:
3850
3906
  // src/mcp/tools.ts
3851
3907
  init_logger();
3852
3908
  import { join as join19 } from "path";
3853
- import { readFileSync as readFileSync15, existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11 } from "fs";
3909
+ import { readFileSync as readFileSync16, existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11 } from "fs";
3854
3910
  import { globSync as globSync4 } from "glob";
3855
3911
  import { toJSONSchema } from "zod";
3856
3912
 
@@ -4094,7 +4150,7 @@ function resolveFeatureFiles(patterns, projectRoot2, config) {
4094
4150
  const matches = globSync4(pattern, { cwd: projectRoot2, absolute: true });
4095
4151
  for (const filePath of matches) {
4096
4152
  if (existsSync19(filePath)) {
4097
- const content = readFileSync15(filePath, "utf-8");
4153
+ const content = readFileSync16(filePath, "utf-8");
4098
4154
  files.push(parseFeatureFile(filePath, content));
4099
4155
  }
4100
4156
  }
@@ -4189,7 +4245,7 @@ function detectAITools2(projectRoot2) {
4189
4245
  detected: existsSync20(join20(projectRoot2, ".cursor")),
4190
4246
  configFiles: cursorConfigs
4191
4247
  });
4192
- const claudeFiles = [".claude/", ".mcp.json", "CLAUDE.md"];
4248
+ const claudeFiles = [".claude/", "CLAUDE.md"];
4193
4249
  const claudeConfigs = claudeFiles.filter((f) => existsSync20(join20(projectRoot2, f)));
4194
4250
  tools.push({
4195
4251
  tool: "claude",
@@ -4208,7 +4264,7 @@ function detectAITools2(projectRoot2) {
4208
4264
 
4209
4265
  // src/mcp/status.ts
4210
4266
  init_logger();
4211
- import { readFileSync as readFileSync16, existsSync as existsSync21, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
4267
+ import { readFileSync as readFileSync17, existsSync as existsSync21, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
4212
4268
  import { join as join21 } from "path";
4213
4269
  import { globSync as globSync5 } from "glob";
4214
4270
  function executeStatusTool(args, projectRoot2) {
@@ -4227,7 +4283,7 @@ function executeStatusTool(args, projectRoot2) {
4227
4283
  let scenarioCount = 0;
4228
4284
  if (existsSync21(path)) {
4229
4285
  try {
4230
- const content = readFileSync16(path, "utf-8");
4286
+ const content = readFileSync17(path, "utf-8");
4231
4287
  const matches = content.match(/Scenario(?: Outline)?:/g);
4232
4288
  scenarioCount = matches?.length ?? 0;
4233
4289
  } catch {
@@ -4250,7 +4306,7 @@ function executeStatusTool(args, projectRoot2) {
4250
4306
  if (args.feature && !filePath.includes(args.feature)) continue;
4251
4307
  let scenarioCount = 0;
4252
4308
  try {
4253
- const content = readFileSync16(filePath, "utf-8");
4309
+ const content = readFileSync17(filePath, "utf-8");
4254
4310
  const matches = content.match(/Scenario(?: Outline)?:/g);
4255
4311
  scenarioCount = matches?.length ?? 0;
4256
4312
  } catch {