speclore 0.1.6 → 0.1.8

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 { readFile, utils } from "xlsx";
948
+ import { readFile, read, utils } from "xlsx";
954
949
 
955
950
  // src/infra/excel-utils.ts
956
951
  function formatCellValue(value) {
@@ -969,6 +964,42 @@ function formatCellValue(value) {
969
964
  }
970
965
 
971
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
+ }
972
1003
  var XlsxReader = class {
973
1004
  name = "xlsx-reader";
974
1005
  supportedFormats = [".xlsx", ".xls"];
@@ -977,40 +1008,7 @@ var XlsxReader = class {
977
1008
  }
978
1009
  read(source) {
979
1010
  const workbook = readFile(source);
980
- const requirements = [];
981
- for (const sheetName of workbook.SheetNames) {
982
- const sheet = workbook.Sheets[sheetName];
983
- if (!sheet) continue;
984
- const allRows = utils.sheet_to_json(sheet, { header: 1, defval: "" });
985
- if (allRows.length === 0) continue;
986
- const headers = allRows[0].map((cell) => formatCellValue(cell));
987
- const dataRows = allRows.slice(1);
988
- const rows = dataRows.map((row) => {
989
- const obj = {};
990
- row.forEach((cell, colNumber) => {
991
- const key = headers[colNumber] ?? `Col${colNumber + 1}`;
992
- obj[key] = formatCellValue(cell);
993
- });
994
- return obj;
995
- });
996
- for (let i = 0; i < rows.length; i++) {
997
- const row = rows[i];
998
- const title = row["Title"] ?? row["title"] ?? row["Name"] ?? row["name"] ?? row["ID"] ?? `Row ${i + 1}`;
999
- const description = row["Description"] ?? row["description"] ?? row["Desc"] ?? "";
1000
- const ac = row["Acceptance Criteria"] ?? row["acceptance"] ?? row["AC"] ?? "";
1001
- if (description || title) {
1002
- requirements.push({
1003
- id: `${sheetName}/${i + 1}`,
1004
- title: String(title),
1005
- description: String(description),
1006
- acceptanceCriteria: ac ? String(ac).split("\n").filter(Boolean) : void 0,
1007
- rawContent: JSON.stringify(row),
1008
- confidence: 0.7
1009
- });
1010
- }
1011
- }
1012
- }
1013
- return Promise.resolve(requirements);
1011
+ return Promise.resolve(parseWorkbook(workbook));
1014
1012
  }
1015
1013
  };
1016
1014
 
@@ -1022,21 +1020,33 @@ var PdfReader = class {
1022
1020
  return /\.pdf$/i.test(source);
1023
1021
  }
1024
1022
  async read(source) {
1025
- let pdfParse;
1023
+ let pdfjsLib;
1026
1024
  try {
1027
- const mod = await import("pdf-parse");
1028
- pdfParse = mod.default ?? mod;
1025
+ pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
1029
1026
  } catch {
1030
- throw new Error("pdf-parse package is required for PDF support. Install: npm i pdf-parse");
1031
- }
1032
- const { readFileSync: readFileSync17 } = await import("fs");
1033
- const buffer = readFileSync17(source);
1034
- 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
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1036
+ }).promise;
1037
+ const textParts = [];
1038
+ for (let i = 1; i <= doc.numPages; i++) {
1039
+ const page = await doc.getPage(i);
1040
+ const textContent = await page.getTextContent();
1041
+ const pageText = textContent.items.map((item) => item.str).join("");
1042
+ if (pageText) textParts.push(pageText);
1043
+ }
1044
+ const text = textParts.join("\n");
1035
1045
  return [{
1036
1046
  id: source.replace(/\\/g, "/").replace(/\.pdf$/i, ""),
1037
1047
  title: source.split("/").pop()?.replace(/\.pdf$/i, "") ?? "Untitled",
1038
- description: data.text,
1039
- rawContent: data.text,
1048
+ description: text,
1049
+ rawContent: text,
1040
1050
  confidence: 0.75
1041
1051
  }];
1042
1052
  }
