speclore 0.1.6 → 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 { 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,35 @@ 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
+ 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");
1035
1047
  return [{
1036
1048
  id: source.replace(/\\/g, "/").replace(/\.pdf$/i, ""),
1037
1049
  title: source.split("/").pop()?.replace(/\.pdf$/i, "") ?? "Untitled",
1038
- description: data.text,
1039
- rawContent: data.text,
1050
+ description: text,
1051
+ rawContent: text,
1040
1052
  confidence: 0.75
1041
1053
  }];
1042
1054
  }
@@ -1050,13 +1062,13 @@ var ImageReader = class {
1050
1062
  return /\.(png|jpe?g|webp)$/i.test(source);
1051
1063
  }
1052
1064
  async read(source) {
1053
- const { existsSync: existsSync22, readFileSync: readFileSync17 } = await import("fs");
1065
+ const { existsSync: existsSync22, readFileSync: readFileSync18 } = await import("fs");
1054
1066
  if (!existsSync22(source)) {
1055
1067
  throw new Error(`Image file not found: ${source}`);
1056
1068
  }
1057
1069
  const ext = source.split(".").pop()?.toLowerCase() ?? "png";
1058
1070
  const mimeType = ext === "jpg" ? "image/jpeg" : `image/${ext}`;
1059
- const buffer = readFileSync17(source);
1071
+ const buffer = readFileSync18(source);
1060
1072
  let text;
1061
1073
  try {
1062
1074
  const { createProvider: createProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
@@ -1650,11 +1662,12 @@ function extractDependencies(content) {
1650
1662
 
1651
1663
  // src/core/requirement-reader/docx-reader.ts
1652
1664
  import { basename as basename2, extname as extname2 } from "path";
1665
+ import { readFileSync as readFileSync4 } from "fs";
1653
1666
  import mammoth from "mammoth";
1654
- async function readDocxFile(filePath) {
1655
- const result = await mammoth.extractRawText({ path: filePath });
1667
+ async function parseDocxBuffer(buffer, idHint = "document") {
1668
+ const result = await mammoth.extractRawText({ buffer });
1656
1669
  const content = result.value;
1657
- 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, "-");
1658
1671
  const lines = content.split("\n").filter((l) => l.trim());
1659
1672
  const title = lines[0]?.trim() ?? id;
1660
1673
  return {
@@ -1665,20 +1678,24 @@ async function readDocxFile(filePath) {
1665
1678
  confidence: 0.9
1666
1679
  };
1667
1680
  }
1681
+ async function readDocxFile(filePath) {
1682
+ const buffer = readFileSync4(filePath);
1683
+ const name = basename2(filePath, extname2(filePath));
1684
+ return parseDocxBuffer(buffer, name);
1685
+ }
1668
1686
 
1669
1687
  // src/core/requirement-reader/xlsx-reader.ts
1670
1688
  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, "-");
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, "-");
1675
1692
  const firstSheetName = workbook.SheetNames[0];
1676
1693
  if (!firstSheetName) {
1677
- return Promise.reject(new Error(`No sheets found in ${filePath}`));
1694
+ return Promise.reject(new Error("No sheets found in workbook"));
1678
1695
  }
1679
1696
  const sheet = workbook.Sheets[firstSheetName];
1680
1697
  if (!sheet) {
1681
- return Promise.reject(new Error(`Sheet "${firstSheetName}" not found in ${filePath}`));
1698
+ return Promise.reject(new Error(`Sheet "${firstSheetName}" not found`));
1682
1699
  }
1683
1700
  const title = firstSheetName;
1684
1701
  const rows = utils2.sheet_to_json(sheet, { header: 1, defval: "" });
@@ -1714,44 +1731,70 @@ function readXlsxFile(filePath) {
1714
1731
  confidence: 0.85
1715
1732
  });
1716
1733
  }
1734
+ function readXlsxFile(filePath) {
1735
+ const workbook = readFile2(filePath);
1736
+ const idHint = basename3(filePath, extname3(filePath));
1737
+ return parseWorkbook2(workbook, idHint);
1738
+ }
1717
1739
 
1718
1740
  // src/core/requirement-reader/pdf-reader.ts
1719
1741
  import { basename as basename4, extname as extname4 } from "path";
1720
1742
  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());
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());
1727
1765
  const title = lines[0]?.trim() ?? id;
