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.
- package/README.en.md +116 -65
- package/README.md +113 -62
- package/dist/cli/index.js +378 -173
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +378 -173
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +159 -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,35 @@ 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
|
+
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:
|
|
1039
|
-
rawContent:
|
|
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:
|
|
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 =
|
|
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
|
|
1655
|
-
const result = await mammoth.extractRawText({
|
|
1667
|
+
async function parseDocxBuffer(buffer, idHint = "document") {
|
|
1668
|
+
const result = await mammoth.extractRawText({ buffer });
|
|
1656
1669
|
const content = result.value;
|
|
1657
|
-
const id =
|
|
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
|
|
1673
|
-
const
|
|
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(
|
|
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
|
|
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
|
|
1722
|
-
const
|
|
1723
|
-
const
|
|
1724
|
-
const
|
|
1725
|
-
|
|
1726
|
-
|
|
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:
|
|
1732
|
-
rawContent:
|
|
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
|
|
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 =
|
|
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"))
|
|
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
|
|
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 =
|
|
2497
|
-
|
|
2498
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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")) ?
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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/", "
|
|
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
|
|
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 =
|
|
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 =
|
|
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 {
|