@@ -1050,13 +1060,13 @@ var ImageReader = class {
1050
1060
  return /\.(png|jpe?g|webp)$/i.test(source);
1051
1061
  }
1052
1062
  async read(source) {
1053
- const { existsSync: existsSync22, readFileSync: readFileSync17 } = await import("fs");
1063
+ const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
1054
1064
  if (!existsSync22(source)) {
1055
1065
  throw new Error(`Image file not found: ${source}`);
1056
1066
  }
1057
1067
  const ext = source.split(".").pop()?.toLowerCase() ?? "png";
1058
1068
  const mimeType = ext === "jpg" ? "image/jpeg" : `image/${ext}`;
1059
- const buffer = readFileSync17(source);
1069
+ const buffer = readFileSync18(source);
1060
1070
  let text;
1061
1071
  try {
1062
1072
  const { createProvider: createProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
@@ -1650,11 +1660,12 @@ function extractDependencies(content) {
1650
1660
 
1651
1661
  // src/core/requirement-reader/docx-reader.ts
1652
1662
  import { basename as basename2, extname as extname2 } from "path";
1663
+ import { readFileSync as readFileSync4 } from "fs";
1653
1664
  import mammoth from "mammoth";
1654
- async function readDocxFile(filePath) {
1655
- const result = await mammoth.extractRawText({ path: filePath });
1665
+ async function parseDocxBuffer(buffer, idHint = "document") {
1666
+ const result = await mammoth.extractRawText({ buffer });
1656
1667
  const content = result.value;
1657
- const id = basename2(filePath, extname2(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1668
+ const id = idHint.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1658
1669
  const lines = content.split("\n").filter((l) => l.trim());
1659
1670
  const title = lines[0]?.trim() ?? id;
1660
1671
  return {
@@ -1665,20 +1676,24 @@ async function readDocxFile(filePath) {
1665
1676
  confidence: 0.9
1666
1677
  };
1667
1678
  }
1679
+ async function readDocxFile(filePath) {
1680
+ const buffer = readFileSync4(filePath);
1681
+ const name = basename2(filePath, extname2(filePath));
1682
+ return parseDocxBuffer(buffer, name);
1683
+ }
1668
1684
 
1669
1685
  // src/core/requirement-reader/xlsx-reader.ts
1670
1686
  import { basename as basename3, extname as extname3 } from "path";
1671
- import { readFile as readFile2, utils as utils2 } from "xlsx";
1672
- function readXlsxFile(filePath) {
1673
- const workbook = readFile2(filePath);
1674
- const id = basename3(filePath, extname3(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1687
+ import { readFile as readFile2, read as read2, utils as utils2 } from "xlsx";
1688
+ function parseWorkbook2(workbook, idHint) {
1689
+ const id = idHint.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1675
1690
  const firstSheetName = workbook.SheetNames[0];
1676
1691
  if (!firstSheetName) {
1677
- return Promise.reject(new Error(`No sheets found in ${filePath}`));
1692
+ return Promise.reject(new Error("No sheets found in workbook"));
1678
1693
  }
1679
1694
  const sheet = workbook.Sheets[firstSheetName];
1680
1695
  if (!sheet) {
1681
- return Promise.reject(new Error(`Sheet "${firstSheetName}" not found in ${filePath}`));
1696
+ return Promise.reject(new Error(`Sheet "${firstSheetName}" not found`));
1682
1697
  }
1683
1698
  const title = firstSheetName;
1684
1699
  const rows = utils2.sheet_to_json(sheet, { header: 1, defval: "" });
@@ -1714,44 +1729,68 @@ function readXlsxFile(filePath) {
1714
1729
  confidence: 0.85
1715
1730
  });
1716
1731
  }
1732
+ function readXlsxFile(filePath) {
1733
+ const workbook = readFile2(filePath);
1734
+ const idHint = basename3(filePath, extname3(filePath));
1735
+ return parseWorkbook2(workbook, idHint);
1736
+ }
1717
1737
 
1718
1738
  // src/core/requirement-reader/pdf-reader.ts
1719
1739
  import { basename as basename4, extname as extname4 } from "path";
1720
1740
  import { readFile as readFile3 } from "fs/promises";
1721
- async function readPdfFile(filePath) {
1722
- const pdfParse = (await import("pdf-parse")).default;
1723
- const dataBuffer = await readFile3(filePath);
1724
- const data = await pdfParse(dataBuffer);
1725
- const id = basename4(filePath, extname4(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1726
- const lines = data.text.split("\n").filter((l) => l.trim());
1741
+ async function extractPdfText(dataBuffer) {
1742
+ const pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
1743
+ const uint8 = new Uint8Array(dataBuffer);
1744
+ const doc = await pdfjsLib.getDocument({
1745
+ data: uint8,
1746
+ useWorkerFetch: false
1747
+ }).promise;
1748
+ const textParts = [];
1749
+ for (let i = 1; i <= doc.numPages; i++) {
1750
+ const page = await doc.getPage(i);
1751
+ const textContent = await page.getTextContent();
1752
+ const pageText = textContent.items.map((item) => item.str).join("");
1753
+ if (pageText) textParts.push(pageText);
1754
+ }
1755
+ return textParts.join("\n");
1756
+ }
1757
+ async function parsePdfBuffer(dataBuffer, idHint = "document") {
1758
+ const text = await extractPdfText(dataBuffer);
1759
+ const id = idHint.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1760
+ const lines = text.split("\n").filter((l) => l.trim());
1727
1761
  const title = lines[0]?.trim() ?? id;
1728
1762
  return {
1729
1763
  id,
1730
1764
  title,
1731
- description: data.text,
1732
- rawContent: data.text,
1765
+ description: text,
1766
+ rawContent: text,
1733
1767
  confidence: 0.8
1734
1768
  };
1735
1769
  }
1770
+ async function readPdfFile(filePath) {
1771
+ const dataBuffer = await readFile3(filePath);
1772
+ const name = basename4(filePath, extname4(filePath));
1773
+ return parsePdfBuffer(dataBuffer, name);
1774
+ }
1736
1775
 
1737
1776
  // src/core/requirement-reader/image-reader.ts
1738
1777
  init_provider();
1739
1778
  init_logger();
1740
1779
  import { basename as basename5, extname as extname5 } from "path";
1741
- import { readFileSync as readFileSync4 } from "fs";
1780
+ import { readFileSync as readFileSync5 } from "fs";
1742
1781
  var MIME_MAP = {
1743
1782
  ".png": "image/png",
1744
1783
  ".jpg": "image/jpeg",
1745
1784
  ".jpeg": "image/jpeg",
1746
1785
  ".webp": "image/webp"
1747
1786
  };
1748
- async function readImageFile(filePath) {
1787
+ async function readImageFile(filePath, providerOverride) {
1749
1788
  const id = basename5(filePath, extname5(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1750
1789
  logger.info(`Reading image via AI Vision: ${filePath}`);
1751
1790
  const ext = extname5(filePath).toLowerCase();
1752
1791
  const mimeType = MIME_MAP[ext] ?? "image/png";
1753
- const buffer = readFileSync4(filePath);
1754
- const provider = await createProvider();
1792
+ const buffer = readFileSync5(filePath);
1793
+ const provider = providerOverride ?? await createProvider();
1755
1794
  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.`;
1756
1795
  if (!provider.generateWithImage) {
1757
1796
  throw new Error(`AI provider '${provider.name}' does not support image/vision input. Use a vision-capable model.`);
@@ -2155,7 +2194,7 @@ function toPosixPath(p) {
2155
2194
 
2156
2195
  // src/core/feature-generator/generator.ts
2157
2196
  var MAX_VALIDATION_RETRIES = 2;
2158
- async function generateFeature(requirement, context, config, projectRoot2) {
2197
+ async function generateFeature(requirement, context, config, projectRoot2, providerOverride) {
2159
2198
  logger.info(`Generating feature for: ${requirement.title}`);
2160
2199
  const registry = getRegistry();
2161
2200
  await registry.invokeLifecycle("beforeSpec", requirement);
@@ -2163,7 +2202,7 @@ async function generateFeature(requirement, context, config, projectRoot2) {
2163
2202
  const aiConfig = config.ai;
2164
2203
  const fallbackConfigs = aiConfig?.fallbackProviders ?? [];
2165
2204
  const providerConfigs = aiConfig ? [aiConfig, ...fallbackConfigs] : [];
2166
- const provider = providerConfigs.length > 1 ? await createProviderChain(providerConfigs) : await createProvider(aiConfig);
2205
+ const provider = providerOverride ?? (providerConfigs.length > 1 ? await createProviderChain(providerConfigs) : await createProvider(aiConfig));
2167
2206
  if (!provider.isAvailable()) {
2168
2207
  throw new Error("AI provider not available. Set API key in environment or config.yaml.");
2169
2208
  }
@@ -2358,9 +2397,9 @@ function detectAITools(projectRoot2) {
2358
2397
  tools.push("cursor");
2359
2398
  logger.debug("Detected Cursor (.cursor/)");
2360
2399
  }
2361
- if (existsSync8(join7(projectRoot2, ".claude")) || existsSync8(join7(projectRoot2, "CLAUDE.md")) || existsSync8(join7(projectRoot2, ".mcp.json"))) {
2400
+ if (existsSync8(join7(projectRoot2, ".claude")) || existsSync8(join7(projectRoot2, "CLAUDE.md"))) {
2362
2401
  tools.push("claude");
2363
- logger.debug("Detected Claude Code (.claude/ or CLAUDE.md or .mcp.json)");
2402
+ logger.debug("Detected Claude Code (.claude/ or CLAUDE.md)");
2364
2403
  }
2365
2404
  if (existsSync8(join7(projectRoot2, ".qoder"))) {
2366
2405
  tools.push("qoder");
@@ -2493,9 +2532,9 @@ function readModuleConfig(config) {
2493
2532
 
2494
2533
  // src/core/constraint-coder/index.ts
2495
2534
  init_logger();
2496
- 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
2497
- \u683C\u5F0F\uFF1A{ "feature": "specs/...", "scenarios": { "Scenario\u540D\u79F0": { "testFile": "...", "testMethod": "..." } } }
2498
- \u6BCF\u6B21\u4FEE\u6539\u6D4B\u8BD5\u65F6\u540C\u6B65\u66F4\u65B0\u6620\u5C04\u6587\u4EF6\u3002`;
2535
+ var MAPPING_INSTRUCTIONS = `Generate a mapping file for each test file at .speclore/mappings/{module}/{feature-name}.json.
2536
+ Format: { "feature": "specs/...", "scenarios": { "Scenario name": { "testFile": "...", "testMethod": "..." } } }
2537
+ Keep mapping files in sync whenever tests are modified.`;
2499
2538
  async function generateConstraints(projectRoot2, features, _context, config) {
2500
2539
  const tools = detectAITools(projectRoot2);
2501
2540
  if (tools.length === 0) {
@@ -2557,7 +2596,7 @@ import { execFileSync } from "child_process";
2557
2596
 
2558
2597
  // src/core/verifier/mapping-resolver.ts
2559
2598
  init_logger();
2560
- import { readFileSync as readFileSync5, existsSync as existsSync10, readdirSync } from "fs";
2599
+ import { readFileSync as readFileSync6, existsSync as existsSync10, readdirSync } from "fs";
2561
2600
  import { join as join9 } from "path";
2562
2601
  function resolveMappings(projectRoot2, features, testOutput) {
2563
2602
  const results = [];
@@ -2587,7 +2626,7 @@ function resolveFromMappingFile(projectRoot2, _feature, scenario) {
2587
2626
  const files = findMappingFiles(mappingsDir);
2588
2627
  for (const file of files) {
2589
2628
  try {
2590
- const content = readFileSync5(file, "utf-8");
2629
+ const content = readFileSync6(file, "utf-8");
2591
2630
  const mapping = JSON.parse(content);
2592
2631
  if (mapping.scenarios && scenario.name in mapping.scenarios) {
2593
2632
  const entry = mapping.scenarios[scenario.name];
@@ -2617,7 +2656,7 @@ function resolveFromTag(projectRoot2, _feature, scenario) {
2617
2656
  const files = findTestFiles(testsDir);
2618
2657
  for (const file of files) {
2619
2658
  try {
2620
- const content = readFileSync5(file, "utf-8");
2659
+ const content = readFileSync6(file, "utf-8");
2621
2660
  const tagRegex = /@speclore-scenario:\s*(.+)/g;
2622
2661
  let match;
2623
2662
  while ((match = tagRegex.exec(content)) !== null) {
@@ -2780,19 +2819,19 @@ function executeTestCommand(projectRoot2, config) {
2780
2819
 
2781
2820
  // src/core/verifier/report-generator.ts
2782
2821
  init_logger();
2783
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
2822
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
2784
2823
  import { join as join10, dirname as dirname2 } from "path";
2785
2824
  import { fileURLToPath } from "url";
2786
2825
 
2787
2826
  // src/core/context-engine/context-writer.ts
2788
2827
  init_logger();
2789
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as existsSync13, statSync as statSync3, mkdirSync as mkdirSync7 } from "fs";
2828
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync13, statSync as statSync3, mkdirSync as mkdirSync7 } from "fs";
2790
2829
  import { join as join13 } from "path";
2791
2830
  import { execFileSync as execFileSync2 } from "child_process";
2792
2831
 
2793
2832
  // src/core/context-engine/graph-builder.ts
2794
2833
  init_logger();
2795
- import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync7 } from "fs";
2834
+ import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync8 } from "fs";
2796
2835
  import { join as join11, basename as basename7, extname as extname7, relative as relative2 } from "path";
2797
2836
  function detectProjectInfo(projectRoot2) {
2798
2837
  const info = {
@@ -2806,7 +2845,7 @@ function detectProjectInfo(projectRoot2) {
2806
2845
  info.language = "typescript";
2807
2846
  info.buildTool = "npm";
2808
2847
  try {
2809
- const pkg = JSON.parse(readFileSync7(join11(projectRoot2, "package.json"), "utf-8"));
2848
+ const pkg = JSON.parse(readFileSync8(join11(projectRoot2, "package.json"), "utf-8"));
2810
2849
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2811
2850
  if ("next" in deps) info.framework = "next.js";
2812
2851
  else if ("@nestjs/core" in deps) info.framework = "nestjs";
@@ -2823,7 +2862,7 @@ function detectProjectInfo(projectRoot2) {
2823
2862
  info.buildTool = "maven";
2824
2863
  info.testFramework = "junit";
2825
2864
  try {
2826
- const pom = readFileSync7(join11(projectRoot2, "pom.xml"), "utf-8");
2865
+ const pom = readFileSync8(join11(projectRoot2, "pom.xml"), "utf-8");
2827
2866
  if (pom.includes("spring-boot")) info.framework = "spring-boot";
2828
2867
  } catch {
2829
2868
  }
@@ -2844,7 +2883,7 @@ function detectProjectInfo(projectRoot2) {
2844
2883
  info.buildTool = "pip";
2845
2884
  info.testFramework = "pytest";
2846
2885
  try {
2847
- const reqs = existsSync12(join11(projectRoot2, "requirements.txt")) ? readFileSync7(join11(projectRoot2, "requirements.txt"), "utf-8") : readFileSync7(join11(projectRoot2, "pyproject.toml"), "utf-8");
2886
+ const reqs = existsSync12(join11(projectRoot2, "requirements.txt")) ? readFileSync8(join11(projectRoot2, "requirements.txt"), "utf-8") : readFileSync8(join11(projectRoot2, "pyproject.toml"), "utf-8");
2848
2887
  if (reqs.includes("django")) info.framework = "django";
2849
2888
  else if (reqs.includes("flask")) info.framework = "flask";
2850
2889
  else if (reqs.includes("fastapi")) info.framework = "fastapi";
@@ -2931,7 +2970,7 @@ function scanImports(dir) {
2931
2970
  const ext = extname7(entry);
2932
2971
  if (![".ts", ".tsx", ".js", ".jsx", ".java", ".py"].includes(ext)) continue;
2933
2972
  try {
2934
- const content = readFileSync7(fullPath, "utf-8");
2973
+ const content = readFileSync8(fullPath, "utf-8");
2935
2974
  const importRegex = /(?:import\s+.*?from\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
2936
2975
  let match;
2937
2976
  while ((match = importRegex.exec(content)) !== null) {
@@ -2968,7 +3007,7 @@ function analyzeFileForEntities(filePath, modName, modPath, modRelativePath) {
2968
3007
  const name = basename7(filePath, ext);
2969
3008
  if (!ANALYZABLE_EXTENSIONS.has(ext)) return entities;
2970
3009
  try {
2971
- const content = readFileSync7(filePath, "utf-8");
3010
+ const content = readFileSync8(filePath, "utf-8");
2972
3011
  const relFile = toPosixPath(join11(modRelativePath, relative2(modPath, filePath)));
2973
3012
  if (/(?:Entity|Model|Domain|Schema)$/i.test(name)) {
2974
3013
  entities.push({ name, module: modName, file: relFile });
@@ -3021,7 +3060,7 @@ function analyzeFileForApis(filePath, modName, modPath, modRelativePath) {
3021
3060
  const name = basename7(filePath, ext);
3022
3061
  if (!ANALYZABLE_EXTENSIONS.has(ext)) return apis;
3023
3062
  try {
3024
- const content = readFileSync7(filePath, "utf-8");
3063
+ const content = readFileSync8(filePath, "utf-8");
3025
3064
  const relFile = toPosixPath(join11(modRelativePath, relative2(modPath, filePath)));
3026
3065
  if (/Controller|Resource|Handler|Route$/i.test(name)) {
3027
3066
  apis.push({
@@ -3138,7 +3177,7 @@ function extractApis(projectRoot2, modules) {
3138
3177
  }
3139
3178
 
3140
3179
  // src/version.ts
3141
- import { readFileSync as readFileSync8 } from "fs";
3180
+ import { readFileSync as readFileSync9 } from "fs";
3142
3181
  import { fileURLToPath as fileURLToPath2 } from "url";
3143
3182
  import { join as join12, dirname as dirname3 } from "path";
3144
3183
  function readVersion() {
@@ -3146,7 +3185,7 @@ function readVersion() {
3146
3185
  for (let depth = 0; depth < 4; depth++) {
3147
3186
  try {
3148
3187
  const pkgPath = join12(currentDir, "package.json");
3149
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
3188
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
3150
3189
  if (typeof pkg.version === "string") return pkg.version;
3151
3190
  } catch {
3152
3191
  }
@@ -3217,7 +3256,7 @@ function loadContext(specLoreDir) {
3217
3256
  return null;
3218
3257
  }
3219
3258
  try {
3220
- const content = readFileSync9(contextPath, "utf-8");
3259
+ const content = readFileSync10(contextPath, "utf-8");
3221
3260
  const context = JSON.parse(content);
3222
3261
  logger.debug("Loaded cached context.json");
3223
3262
  return context;
@@ -3236,7 +3275,7 @@ function hasGitHeadChanged(specLoreDir) {
3236
3275
  }).trim();
3237
3276
  const headFile = join13(specLoreDir, ".git-head");
3238
3277
  if (!existsSync13(headFile)) return true;
3239
- const savedHead = readFileSync9(headFile, "utf-8").trim();
3278
+ const savedHead = readFileSync10(headFile, "utf-8").trim();
3240
3279
  if (savedHead !== head) {
3241
3280
  writeFileSync8(headFile, head, "utf-8");
3242
3281
  return true;
@@ -3255,7 +3294,7 @@ function truncateTo(text, maxLines) {
3255
3294
 
3256
3295
  // src/core/analyzer/rdg-builder.ts
3257
3296
  init_logger();
3258
- import { readFileSync as readFileSync10, existsSync as existsSync14, readdirSync as readdirSync3 } from "fs";
3297
+ import { readFileSync as readFileSync11, existsSync as existsSync14, readdirSync as readdirSync3 } from "fs";
3259
3298
  import { join as join14 } from "path";
3260
3299
 
3261
3300
  // src/core/analyzer/cdg-builder.ts
@@ -3270,6 +3309,9 @@ import { execFileSync as execFileSync3 } from "child_process";
3270
3309
  import { globSync as globSync2 } from "glob";
3271
3310
  function analyzeImpact(projectRoot2, context, config) {
3272
3311
  const changedFiles = getChangedFiles(projectRoot2);
3312
+ return analyzeImpactWithChanges(changedFiles, context, config, projectRoot2);
3313
+ }
3314
+ function analyzeImpactWithChanges(changedFiles, context, config, projectRoot2) {
3273
3315
  const affectedModules = determineAffectedModules(changedFiles, context);
3274
3316
  const affectedFeatures = determineAffectedFeatures(affectedModules, projectRoot2, config.spec.outputDir);
3275
3317
  logger.info(
@@ -3327,7 +3369,7 @@ function determineAffectedFeatures(affectedModules, projectRoot2, outputDir) {
3327
3369
  }
3328
3370
 
3329
3371
  // src/core/state-manager/index.ts
3330
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync15, mkdirSync as mkdirSync8 } from "fs";
3372
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync15, mkdirSync as mkdirSync8 } from "fs";
3331
3373
  import { join as join15 } from "path";
3332
3374
  import yaml from "js-yaml";
3333
3375
  import { globSync as globSync3 } from "glob";
@@ -3359,7 +3401,7 @@ var StateManager = class {
3359
3401
  return createDefaultState();
3360
3402
  }
3361
3403
  try {
3362
- const content = readFileSync11(this.statePath, "utf-8");
3404
+ const content = readFileSync12(this.statePath, "utf-8");
3363
3405
  const parsed = yaml.load(content, { schema: yaml.JSON_SCHEMA });
3364
3406
  if (parsed && typeof parsed === "object") {
3365
3407
  return parsed;
@@ -3521,11 +3563,11 @@ var StateManager = class {
3521
3563
  };
3522
3564
 
3523
3565
  // src/core/test-scaffolder/index.ts
3524
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
3566
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
3525
3567
  import { join as join17, relative as relative3, dirname as dirname4, basename as basename8 } from "path";
3526
3568
 
3527
3569
  // src/core/test-scaffolder/framework-detector.ts
3528
- import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
3570
+ import { readFileSync as readFileSync13, existsSync as existsSync16 } from "fs";
3529
3571
  import { join as join16 } from "path";
3530
3572
  function detectTestFramework(projectRoot2) {
3531
3573
  const pkgPath = join16(projectRoot2, "package.json");
@@ -3533,7 +3575,7 @@ function detectTestFramework(projectRoot2) {
3533
3575
  return "vitest";
3534
3576
  }
3535
3577
  try {
3536
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
3578
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
3537
3579
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3538
3580
  if ("vitest" in deps) return "vitest";
3539
3581
  if ("jest" in deps || "@jest/core" in deps) return "jest";
@@ -3639,7 +3681,7 @@ function generateTestFileContent(featureName, scenarios, framework) {
3639
3681
  return lines.join("\n");
3640
3682
  }
3641
3683
  function appendMissingScenarios(testFilePath, scenarios, _framework) {
3642
- const content = readFileSync13(testFilePath, "utf-8");
3684
+ const content = readFileSync14(testFilePath, "utf-8");
3643
3685
  const missing = [];
3644
3686
  for (const scenario of scenarios) {
3645
3687
  if (!content.includes(`Scenario: ${scenario.name}`)) {
@@ -3673,7 +3715,7 @@ function appendMissingScenarios(testFilePath, scenarios, _framework) {
3673
3715
  }
3674
3716
 
3675
3717
  // src/infra/config.ts
3676
- import { readFileSync as readFileSync14, existsSync as existsSync18 } from "fs";
3718
+ import { readFileSync as readFileSync15, existsSync as existsSync18 } from "fs";
3677
3719
  import { join as join18 } from "path";
3678
3720
  import { homedir } from "os";
3679
3721
  import yaml2 from "js-yaml";
@@ -3736,7 +3778,7 @@ function readYamlIfExists(filePath) {
3736
3778
  return null;
3737
3779
  }
3738
3780
  try {
3739
- const content = readFileSync14(filePath, "utf-8");
3781
+ const content = readFileSync15(filePath, "utf-8");
3740
3782
  const parsed = yaml2.load(content, { schema: yaml2.JSON_SCHEMA });
3741
3783
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3742
3784
  return parsed;
@@ -3860,7 +3902,7 @@ verify:
3860
3902
  // src/mcp/tools.ts
3861
3903
  init_logger();
3862
3904
  import { join as join19 } from "path";
3863
- import { readFileSync as readFileSync15, existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11 } from "fs";
3905
+ import { readFileSync as readFileSync16, existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11 } from "fs";
3864
3906
  import { globSync as globSync4 } from "glob";
3865
3907
  import { toJSONSchema } from "zod";
3866
3908
 
@@ -4104,7 +4146,7 @@ function resolveFeatureFiles(patterns, projectRoot2, config) {
4104
4146
  const matches = globSync4(pattern, { cwd: projectRoot2, absolute: true });
4105
4147
  for (const filePath of matches) {
4106
4148
  if (existsSync19(filePath)) {
4107
- const content = readFileSync15(filePath, "utf-8");
4149
+ const content = readFileSync16(filePath, "utf-8");
4108
4150
  files.push(parseFeatureFile(filePath, content));
4109
4151
  }
4110
4152
  }
@@ -4199,7 +4241,7 @@ function detectAITools2(projectRoot2) {
4199
4241
  detected: existsSync20(join20(projectRoot2, ".cursor")),
4200
4242
  configFiles: cursorConfigs
4201
4243
  });
4202
- const claudeFiles = [".claude/", ".mcp.json", "CLAUDE.md"];
4244
+ const claudeFiles = [".claude/", "CLAUDE.md"];
4203
4245
  const claudeConfigs = claudeFiles.filter((f) => existsSync20(join20(projectRoot2, f)));
4204
4246
  tools.push({
4205
4247
  tool: "claude",
@@ -4218,7 +4260,7 @@ function detectAITools2(projectRoot2) {
4218
4260
 
4219
4261
  // src/mcp/status.ts
4220
4262
  init_logger();
4221
- import { readFileSync as readFileSync16, existsSync as existsSync21, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
4263
+ import { readFileSync as readFileSync17, existsSync as existsSync21, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
4222
4264
  import { join as join21 } from "path";
4223
4265
  import { globSync as globSync5 } from "glob";
4224
4266
  function executeStatusTool(args, projectRoot2) {
@@ -4237,7 +4279,7 @@ function executeStatusTool(args, projectRoot2) {
4237
4279
  let scenarioCount = 0;
4238
4280
  if (existsSync21(path)) {
4239
4281
  try {
4240
- const content = readFileSync16(path, "utf-8");
4282
+ const content = readFileSync17(path, "utf-8");
4241
4283
  const matches = content.match(/Scenario(?: Outline)?:/g);
4242
4284
  scenarioCount = matches?.length ?? 0;
4243
4285
  } catch {
@@ -4260,7 +4302,7 @@ function executeStatusTool(args, projectRoot2) {
4260
4302
  if (args.feature && !filePath.includes(args.feature)) continue;
4261
4303
  let scenarioCount = 0;
4262
4304
  try {
4263
- const content = readFileSync16(filePath, "utf-8");
4305
+ const content = readFileSync17(filePath, "utf-8");
4264
4306
  const matches = content.match(/Scenario(?: Outline)?:/g);
4265
4307
  scenarioCount = matches?.length ?? 0;
4266
4308
  } catch {