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.
- package/README.en.md +116 -65
- package/README.md +113 -62
- package/dist/cli/index.js +374 -173
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +374 -173
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +155 -113
- package/dist/mcp/server.js.map +1 -1
- package/package.json +12 -4
package/dist/mcp/server.js
CHANGED
|
@@ -2,13 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __esm = (fn, res
|
|
6
|
-
|
|
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
|
-
|
|
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
|
|
1023
|
+
let pdfjsLib;
|
|
1026
1024
|
try {
|
|
1027
|
-
|
|
1028
|
-
pdfParse = mod.default ?? mod;
|
|
1025
|
+
pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
1029
1026
|
} catch {
|
|
1030
|
-
throw new Error("
|
|
1031
|
-
}
|
|
1032
|
-
const { readFileSync:
|
|
1033
|
-
const buffer =
|
|
1034
|
-
const
|
|
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:
|
|
1039
|
-
rawContent:
|
|
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:
|
|
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 =
|
|
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
|
|
1655
|
-
const result = await mammoth.extractRawText({
|
|
1665
|
+
async function parseDocxBuffer(buffer, idHint = "document") {
|
|
1666
|
+
const result = await mammoth.extractRawText({ buffer });
|
|
1656
1667
|
const content = result.value;
|
|
1657
|
-
const id =
|
|
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
|
|
1673
|
-
const
|
|
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(
|
|
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
|
|
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
|
|
1722
|
-
const
|
|
1723
|
-
const
|
|
1724
|
-
const
|
|
1725
|
-
|
|
1726
|
-
|
|
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:
|
|
1732
|
-
rawContent:
|
|
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
|
|
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 =
|
|
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"))
|
|
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
|
|
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 =
|
|
2497
|
-
|
|
2498
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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")) ?
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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/", "
|
|
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
|
|
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 =
|
|
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 =
|
|
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 {
|