1728
1766
  return {
1729
1767
  id,
1730
1768
  title,
1731
- description: data.text,
1732
- rawContent: data.text,
1769
+ description: text,
1770
+ rawContent: text,
1733
1771
  confidence: 0.8
1734
1772
  };
1735
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
+ }
1736
1779
 
1737
1780
  // src/core/requirement-reader/image-reader.ts
1738
1781
  init_provider();
1739
1782
  init_logger();
1740
1783
  import { basename as basename5, extname as extname5 } from "path";
1741
- import { readFileSync as readFileSync4 } from "fs";
1784
+ import { readFileSync as readFileSync5 } from "fs";
1742
1785
  var MIME_MAP = {
1743
1786
  ".png": "image/png",
1744
1787
  ".jpg": "image/jpeg",
1745
1788
  ".jpeg": "image/jpeg",
1746
1789
  ".webp": "image/webp"
1747
1790
  };
1748
- async function readImageFile(filePath) {
1791
+ async function readImageFile(filePath, providerOverride) {
1749
1792
  const id = basename5(filePath, extname5(filePath)).toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff-]/g, "-").replace(/-+/g, "-");
1750
1793
  logger.info(`Reading image via AI Vision: ${filePath}`);
1751
1794
  const ext = extname5(filePath).toLowerCase();
1752
1795
  const mimeType = MIME_MAP[ext] ?? "image/png";
1753
- const buffer = readFileSync4(filePath);
1754
- const provider = await createProvider();
1796
+ const buffer = readFileSync5(filePath);
1797
+ const provider = providerOverride ?? await createProvider();
1755
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.`;
1756
1799
  if (!provider.generateWithImage) {
1757
1800
  throw new Error(`AI provider '${provider.name}' does not support image/vision input. Use a vision-capable model.`);
@@ -2155,7 +2198,7 @@ function toPosixPath(p) {
2155
2198
 
2156
2199
  // src/core/feature-generator/generator.ts
2157
2200
  var MAX_VALIDATION_RETRIES = 2;
2158
- async function generateFeature(requirement, context, config, projectRoot2) {
2201
+ async function generateFeature(requirement, context, config, projectRoot2, providerOverride) {
2159
2202
  logger.info(`Generating feature for: ${requirement.title}`);
2160
2203
  const registry = getRegistry();
2161
2204
  await registry.invokeLifecycle("beforeSpec", requirement);
@@ -2163,7 +2206,7 @@ async function generateFeature(requirement, context, config, projectRoot2) {
2163
2206
  const aiConfig = config.ai;
2164
2207
  const fallbackConfigs = aiConfig?.fallbackProviders ?? [];
2165
2208
  const providerConfigs = aiConfig ? [aiConfig, ...fallbackConfigs] : [];
2166
- const provider = providerConfigs.length > 1 ? await createProviderChain(providerConfigs) : await createProvider(aiConfig);
2209
+ const provider = providerOverride ?? (providerConfigs.length > 1 ? await createProviderChain(providerConfigs) : await createProvider(aiConfig));
2167
2210
  if (!provider.isAvailable()) {
2168
2211
  throw new Error("AI provider not available. Set API key in environment or config.yaml.");
2169
2212
  }
@@ -2358,9 +2401,9 @@ function detectAITools(projectRoot2) {
2358
2401
  tools.push("cursor");
2359
2402
  logger.debug("Detected Cursor (.cursor/)");
2360
2403
  }
2361
- 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"))) {
2362
2405
  tools.push("claude");
2363
- logger.debug("Detected Claude Code (.claude/ or CLAUDE.md or .mcp.json)");
2406
+ logger.debug("Detected Claude Code (.claude/ or CLAUDE.md)");
2364
2407
  }
2365
2408
  if (existsSync8(join7(projectRoot2, ".qoder"))) {
2366
2409
  tools.push("qoder");
@@ -2493,9 +2536,9 @@ function readModuleConfig(config) {
2493
2536
 
2494
2537
  // src/core/constraint-coder/index.ts
2495
2538
  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`;
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.`;
2499
2542
  async function generateConstraints(projectRoot2, features, _context, config) {
2500
2543
  const tools = detectAITools(projectRoot2);
2501
2544
  if (tools.length === 0) {
@@ -2557,7 +2600,7 @@ import { execFileSync } from "child_process";
2557
2600
 
2558
2601
  // src/core/verifier/mapping-resolver.ts
2559
2602
  init_logger();
2560
- import { readFileSync as readFileSync5, existsSync as existsSync10, readdirSync } from "fs";
2603
+ import { readFileSync as readFileSync6, existsSync as existsSync10, readdirSync } from "fs";
2561
2604
  import { join as join9 } from "path";
2562
2605
  function resolveMappings(projectRoot2, features, testOutput) {
2563
2606
  const results = [];
@@ -2587,7 +2630,7 @@ function resolveFromMappingFile(projectRoot2, _feature, scenario) {
2587
2630
  const files = findMappingFiles(mappingsDir);
2588
2631
  for (const file of files) {
2589
2632
  try {
2590
- const content = readFileSync5(file, "utf-8");
2633
+ const content = readFileSync6(file, "utf-8");
2591
2634
  const mapping = JSON.parse(content);
2592
2635
  if (mapping.scenarios && scenario.name in mapping.scenarios) {
2593
2636
  const entry = mapping.scenarios[scenario.name];
@@ -2617,7 +2660,7 @@ function resolveFromTag(projectRoot2, _feature, scenario) {
2617
2660
  const files = findTestFiles(testsDir);
2618
2661
  for (const file of files) {
2619
2662
  try {
2620
- const content = readFileSync5(file, "utf-8");
2663
+ const content = readFileSync6(file, "utf-8");
2621
2664
  const tagRegex = /@speclore-scenario:\s*(.+)/g;
2622
2665
  let match;
2623
2666
  while ((match = tagRegex.exec(content)) !== null) {
@@ -2780,19 +2823,19 @@ function executeTestCommand(projectRoot2, config) {
2780
2823
 
2781
2824
  // src/core/verifier/report-generator.ts
2782
2825
  init_logger();
2783
- 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";
2784
2827
  import { join as join10, dirname as dirname2 } from "path";
2785
2828
  import { fileURLToPath } from "url";
2786
2829
 
2787
2830
  // src/core/context-engine/context-writer.ts
2788
2831
  init_logger();
2789
- 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";
2790
2833
  import { join as join13 } from "path";
2791
2834
  import { execFileSync as execFileSync2 } from "child_process";
2792
2835
 
2793
2836
  // src/core/context-engine/graph-builder.ts
2794
2837
  init_logger();
2795
- 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";
2796
2839
  import { join as join11, basename as basename7, extname as extname7, relative as relative2 } from "path";
2797
2840
  function detectProjectInfo(projectRoot2) {
2798
2841
  const info = {
@@ -2806,7 +2849,7 @@ function detectProjectInfo(projectRoot2) {
2806
2849
  info.language = "typescript";
2807
2850
  info.buildTool = "npm";
2808
2851
  try {
2809
- const pkg = JSON.parse(readFileSync7(join11(projectRoot2, "package.json"), "utf-8"));
2852
+ const pkg = JSON.parse(readFileSync8(join11(projectRoot2, "package.json"), "utf-8"));
2810
2853
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2811
2854
  if ("next" in deps) info.framework = "next.js";
2812
2855
  else if ("@nestjs/core" in deps) info.framework = "nestjs";
@@ -2823,7 +2866,7 @@ function detectProjectInfo(projectRoot2) {
2823
2866
  info.buildTool = "maven";
2824
2867
  info.testFramework = "junit";
2825
2868
  try {
2826
- const pom = readFileSync7(join11(projectRoot2, "pom.xml"), "utf-8");
2869
+ const pom = readFileSync8(join11(projectRoot2, "pom.xml"), "utf-8");
2827
2870
  if (pom.includes("spring-boot")) info.framework = "spring-boot";
2828
2871
  } catch {
2829
2872
  }
@@ -2844,7 +2887,7 @@ function detectProjectInfo(projectRoot2) {
2844
2887
  info.buildTool = "pip";
2845
2888
  info.testFramework = "pytest";
2846
2889
  try {
2847
- 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");
2848
2891
  if (reqs.includes("django")) info.framework = "django";
2849
2892
  else if (reqs.includes("flask")) info.framework = "flask";
2850
2893
  else if (reqs.includes("fastapi")) info.framework = "fastapi";
@@ -2931,7 +2974,7 @@ function scanImports(dir) {
2931
2974
  const ext = extname7(entry);
2932
2975
  if (![".ts", ".tsx", ".js", ".jsx", ".java", ".py"].includes(ext)) continue;
2933
2976
  try {
2934
- const content = readFileSync7(fullPath, "utf-8");
2977
+ const content = readFileSync8(fullPath, "utf-8");
2935
2978
  const importRegex = /(?:import\s+.*?from\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
2936
2979
  let match;
2937
2980
  while ((match = importRegex.exec(content)) !== null) {
@@ -2968,7 +3011,7 @@ function analyzeFileForEntities(filePath, modName, modPath, modRelativePath) {
2968
3011
  const name = basename7(filePath, ext);
2969
3012
  if (!ANALYZABLE_EXTENSIONS.has(ext)) return entities;
2970
3013
  try {
2971
- const content = readFileSync7(filePath, "utf-8");
3014
+ const content = readFileSync8(filePath, "utf-8");
2972
3015
  const relFile = toPosixPath(join11(modRelativePath, relative2(modPath, filePath)));
2973
3016
  if (/(?:Entity|Model|Domain|Schema)$/i.test(name)) {
2974
3017
  entities.push({ name, module: modName, file: relFile });
@@ -3021,7 +3064,7 @@ function analyzeFileForApis(filePath, modName, modPath, modRelativePath) {
3021
3064
  const name = basename7(filePath, ext);
3022
3065
  if (!ANALYZABLE_EXTENSIONS.has(ext)) return apis;
3023
3066
  try {
3024
- const content = readFileSync7(filePath, "utf-8");
3067
+ const content = readFileSync8(filePath, "utf-8");
3025
3068
  const relFile = toPosixPath(join11(modRelativePath, relative2(modPath, filePath)));
3026
3069
  if (/Controller|Resource|Handler|Route$/i.test(name)) {
3027
3070
  apis.push({
@@ -3138,7 +3181,7 @@ function extractApis(projectRoot2, modules) {
3138
3181
  }
3139
3182
 
3140
3183
  // src/version.ts
3141
- import { readFileSync as readFileSync8 } from "fs";
3184
+ import { readFileSync as readFileSync9 } from "fs";
3142
3185
  import { fileURLToPath as fileURLToPath2 } from "url";
3143
3186
  import { join as join12, dirname as dirname3 } from "path";
3144
3187
  function readVersion() {
@@ -3146,7 +3189,7 @@ function readVersion() {
3146
3189
  for (let depth = 0; depth < 4; depth++) {
3147
3190
  try {
3148
3191
  const pkgPath = join12(currentDir, "package.json");
3149
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
3192
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
3150
3193
  if (typeof pkg.version === "string") return pkg.version;
3151
3194
  } catch {
3152
3195
  }
@@ -3217,7 +3260,7 @@ function loadContext(specLoreDir) {
3217
3260
  return null;
3218
3261
  }
3219
3262
  try {
3220
- const content = readFileSync9(contextPath, "utf-8");
3263
+ const content = readFileSync10(contextPath, "utf-8");
3221
3264
  const context = JSON.parse(content);
3222
3265
  logger.debug("Loaded cached context.json");
3223
3266
  return context;
@@ -3236,7 +3279,7 @@ function hasGitHeadChanged(specLoreDir) {
3236
3279
  }).trim();
3237
3280
  const headFile = join13(specLoreDir, ".git-head");
3238
3281
  if (!existsSync13(headFile)) return true;
3239
- const savedHead = readFileSync9(headFile, "utf-8").trim();
3282
+ const savedHead = readFileSync10(headFile, "utf-8").trim();
3240
3283
  if (savedHead !== head) {
3241
3284
  writeFileSync8(headFile, head, "utf-8");
3242
3285
  return true;
@@ -3255,7 +3298,7 @@ function truncateTo(text, maxLines) {
3255
3298
 
3256
3299
  // src/core/analyzer/rdg-builder.ts
3257
3300
  init_logger();
3258
- 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";
3259
3302
  import { join as join14 } from "path";
3260
3303
 
3261
3304
  // src/core/analyzer/cdg-builder.ts
@@ -3270,6 +3313,9 @@ import { execFileSync as execFileSync3 } from "child_process";
3270
3313
  import { globSync as globSync2 } from "glob";
3271
3314
  function analyzeImpact(projectRoot2, context, config) {
3272
3315
  const changedFiles = getChangedFiles(projectRoot2);
3316
+ return analyzeImpactWithChanges(changedFiles, context, config, projectRoot2);
3317
+ }
3318
+ function analyzeImpactWithChanges(changedFiles, context, config, projectRoot2) {
3273
3319
  const affectedModules = determineAffectedModules(changedFiles, context);
3274
3320
  const affectedFeatures = determineAffectedFeatures(affectedModules, projectRoot2, config.spec.outputDir);
3275
3321
  logger.info(
@@ -3327,7 +3373,7 @@ function determineAffectedFeatures(affectedModules, projectRoot2, outputDir) {
3327
3373
  }
3328
3374
 
3329
3375
  // src/core/state-manager/index.ts
3330
- 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";
3331
3377
  import { join as join15 } from "path";
3332
3378
  import yaml from "js-yaml";
3333
3379
  import { globSync as globSync3 } from "glob";
@@ -3359,7 +3405,7 @@ var StateManager = class {
3359
3405
  return createDefaultState();
3360
3406
  }
3361
3407
  try {
3362
- const content = readFileSync11(this.statePath, "utf-8");
3408
+ const content = readFileSync12(this.statePath, "utf-8");
3363
3409
  const parsed = yaml.load(content, { schema: yaml.JSON_SCHEMA });
3364
3410
  if (parsed && typeof parsed === "object") {
3365
3411
  return parsed;
@@ -3521,11 +3567,11 @@ var StateManager = class {
3521
3567
  };
3522
3568
 
3523
3569
  // src/core/test-scaffolder/index.ts
3524
- 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";
3525
3571
  import { join as join17, relative as relative3, dirname as dirname4, basename as basename8 } from "path";
3526
3572
 
3527
3573
  // src/core/test-scaffolder/framework-detector.ts
3528
- import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
3574
+ import { readFileSync as readFileSync13, existsSync as existsSync16 } from "fs";
3529
3575
  import { join as join16 } from "path";
3530
3576
  function detectTestFramework(projectRoot2) {
3531
3577
  const pkgPath = join16(projectRoot2, "package.json");
@@ -3533,7 +3579,7 @@ function detectTestFramework(projectRoot2) {
3533
3579
  return "vitest";
3534
3580
  }
3535
3581
  try {
3536
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
3582
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
3537
3583
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3538
3584
  if ("vitest" in deps) return "vitest";
3539
3585
  if ("jest" in deps || "@jest/core" in deps) return "jest";
@@ -3639,7 +3685,7 @@ function generateTestFileContent(featureName, scenarios, framework) {
3639
3685
  return lines.join("\n");
3640
3686
  }
3641
3687
  function appendMissingScenarios(testFilePath, scenarios, _framework) {
3642
- const content = readFileSync13(testFilePath, "utf-8");
3688
+ const content = readFileSync14(testFilePath, "utf-8");
3643
3689
  const missing = [];
3644
3690
  for (const scenario of scenarios) {
3645
3691
  if (!content.includes(`Scenario: ${scenario.name}`)) {
@@ -3673,7 +3719,7 @@ function appendMissingScenarios(testFilePath, scenarios, _framework) {
3673
3719
  }
3674
3720
 
3675
3721
  // src/infra/config.ts
3676
- import { readFileSync as readFileSync14, existsSync as existsSync18 } from "fs";
3722
+ import { readFileSync as readFileSync15, existsSync as existsSync18 } from "fs";
3677
3723
  import { join as join18 } from "path";
3678
3724
  import { homedir } from "os";
3679
3725
  import yaml2 from "js-yaml";
@@ -3736,7 +3782,7 @@ function readYamlIfExists(filePath) {
3736
3782
  return null;
3737
3783
  }
3738
3784
  try {
3739
- const content = readFileSync14(filePath, "utf-8");
3785
+ const content = readFileSync15(filePath, "utf-8");
3740
3786
  const parsed = yaml2.load(content, { schema: yaml2.JSON_SCHEMA });
3741
3787
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3742
3788
  return parsed;
@@ -3860,7 +3906,7 @@ verify:
3860
3906
  // src/mcp/tools.ts
3861
3907
  init_logger();
3862
3908
  import { join as join19 } from "path";
3863
- 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";
3864
3910
  import { globSync as globSync4 } from "glob";
3865
3911
  import { toJSONSchema } from "zod";
3866
3912
 
@@ -4104,7 +4150,7 @@ function resolveFeatureFiles(patterns, projectRoot2, config) {
4104
4150
  const matches = globSync4(pattern, { cwd: projectRoot2, absolute: true });
4105
4151
  for (const filePath of matches) {
4106
4152
  if (existsSync19(filePath)) {
4107
- const content = readFileSync15(filePath, "utf-8");
4153
+ const content = readFileSync16(filePath, "utf-8");
4108
4154
  files.push(parseFeatureFile(filePath, content));
4109
4155
  }
4110
4156
  }
@@ -4199,7 +4245,7 @@ function detectAITools2(projectRoot2) {
4199
4245
  detected: existsSync20(join20(projectRoot2, ".cursor")),
4200
4246
  configFiles: cursorConfigs
4201
4247
  });
4202
- const claudeFiles = [".claude/", ".mcp.json", "CLAUDE.md"];
4248
+ const claudeFiles = [".claude/", "CLAUDE.md"];
4203
4249
  const claudeConfigs = claudeFiles.filter((f) => existsSync20(join20(projectRoot2, f)));
4204
4250
  tools.push({
4205
4251
  tool: "claude",
@@ -4218,7 +4264,7 @@ function detectAITools2(projectRoot2) {
4218
4264
 
4219
4265
  // src/mcp/status.ts
4220
4266
  init_logger();
4221
- 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";
4222
4268
  import { join as join21 } from "path";
4223
4269
  import { globSync as globSync5 } from "glob";
4224
4270
  function executeStatusTool(args, projectRoot2) {
@@ -4237,7 +4283,7 @@ function executeStatusTool(args, projectRoot2) {
4237
4283
  let scenarioCount = 0;
4238
4284
  if (existsSync21(path)) {
4239
4285
  try {
4240
- const content = readFileSync16(path, "utf-8");
4286
+ const content = readFileSync17(path, "utf-8");
4241
4287
  const matches = content.match(/Scenario(?: Outline)?:/g);
4242
4288
  scenarioCount = matches?.length ?? 0;
4243
4289
  } catch {
@@ -4260,7 +4306,7 @@ function executeStatusTool(args, projectRoot2) {
4260
4306
  if (args.feature && !filePath.includes(args.feature)) continue;
4261
4307
  let scenarioCount = 0;
4262
4308
  try {
4263
- const content = readFileSync16(filePath, "utf-8");
4309
+ const content = readFileSync17(filePath, "utf-8");
4264
4310
  const matches = content.match(/Scenario(?: Outline)?:/g);
4265
4311
  scenarioCount = matches?.length ?? 0;
4266
4312
  } catch {