opencode-swarm 6.9.0 → 6.11.0
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.md +163 -4
- package/dist/config/index.d.ts +2 -2
- package/dist/config/schema.d.ts +7 -0
- package/dist/index.js +1898 -256
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/pre-check-batch.d.ts +54 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -29198,6 +29198,7 @@ var init_dist = __esm(() => {
|
|
|
29198
29198
|
});
|
|
29199
29199
|
|
|
29200
29200
|
// src/tools/lint.ts
|
|
29201
|
+
import * as path10 from "path";
|
|
29201
29202
|
function validateArgs(args2) {
|
|
29202
29203
|
if (typeof args2 !== "object" || args2 === null)
|
|
29203
29204
|
return false;
|
|
@@ -29208,17 +29209,20 @@ function validateArgs(args2) {
|
|
|
29208
29209
|
}
|
|
29209
29210
|
function getLinterCommand(linter, mode) {
|
|
29210
29211
|
const isWindows = process.platform === "win32";
|
|
29212
|
+
const binDir = path10.join(process.cwd(), "node_modules", ".bin");
|
|
29213
|
+
const biomeBin = isWindows ? path10.join(binDir, "biome.EXE") : path10.join(binDir, "biome");
|
|
29214
|
+
const eslintBin = isWindows ? path10.join(binDir, "eslint.cmd") : path10.join(binDir, "eslint");
|
|
29211
29215
|
switch (linter) {
|
|
29212
29216
|
case "biome":
|
|
29213
29217
|
if (mode === "fix") {
|
|
29214
|
-
return isWindows ? [
|
|
29218
|
+
return isWindows ? [biomeBin, "check", "--write", "."] : [biomeBin, "check", "--write", "."];
|
|
29215
29219
|
}
|
|
29216
|
-
return isWindows ? [
|
|
29220
|
+
return isWindows ? [biomeBin, "check", "."] : [biomeBin, "check", "."];
|
|
29217
29221
|
case "eslint":
|
|
29218
29222
|
if (mode === "fix") {
|
|
29219
|
-
return isWindows ? [
|
|
29223
|
+
return isWindows ? [eslintBin, ".", "--fix"] : [eslintBin, ".", "--fix"];
|
|
29220
29224
|
}
|
|
29221
|
-
return isWindows ? [
|
|
29225
|
+
return isWindows ? [eslintBin, "."] : [eslintBin, "."];
|
|
29222
29226
|
}
|
|
29223
29227
|
}
|
|
29224
29228
|
async function detectAvailableLinter() {
|
|
@@ -29346,7 +29350,7 @@ var init_lint = __esm(() => {
|
|
|
29346
29350
|
|
|
29347
29351
|
// src/tools/secretscan.ts
|
|
29348
29352
|
import * as fs6 from "fs";
|
|
29349
|
-
import * as
|
|
29353
|
+
import * as path11 from "path";
|
|
29350
29354
|
function calculateShannonEntropy(str) {
|
|
29351
29355
|
if (str.length === 0)
|
|
29352
29356
|
return 0;
|
|
@@ -29373,7 +29377,7 @@ function isHighEntropyString(str) {
|
|
|
29373
29377
|
function containsPathTraversal(str) {
|
|
29374
29378
|
if (/\.\.[/\\]/.test(str))
|
|
29375
29379
|
return true;
|
|
29376
|
-
const normalized =
|
|
29380
|
+
const normalized = path11.normalize(str);
|
|
29377
29381
|
if (/\.\.[/\\]/.test(normalized))
|
|
29378
29382
|
return true;
|
|
29379
29383
|
if (str.includes("%2e%2e") || str.includes("%2E%2E"))
|
|
@@ -29401,7 +29405,7 @@ function validateDirectoryInput(dir) {
|
|
|
29401
29405
|
return null;
|
|
29402
29406
|
}
|
|
29403
29407
|
function isBinaryFile(filePath, buffer) {
|
|
29404
|
-
const ext =
|
|
29408
|
+
const ext = path11.extname(filePath).toLowerCase();
|
|
29405
29409
|
if (DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
|
|
29406
29410
|
return true;
|
|
29407
29411
|
}
|
|
@@ -29538,9 +29542,9 @@ function isSymlinkLoop(realPath, visited) {
|
|
|
29538
29542
|
return false;
|
|
29539
29543
|
}
|
|
29540
29544
|
function isPathWithinScope(realPath, scanDir) {
|
|
29541
|
-
const resolvedScanDir =
|
|
29542
|
-
const resolvedRealPath =
|
|
29543
|
-
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir +
|
|
29545
|
+
const resolvedScanDir = path11.resolve(scanDir);
|
|
29546
|
+
const resolvedRealPath = path11.resolve(realPath);
|
|
29547
|
+
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path11.sep) || resolvedRealPath.startsWith(resolvedScanDir + "/") || resolvedRealPath.startsWith(resolvedScanDir + "\\");
|
|
29544
29548
|
}
|
|
29545
29549
|
function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
29546
29550
|
skippedDirs: 0,
|
|
@@ -29570,7 +29574,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
29570
29574
|
stats.skippedDirs++;
|
|
29571
29575
|
continue;
|
|
29572
29576
|
}
|
|
29573
|
-
const fullPath =
|
|
29577
|
+
const fullPath = path11.join(dir, entry);
|
|
29574
29578
|
let lstat;
|
|
29575
29579
|
try {
|
|
29576
29580
|
lstat = fs6.lstatSync(fullPath);
|
|
@@ -29601,7 +29605,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
29601
29605
|
const subFiles = findScannableFiles(fullPath, excludeDirs, scanDir, visited, stats);
|
|
29602
29606
|
files.push(...subFiles);
|
|
29603
29607
|
} else if (lstat.isFile()) {
|
|
29604
|
-
const ext =
|
|
29608
|
+
const ext = path11.extname(fullPath).toLowerCase();
|
|
29605
29609
|
if (!DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
|
|
29606
29610
|
files.push(fullPath);
|
|
29607
29611
|
} else {
|
|
@@ -29867,7 +29871,7 @@ var init_secretscan = __esm(() => {
|
|
|
29867
29871
|
}
|
|
29868
29872
|
}
|
|
29869
29873
|
try {
|
|
29870
|
-
const scanDir =
|
|
29874
|
+
const scanDir = path11.resolve(directory);
|
|
29871
29875
|
if (!fs6.existsSync(scanDir)) {
|
|
29872
29876
|
const errorResult = {
|
|
29873
29877
|
error: "directory not found",
|
|
@@ -29996,7 +30000,7 @@ var init_secretscan = __esm(() => {
|
|
|
29996
30000
|
|
|
29997
30001
|
// src/tools/test-runner.ts
|
|
29998
30002
|
import * as fs7 from "fs";
|
|
29999
|
-
import * as
|
|
30003
|
+
import * as path12 from "path";
|
|
30000
30004
|
function containsPathTraversal2(str) {
|
|
30001
30005
|
if (/\.\.[/\\]/.test(str))
|
|
30002
30006
|
return true;
|
|
@@ -30090,7 +30094,7 @@ function hasDevDependency(devDeps, ...patterns) {
|
|
|
30090
30094
|
}
|
|
30091
30095
|
async function detectTestFramework() {
|
|
30092
30096
|
try {
|
|
30093
|
-
const packageJsonPath =
|
|
30097
|
+
const packageJsonPath = path12.join(process.cwd(), "package.json");
|
|
30094
30098
|
if (fs7.existsSync(packageJsonPath)) {
|
|
30095
30099
|
const content = fs7.readFileSync(packageJsonPath, "utf-8");
|
|
30096
30100
|
const pkg = JSON.parse(content);
|
|
@@ -30111,16 +30115,16 @@ async function detectTestFramework() {
|
|
|
30111
30115
|
return "jest";
|
|
30112
30116
|
if (hasDevDependency(devDeps, "mocha", "@types/mocha"))
|
|
30113
30117
|
return "mocha";
|
|
30114
|
-
if (fs7.existsSync(
|
|
30118
|
+
if (fs7.existsSync(path12.join(process.cwd(), "bun.lockb")) || fs7.existsSync(path12.join(process.cwd(), "bun.lock"))) {
|
|
30115
30119
|
if (scripts.test?.includes("bun"))
|
|
30116
30120
|
return "bun";
|
|
30117
30121
|
}
|
|
30118
30122
|
}
|
|
30119
30123
|
} catch {}
|
|
30120
30124
|
try {
|
|
30121
|
-
const pyprojectTomlPath =
|
|
30122
|
-
const setupCfgPath =
|
|
30123
|
-
const requirementsTxtPath =
|
|
30125
|
+
const pyprojectTomlPath = path12.join(process.cwd(), "pyproject.toml");
|
|
30126
|
+
const setupCfgPath = path12.join(process.cwd(), "setup.cfg");
|
|
30127
|
+
const requirementsTxtPath = path12.join(process.cwd(), "requirements.txt");
|
|
30124
30128
|
if (fs7.existsSync(pyprojectTomlPath)) {
|
|
30125
30129
|
const content = fs7.readFileSync(pyprojectTomlPath, "utf-8");
|
|
30126
30130
|
if (content.includes("[tool.pytest"))
|
|
@@ -30140,7 +30144,7 @@ async function detectTestFramework() {
|
|
|
30140
30144
|
}
|
|
30141
30145
|
} catch {}
|
|
30142
30146
|
try {
|
|
30143
|
-
const cargoTomlPath =
|
|
30147
|
+
const cargoTomlPath = path12.join(process.cwd(), "Cargo.toml");
|
|
30144
30148
|
if (fs7.existsSync(cargoTomlPath)) {
|
|
30145
30149
|
const content = fs7.readFileSync(cargoTomlPath, "utf-8");
|
|
30146
30150
|
if (content.includes("[dev-dependencies]")) {
|
|
@@ -30151,9 +30155,9 @@ async function detectTestFramework() {
|
|
|
30151
30155
|
}
|
|
30152
30156
|
} catch {}
|
|
30153
30157
|
try {
|
|
30154
|
-
const pesterConfigPath =
|
|
30155
|
-
const pesterConfigJsonPath =
|
|
30156
|
-
const pesterPs1Path =
|
|
30158
|
+
const pesterConfigPath = path12.join(process.cwd(), "pester.config.ps1");
|
|
30159
|
+
const pesterConfigJsonPath = path12.join(process.cwd(), "pester.config.ps1.json");
|
|
30160
|
+
const pesterPs1Path = path12.join(process.cwd(), "tests.ps1");
|
|
30157
30161
|
if (fs7.existsSync(pesterConfigPath) || fs7.existsSync(pesterConfigJsonPath) || fs7.existsSync(pesterPs1Path)) {
|
|
30158
30162
|
return "pester";
|
|
30159
30163
|
}
|
|
@@ -30167,8 +30171,8 @@ function hasCompoundTestExtension(filename) {
|
|
|
30167
30171
|
function getTestFilesFromConvention(sourceFiles) {
|
|
30168
30172
|
const testFiles = [];
|
|
30169
30173
|
for (const file3 of sourceFiles) {
|
|
30170
|
-
const basename2 =
|
|
30171
|
-
const dirname4 =
|
|
30174
|
+
const basename2 = path12.basename(file3);
|
|
30175
|
+
const dirname4 = path12.dirname(file3);
|
|
30172
30176
|
if (hasCompoundTestExtension(basename2) || basename2.includes(".spec.") || basename2.includes(".test.")) {
|
|
30173
30177
|
if (!testFiles.includes(file3)) {
|
|
30174
30178
|
testFiles.push(file3);
|
|
@@ -30177,13 +30181,13 @@ function getTestFilesFromConvention(sourceFiles) {
|
|
|
30177
30181
|
}
|
|
30178
30182
|
for (const pattern of TEST_PATTERNS) {
|
|
30179
30183
|
const nameWithoutExt = basename2.replace(/\.[^.]+$/, "");
|
|
30180
|
-
const ext =
|
|
30184
|
+
const ext = path12.extname(basename2);
|
|
30181
30185
|
const possibleTestFiles = [
|
|
30182
|
-
|
|
30183
|
-
|
|
30184
|
-
|
|
30185
|
-
|
|
30186
|
-
|
|
30186
|
+
path12.join(dirname4, `${nameWithoutExt}.spec${ext}`),
|
|
30187
|
+
path12.join(dirname4, `${nameWithoutExt}.test${ext}`),
|
|
30188
|
+
path12.join(dirname4, "__tests__", `${nameWithoutExt}${ext}`),
|
|
30189
|
+
path12.join(dirname4, "tests", `${nameWithoutExt}${ext}`),
|
|
30190
|
+
path12.join(dirname4, "test", `${nameWithoutExt}${ext}`)
|
|
30187
30191
|
];
|
|
30188
30192
|
for (const testFile of possibleTestFiles) {
|
|
30189
30193
|
if (fs7.existsSync(testFile) && !testFiles.includes(testFile)) {
|
|
@@ -30203,15 +30207,15 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30203
30207
|
for (const testFile of candidateTestFiles) {
|
|
30204
30208
|
try {
|
|
30205
30209
|
const content = fs7.readFileSync(testFile, "utf-8");
|
|
30206
|
-
const testDir =
|
|
30210
|
+
const testDir = path12.dirname(testFile);
|
|
30207
30211
|
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
30208
30212
|
let match;
|
|
30209
30213
|
while ((match = importRegex.exec(content)) !== null) {
|
|
30210
30214
|
const importPath = match[1];
|
|
30211
30215
|
let resolvedImport;
|
|
30212
30216
|
if (importPath.startsWith(".")) {
|
|
30213
|
-
resolvedImport =
|
|
30214
|
-
const existingExt =
|
|
30217
|
+
resolvedImport = path12.resolve(testDir, importPath);
|
|
30218
|
+
const existingExt = path12.extname(resolvedImport);
|
|
30215
30219
|
if (!existingExt) {
|
|
30216
30220
|
for (const extToTry of [
|
|
30217
30221
|
".ts",
|
|
@@ -30231,12 +30235,12 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30231
30235
|
} else {
|
|
30232
30236
|
continue;
|
|
30233
30237
|
}
|
|
30234
|
-
const importBasename =
|
|
30235
|
-
const importDir =
|
|
30238
|
+
const importBasename = path12.basename(resolvedImport, path12.extname(resolvedImport));
|
|
30239
|
+
const importDir = path12.dirname(resolvedImport);
|
|
30236
30240
|
for (const sourceFile of sourceFiles) {
|
|
30237
|
-
const sourceDir =
|
|
30238
|
-
const sourceBasename =
|
|
30239
|
-
const isRelatedDir = importDir === sourceDir || importDir ===
|
|
30241
|
+
const sourceDir = path12.dirname(sourceFile);
|
|
30242
|
+
const sourceBasename = path12.basename(sourceFile, path12.extname(sourceFile));
|
|
30243
|
+
const isRelatedDir = importDir === sourceDir || importDir === path12.join(sourceDir, "__tests__") || importDir === path12.join(sourceDir, "tests") || importDir === path12.join(sourceDir, "test");
|
|
30240
30244
|
if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
|
|
30241
30245
|
if (!testFiles.includes(testFile)) {
|
|
30242
30246
|
testFiles.push(testFile);
|
|
@@ -30249,8 +30253,8 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30249
30253
|
while ((match = requireRegex.exec(content)) !== null) {
|
|
30250
30254
|
const importPath = match[1];
|
|
30251
30255
|
if (importPath.startsWith(".")) {
|
|
30252
|
-
let resolvedImport =
|
|
30253
|
-
const existingExt =
|
|
30256
|
+
let resolvedImport = path12.resolve(testDir, importPath);
|
|
30257
|
+
const existingExt = path12.extname(resolvedImport);
|
|
30254
30258
|
if (!existingExt) {
|
|
30255
30259
|
for (const extToTry of [
|
|
30256
30260
|
".ts",
|
|
@@ -30267,12 +30271,12 @@ async function getTestFilesFromGraph(sourceFiles) {
|
|
|
30267
30271
|
}
|
|
30268
30272
|
}
|
|
30269
30273
|
}
|
|
30270
|
-
const importDir =
|
|
30271
|
-
const importBasename =
|
|
30274
|
+
const importDir = path12.dirname(resolvedImport);
|
|
30275
|
+
const importBasename = path12.basename(resolvedImport, path12.extname(resolvedImport));
|
|
30272
30276
|
for (const sourceFile of sourceFiles) {
|
|
30273
|
-
const sourceDir =
|
|
30274
|
-
const sourceBasename =
|
|
30275
|
-
const isRelatedDir = importDir === sourceDir || importDir ===
|
|
30277
|
+
const sourceDir = path12.dirname(sourceFile);
|
|
30278
|
+
const sourceBasename = path12.basename(sourceFile, path12.extname(sourceFile));
|
|
30279
|
+
const isRelatedDir = importDir === sourceDir || importDir === path12.join(sourceDir, "__tests__") || importDir === path12.join(sourceDir, "tests") || importDir === path12.join(sourceDir, "test");
|
|
30276
30280
|
if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
|
|
30277
30281
|
if (!testFiles.includes(testFile)) {
|
|
30278
30282
|
testFiles.push(testFile);
|
|
@@ -30578,7 +30582,7 @@ function findSourceFiles(dir, files = []) {
|
|
|
30578
30582
|
for (const entry of entries) {
|
|
30579
30583
|
if (SKIP_DIRECTORIES.has(entry))
|
|
30580
30584
|
continue;
|
|
30581
|
-
const fullPath =
|
|
30585
|
+
const fullPath = path12.join(dir, entry);
|
|
30582
30586
|
let stat;
|
|
30583
30587
|
try {
|
|
30584
30588
|
stat = fs7.statSync(fullPath);
|
|
@@ -30588,7 +30592,7 @@ function findSourceFiles(dir, files = []) {
|
|
|
30588
30592
|
if (stat.isDirectory()) {
|
|
30589
30593
|
findSourceFiles(fullPath, files);
|
|
30590
30594
|
} else if (stat.isFile()) {
|
|
30591
|
-
const ext =
|
|
30595
|
+
const ext = path12.extname(fullPath).toLowerCase();
|
|
30592
30596
|
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
30593
30597
|
files.push(fullPath);
|
|
30594
30598
|
}
|
|
@@ -30695,13 +30699,13 @@ var init_test_runner = __esm(() => {
|
|
|
30695
30699
|
testFiles = [];
|
|
30696
30700
|
} else if (scope === "convention") {
|
|
30697
30701
|
const sourceFiles = args2.files && args2.files.length > 0 ? args2.files.filter((f) => {
|
|
30698
|
-
const ext =
|
|
30702
|
+
const ext = path12.extname(f).toLowerCase();
|
|
30699
30703
|
return SOURCE_EXTENSIONS.has(ext);
|
|
30700
30704
|
}) : findSourceFiles(process.cwd());
|
|
30701
30705
|
testFiles = getTestFilesFromConvention(sourceFiles);
|
|
30702
30706
|
} else if (scope === "graph") {
|
|
30703
30707
|
const sourceFiles = args2.files && args2.files.length > 0 ? args2.files.filter((f) => {
|
|
30704
|
-
const ext =
|
|
30708
|
+
const ext = path12.extname(f).toLowerCase();
|
|
30705
30709
|
return SOURCE_EXTENSIONS.has(ext);
|
|
30706
30710
|
}) : findSourceFiles(process.cwd());
|
|
30707
30711
|
const graphTestFiles = await getTestFilesFromGraph(sourceFiles);
|
|
@@ -30724,7 +30728,7 @@ var init_test_runner = __esm(() => {
|
|
|
30724
30728
|
|
|
30725
30729
|
// src/services/preflight-service.ts
|
|
30726
30730
|
import * as fs8 from "fs";
|
|
30727
|
-
import * as
|
|
30731
|
+
import * as path13 from "path";
|
|
30728
30732
|
function validateDirectoryPath(dir) {
|
|
30729
30733
|
if (!dir || typeof dir !== "string") {
|
|
30730
30734
|
throw new Error("Directory path is required");
|
|
@@ -30732,8 +30736,8 @@ function validateDirectoryPath(dir) {
|
|
|
30732
30736
|
if (dir.includes("..")) {
|
|
30733
30737
|
throw new Error("Directory path must not contain path traversal sequences");
|
|
30734
30738
|
}
|
|
30735
|
-
const normalized =
|
|
30736
|
-
const absolutePath =
|
|
30739
|
+
const normalized = path13.normalize(dir);
|
|
30740
|
+
const absolutePath = path13.isAbsolute(normalized) ? normalized : path13.resolve(normalized);
|
|
30737
30741
|
return absolutePath;
|
|
30738
30742
|
}
|
|
30739
30743
|
function validateTimeout(timeoutMs, defaultValue) {
|
|
@@ -30756,7 +30760,7 @@ function validateTimeout(timeoutMs, defaultValue) {
|
|
|
30756
30760
|
}
|
|
30757
30761
|
function getPackageVersion(dir) {
|
|
30758
30762
|
try {
|
|
30759
|
-
const packagePath =
|
|
30763
|
+
const packagePath = path13.join(dir, "package.json");
|
|
30760
30764
|
if (fs8.existsSync(packagePath)) {
|
|
30761
30765
|
const content = fs8.readFileSync(packagePath, "utf-8");
|
|
30762
30766
|
const pkg = JSON.parse(content);
|
|
@@ -30767,7 +30771,7 @@ function getPackageVersion(dir) {
|
|
|
30767
30771
|
}
|
|
30768
30772
|
function getChangelogVersion(dir) {
|
|
30769
30773
|
try {
|
|
30770
|
-
const changelogPath =
|
|
30774
|
+
const changelogPath = path13.join(dir, "CHANGELOG.md");
|
|
30771
30775
|
if (fs8.existsSync(changelogPath)) {
|
|
30772
30776
|
const content = fs8.readFileSync(changelogPath, "utf-8");
|
|
30773
30777
|
const match = content.match(/^##\s*\[?(\d+\.\d+\.\d+)\]?/m);
|
|
@@ -30781,7 +30785,7 @@ function getChangelogVersion(dir) {
|
|
|
30781
30785
|
function getVersionFileVersion(dir) {
|
|
30782
30786
|
const possibleFiles = ["VERSION.txt", "version.txt", "VERSION", "version"];
|
|
30783
30787
|
for (const file3 of possibleFiles) {
|
|
30784
|
-
const filePath =
|
|
30788
|
+
const filePath = path13.join(dir, file3);
|
|
30785
30789
|
if (fs8.existsSync(filePath)) {
|
|
30786
30790
|
try {
|
|
30787
30791
|
const content = fs8.readFileSync(filePath, "utf-8").trim();
|
|
@@ -31362,7 +31366,7 @@ var init_preflight_integration = __esm(() => {
|
|
|
31362
31366
|
});
|
|
31363
31367
|
|
|
31364
31368
|
// src/index.ts
|
|
31365
|
-
import * as
|
|
31369
|
+
import * as path31 from "path";
|
|
31366
31370
|
|
|
31367
31371
|
// src/config/constants.ts
|
|
31368
31372
|
var QA_AGENTS = ["reviewer", "critic"];
|
|
@@ -31548,6 +31552,9 @@ var GateConfigSchema = exports_external.object({
|
|
|
31548
31552
|
build_check: GateFeatureSchema.default({ enabled: true }),
|
|
31549
31553
|
quality_budget: QualityBudgetConfigSchema
|
|
31550
31554
|
});
|
|
31555
|
+
var PipelineConfigSchema = exports_external.object({
|
|
31556
|
+
parallel_precheck: exports_external.boolean().default(true)
|
|
31557
|
+
});
|
|
31551
31558
|
var SummaryConfigSchema = exports_external.object({
|
|
31552
31559
|
enabled: exports_external.boolean().default(true),
|
|
31553
31560
|
threshold_bytes: exports_external.number().min(1024).max(1048576).default(20480),
|
|
@@ -31799,6 +31806,7 @@ var PluginConfigSchema = exports_external.object({
|
|
|
31799
31806
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
31800
31807
|
swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
|
|
31801
31808
|
max_iterations: exports_external.number().min(1).max(10).default(5),
|
|
31809
|
+
pipeline: PipelineConfigSchema.optional(),
|
|
31802
31810
|
qa_retry_limit: exports_external.number().min(1).max(10).default(3),
|
|
31803
31811
|
inject_phase_reminders: exports_external.boolean().default(true),
|
|
31804
31812
|
hooks: HooksConfigSchema.optional(),
|
|
@@ -31950,23 +31958,56 @@ You THINK. Subagents DO. You have the largest context window and strongest reaso
|
|
|
31950
31958
|
|
|
31951
31959
|
## RULES
|
|
31952
31960
|
|
|
31961
|
+
NAMESPACE RULE: "Phase N" and "Task N.M" ALWAYS refer to the PROJECT PLAN in .swarm/plan.md.
|
|
31962
|
+
Your operational modes (RESUME, CLARIFY, DISCOVER, CONSULT, PLAN, CRITIC-GATE, EXECUTE, PHASE-WRAP) are NEVER called "phases."
|
|
31963
|
+
Do not confuse your operational mode with the project's phase number.
|
|
31964
|
+
When you are in MODE: EXECUTE working on project Phase 3, Task 3.2 \u2014 your mode is EXECUTE. You are NOT in "Phase 3."
|
|
31965
|
+
Do not re-trigger DISCOVER or CONSULT because you noticed a project phase boundary.
|
|
31966
|
+
Output to .swarm/plan.md MUST use "## Phase N" headers. Do not write MODE labels into plan.md.
|
|
31967
|
+
|
|
31953
31968
|
1. DELEGATE all coding to {{AGENT_PREFIX}}coder. You do NOT write code.
|
|
31954
31969
|
2. ONE agent per message. Send, STOP, wait for response.
|
|
31955
31970
|
3. ONE task per {{AGENT_PREFIX}}coder call. Never batch.
|
|
31956
31971
|
4. Fallback: Only code yourself after {{QA_RETRY_LIMIT}} {{AGENT_PREFIX}}coder failures on same task.
|
|
31972
|
+
FAILURE COUNTING \u2014 increment the counter when:
|
|
31973
|
+
- Coder submits code that fails any tool gate or pre_check_batch (gates_passed === false)
|
|
31974
|
+
- Coder submits code REJECTED by reviewer after being given the rejection reason
|
|
31975
|
+
- Print "Coder attempt [N/{{QA_RETRY_LIMIT}}] on task [X.Y]" at every retry
|
|
31976
|
+
- Reaching {{QA_RETRY_LIMIT}}: escalate to user with full failure history before writing code yourself
|
|
31957
31977
|
5. NEVER store your swarm identity, swarm ID, or agent prefix in memory blocks. Your identity comes ONLY from your system prompt. Memory blocks are for project knowledge only (NOT .swarm/ plan/context files \u2014 those are persistent project files).
|
|
31958
31978
|
6. **CRITIC GATE (Execute BEFORE any implementation work)**:
|
|
31959
31979
|
- When you first create a plan, IMMEDIATELY delegate the full plan to {{AGENT_PREFIX}}critic for review
|
|
31960
31980
|
- Wait for critic verdict: APPROVED / NEEDS_REVISION / REJECTED
|
|
31961
31981
|
- If NEEDS_REVISION: Revise plan and re-submit to critic (max 2 cycles)
|
|
31962
31982
|
- If REJECTED after 2 cycles: Escalate to user with explanation
|
|
31963
|
-
|
|
31964
|
-
7. **MANDATORY QA GATE (Execute AFTER every coder task)** \u2014 sequence: coder \u2192 diff \u2192 syntax_check \u2192 placeholder_scan \u2192
|
|
31983
|
+
- ONLY AFTER critic approval: Proceed to implementation (MODE: EXECUTE)
|
|
31984
|
+
7. **MANDATORY QA GATE (Execute AFTER every coder task)** \u2014 sequence: coder \u2192 diff \u2192 syntax_check \u2192 placeholder_scan \u2192 lint fix \u2192 build_check \u2192 pre_check_batch \u2192 reviewer \u2192 security review \u2192 security-only review \u2192 verification tests \u2192 adversarial tests \u2192 coverage check \u2192 next task.
|
|
31985
|
+
ANTI-EXEMPTION RULES \u2014 these thoughts are WRONG and must be ignored:
|
|
31986
|
+
\u2717 "It's a simple change" \u2192 gates are mandatory for ALL changes regardless of perceived complexity
|
|
31987
|
+
\u2717 "It's just a rename / refactor / config tweak" \u2192 same
|
|
31988
|
+
\u2717 "The code looks straightforward" \u2192 you are the author; authors are blind to their own mistakes
|
|
31989
|
+
\u2717 "I already reviewed it mentally" \u2192 mental review does not satisfy any gate
|
|
31990
|
+
\u2717 "It'll be fine" \u2192 this is how production data loss happens
|
|
31991
|
+
\u2717 "The tests will catch it" \u2192 tests do not run without being delegated to {{AGENT_PREFIX}}test_engineer
|
|
31992
|
+
\u2717 "It's just one file" \u2192 file count does not determine gate requirements
|
|
31993
|
+
\u2717 "pre_check_batch will catch any issues" \u2192 pre_check_batch only runs if you run it
|
|
31994
|
+
|
|
31995
|
+
There are NO simple changes. There are NO exceptions to the QA gate sequence.
|
|
31996
|
+
The gates exist because the author cannot objectively evaluate their own work.
|
|
31965
31997
|
- After coder completes: run \`diff\` tool. If \`hasContractChanges\` is true \u2192 delegate {{AGENT_PREFIX}}explorer for integration impact analysis. BREAKING \u2192 return to coder. COMPATIBLE \u2192 proceed.
|
|
31966
31998
|
- Run \`syntax_check\` tool. SYNTACTIC ERRORS \u2192 return to coder. NO ERRORS \u2192 proceed to placeholder_scan.
|
|
31967
31999
|
- Run \`placeholder_scan\` tool. PLACEHOLDER FINDINGS \u2192 return to coder. NO FINDINGS \u2192 proceed to imports check.
|
|
31968
|
-
- Run \`
|
|
31969
|
-
- Run \`
|
|
32000
|
+
- Run \`imports\` tool. Record results for dependency audit. Proceed to lint fix.
|
|
32001
|
+
- Run \`lint\` tool (mode: fix) \u2192 allow auto-corrections. LINT FIX FAILS \u2192 return to coder. SUCCESS \u2192 proceed to build_check.
|
|
32002
|
+
- Run \`build_check\` tool. BUILD FAILS \u2192 return to coder. SUCCESS \u2192 proceed to pre_check_batch.
|
|
32003
|
+
- Run \`pre_check_batch\` tool \u2192 runs four verification tools in parallel (max 4 concurrent):
|
|
32004
|
+
- lint:check (code quality verification)
|
|
32005
|
+
- secretscan (secret detection)
|
|
32006
|
+
- sast_scan (static security analysis)
|
|
32007
|
+
- quality_budget (maintainability metrics)
|
|
32008
|
+
\u2192 Returns { gates_passed, lint, secretscan, sast_scan, quality_budget, total_duration_ms }
|
|
32009
|
+
\u2192 If gates_passed === false: read individual tool results, identify which tool(s) failed, return structured rejection to @coder with specific tool failures. Do NOT call @reviewer.
|
|
32010
|
+
\u2192 If gates_passed === true: proceed to @reviewer.
|
|
31970
32011
|
- Delegate {{AGENT_PREFIX}}reviewer with CHECK dimensions. REJECTED \u2192 return to coder (max {{QA_RETRY_LIMIT}} attempts). APPROVED \u2192 continue.
|
|
31971
32012
|
- If file matches security globs (auth, api, crypto, security, middleware, session, token, config/, env, credentials, authorization, roles, permissions, access) OR content has security keywords (see SECURITY_KEYWORDS list) OR secretscan has ANY findings OR sast_scan has ANY findings at or above threshold \u2192 MUST delegate {{AGENT_PREFIX}}reviewer AGAIN with security-only CHECK review. REJECTED \u2192 return to coder (max {{QA_RETRY_LIMIT}} attempts). If REJECTED after {{QA_RETRY_LIMIT}} attempts on security-only review \u2192 escalate to user.
|
|
31972
32013
|
- Delegate {{AGENT_PREFIX}}test_engineer for verification tests. FAIL \u2192 return to coder.
|
|
@@ -31996,7 +32037,7 @@ SECURITY_KEYWORDS: password, secret, token, credential, auth, login, encryption,
|
|
|
31996
32037
|
|
|
31997
32038
|
SMEs advise only. Reviewer and critic review only. None of them write code.
|
|
31998
32039
|
|
|
31999
|
-
Available Tools: symbols (code symbol search), checkpoint (state snapshots), diff (structured git diff with contract change detection), imports (dependency audit), lint (code quality), placeholder_scan (placeholder/todo detection), secretscan (secret detection), sast_scan (static analysis security scan), syntax_check (syntax validation), test_runner (auto-detect and run tests), pkg_audit (dependency vulnerability scan \u2014 npm/pip/cargo), complexity_hotspots (git churn \xD7 complexity risk map), schema_drift (OpenAPI spec vs route drift), todo_extract (structured TODO/FIXME extraction), evidence_check (verify task evidence completeness), sbom_generate (SBOM generation for dependency inventory), build_check (build verification), quality_budget (code quality budget check)
|
|
32040
|
+
Available Tools: symbols (code symbol search), checkpoint (state snapshots), diff (structured git diff with contract change detection), imports (dependency audit), lint (code quality), placeholder_scan (placeholder/todo detection), secretscan (secret detection), sast_scan (static analysis security scan), syntax_check (syntax validation), test_runner (auto-detect and run tests), pkg_audit (dependency vulnerability scan \u2014 npm/pip/cargo), complexity_hotspots (git churn \xD7 complexity risk map), schema_drift (OpenAPI spec vs route drift), todo_extract (structured TODO/FIXME extraction), evidence_check (verify task evidence completeness), sbom_generate (SBOM generation for dependency inventory), build_check (build verification), quality_budget (code quality budget check), pre_check_batch (parallel verification: lint:check + secretscan + sast_scan + quality_budget)
|
|
32000
32041
|
|
|
32001
32042
|
## DELEGATION FORMAT
|
|
32002
32043
|
|
|
@@ -32090,7 +32131,7 @@ OUTPUT: Code scaffold for src/pages/Settings.tsx with component tree, typed prop
|
|
|
32090
32131
|
|
|
32091
32132
|
## WORKFLOW
|
|
32092
32133
|
|
|
32093
|
-
###
|
|
32134
|
+
### MODE: RESUME
|
|
32094
32135
|
If .swarm/plan.md exists:
|
|
32095
32136
|
1. Read plan.md header for "Swarm:" field
|
|
32096
32137
|
2. If Swarm field missing or matches "{{SWARM_ID}}" \u2192 Resume at current task
|
|
@@ -32101,14 +32142,14 @@ If .swarm/plan.md exists:
|
|
|
32101
32142
|
- Update context.md Swarm field to "{{SWARM_ID}}"
|
|
32102
32143
|
- Inform user: "Resuming project from [other] swarm. Cleared stale context. Ready to continue."
|
|
32103
32144
|
- Resume at current task
|
|
32104
|
-
If .swarm/plan.md does not exist \u2192 New project, proceed to
|
|
32145
|
+
If .swarm/plan.md does not exist \u2192 New project, proceed to MODE: CLARIFY
|
|
32105
32146
|
If new project: Run \`complexity_hotspots\` tool (90 days) to generate a risk map. Note modules with recommendation "security_review" or "full_gates" in context.md for stricter QA gates during Phase 5. Optionally run \`todo_extract\` to capture existing technical debt for plan consideration. After initial discovery, run \`sbom_generate\` with scope='all' to capture baseline dependency inventory (saved to .swarm/evidence/sbom/).
|
|
32106
32147
|
|
|
32107
|
-
###
|
|
32148
|
+
### MODE: CLARIFY
|
|
32108
32149
|
Ambiguous request \u2192 Ask up to 3 questions, wait for answers
|
|
32109
|
-
Clear request \u2192
|
|
32150
|
+
Clear request \u2192 MODE: DISCOVER
|
|
32110
32151
|
|
|
32111
|
-
###
|
|
32152
|
+
### MODE: DISCOVER
|
|
32112
32153
|
Delegate to {{AGENT_PREFIX}}explorer. Wait for response.
|
|
32113
32154
|
For complex tasks, make a second explorer call focused on risk/gap analysis:
|
|
32114
32155
|
- Hidden requirements, unstated assumptions, scope risks
|
|
@@ -32117,51 +32158,123 @@ After explorer returns:
|
|
|
32117
32158
|
- Run \`symbols\` tool on key files identified by explorer to understand public API surfaces
|
|
32118
32159
|
- Run \`complexity_hotspots\` if not already run in Phase 0 (check context.md for existing analysis). Note modules with recommendation "security_review" or "full_gates" in context.md.
|
|
32119
32160
|
|
|
32120
|
-
###
|
|
32161
|
+
### MODE: CONSULT
|
|
32121
32162
|
Check .swarm/context.md for cached guidance first.
|
|
32122
32163
|
Identify 1-3 relevant domains from the task requirements.
|
|
32123
32164
|
Call {{AGENT_PREFIX}}sme once per domain, serially. Max 3 SME calls per project phase.
|
|
32124
32165
|
Re-consult if a new domain emerges or if significant changes require fresh evaluation.
|
|
32125
32166
|
Cache guidance in context.md.
|
|
32126
32167
|
|
|
32127
|
-
###
|
|
32128
|
-
|
|
32168
|
+
### MODE: PLAN
|
|
32169
|
+
|
|
32170
|
+
Create .swarm/plan.md
|
|
32129
32171
|
- Phases with discrete tasks
|
|
32130
32172
|
- Dependencies (depends: X.Y)
|
|
32131
32173
|
- Acceptance criteria per task
|
|
32132
32174
|
|
|
32133
|
-
|
|
32175
|
+
TASK GRANULARITY RULES:
|
|
32176
|
+
- SMALL task: 1 file, 1 function/class/component, 1 logical concern. Delegate as-is.
|
|
32177
|
+
- MEDIUM task: If it touches >1 file, SPLIT into sequential file-scoped subtasks before writing to plan.
|
|
32178
|
+
- LARGE task: MUST be decomposed before writing to plan. A LARGE task in the plan is a planning error.
|
|
32179
|
+
- Litmus test: If you cannot write TASK + FILE + constraint in 3 bullet points, the task is too large. Split it.
|
|
32180
|
+
- NEVER write a task with compound verbs: "implement X and add Y and update Z" = 3 tasks, not 1. Split before writing to plan.
|
|
32181
|
+
- Coder receives ONE task. You make ALL scope decisions in the plan. Coder makes zero scope decisions.
|
|
32182
|
+
|
|
32183
|
+
Create .swarm/context.md
|
|
32134
32184
|
- Decisions, patterns, SME cache, file map
|
|
32135
32185
|
|
|
32136
|
-
###
|
|
32186
|
+
### MODE: CRITIC-GATE
|
|
32137
32187
|
Delegate plan to {{AGENT_PREFIX}}critic for review BEFORE any implementation begins.
|
|
32138
32188
|
- Send the full plan.md content and codebase context summary
|
|
32139
|
-
- **APPROVED** \u2192 Proceed to
|
|
32140
|
-
- **NEEDS_REVISION** \u2192 Revise the plan based on critic feedback, then resubmit (max 2
|
|
32189
|
+
- **APPROVED** \u2192 Proceed to MODE: EXECUTE
|
|
32190
|
+
- **NEEDS_REVISION** \u2192 Revise the plan based on critic feedback, then resubmit (max 2 cycles)
|
|
32141
32191
|
- **REJECTED** \u2192 Inform the user of fundamental issues and ask for guidance before proceeding
|
|
32142
32192
|
|
|
32143
|
-
|
|
32193
|
+
\u26D4 HARD STOP \u2014 Print this checklist before advancing to MODE: EXECUTE:
|
|
32194
|
+
[ ] {{AGENT_PREFIX}}critic returned a verdict
|
|
32195
|
+
[ ] APPROVED \u2192 proceed to MODE: EXECUTE
|
|
32196
|
+
[ ] NEEDS_REVISION \u2192 revised and resubmitted (attempt N of max 2)
|
|
32197
|
+
[ ] REJECTED (any cycle) \u2192 informed user. STOP.
|
|
32198
|
+
|
|
32199
|
+
You MUST NOT proceed to MODE: EXECUTE without printing this checklist with filled values.
|
|
32200
|
+
|
|
32201
|
+
CRITIC-GATE TRIGGER: Run ONCE when you first write the complete .swarm/plan.md.
|
|
32202
|
+
Do NOT re-run CRITIC-GATE before every project phase.
|
|
32203
|
+
If resuming a project with an existing approved plan, CRITIC-GATE is already satisfied.
|
|
32204
|
+
|
|
32205
|
+
### MODE: EXECUTE
|
|
32144
32206
|
For each task (respecting dependencies):
|
|
32145
32207
|
|
|
32208
|
+
RETRY PROTOCOL \u2014 when returning to coder after any gate failure:
|
|
32209
|
+
1. Provide structured rejection: "GATE FAILED: [gate name] | REASON: [details] | REQUIRED FIX: [specific action required]"
|
|
32210
|
+
2. Re-enter at step 5b ({{AGENT_PREFIX}}coder) with full failure context
|
|
32211
|
+
3. Resume execution at the failed step (do not restart from 5a)
|
|
32212
|
+
Exception: if coder modified files outside the original task scope, restart from step 5c
|
|
32213
|
+
4. Gates already PASSED may be skipped on retry if their input files are unchanged
|
|
32214
|
+
5. Print "Resuming at step [5X] after coder retry [N/{{QA_RETRY_LIMIT}}]" before re-executing
|
|
32215
|
+
|
|
32146
32216
|
5a. **UI DESIGN GATE** (conditional \u2014 Rule 9): If task matches UI trigger \u2192 {{AGENT_PREFIX}}designer produces scaffold \u2192 pass scaffold to coder as INPUT. If no match \u2192 skip.
|
|
32147
32217
|
5b. {{AGENT_PREFIX}}coder - Implement (if designer scaffold produced, include it as INPUT).
|
|
32148
32218
|
5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. BREAKING \u2192 coder retry.
|
|
32219
|
+
\u2192 REQUIRED: Print "diff: [PASS | CONTRACT CHANGE \u2014 details]"
|
|
32149
32220
|
5d. Run \`syntax_check\` tool. SYNTACTIC ERRORS \u2192 return to coder. NO ERRORS \u2192 proceed to placeholder_scan.
|
|
32221
|
+
\u2192 REQUIRED: Print "syntaxcheck: [PASS | FAIL \u2014 N errors]"
|
|
32150
32222
|
5e. Run \`placeholder_scan\` tool. PLACEHOLDER FINDINGS \u2192 return to coder. NO FINDINGS \u2192 proceed to imports.
|
|
32223
|
+
\u2192 REQUIRED: Print "placeholderscan: [PASS | FAIL \u2014 N findings]"
|
|
32151
32224
|
5f. Run \`imports\` tool for dependency audit. ISSUES \u2192 return to coder.
|
|
32225
|
+
\u2192 REQUIRED: Print "imports: [PASS | ISSUES \u2014 details]"
|
|
32152
32226
|
5g. Run \`lint\` tool with fix mode for auto-fixes. If issues remain \u2192 run \`lint\` tool with check mode. FAIL \u2192 return to coder.
|
|
32153
|
-
|
|
32154
|
-
|
|
32155
|
-
|
|
32156
|
-
|
|
32157
|
-
|
|
32158
|
-
|
|
32159
|
-
|
|
32160
|
-
|
|
32161
|
-
|
|
32162
|
-
|
|
32163
|
-
|
|
32164
|
-
|
|
32227
|
+
\u2192 REQUIRED: Print "lint: [PASS | FAIL \u2014 details]"
|
|
32228
|
+
5h. Run \`build_check\` tool. BUILD FAILS \u2192 return to coder. SUCCESS \u2192 proceed to pre_check_batch.
|
|
32229
|
+
\u2192 REQUIRED: Print "buildcheck: [PASS | FAIL | SKIPPED \u2014 no toolchain]"
|
|
32230
|
+
5i. Run \`pre_check_batch\` tool \u2192 runs four verification tools in parallel (max 4 concurrent):
|
|
32231
|
+
- lint:check (code quality verification)
|
|
32232
|
+
- secretscan (secret detection)
|
|
32233
|
+
- sast_scan (static security analysis)
|
|
32234
|
+
- quality_budget (maintainability metrics)
|
|
32235
|
+
\u2192 Returns { gates_passed, lint, secretscan, sast_scan, quality_budget, total_duration_ms }
|
|
32236
|
+
\u2192 If gates_passed === false: read individual tool results, identify which tool(s) failed, return structured rejection to @coder with specific tool failures. Do NOT call @reviewer.
|
|
32237
|
+
\u2192 If gates_passed === true: proceed to @reviewer.
|
|
32238
|
+
\u2192 REQUIRED: Print "pre_check_batch: [PASS \u2014 all gates passed | FAIL \u2014 [gate]: [details]]"
|
|
32239
|
+
5j. {{AGENT_PREFIX}}reviewer - General review. REJECTED (< {{QA_RETRY_LIMIT}}) \u2192 coder retry. REJECTED ({{QA_RETRY_LIMIT}}) \u2192 escalate.
|
|
32240
|
+
\u2192 REQUIRED: Print "reviewer: [APPROVED | REJECTED \u2014 reason]"
|
|
32241
|
+
5k. Security gate: if file matches security globs (auth, api, crypto, security, middleware, session, token, config/, env, credentials, authorization, roles, permissions, access) OR content has security keywords (see SECURITY_KEYWORDS list) OR secretscan has ANY findings OR sast_scan has ANY findings at or above threshold \u2192 MUST delegate {{AGENT_PREFIX}}reviewer security-only review. REJECTED (< {{QA_RETRY_LIMIT}}) \u2192 coder retry. REJECTED ({{QA_RETRY_LIMIT}}) \u2192 escalate to user.
|
|
32242
|
+
\u2192 REQUIRED: Print "security-reviewer: [TRIGGERED | NOT TRIGGERED \u2014 reason]"
|
|
32243
|
+
\u2192 If TRIGGERED: Print "security-reviewer: [APPROVED | REJECTED \u2014 reason]"
|
|
32244
|
+
5l. {{AGENT_PREFIX}}test_engineer - Verification tests. FAIL \u2192 coder retry from 5g.
|
|
32245
|
+
\u2192 REQUIRED: Print "testengineer-verification: [PASS N/N | FAIL \u2014 details]"
|
|
32246
|
+
5m. {{AGENT_PREFIX}}test_engineer - Adversarial tests. FAIL \u2192 coder retry from 5g.
|
|
32247
|
+
\u2192 REQUIRED: Print "testengineer-adversarial: [PASS | FAIL \u2014 details]"
|
|
32248
|
+
5n. COVERAGE CHECK: If test_engineer reports coverage < 70% \u2192 delegate {{AGENT_PREFIX}}test_engineer for an additional test pass targeting uncovered paths. This is a soft guideline; use judgment for trivial tasks.
|
|
32249
|
+
|
|
32250
|
+
PRE-COMMIT RULE \u2014 Before ANY commit or push:
|
|
32251
|
+
You MUST answer YES to ALL of the following:
|
|
32252
|
+
[ ] Did {{AGENT_PREFIX}}reviewer run and return APPROVED? (not "I reviewed it" \u2014 the agent must have run)
|
|
32253
|
+
[ ] Did {{AGENT_PREFIX}}test_engineer run and return PASS? (not "the code looks correct" \u2014 the agent must have run)
|
|
32254
|
+
[ ] Did pre_check_batch run with gates_passed true?
|
|
32255
|
+
[ ] Did the diff step run?
|
|
32256
|
+
|
|
32257
|
+
If ANY box is unchecked: DO NOT COMMIT. Return to step 5b.
|
|
32258
|
+
There is no override. A commit without a completed QA gate is a workflow violation.
|
|
32259
|
+
|
|
32260
|
+
TASK COMPLETION CHECKLIST \u2014 emit before marking \u2713 in plan.md:
|
|
32261
|
+
[TOOL] diff: PASS / SKIP
|
|
32262
|
+
[TOOL] syntaxcheck: PASS
|
|
32263
|
+
[tool] placeholderscan: PASS
|
|
32264
|
+
[tool] imports: PASS
|
|
32265
|
+
[tool] lint: PASS
|
|
32266
|
+
[tool] buildcheck: PASS / SKIPPED (no toolchain)
|
|
32267
|
+
[tool] pre_check_batch: PASS (lint:check \u2713 secretscan \u2713 sast_scan \u2713 quality_budget \u2713)
|
|
32268
|
+
[gate] reviewer: APPROVED
|
|
32269
|
+
[gate] security-reviewer: SKIPPED (no security trigger)
|
|
32270
|
+
[gate] testengineer-verification: PASS
|
|
32271
|
+
[gate] testengineer-adversarial: PASS
|
|
32272
|
+
[gate] coverage: PASS or soft-skip (trivial task)
|
|
32273
|
+
All fields filled \u2192 update plan.md \u2713, proceed to next task.
|
|
32274
|
+
|
|
32275
|
+
5o. Update plan.md [x], proceed to next task.
|
|
32276
|
+
|
|
32277
|
+
### MODE: PHASE-WRAP
|
|
32165
32278
|
1. {{AGENT_PREFIX}}explorer - Rescan
|
|
32166
32279
|
2. {{AGENT_PREFIX}}docs - Update documentation for all changes in this phase. Provide:
|
|
32167
32280
|
- Complete list of files changed during this phase
|
|
@@ -32254,7 +32367,14 @@ RULES:
|
|
|
32254
32367
|
|
|
32255
32368
|
OUTPUT FORMAT:
|
|
32256
32369
|
DONE: [one-line summary]
|
|
32257
|
-
CHANGED: [file]: [what changed]
|
|
32370
|
+
CHANGED: [file]: [what changed]
|
|
32371
|
+
|
|
32372
|
+
AUTHOR BLINDNESS WARNING:
|
|
32373
|
+
Your output is NOT reviewed, tested, or approved until the Architect runs the full QA gate.
|
|
32374
|
+
Do NOT add commentary like "this looks good," "should be fine," or "ready for production."
|
|
32375
|
+
You wrote the code. You cannot objectively evaluate it. That is what the gates are for.
|
|
32376
|
+
Output only: DONE [one-line summary] / CHANGED [file] [what changed]
|
|
32377
|
+
`;
|
|
32258
32378
|
function createCoderAgent(model, customPrompt, customAppendPrompt) {
|
|
32259
32379
|
let prompt = CODER_PROMPT;
|
|
32260
32380
|
if (customPrompt) {
|
|
@@ -32293,10 +32413,11 @@ CONTEXT: [codebase summary, constraints]
|
|
|
32293
32413
|
REVIEW CHECKLIST:
|
|
32294
32414
|
- Completeness: Are all requirements addressed? Missing edge cases?
|
|
32295
32415
|
- Feasibility: Can each task actually be implemented as described? Are file paths real?
|
|
32296
|
-
- Scope: Is the plan doing too much or too little? Feature creep detection
|
|
32416
|
+
- Scope: Is the plan doing too much or too little? Feature creep detection?
|
|
32297
32417
|
- Dependencies: Are task dependencies correct? Will ordering work?
|
|
32298
32418
|
- Risk: Are high-risk changes identified? Is there a rollback path?
|
|
32299
32419
|
- AI-Slop Detection: Does the plan contain vague filler ("robust", "comprehensive", "leverage") without concrete specifics?
|
|
32420
|
+
- Task Atomicity: Does any single task touch 2+ files or contain compound verbs ("implement X and add Y and update Z")? Flag as MAJOR \u2014 oversized tasks blow coder's context and cause downstream gate failures. Suggested fix: Split into sequential single-file tasks before proceeding.
|
|
32300
32421
|
|
|
32301
32422
|
OUTPUT FORMAT:
|
|
32302
32423
|
VERDICT: APPROVED | NEEDS_REVISION | REJECTED
|
|
@@ -35138,7 +35259,7 @@ async function handleResetCommand(directory, args2) {
|
|
|
35138
35259
|
init_utils2();
|
|
35139
35260
|
init_utils();
|
|
35140
35261
|
import { mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync6 } from "fs";
|
|
35141
|
-
import * as
|
|
35262
|
+
import * as path14 from "path";
|
|
35142
35263
|
var SUMMARY_ID_REGEX = /^S\d+$/;
|
|
35143
35264
|
function sanitizeSummaryId(id) {
|
|
35144
35265
|
if (!id || id.length === 0) {
|
|
@@ -35166,9 +35287,9 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
35166
35287
|
if (outputBytes > maxStoredBytes) {
|
|
35167
35288
|
throw new Error(`Summary fullOutput size (${outputBytes} bytes) exceeds maximum (${maxStoredBytes} bytes)`);
|
|
35168
35289
|
}
|
|
35169
|
-
const relativePath =
|
|
35290
|
+
const relativePath = path14.join("summaries", `${sanitizedId}.json`);
|
|
35170
35291
|
const summaryPath = validateSwarmPath(directory, relativePath);
|
|
35171
|
-
const summaryDir =
|
|
35292
|
+
const summaryDir = path14.dirname(summaryPath);
|
|
35172
35293
|
const entry = {
|
|
35173
35294
|
id: sanitizedId,
|
|
35174
35295
|
summaryText,
|
|
@@ -35178,7 +35299,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
35178
35299
|
};
|
|
35179
35300
|
const entryJson = JSON.stringify(entry);
|
|
35180
35301
|
mkdirSync5(summaryDir, { recursive: true });
|
|
35181
|
-
const tempPath =
|
|
35302
|
+
const tempPath = path14.join(summaryDir, `${sanitizedId}.json.tmp.${Date.now()}.${process.pid}`);
|
|
35182
35303
|
try {
|
|
35183
35304
|
await Bun.write(tempPath, entryJson);
|
|
35184
35305
|
renameSync2(tempPath, summaryPath);
|
|
@@ -35191,7 +35312,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
35191
35312
|
}
|
|
35192
35313
|
async function loadFullOutput(directory, id) {
|
|
35193
35314
|
const sanitizedId = sanitizeSummaryId(id);
|
|
35194
|
-
const relativePath =
|
|
35315
|
+
const relativePath = path14.join("summaries", `${sanitizedId}.json`);
|
|
35195
35316
|
validateSwarmPath(directory, relativePath);
|
|
35196
35317
|
const content = await readSwarmFileAsync(directory, relativePath);
|
|
35197
35318
|
if (content === null) {
|
|
@@ -35695,8 +35816,8 @@ async function doFlush(directory) {
|
|
|
35695
35816
|
const activitySection = renderActivitySection();
|
|
35696
35817
|
const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
|
|
35697
35818
|
const flushedCount = swarmState.pendingEvents;
|
|
35698
|
-
const
|
|
35699
|
-
await Bun.write(
|
|
35819
|
+
const path15 = `${directory}/.swarm/context.md`;
|
|
35820
|
+
await Bun.write(path15, updated);
|
|
35700
35821
|
swarmState.pendingEvents = Math.max(0, swarmState.pendingEvents - flushedCount);
|
|
35701
35822
|
} catch (error93) {
|
|
35702
35823
|
warn("Agent activity flush failed:", error93);
|
|
@@ -36252,14 +36373,14 @@ ${originalText}`;
|
|
|
36252
36373
|
}
|
|
36253
36374
|
// src/hooks/system-enhancer.ts
|
|
36254
36375
|
import * as fs11 from "fs";
|
|
36255
|
-
import * as
|
|
36376
|
+
import * as path16 from "path";
|
|
36256
36377
|
init_manager2();
|
|
36257
36378
|
|
|
36258
36379
|
// src/services/decision-drift-analyzer.ts
|
|
36259
36380
|
init_utils2();
|
|
36260
36381
|
init_manager2();
|
|
36261
36382
|
import * as fs10 from "fs";
|
|
36262
|
-
import * as
|
|
36383
|
+
import * as path15 from "path";
|
|
36263
36384
|
var DEFAULT_DRIFT_CONFIG = {
|
|
36264
36385
|
staleThresholdPhases: 1,
|
|
36265
36386
|
detectContradictions: true,
|
|
@@ -36413,7 +36534,7 @@ async function analyzeDecisionDrift(directory, config3 = {}) {
|
|
|
36413
36534
|
currentPhase = legacyPhase;
|
|
36414
36535
|
}
|
|
36415
36536
|
}
|
|
36416
|
-
const contextPath =
|
|
36537
|
+
const contextPath = path15.join(directory, ".swarm", "context.md");
|
|
36417
36538
|
let contextContent = "";
|
|
36418
36539
|
try {
|
|
36419
36540
|
if (fs10.existsSync(contextPath)) {
|
|
@@ -36674,18 +36795,28 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
36674
36795
|
if (config3.secretscan?.enabled === false) {
|
|
36675
36796
|
tryInject("[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.");
|
|
36676
36797
|
}
|
|
36798
|
+
const sessionId_preflight = _input.sessionID;
|
|
36799
|
+
const activeAgent_preflight = swarmState.activeAgent.get(sessionId_preflight ?? "");
|
|
36800
|
+
const isArchitectForPreflight = !activeAgent_preflight || stripKnownSwarmPrefix(activeAgent_preflight) === "architect";
|
|
36801
|
+
if (isArchitectForPreflight) {
|
|
36802
|
+
if (config3.pipeline?.parallel_precheck !== false) {
|
|
36803
|
+
tryInject("[SWARM HINT] Parallel pre-check enabled: call pre_check_batch(files, directory) after lint --fix and build_check to run lint:check + secretscan + sast_scan + quality_budget concurrently (max 4 parallel). Check gates_passed before calling @reviewer.");
|
|
36804
|
+
} else {
|
|
36805
|
+
tryInject("[SWARM HINT] Parallel pre-check disabled: run lint:check \u2192 secretscan \u2192 sast_scan \u2192 quality_budget sequentially.");
|
|
36806
|
+
}
|
|
36807
|
+
}
|
|
36677
36808
|
const sessionId_retro = _input.sessionID;
|
|
36678
36809
|
const activeAgent_retro = swarmState.activeAgent.get(sessionId_retro ?? "");
|
|
36679
36810
|
const isArchitect = !activeAgent_retro || stripKnownSwarmPrefix(activeAgent_retro) === "architect";
|
|
36680
36811
|
if (isArchitect) {
|
|
36681
36812
|
try {
|
|
36682
|
-
const evidenceDir =
|
|
36813
|
+
const evidenceDir = path16.join(directory, ".swarm", "evidence");
|
|
36683
36814
|
if (fs11.existsSync(evidenceDir)) {
|
|
36684
36815
|
const files = fs11.readdirSync(evidenceDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
36685
36816
|
for (const file3 of files.slice(0, 5)) {
|
|
36686
36817
|
let content;
|
|
36687
36818
|
try {
|
|
36688
|
-
content = JSON.parse(fs11.readFileSync(
|
|
36819
|
+
content = JSON.parse(fs11.readFileSync(path16.join(evidenceDir, file3), "utf-8"));
|
|
36689
36820
|
} catch {
|
|
36690
36821
|
continue;
|
|
36691
36822
|
}
|
|
@@ -36900,18 +37031,32 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
36900
37031
|
metadata: { contentType: "prose" }
|
|
36901
37032
|
});
|
|
36902
37033
|
}
|
|
37034
|
+
const sessionId_preflight_b = _input.sessionID;
|
|
37035
|
+
const activeAgent_preflight_b = swarmState.activeAgent.get(sessionId_preflight_b ?? "");
|
|
37036
|
+
const isArchitectForPreflight_b = !activeAgent_preflight_b || stripKnownSwarmPrefix(activeAgent_preflight_b) === "architect";
|
|
37037
|
+
if (isArchitectForPreflight_b) {
|
|
37038
|
+
const hintText_b = config3.pipeline?.parallel_precheck !== false ? "[SWARM HINT] Parallel pre-check enabled: call pre_check_batch(files, directory) after lint --fix and build_check to run lint:check + secretscan + sast_scan + quality_budget concurrently (max 4 parallel). Check gates_passed before calling @reviewer." : "[SWARM HINT] Parallel pre-check disabled: run lint:check \u2192 secretscan \u2192 sast_scan \u2192 quality_budget sequentially.";
|
|
37039
|
+
candidates.push({
|
|
37040
|
+
id: `candidate-${idCounter++}`,
|
|
37041
|
+
kind: "phase",
|
|
37042
|
+
text: hintText_b,
|
|
37043
|
+
tokens: estimateTokens(hintText_b),
|
|
37044
|
+
priority: 1,
|
|
37045
|
+
metadata: { contentType: "prose" }
|
|
37046
|
+
});
|
|
37047
|
+
}
|
|
36903
37048
|
const sessionId_retro_b = _input.sessionID;
|
|
36904
37049
|
const activeAgent_retro_b = swarmState.activeAgent.get(sessionId_retro_b ?? "");
|
|
36905
37050
|
const isArchitect_b = !activeAgent_retro_b || stripKnownSwarmPrefix(activeAgent_retro_b) === "architect";
|
|
36906
37051
|
if (isArchitect_b) {
|
|
36907
37052
|
try {
|
|
36908
|
-
const evidenceDir_b =
|
|
37053
|
+
const evidenceDir_b = path16.join(directory, ".swarm", "evidence");
|
|
36909
37054
|
if (fs11.existsSync(evidenceDir_b)) {
|
|
36910
37055
|
const files_b = fs11.readdirSync(evidenceDir_b).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
36911
37056
|
for (const file3 of files_b.slice(0, 5)) {
|
|
36912
37057
|
let content_b;
|
|
36913
37058
|
try {
|
|
36914
|
-
content_b = JSON.parse(fs11.readFileSync(
|
|
37059
|
+
content_b = JSON.parse(fs11.readFileSync(path16.join(evidenceDir_b, file3), "utf-8"));
|
|
36915
37060
|
} catch {
|
|
36916
37061
|
continue;
|
|
36917
37062
|
}
|
|
@@ -37207,7 +37352,7 @@ init_dist();
|
|
|
37207
37352
|
// src/build/discovery.ts
|
|
37208
37353
|
init_dist();
|
|
37209
37354
|
import * as fs12 from "fs";
|
|
37210
|
-
import * as
|
|
37355
|
+
import * as path17 from "path";
|
|
37211
37356
|
var ECOSYSTEMS = [
|
|
37212
37357
|
{
|
|
37213
37358
|
ecosystem: "node",
|
|
@@ -37325,11 +37470,11 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
37325
37470
|
return regex.test(f);
|
|
37326
37471
|
});
|
|
37327
37472
|
if (matches.length > 0) {
|
|
37328
|
-
return
|
|
37473
|
+
return path17.join(dir, matches[0]);
|
|
37329
37474
|
}
|
|
37330
37475
|
} catch {}
|
|
37331
37476
|
} else {
|
|
37332
|
-
const filePath =
|
|
37477
|
+
const filePath = path17.join(workingDir, pattern);
|
|
37333
37478
|
if (fs12.existsSync(filePath)) {
|
|
37334
37479
|
return filePath;
|
|
37335
37480
|
}
|
|
@@ -37338,7 +37483,7 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
37338
37483
|
return null;
|
|
37339
37484
|
}
|
|
37340
37485
|
function getRepoDefinedScripts(workingDir, scripts) {
|
|
37341
|
-
const packageJsonPath =
|
|
37486
|
+
const packageJsonPath = path17.join(workingDir, "package.json");
|
|
37342
37487
|
if (!fs12.existsSync(packageJsonPath)) {
|
|
37343
37488
|
return [];
|
|
37344
37489
|
}
|
|
@@ -37379,7 +37524,7 @@ function findAllBuildFiles(workingDir) {
|
|
|
37379
37524
|
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
37380
37525
|
findFilesRecursive(workingDir, regex, allBuildFiles);
|
|
37381
37526
|
} else {
|
|
37382
|
-
const filePath =
|
|
37527
|
+
const filePath = path17.join(workingDir, pattern);
|
|
37383
37528
|
if (fs12.existsSync(filePath)) {
|
|
37384
37529
|
allBuildFiles.add(filePath);
|
|
37385
37530
|
}
|
|
@@ -37392,7 +37537,7 @@ function findFilesRecursive(dir, regex, results) {
|
|
|
37392
37537
|
try {
|
|
37393
37538
|
const entries = fs12.readdirSync(dir, { withFileTypes: true });
|
|
37394
37539
|
for (const entry of entries) {
|
|
37395
|
-
const fullPath =
|
|
37540
|
+
const fullPath = path17.join(dir, entry.name);
|
|
37396
37541
|
if (entry.isDirectory() && !["node_modules", ".git", "dist", "build", "target"].includes(entry.name)) {
|
|
37397
37542
|
findFilesRecursive(fullPath, regex, results);
|
|
37398
37543
|
} else if (entry.isFile() && regex.test(entry.name)) {
|
|
@@ -37634,7 +37779,7 @@ var build_check = tool({
|
|
|
37634
37779
|
init_tool();
|
|
37635
37780
|
import { spawnSync } from "child_process";
|
|
37636
37781
|
import * as fs13 from "fs";
|
|
37637
|
-
import * as
|
|
37782
|
+
import * as path18 from "path";
|
|
37638
37783
|
var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
|
|
37639
37784
|
var MAX_LABEL_LENGTH = 100;
|
|
37640
37785
|
var GIT_TIMEOUT_MS = 30000;
|
|
@@ -37685,7 +37830,7 @@ function validateLabel(label) {
|
|
|
37685
37830
|
return null;
|
|
37686
37831
|
}
|
|
37687
37832
|
function getCheckpointLogPath() {
|
|
37688
|
-
return
|
|
37833
|
+
return path18.join(process.cwd(), CHECKPOINT_LOG_PATH);
|
|
37689
37834
|
}
|
|
37690
37835
|
function readCheckpointLog() {
|
|
37691
37836
|
const logPath = getCheckpointLogPath();
|
|
@@ -37703,7 +37848,7 @@ function readCheckpointLog() {
|
|
|
37703
37848
|
}
|
|
37704
37849
|
function writeCheckpointLog(log2) {
|
|
37705
37850
|
const logPath = getCheckpointLogPath();
|
|
37706
|
-
const dir =
|
|
37851
|
+
const dir = path18.dirname(logPath);
|
|
37707
37852
|
if (!fs13.existsSync(dir)) {
|
|
37708
37853
|
fs13.mkdirSync(dir, { recursive: true });
|
|
37709
37854
|
}
|
|
@@ -37910,7 +38055,7 @@ var checkpoint = tool({
|
|
|
37910
38055
|
// src/tools/complexity-hotspots.ts
|
|
37911
38056
|
init_dist();
|
|
37912
38057
|
import * as fs14 from "fs";
|
|
37913
|
-
import * as
|
|
38058
|
+
import * as path19 from "path";
|
|
37914
38059
|
var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
|
|
37915
38060
|
var DEFAULT_DAYS = 90;
|
|
37916
38061
|
var DEFAULT_TOP_N = 20;
|
|
@@ -38053,7 +38198,7 @@ async function analyzeHotspots(days, topN, extensions) {
|
|
|
38053
38198
|
const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
|
|
38054
38199
|
const filteredChurn = new Map;
|
|
38055
38200
|
for (const [file3, count] of churnMap) {
|
|
38056
|
-
const ext =
|
|
38201
|
+
const ext = path19.extname(file3).toLowerCase();
|
|
38057
38202
|
if (extSet.has(ext)) {
|
|
38058
38203
|
filteredChurn.set(file3, count);
|
|
38059
38204
|
}
|
|
@@ -38064,7 +38209,7 @@ async function analyzeHotspots(days, topN, extensions) {
|
|
|
38064
38209
|
for (const [file3, churnCount] of filteredChurn) {
|
|
38065
38210
|
let fullPath = file3;
|
|
38066
38211
|
if (!fs14.existsSync(fullPath)) {
|
|
38067
|
-
fullPath =
|
|
38212
|
+
fullPath = path19.join(cwd, file3);
|
|
38068
38213
|
}
|
|
38069
38214
|
const complexity = getComplexityForFile(fullPath);
|
|
38070
38215
|
if (complexity !== null) {
|
|
@@ -38222,14 +38367,14 @@ function validateBase(base) {
|
|
|
38222
38367
|
function validatePaths(paths) {
|
|
38223
38368
|
if (!paths)
|
|
38224
38369
|
return null;
|
|
38225
|
-
for (const
|
|
38226
|
-
if (!
|
|
38370
|
+
for (const path20 of paths) {
|
|
38371
|
+
if (!path20 || path20.length === 0) {
|
|
38227
38372
|
return "empty path not allowed";
|
|
38228
38373
|
}
|
|
38229
|
-
if (
|
|
38374
|
+
if (path20.length > MAX_PATH_LENGTH) {
|
|
38230
38375
|
return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
|
|
38231
38376
|
}
|
|
38232
|
-
if (SHELL_METACHARACTERS2.test(
|
|
38377
|
+
if (SHELL_METACHARACTERS2.test(path20)) {
|
|
38233
38378
|
return "path contains shell metacharacters";
|
|
38234
38379
|
}
|
|
38235
38380
|
}
|
|
@@ -38292,8 +38437,8 @@ var diff = tool({
|
|
|
38292
38437
|
if (parts2.length >= 3) {
|
|
38293
38438
|
const additions = parseInt(parts2[0]) || 0;
|
|
38294
38439
|
const deletions = parseInt(parts2[1]) || 0;
|
|
38295
|
-
const
|
|
38296
|
-
files.push({ path:
|
|
38440
|
+
const path20 = parts2[2];
|
|
38441
|
+
files.push({ path: path20, additions, deletions });
|
|
38297
38442
|
}
|
|
38298
38443
|
}
|
|
38299
38444
|
const contractChanges = [];
|
|
@@ -38522,7 +38667,7 @@ Use these as DOMAIN values when delegating to @sme.`;
|
|
|
38522
38667
|
// src/tools/evidence-check.ts
|
|
38523
38668
|
init_dist();
|
|
38524
38669
|
import * as fs15 from "fs";
|
|
38525
|
-
import * as
|
|
38670
|
+
import * as path20 from "path";
|
|
38526
38671
|
var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
|
|
38527
38672
|
var MAX_EVIDENCE_FILES = 1000;
|
|
38528
38673
|
var EVIDENCE_DIR = ".swarm/evidence";
|
|
@@ -38545,9 +38690,9 @@ function validateRequiredTypes(input) {
|
|
|
38545
38690
|
return null;
|
|
38546
38691
|
}
|
|
38547
38692
|
function isPathWithinSwarm(filePath, cwd) {
|
|
38548
|
-
const normalizedCwd =
|
|
38549
|
-
const swarmPath =
|
|
38550
|
-
const normalizedPath =
|
|
38693
|
+
const normalizedCwd = path20.resolve(cwd);
|
|
38694
|
+
const swarmPath = path20.join(normalizedCwd, ".swarm");
|
|
38695
|
+
const normalizedPath = path20.resolve(filePath);
|
|
38551
38696
|
return normalizedPath.startsWith(swarmPath);
|
|
38552
38697
|
}
|
|
38553
38698
|
function parseCompletedTasks(planContent) {
|
|
@@ -38578,10 +38723,10 @@ function readEvidenceFiles(evidenceDir, cwd) {
|
|
|
38578
38723
|
if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
|
|
38579
38724
|
continue;
|
|
38580
38725
|
}
|
|
38581
|
-
const filePath =
|
|
38726
|
+
const filePath = path20.join(evidenceDir, filename);
|
|
38582
38727
|
try {
|
|
38583
|
-
const resolvedPath =
|
|
38584
|
-
const evidenceDirResolved =
|
|
38728
|
+
const resolvedPath = path20.resolve(filePath);
|
|
38729
|
+
const evidenceDirResolved = path20.resolve(evidenceDir);
|
|
38585
38730
|
if (!resolvedPath.startsWith(evidenceDirResolved)) {
|
|
38586
38731
|
continue;
|
|
38587
38732
|
}
|
|
@@ -38688,7 +38833,7 @@ var evidence_check = tool({
|
|
|
38688
38833
|
return JSON.stringify(errorResult, null, 2);
|
|
38689
38834
|
}
|
|
38690
38835
|
const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
38691
|
-
const planPath =
|
|
38836
|
+
const planPath = path20.join(cwd, PLAN_FILE);
|
|
38692
38837
|
if (!isPathWithinSwarm(planPath, cwd)) {
|
|
38693
38838
|
const errorResult = {
|
|
38694
38839
|
error: "plan file path validation failed",
|
|
@@ -38720,7 +38865,7 @@ var evidence_check = tool({
|
|
|
38720
38865
|
};
|
|
38721
38866
|
return JSON.stringify(result2, null, 2);
|
|
38722
38867
|
}
|
|
38723
|
-
const evidenceDir =
|
|
38868
|
+
const evidenceDir = path20.join(cwd, EVIDENCE_DIR);
|
|
38724
38869
|
const evidence = readEvidenceFiles(evidenceDir, cwd);
|
|
38725
38870
|
const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
|
|
38726
38871
|
const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
|
|
@@ -38737,7 +38882,7 @@ var evidence_check = tool({
|
|
|
38737
38882
|
// src/tools/file-extractor.ts
|
|
38738
38883
|
init_tool();
|
|
38739
38884
|
import * as fs16 from "fs";
|
|
38740
|
-
import * as
|
|
38885
|
+
import * as path21 from "path";
|
|
38741
38886
|
var EXT_MAP = {
|
|
38742
38887
|
python: ".py",
|
|
38743
38888
|
py: ".py",
|
|
@@ -38815,12 +38960,12 @@ var extract_code_blocks = tool({
|
|
|
38815
38960
|
if (prefix) {
|
|
38816
38961
|
filename = `${prefix}_${filename}`;
|
|
38817
38962
|
}
|
|
38818
|
-
let filepath =
|
|
38819
|
-
const base =
|
|
38820
|
-
const ext =
|
|
38963
|
+
let filepath = path21.join(targetDir, filename);
|
|
38964
|
+
const base = path21.basename(filepath, path21.extname(filepath));
|
|
38965
|
+
const ext = path21.extname(filepath);
|
|
38821
38966
|
let counter = 1;
|
|
38822
38967
|
while (fs16.existsSync(filepath)) {
|
|
38823
|
-
filepath =
|
|
38968
|
+
filepath = path21.join(targetDir, `${base}_${counter}${ext}`);
|
|
38824
38969
|
counter++;
|
|
38825
38970
|
}
|
|
38826
38971
|
try {
|
|
@@ -38933,7 +39078,7 @@ var gitingest = tool({
|
|
|
38933
39078
|
// src/tools/imports.ts
|
|
38934
39079
|
init_dist();
|
|
38935
39080
|
import * as fs17 from "fs";
|
|
38936
|
-
import * as
|
|
39081
|
+
import * as path22 from "path";
|
|
38937
39082
|
var MAX_FILE_PATH_LENGTH2 = 500;
|
|
38938
39083
|
var MAX_SYMBOL_LENGTH = 256;
|
|
38939
39084
|
var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
|
|
@@ -38987,7 +39132,7 @@ function validateSymbolInput(symbol3) {
|
|
|
38987
39132
|
return null;
|
|
38988
39133
|
}
|
|
38989
39134
|
function isBinaryFile2(filePath, buffer) {
|
|
38990
|
-
const ext =
|
|
39135
|
+
const ext = path22.extname(filePath).toLowerCase();
|
|
38991
39136
|
if (ext === ".json" || ext === ".md" || ext === ".txt") {
|
|
38992
39137
|
return false;
|
|
38993
39138
|
}
|
|
@@ -39011,15 +39156,15 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
39011
39156
|
const imports = [];
|
|
39012
39157
|
let resolvedTarget;
|
|
39013
39158
|
try {
|
|
39014
|
-
resolvedTarget =
|
|
39159
|
+
resolvedTarget = path22.resolve(targetFile);
|
|
39015
39160
|
} catch {
|
|
39016
39161
|
resolvedTarget = targetFile;
|
|
39017
39162
|
}
|
|
39018
|
-
const targetBasename =
|
|
39163
|
+
const targetBasename = path22.basename(targetFile, path22.extname(targetFile));
|
|
39019
39164
|
const targetWithExt = targetFile;
|
|
39020
39165
|
const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
39021
|
-
const normalizedTargetWithExt =
|
|
39022
|
-
const normalizedTargetWithoutExt =
|
|
39166
|
+
const normalizedTargetWithExt = path22.normalize(targetWithExt).replace(/\\/g, "/");
|
|
39167
|
+
const normalizedTargetWithoutExt = path22.normalize(targetWithoutExt).replace(/\\/g, "/");
|
|
39023
39168
|
const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
39024
39169
|
let match;
|
|
39025
39170
|
while ((match = importRegex.exec(content)) !== null) {
|
|
@@ -39043,9 +39188,9 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
39043
39188
|
}
|
|
39044
39189
|
const normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
|
|
39045
39190
|
let isMatch = false;
|
|
39046
|
-
const targetDir =
|
|
39047
|
-
const targetExt =
|
|
39048
|
-
const targetBasenameNoExt =
|
|
39191
|
+
const targetDir = path22.dirname(targetFile);
|
|
39192
|
+
const targetExt = path22.extname(targetFile);
|
|
39193
|
+
const targetBasenameNoExt = path22.basename(targetFile, targetExt);
|
|
39049
39194
|
const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
39050
39195
|
const moduleName = modulePath.split(/[/\\]/).pop() || "";
|
|
39051
39196
|
const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
@@ -39113,10 +39258,10 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
39113
39258
|
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
39114
39259
|
for (const entry of entries) {
|
|
39115
39260
|
if (SKIP_DIRECTORIES2.has(entry)) {
|
|
39116
|
-
stats.skippedDirs.push(
|
|
39261
|
+
stats.skippedDirs.push(path22.join(dir, entry));
|
|
39117
39262
|
continue;
|
|
39118
39263
|
}
|
|
39119
|
-
const fullPath =
|
|
39264
|
+
const fullPath = path22.join(dir, entry);
|
|
39120
39265
|
let stat;
|
|
39121
39266
|
try {
|
|
39122
39267
|
stat = fs17.statSync(fullPath);
|
|
@@ -39130,7 +39275,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
39130
39275
|
if (stat.isDirectory()) {
|
|
39131
39276
|
findSourceFiles2(fullPath, files, stats);
|
|
39132
39277
|
} else if (stat.isFile()) {
|
|
39133
|
-
const ext =
|
|
39278
|
+
const ext = path22.extname(fullPath).toLowerCase();
|
|
39134
39279
|
if (SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
39135
39280
|
files.push(fullPath);
|
|
39136
39281
|
}
|
|
@@ -39186,7 +39331,7 @@ var imports = tool({
|
|
|
39186
39331
|
return JSON.stringify(errorResult, null, 2);
|
|
39187
39332
|
}
|
|
39188
39333
|
try {
|
|
39189
|
-
const targetFile =
|
|
39334
|
+
const targetFile = path22.resolve(file3);
|
|
39190
39335
|
if (!fs17.existsSync(targetFile)) {
|
|
39191
39336
|
const errorResult = {
|
|
39192
39337
|
error: `target file not found: ${file3}`,
|
|
@@ -39208,7 +39353,7 @@ var imports = tool({
|
|
|
39208
39353
|
};
|
|
39209
39354
|
return JSON.stringify(errorResult, null, 2);
|
|
39210
39355
|
}
|
|
39211
|
-
const baseDir =
|
|
39356
|
+
const baseDir = path22.dirname(targetFile);
|
|
39212
39357
|
const scanStats = {
|
|
39213
39358
|
skippedDirs: [],
|
|
39214
39359
|
skippedFiles: 0,
|
|
@@ -39298,7 +39443,7 @@ init_lint();
|
|
|
39298
39443
|
// src/tools/pkg-audit.ts
|
|
39299
39444
|
init_dist();
|
|
39300
39445
|
import * as fs18 from "fs";
|
|
39301
|
-
import * as
|
|
39446
|
+
import * as path23 from "path";
|
|
39302
39447
|
var MAX_OUTPUT_BYTES5 = 52428800;
|
|
39303
39448
|
var AUDIT_TIMEOUT_MS = 120000;
|
|
39304
39449
|
function isValidEcosystem(value) {
|
|
@@ -39316,13 +39461,13 @@ function validateArgs3(args2) {
|
|
|
39316
39461
|
function detectEcosystems() {
|
|
39317
39462
|
const ecosystems = [];
|
|
39318
39463
|
const cwd = process.cwd();
|
|
39319
|
-
if (fs18.existsSync(
|
|
39464
|
+
if (fs18.existsSync(path23.join(cwd, "package.json"))) {
|
|
39320
39465
|
ecosystems.push("npm");
|
|
39321
39466
|
}
|
|
39322
|
-
if (fs18.existsSync(
|
|
39467
|
+
if (fs18.existsSync(path23.join(cwd, "pyproject.toml")) || fs18.existsSync(path23.join(cwd, "requirements.txt"))) {
|
|
39323
39468
|
ecosystems.push("pip");
|
|
39324
39469
|
}
|
|
39325
|
-
if (fs18.existsSync(
|
|
39470
|
+
if (fs18.existsSync(path23.join(cwd, "Cargo.toml"))) {
|
|
39326
39471
|
ecosystems.push("cargo");
|
|
39327
39472
|
}
|
|
39328
39473
|
return ecosystems;
|
|
@@ -41230,11 +41375,11 @@ var Module2 = (() => {
|
|
|
41230
41375
|
throw toThrow;
|
|
41231
41376
|
}, "quit_");
|
|
41232
41377
|
var scriptDirectory = "";
|
|
41233
|
-
function locateFile(
|
|
41378
|
+
function locateFile(path24) {
|
|
41234
41379
|
if (Module["locateFile"]) {
|
|
41235
|
-
return Module["locateFile"](
|
|
41380
|
+
return Module["locateFile"](path24, scriptDirectory);
|
|
41236
41381
|
}
|
|
41237
|
-
return scriptDirectory +
|
|
41382
|
+
return scriptDirectory + path24;
|
|
41238
41383
|
}
|
|
41239
41384
|
__name(locateFile, "locateFile");
|
|
41240
41385
|
var readAsync, readBinary;
|
|
@@ -43023,6 +43168,9 @@ for (const definition of languageDefinitions) {
|
|
|
43023
43168
|
extensionMap.set(extension, definition);
|
|
43024
43169
|
}
|
|
43025
43170
|
}
|
|
43171
|
+
function getLanguageForExtension(extension) {
|
|
43172
|
+
return extensionMap.get(extension.toLowerCase());
|
|
43173
|
+
}
|
|
43026
43174
|
|
|
43027
43175
|
// src/tools/placeholder-scan.ts
|
|
43028
43176
|
var MAX_FILE_SIZE = 1024 * 1024;
|
|
@@ -43043,54 +43191,799 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
|
|
|
43043
43191
|
".php",
|
|
43044
43192
|
".rb"
|
|
43045
43193
|
]);
|
|
43194
|
+
// src/tools/pre-check-batch.ts
|
|
43195
|
+
init_dist();
|
|
43196
|
+
import * as path26 from "path";
|
|
43197
|
+
|
|
43198
|
+
// node_modules/yocto-queue/index.js
|
|
43199
|
+
class Node2 {
|
|
43200
|
+
value;
|
|
43201
|
+
next;
|
|
43202
|
+
constructor(value) {
|
|
43203
|
+
this.value = value;
|
|
43204
|
+
}
|
|
43205
|
+
}
|
|
43206
|
+
|
|
43207
|
+
class Queue {
|
|
43208
|
+
#head;
|
|
43209
|
+
#tail;
|
|
43210
|
+
#size;
|
|
43211
|
+
constructor() {
|
|
43212
|
+
this.clear();
|
|
43213
|
+
}
|
|
43214
|
+
enqueue(value) {
|
|
43215
|
+
const node = new Node2(value);
|
|
43216
|
+
if (this.#head) {
|
|
43217
|
+
this.#tail.next = node;
|
|
43218
|
+
this.#tail = node;
|
|
43219
|
+
} else {
|
|
43220
|
+
this.#head = node;
|
|
43221
|
+
this.#tail = node;
|
|
43222
|
+
}
|
|
43223
|
+
this.#size++;
|
|
43224
|
+
}
|
|
43225
|
+
dequeue() {
|
|
43226
|
+
const current = this.#head;
|
|
43227
|
+
if (!current) {
|
|
43228
|
+
return;
|
|
43229
|
+
}
|
|
43230
|
+
this.#head = this.#head.next;
|
|
43231
|
+
this.#size--;
|
|
43232
|
+
if (!this.#head) {
|
|
43233
|
+
this.#tail = undefined;
|
|
43234
|
+
}
|
|
43235
|
+
return current.value;
|
|
43236
|
+
}
|
|
43237
|
+
peek() {
|
|
43238
|
+
if (!this.#head) {
|
|
43239
|
+
return;
|
|
43240
|
+
}
|
|
43241
|
+
return this.#head.value;
|
|
43242
|
+
}
|
|
43243
|
+
clear() {
|
|
43244
|
+
this.#head = undefined;
|
|
43245
|
+
this.#tail = undefined;
|
|
43246
|
+
this.#size = 0;
|
|
43247
|
+
}
|
|
43248
|
+
get size() {
|
|
43249
|
+
return this.#size;
|
|
43250
|
+
}
|
|
43251
|
+
*[Symbol.iterator]() {
|
|
43252
|
+
let current = this.#head;
|
|
43253
|
+
while (current) {
|
|
43254
|
+
yield current.value;
|
|
43255
|
+
current = current.next;
|
|
43256
|
+
}
|
|
43257
|
+
}
|
|
43258
|
+
*drain() {
|
|
43259
|
+
while (this.#head) {
|
|
43260
|
+
yield this.dequeue();
|
|
43261
|
+
}
|
|
43262
|
+
}
|
|
43263
|
+
}
|
|
43264
|
+
|
|
43265
|
+
// node_modules/p-limit/index.js
|
|
43266
|
+
function pLimit(concurrency) {
|
|
43267
|
+
let rejectOnClear = false;
|
|
43268
|
+
if (typeof concurrency === "object") {
|
|
43269
|
+
({ concurrency, rejectOnClear = false } = concurrency);
|
|
43270
|
+
}
|
|
43271
|
+
validateConcurrency(concurrency);
|
|
43272
|
+
if (typeof rejectOnClear !== "boolean") {
|
|
43273
|
+
throw new TypeError("Expected `rejectOnClear` to be a boolean");
|
|
43274
|
+
}
|
|
43275
|
+
const queue = new Queue;
|
|
43276
|
+
let activeCount = 0;
|
|
43277
|
+
const resumeNext = () => {
|
|
43278
|
+
if (activeCount < concurrency && queue.size > 0) {
|
|
43279
|
+
activeCount++;
|
|
43280
|
+
queue.dequeue().run();
|
|
43281
|
+
}
|
|
43282
|
+
};
|
|
43283
|
+
const next = () => {
|
|
43284
|
+
activeCount--;
|
|
43285
|
+
resumeNext();
|
|
43286
|
+
};
|
|
43287
|
+
const run2 = async (function_, resolve10, arguments_2) => {
|
|
43288
|
+
const result = (async () => function_(...arguments_2))();
|
|
43289
|
+
resolve10(result);
|
|
43290
|
+
try {
|
|
43291
|
+
await result;
|
|
43292
|
+
} catch {}
|
|
43293
|
+
next();
|
|
43294
|
+
};
|
|
43295
|
+
const enqueue = (function_, resolve10, reject, arguments_2) => {
|
|
43296
|
+
const queueItem = { reject };
|
|
43297
|
+
new Promise((internalResolve) => {
|
|
43298
|
+
queueItem.run = internalResolve;
|
|
43299
|
+
queue.enqueue(queueItem);
|
|
43300
|
+
}).then(run2.bind(undefined, function_, resolve10, arguments_2));
|
|
43301
|
+
if (activeCount < concurrency) {
|
|
43302
|
+
resumeNext();
|
|
43303
|
+
}
|
|
43304
|
+
};
|
|
43305
|
+
const generator = (function_, ...arguments_2) => new Promise((resolve10, reject) => {
|
|
43306
|
+
enqueue(function_, resolve10, reject, arguments_2);
|
|
43307
|
+
});
|
|
43308
|
+
Object.defineProperties(generator, {
|
|
43309
|
+
activeCount: {
|
|
43310
|
+
get: () => activeCount
|
|
43311
|
+
},
|
|
43312
|
+
pendingCount: {
|
|
43313
|
+
get: () => queue.size
|
|
43314
|
+
},
|
|
43315
|
+
clearQueue: {
|
|
43316
|
+
value() {
|
|
43317
|
+
if (!rejectOnClear) {
|
|
43318
|
+
queue.clear();
|
|
43319
|
+
return;
|
|
43320
|
+
}
|
|
43321
|
+
const abortError = AbortSignal.abort().reason;
|
|
43322
|
+
while (queue.size > 0) {
|
|
43323
|
+
queue.dequeue().reject(abortError);
|
|
43324
|
+
}
|
|
43325
|
+
}
|
|
43326
|
+
},
|
|
43327
|
+
concurrency: {
|
|
43328
|
+
get: () => concurrency,
|
|
43329
|
+
set(newConcurrency) {
|
|
43330
|
+
validateConcurrency(newConcurrency);
|
|
43331
|
+
concurrency = newConcurrency;
|
|
43332
|
+
queueMicrotask(() => {
|
|
43333
|
+
while (activeCount < concurrency && queue.size > 0) {
|
|
43334
|
+
resumeNext();
|
|
43335
|
+
}
|
|
43336
|
+
});
|
|
43337
|
+
}
|
|
43338
|
+
},
|
|
43339
|
+
map: {
|
|
43340
|
+
async value(iterable, function_) {
|
|
43341
|
+
const promises = Array.from(iterable, (value, index) => this(function_, value, index));
|
|
43342
|
+
return Promise.all(promises);
|
|
43343
|
+
}
|
|
43344
|
+
}
|
|
43345
|
+
});
|
|
43346
|
+
return generator;
|
|
43347
|
+
}
|
|
43348
|
+
function validateConcurrency(concurrency) {
|
|
43349
|
+
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
|
43350
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
43351
|
+
}
|
|
43352
|
+
}
|
|
43353
|
+
|
|
43354
|
+
// src/tools/pre-check-batch.ts
|
|
43355
|
+
init_utils();
|
|
43356
|
+
init_lint();
|
|
43357
|
+
|
|
43046
43358
|
// src/tools/quality-budget.ts
|
|
43047
43359
|
init_manager();
|
|
43048
43360
|
|
|
43049
43361
|
// src/quality/metrics.ts
|
|
43362
|
+
import * as fs19 from "fs";
|
|
43363
|
+
import * as path24 from "path";
|
|
43050
43364
|
var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
|
|
43051
|
-
|
|
43052
|
-
|
|
43053
|
-
|
|
43054
|
-
|
|
43055
|
-
|
|
43056
|
-
|
|
43057
|
-
|
|
43058
|
-
|
|
43059
|
-
|
|
43060
|
-
|
|
43061
|
-
|
|
43062
|
-
|
|
43063
|
-
|
|
43064
|
-
|
|
43065
|
-
|
|
43365
|
+
var MIN_DUPLICATION_LINES = 10;
|
|
43366
|
+
function estimateCyclomaticComplexity(content) {
|
|
43367
|
+
let processed = content.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
43368
|
+
processed = processed.replace(/\/\/.*/g, "");
|
|
43369
|
+
processed = processed.replace(/#.*/g, "");
|
|
43370
|
+
processed = processed.replace(/'[^']*'/g, "");
|
|
43371
|
+
processed = processed.replace(/"[^"]*"/g, "");
|
|
43372
|
+
processed = processed.replace(/`[^`]*`/g, "");
|
|
43373
|
+
let complexity = 1;
|
|
43374
|
+
const decisionPatterns = [
|
|
43375
|
+
/\bif\b/g,
|
|
43376
|
+
/\belse\s+if\b/g,
|
|
43377
|
+
/\bfor\b/g,
|
|
43378
|
+
/\bwhile\b/g,
|
|
43379
|
+
/\bswitch\b/g,
|
|
43380
|
+
/\bcase\b/g,
|
|
43381
|
+
/\bcatch\b/g,
|
|
43382
|
+
/\?\./g,
|
|
43383
|
+
/\?\?/g,
|
|
43384
|
+
/&&/g,
|
|
43385
|
+
/\|\|/g
|
|
43386
|
+
];
|
|
43387
|
+
for (const pattern of decisionPatterns) {
|
|
43388
|
+
const matches = processed.match(pattern);
|
|
43389
|
+
if (matches) {
|
|
43390
|
+
complexity += matches.length;
|
|
43391
|
+
}
|
|
43392
|
+
}
|
|
43393
|
+
const ternaryMatches = processed.match(/\?[^:]/g);
|
|
43394
|
+
if (ternaryMatches) {
|
|
43395
|
+
complexity += ternaryMatches.length;
|
|
43396
|
+
}
|
|
43397
|
+
return complexity;
|
|
43398
|
+
}
|
|
43399
|
+
function getComplexityForFile2(filePath) {
|
|
43400
|
+
try {
|
|
43401
|
+
const stat = fs19.statSync(filePath);
|
|
43402
|
+
if (stat.size > MAX_FILE_SIZE_BYTES5) {
|
|
43403
|
+
return null;
|
|
43404
|
+
}
|
|
43405
|
+
const content = fs19.readFileSync(filePath, "utf-8");
|
|
43406
|
+
return estimateCyclomaticComplexity(content);
|
|
43407
|
+
} catch {
|
|
43408
|
+
return null;
|
|
43409
|
+
}
|
|
43410
|
+
}
|
|
43411
|
+
async function computeComplexityDelta(files, workingDir) {
|
|
43412
|
+
let totalComplexity = 0;
|
|
43413
|
+
const analyzedFiles = [];
|
|
43414
|
+
for (const file3 of files) {
|
|
43415
|
+
const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
|
|
43416
|
+
if (!fs19.existsSync(fullPath)) {
|
|
43417
|
+
continue;
|
|
43418
|
+
}
|
|
43419
|
+
const complexity = getComplexityForFile2(fullPath);
|
|
43420
|
+
if (complexity !== null) {
|
|
43421
|
+
totalComplexity += complexity;
|
|
43422
|
+
analyzedFiles.push(file3);
|
|
43423
|
+
}
|
|
43424
|
+
}
|
|
43425
|
+
return { delta: totalComplexity, analyzedFiles };
|
|
43426
|
+
}
|
|
43427
|
+
function countExportsInFile(content) {
|
|
43428
|
+
let count = 0;
|
|
43429
|
+
const exportFunctionMatches = content.match(/export\s+function\s+\w+/g);
|
|
43430
|
+
if (exportFunctionMatches)
|
|
43431
|
+
count += exportFunctionMatches.length;
|
|
43432
|
+
const exportClassMatches = content.match(/export\s+class\s+\w+/g);
|
|
43433
|
+
if (exportClassMatches)
|
|
43434
|
+
count += exportClassMatches.length;
|
|
43435
|
+
const exportConstMatches = content.match(/export\s+const\s+\w+/g);
|
|
43436
|
+
if (exportConstMatches)
|
|
43437
|
+
count += exportConstMatches.length;
|
|
43438
|
+
const exportLetMatches = content.match(/export\s+let\s+\w+/g);
|
|
43439
|
+
if (exportLetMatches)
|
|
43440
|
+
count += exportLetMatches.length;
|
|
43441
|
+
const exportVarMatches = content.match(/export\s+var\s+\w+/g);
|
|
43442
|
+
if (exportVarMatches)
|
|
43443
|
+
count += exportVarMatches.length;
|
|
43444
|
+
const exportNamedMatches = content.match(/export\s*\{[^}]+\}/g);
|
|
43445
|
+
if (exportNamedMatches) {
|
|
43446
|
+
for (const match of exportNamedMatches) {
|
|
43447
|
+
const names = match.match(/\b[a-zA-Z_$][a-zA-Z0-9_$]*\b/g);
|
|
43448
|
+
const filteredNames = names ? names.filter((n) => n !== "export") : [];
|
|
43449
|
+
count += filteredNames.length;
|
|
43450
|
+
}
|
|
43451
|
+
}
|
|
43452
|
+
const exportDefaultMatches = content.match(/export\s+default/g);
|
|
43453
|
+
if (exportDefaultMatches)
|
|
43454
|
+
count += exportDefaultMatches.length;
|
|
43455
|
+
const exportTypeMatches = content.match(/export\s+(type|interface)\s+\w+/g);
|
|
43456
|
+
if (exportTypeMatches)
|
|
43457
|
+
count += exportTypeMatches.length;
|
|
43458
|
+
const exportEnumMatches = content.match(/export\s+enum\s+\w+/g);
|
|
43459
|
+
if (exportEnumMatches)
|
|
43460
|
+
count += exportEnumMatches.length;
|
|
43461
|
+
const exportEqualsMatches = content.match(/export\s+=/g);
|
|
43462
|
+
if (exportEqualsMatches)
|
|
43463
|
+
count += exportEqualsMatches.length;
|
|
43464
|
+
return count;
|
|
43465
|
+
}
|
|
43466
|
+
function countPythonExports(content) {
|
|
43467
|
+
let count = 0;
|
|
43468
|
+
const noComments = content.replace(/#.*/g, "");
|
|
43469
|
+
const noStrings = noComments.replace(/"[^"]*"/g, "").replace(/'[^']*'/g, "");
|
|
43470
|
+
const functionMatches = noStrings.match(/^\s*def\s+\w+/gm);
|
|
43471
|
+
if (functionMatches)
|
|
43472
|
+
count += functionMatches.length;
|
|
43473
|
+
const classMatches = noStrings.match(/^\s*class\s+\w+/gm);
|
|
43474
|
+
if (classMatches)
|
|
43475
|
+
count += classMatches.length;
|
|
43476
|
+
const originalContent = content;
|
|
43477
|
+
const allMatchOriginal = originalContent.match(/__all__\s*=\s*\[([^\]]+)\]/);
|
|
43478
|
+
if (allMatchOriginal?.[1]) {
|
|
43479
|
+
const names = allMatchOriginal[1].match(/['"]?(\w+)['"]?/g);
|
|
43480
|
+
if (names) {
|
|
43481
|
+
const cleanNames = names.map((n) => n.replace(/['"]/g, ""));
|
|
43482
|
+
count += cleanNames.length;
|
|
43483
|
+
}
|
|
43484
|
+
}
|
|
43485
|
+
return count;
|
|
43486
|
+
}
|
|
43487
|
+
function countRustExports(content) {
|
|
43488
|
+
let count = 0;
|
|
43489
|
+
let processed = content.replace(/\/\/.*/g, "");
|
|
43490
|
+
processed = processed.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
43491
|
+
const pubFnMatches = processed.match(/pub\s+fn\s+\w+/g);
|
|
43492
|
+
if (pubFnMatches)
|
|
43493
|
+
count += pubFnMatches.length;
|
|
43494
|
+
const pubStructMatches = processed.match(/pub\s+struct\s+\w+/g);
|
|
43495
|
+
if (pubStructMatches)
|
|
43496
|
+
count += pubStructMatches.length;
|
|
43497
|
+
const pubEnumMatches = processed.match(/pub\s+enum\s+\w+/g);
|
|
43498
|
+
if (pubEnumMatches)
|
|
43499
|
+
count += pubEnumMatches.length;
|
|
43500
|
+
const pubUseMatches = processed.match(/pub\s+use\s+/g);
|
|
43501
|
+
if (pubUseMatches)
|
|
43502
|
+
count += pubUseMatches.length;
|
|
43503
|
+
const pubConstMatches = processed.match(/pub\s+const\s+\w+/g);
|
|
43504
|
+
if (pubConstMatches)
|
|
43505
|
+
count += pubConstMatches.length;
|
|
43506
|
+
const pubModMatches = processed.match(/pub\s+mod\s+\w+/g);
|
|
43507
|
+
if (pubModMatches)
|
|
43508
|
+
count += pubModMatches.length;
|
|
43509
|
+
const pubTypeMatches = processed.match(/pub\s+type\s+\w+/g);
|
|
43510
|
+
if (pubTypeMatches)
|
|
43511
|
+
count += pubTypeMatches.length;
|
|
43512
|
+
return count;
|
|
43513
|
+
}
|
|
43514
|
+
function countGoExports(content) {
|
|
43515
|
+
let count = 0;
|
|
43516
|
+
let processed = content.replace(/\/\/.*/gm, "");
|
|
43517
|
+
processed = processed.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
43518
|
+
const exportedFuncMatches = processed.match(/^\s*func\s+[A-Z]\w*/gm);
|
|
43519
|
+
if (exportedFuncMatches)
|
|
43520
|
+
count += exportedFuncMatches.length;
|
|
43521
|
+
const exportedVarMatches = processed.match(/^\s*var\s+[A-Z]\w*/gm);
|
|
43522
|
+
if (exportedVarMatches)
|
|
43523
|
+
count += exportedVarMatches.length;
|
|
43524
|
+
const exportedTypeMatches = processed.match(/^\s*type\s+[A-Z]\w*/gm);
|
|
43525
|
+
if (exportedTypeMatches)
|
|
43526
|
+
count += exportedTypeMatches.length;
|
|
43527
|
+
const exportedConstMatches = processed.match(/^\s*const\s+[A-Z]\w*/gm);
|
|
43528
|
+
if (exportedConstMatches)
|
|
43529
|
+
count += exportedConstMatches.length;
|
|
43530
|
+
const packageMatch = processed.match(/^\s*package\s+\w+/m);
|
|
43531
|
+
if (packageMatch)
|
|
43532
|
+
count += 1;
|
|
43533
|
+
return count;
|
|
43534
|
+
}
|
|
43535
|
+
function getExportCountForFile(filePath) {
|
|
43536
|
+
try {
|
|
43537
|
+
const content = fs19.readFileSync(filePath, "utf-8");
|
|
43538
|
+
const ext = path24.extname(filePath).toLowerCase();
|
|
43539
|
+
switch (ext) {
|
|
43540
|
+
case ".ts":
|
|
43541
|
+
case ".tsx":
|
|
43542
|
+
case ".js":
|
|
43543
|
+
case ".jsx":
|
|
43544
|
+
case ".mjs":
|
|
43545
|
+
case ".cjs":
|
|
43546
|
+
return countExportsInFile(content);
|
|
43547
|
+
case ".py":
|
|
43548
|
+
return countPythonExports(content);
|
|
43549
|
+
case ".rs":
|
|
43550
|
+
return countRustExports(content);
|
|
43551
|
+
case ".go":
|
|
43552
|
+
return countGoExports(content);
|
|
43553
|
+
default:
|
|
43554
|
+
return countExportsInFile(content);
|
|
43555
|
+
}
|
|
43556
|
+
} catch {
|
|
43557
|
+
return 0;
|
|
43558
|
+
}
|
|
43559
|
+
}
|
|
43560
|
+
async function computePublicApiDelta(files, workingDir) {
|
|
43561
|
+
let totalExports = 0;
|
|
43562
|
+
const analyzedFiles = [];
|
|
43563
|
+
for (const file3 of files) {
|
|
43564
|
+
const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
|
|
43565
|
+
if (!fs19.existsSync(fullPath)) {
|
|
43566
|
+
continue;
|
|
43567
|
+
}
|
|
43568
|
+
const exports = getExportCountForFile(fullPath);
|
|
43569
|
+
totalExports += exports;
|
|
43570
|
+
analyzedFiles.push(file3);
|
|
43571
|
+
}
|
|
43572
|
+
return { delta: totalExports, analyzedFiles };
|
|
43573
|
+
}
|
|
43574
|
+
function findDuplicateLines(content, minLines) {
|
|
43575
|
+
const lines = content.split(`
|
|
43576
|
+
`).filter((line) => line.trim().length > 0);
|
|
43577
|
+
if (lines.length < minLines) {
|
|
43578
|
+
return 0;
|
|
43579
|
+
}
|
|
43580
|
+
const lineCounts = new Map;
|
|
43581
|
+
for (const line of lines) {
|
|
43582
|
+
const normalized = line.trim();
|
|
43583
|
+
lineCounts.set(normalized, (lineCounts.get(normalized) || 0) + 1);
|
|
43584
|
+
}
|
|
43585
|
+
let duplicateCount = 0;
|
|
43586
|
+
for (const [_line, count] of lineCounts) {
|
|
43587
|
+
if (count > 1) {
|
|
43588
|
+
duplicateCount += count - 1;
|
|
43589
|
+
}
|
|
43590
|
+
}
|
|
43591
|
+
return duplicateCount;
|
|
43592
|
+
}
|
|
43593
|
+
async function computeDuplicationRatio(files, workingDir) {
|
|
43594
|
+
let totalLines = 0;
|
|
43595
|
+
let duplicateLines = 0;
|
|
43596
|
+
const analyzedFiles = [];
|
|
43597
|
+
for (const file3 of files) {
|
|
43598
|
+
const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
|
|
43599
|
+
if (!fs19.existsSync(fullPath)) {
|
|
43600
|
+
continue;
|
|
43066
43601
|
}
|
|
43067
|
-
let fullOutput;
|
|
43068
43602
|
try {
|
|
43069
|
-
|
|
43070
|
-
|
|
43071
|
-
|
|
43603
|
+
const stat = fs19.statSync(fullPath);
|
|
43604
|
+
if (stat.size > MAX_FILE_SIZE_BYTES5) {
|
|
43605
|
+
continue;
|
|
43606
|
+
}
|
|
43607
|
+
const content = fs19.readFileSync(fullPath, "utf-8");
|
|
43608
|
+
const lines = content.split(`
|
|
43609
|
+
`).filter((line) => line.trim().length > 0);
|
|
43610
|
+
if (lines.length < MIN_DUPLICATION_LINES) {
|
|
43611
|
+
analyzedFiles.push(file3);
|
|
43612
|
+
continue;
|
|
43613
|
+
}
|
|
43614
|
+
totalLines += lines.length;
|
|
43615
|
+
duplicateLines += findDuplicateLines(content, MIN_DUPLICATION_LINES);
|
|
43616
|
+
analyzedFiles.push(file3);
|
|
43617
|
+
} catch {}
|
|
43618
|
+
}
|
|
43619
|
+
const ratio = totalLines > 0 ? duplicateLines / totalLines : 0;
|
|
43620
|
+
return { ratio, analyzedFiles };
|
|
43621
|
+
}
|
|
43622
|
+
function countCodeLines(content) {
|
|
43623
|
+
let processed = content.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
43624
|
+
processed = processed.replace(/\/\/.*/g, "");
|
|
43625
|
+
processed = processed.replace(/#.*/g, "");
|
|
43626
|
+
const lines = processed.split(`
|
|
43627
|
+
`).filter((line) => line.trim().length > 0);
|
|
43628
|
+
return lines.length;
|
|
43629
|
+
}
|
|
43630
|
+
function isTestFile(filePath) {
|
|
43631
|
+
const basename5 = path24.basename(filePath);
|
|
43632
|
+
const _ext = path24.extname(filePath).toLowerCase();
|
|
43633
|
+
const testPatterns = [
|
|
43634
|
+
".test.",
|
|
43635
|
+
".spec.",
|
|
43636
|
+
".tests.",
|
|
43637
|
+
".test.ts",
|
|
43638
|
+
".test.js",
|
|
43639
|
+
".spec.ts",
|
|
43640
|
+
".spec.js",
|
|
43641
|
+
".test.tsx",
|
|
43642
|
+
".test.jsx",
|
|
43643
|
+
".spec.tsx",
|
|
43644
|
+
".spec.jsx"
|
|
43645
|
+
];
|
|
43646
|
+
for (const pattern of testPatterns) {
|
|
43647
|
+
if (basename5.includes(pattern)) {
|
|
43648
|
+
return true;
|
|
43072
43649
|
}
|
|
43073
|
-
|
|
43074
|
-
|
|
43650
|
+
}
|
|
43651
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
43652
|
+
if (normalizedPath.includes("/tests/") || normalizedPath.includes("/test/")) {
|
|
43653
|
+
return true;
|
|
43654
|
+
}
|
|
43655
|
+
if (normalizedPath.includes("/__tests__/")) {
|
|
43656
|
+
return true;
|
|
43657
|
+
}
|
|
43658
|
+
return false;
|
|
43659
|
+
}
|
|
43660
|
+
function shouldExcludeFile(filePath, excludeGlobs) {
|
|
43661
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
43662
|
+
for (const glob of excludeGlobs) {
|
|
43663
|
+
const pattern = glob.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
43664
|
+
const regex = new RegExp(pattern);
|
|
43665
|
+
if (regex.test(normalizedPath)) {
|
|
43666
|
+
return true;
|
|
43075
43667
|
}
|
|
43076
|
-
|
|
43077
|
-
|
|
43668
|
+
}
|
|
43669
|
+
return false;
|
|
43670
|
+
}
|
|
43671
|
+
async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
43672
|
+
let testLines = 0;
|
|
43673
|
+
let codeLines = 0;
|
|
43674
|
+
const srcDir = path24.join(workingDir, "src");
|
|
43675
|
+
if (fs19.existsSync(srcDir)) {
|
|
43676
|
+
await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
43677
|
+
codeLines += lines;
|
|
43678
|
+
});
|
|
43679
|
+
}
|
|
43680
|
+
const possibleSrcDirs = ["lib", "app", "source", "core"];
|
|
43681
|
+
for (const dir of possibleSrcDirs) {
|
|
43682
|
+
const dirPath = path24.join(workingDir, dir);
|
|
43683
|
+
if (fs19.existsSync(dirPath)) {
|
|
43684
|
+
await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
43685
|
+
codeLines += lines;
|
|
43686
|
+
});
|
|
43078
43687
|
}
|
|
43079
|
-
return fullOutput;
|
|
43080
43688
|
}
|
|
43081
|
-
|
|
43082
|
-
|
|
43083
|
-
|
|
43084
|
-
|
|
43085
|
-
|
|
43086
|
-
|
|
43087
|
-
|
|
43088
|
-
|
|
43089
|
-
|
|
43090
|
-
|
|
43091
|
-
|
|
43092
|
-
|
|
43093
|
-
|
|
43689
|
+
const testsDir = path24.join(workingDir, "tests");
|
|
43690
|
+
if (fs19.existsSync(testsDir)) {
|
|
43691
|
+
await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
43692
|
+
testLines += lines;
|
|
43693
|
+
});
|
|
43694
|
+
}
|
|
43695
|
+
const possibleTestDirs = ["test", "__tests__", "specs"];
|
|
43696
|
+
for (const dir of possibleTestDirs) {
|
|
43697
|
+
const dirPath = path24.join(workingDir, dir);
|
|
43698
|
+
if (fs19.existsSync(dirPath) && dirPath !== testsDir) {
|
|
43699
|
+
await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
43700
|
+
testLines += lines;
|
|
43701
|
+
});
|
|
43702
|
+
}
|
|
43703
|
+
}
|
|
43704
|
+
const totalLines = testLines + codeLines;
|
|
43705
|
+
const ratio = totalLines > 0 ? testLines / totalLines : 0;
|
|
43706
|
+
return { ratio, testLines, codeLines };
|
|
43707
|
+
}
|
|
43708
|
+
async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTestScan, callback) {
|
|
43709
|
+
try {
|
|
43710
|
+
const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
|
|
43711
|
+
for (const entry of entries) {
|
|
43712
|
+
const fullPath = path24.join(dirPath, entry.name);
|
|
43713
|
+
if (entry.isDirectory()) {
|
|
43714
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
|
|
43715
|
+
continue;
|
|
43716
|
+
}
|
|
43717
|
+
await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
|
|
43718
|
+
} else if (entry.isFile()) {
|
|
43719
|
+
const relativePath = fullPath.replace(`${process.cwd()}/`, "");
|
|
43720
|
+
const ext = path24.extname(entry.name).toLowerCase();
|
|
43721
|
+
const validExts = [
|
|
43722
|
+
".ts",
|
|
43723
|
+
".tsx",
|
|
43724
|
+
".js",
|
|
43725
|
+
".jsx",
|
|
43726
|
+
".py",
|
|
43727
|
+
".rs",
|
|
43728
|
+
".go",
|
|
43729
|
+
".java",
|
|
43730
|
+
".cs"
|
|
43731
|
+
];
|
|
43732
|
+
if (!validExts.includes(ext)) {
|
|
43733
|
+
continue;
|
|
43734
|
+
}
|
|
43735
|
+
if (isTestScan && !isTestFile(fullPath)) {
|
|
43736
|
+
continue;
|
|
43737
|
+
}
|
|
43738
|
+
if (!isTestScan && isTestFile(fullPath)) {
|
|
43739
|
+
continue;
|
|
43740
|
+
}
|
|
43741
|
+
if (shouldExcludeFile(relativePath, excludeGlobs)) {
|
|
43742
|
+
continue;
|
|
43743
|
+
}
|
|
43744
|
+
if (!isTestScan && includeGlobs.length > 0 && !includeGlobs.includes("**")) {
|
|
43745
|
+
let matches = false;
|
|
43746
|
+
for (const glob of includeGlobs) {
|
|
43747
|
+
const pattern = glob.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
43748
|
+
const regex = new RegExp(pattern);
|
|
43749
|
+
if (regex.test(relativePath)) {
|
|
43750
|
+
matches = true;
|
|
43751
|
+
break;
|
|
43752
|
+
}
|
|
43753
|
+
}
|
|
43754
|
+
if (!matches)
|
|
43755
|
+
continue;
|
|
43756
|
+
}
|
|
43757
|
+
try {
|
|
43758
|
+
const content = fs19.readFileSync(fullPath, "utf-8");
|
|
43759
|
+
const lines = countCodeLines(content);
|
|
43760
|
+
callback(lines);
|
|
43761
|
+
} catch {}
|
|
43762
|
+
}
|
|
43763
|
+
}
|
|
43764
|
+
} catch {}
|
|
43765
|
+
}
|
|
43766
|
+
function detectViolations(metrics, thresholds) {
|
|
43767
|
+
const violations = [];
|
|
43768
|
+
if (metrics.complexity_delta > thresholds.max_complexity_delta) {
|
|
43769
|
+
violations.push({
|
|
43770
|
+
type: "complexity",
|
|
43771
|
+
message: `Complexity delta (${metrics.complexity_delta}) exceeds threshold (${thresholds.max_complexity_delta})`,
|
|
43772
|
+
severity: metrics.complexity_delta > thresholds.max_complexity_delta * 1.5 ? "error" : "warning",
|
|
43773
|
+
files: metrics.files_analyzed
|
|
43774
|
+
});
|
|
43775
|
+
}
|
|
43776
|
+
if (metrics.public_api_delta > thresholds.max_public_api_delta) {
|
|
43777
|
+
violations.push({
|
|
43778
|
+
type: "api",
|
|
43779
|
+
message: `Public API delta (${metrics.public_api_delta}) exceeds threshold (${thresholds.max_public_api_delta})`,
|
|
43780
|
+
severity: metrics.public_api_delta > thresholds.max_public_api_delta * 1.5 ? "error" : "warning",
|
|
43781
|
+
files: metrics.files_analyzed
|
|
43782
|
+
});
|
|
43783
|
+
}
|
|
43784
|
+
if (metrics.duplication_ratio > thresholds.max_duplication_ratio) {
|
|
43785
|
+
violations.push({
|
|
43786
|
+
type: "duplication",
|
|
43787
|
+
message: `Duplication ratio (${(metrics.duplication_ratio * 100).toFixed(1)}%) exceeds threshold (${(thresholds.max_duplication_ratio * 100).toFixed(1)}%)`,
|
|
43788
|
+
severity: metrics.duplication_ratio > thresholds.max_duplication_ratio * 1.5 ? "error" : "warning",
|
|
43789
|
+
files: metrics.files_analyzed
|
|
43790
|
+
});
|
|
43791
|
+
}
|
|
43792
|
+
if (metrics.files_analyzed.length > 0 && metrics.test_to_code_ratio < thresholds.min_test_to_code_ratio) {
|
|
43793
|
+
violations.push({
|
|
43794
|
+
type: "test_ratio",
|
|
43795
|
+
message: `Test-to-code ratio (${(metrics.test_to_code_ratio * 100).toFixed(1)}%) below threshold (${(thresholds.min_test_to_code_ratio * 100).toFixed(1)}%)`,
|
|
43796
|
+
severity: metrics.test_to_code_ratio < thresholds.min_test_to_code_ratio * 0.5 ? "error" : "warning",
|
|
43797
|
+
files: metrics.files_analyzed
|
|
43798
|
+
});
|
|
43799
|
+
}
|
|
43800
|
+
return violations;
|
|
43801
|
+
}
|
|
43802
|
+
async function computeQualityMetrics(changedFiles, thresholds, workingDir) {
|
|
43803
|
+
const config3 = {
|
|
43804
|
+
enabled: thresholds.enabled ?? true,
|
|
43805
|
+
max_complexity_delta: thresholds.max_complexity_delta ?? 5,
|
|
43806
|
+
max_public_api_delta: thresholds.max_public_api_delta ?? 10,
|
|
43807
|
+
max_duplication_ratio: thresholds.max_duplication_ratio ?? 0.05,
|
|
43808
|
+
min_test_to_code_ratio: thresholds.min_test_to_code_ratio ?? 0.3,
|
|
43809
|
+
enforce_on_globs: thresholds.enforce_on_globs ?? ["src/**"],
|
|
43810
|
+
exclude_globs: thresholds.exclude_globs ?? [
|
|
43811
|
+
"docs/**",
|
|
43812
|
+
"tests/**",
|
|
43813
|
+
"**/*.test.*"
|
|
43814
|
+
]
|
|
43815
|
+
};
|
|
43816
|
+
const filteredFiles = changedFiles.filter((file3) => {
|
|
43817
|
+
const normalizedPath = file3.replace(/\\/g, "/");
|
|
43818
|
+
for (const glob of config3.enforce_on_globs) {
|
|
43819
|
+
const pattern = glob.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
43820
|
+
const regex = new RegExp(pattern);
|
|
43821
|
+
if (regex.test(normalizedPath)) {
|
|
43822
|
+
for (const exclude of config3.exclude_globs) {
|
|
43823
|
+
const excludePattern = exclude.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
43824
|
+
const excludeRegex = new RegExp(excludePattern);
|
|
43825
|
+
if (excludeRegex.test(normalizedPath)) {
|
|
43826
|
+
return false;
|
|
43827
|
+
}
|
|
43828
|
+
}
|
|
43829
|
+
return true;
|
|
43830
|
+
}
|
|
43831
|
+
}
|
|
43832
|
+
return false;
|
|
43833
|
+
});
|
|
43834
|
+
const [complexityResult, apiResult, duplicationResult, testRatioResult] = await Promise.all([
|
|
43835
|
+
computeComplexityDelta(filteredFiles, workingDir),
|
|
43836
|
+
computePublicApiDelta(filteredFiles, workingDir),
|
|
43837
|
+
computeDuplicationRatio(filteredFiles, workingDir),
|
|
43838
|
+
computeTestToCodeRatio(workingDir, config3.enforce_on_globs, config3.exclude_globs)
|
|
43839
|
+
]);
|
|
43840
|
+
const allAnalyzedFiles = [
|
|
43841
|
+
...new Set([
|
|
43842
|
+
...complexityResult.analyzedFiles,
|
|
43843
|
+
...apiResult.analyzedFiles,
|
|
43844
|
+
...duplicationResult.analyzedFiles
|
|
43845
|
+
])
|
|
43846
|
+
];
|
|
43847
|
+
const violations = detectViolations({
|
|
43848
|
+
complexity_delta: complexityResult.delta,
|
|
43849
|
+
public_api_delta: apiResult.delta,
|
|
43850
|
+
duplication_ratio: duplicationResult.ratio,
|
|
43851
|
+
test_to_code_ratio: testRatioResult.ratio,
|
|
43852
|
+
files_analyzed: allAnalyzedFiles,
|
|
43853
|
+
thresholds: config3,
|
|
43854
|
+
violations: []
|
|
43855
|
+
}, config3);
|
|
43856
|
+
return {
|
|
43857
|
+
complexity_delta: complexityResult.delta,
|
|
43858
|
+
public_api_delta: apiResult.delta,
|
|
43859
|
+
duplication_ratio: duplicationResult.ratio,
|
|
43860
|
+
test_to_code_ratio: testRatioResult.ratio,
|
|
43861
|
+
files_analyzed: allAnalyzedFiles,
|
|
43862
|
+
thresholds: config3,
|
|
43863
|
+
violations
|
|
43864
|
+
};
|
|
43865
|
+
}
|
|
43866
|
+
|
|
43867
|
+
// src/tools/quality-budget.ts
|
|
43868
|
+
function validateInput(input) {
|
|
43869
|
+
if (!input || typeof input !== "object") {
|
|
43870
|
+
return { valid: false, error: "Input must be an object" };
|
|
43871
|
+
}
|
|
43872
|
+
const typedInput = input;
|
|
43873
|
+
if (!Array.isArray(typedInput.changed_files)) {
|
|
43874
|
+
return { valid: false, error: "changed_files must be an array" };
|
|
43875
|
+
}
|
|
43876
|
+
for (const file3 of typedInput.changed_files) {
|
|
43877
|
+
if (typeof file3 !== "string") {
|
|
43878
|
+
return { valid: false, error: "changed_files must contain strings" };
|
|
43879
|
+
}
|
|
43880
|
+
}
|
|
43881
|
+
if (typedInput.config !== undefined) {
|
|
43882
|
+
if (!typedInput.config || typeof typedInput.config !== "object") {
|
|
43883
|
+
return { valid: false, error: "config must be an object if provided" };
|
|
43884
|
+
}
|
|
43885
|
+
}
|
|
43886
|
+
return { valid: true };
|
|
43887
|
+
}
|
|
43888
|
+
async function qualityBudget(input, directory) {
|
|
43889
|
+
const validation = validateInput(input);
|
|
43890
|
+
if (!validation.valid) {
|
|
43891
|
+
throw new Error(`Invalid input: ${validation.error}`);
|
|
43892
|
+
}
|
|
43893
|
+
const { changed_files: changedFiles, config: config3 } = input;
|
|
43894
|
+
const thresholds = {
|
|
43895
|
+
enabled: config3?.enabled ?? true,
|
|
43896
|
+
max_complexity_delta: config3?.max_complexity_delta ?? 5,
|
|
43897
|
+
max_public_api_delta: config3?.max_public_api_delta ?? 10,
|
|
43898
|
+
max_duplication_ratio: config3?.max_duplication_ratio ?? 0.05,
|
|
43899
|
+
min_test_to_code_ratio: config3?.min_test_to_code_ratio ?? 0.3,
|
|
43900
|
+
enforce_on_globs: config3?.enforce_on_globs ?? ["src/**"],
|
|
43901
|
+
exclude_globs: config3?.exclude_globs ?? [
|
|
43902
|
+
"docs/**",
|
|
43903
|
+
"tests/**",
|
|
43904
|
+
"**/*.test.*"
|
|
43905
|
+
]
|
|
43906
|
+
};
|
|
43907
|
+
if (!thresholds.enabled) {
|
|
43908
|
+
return {
|
|
43909
|
+
verdict: "pass",
|
|
43910
|
+
metrics: {
|
|
43911
|
+
complexity_delta: 0,
|
|
43912
|
+
public_api_delta: 0,
|
|
43913
|
+
duplication_ratio: 0,
|
|
43914
|
+
test_to_code_ratio: 0,
|
|
43915
|
+
files_analyzed: [],
|
|
43916
|
+
thresholds,
|
|
43917
|
+
violations: []
|
|
43918
|
+
},
|
|
43919
|
+
violations: [],
|
|
43920
|
+
summary: {
|
|
43921
|
+
files_analyzed: 0,
|
|
43922
|
+
violations_count: 0,
|
|
43923
|
+
errors_count: 0,
|
|
43924
|
+
warnings_count: 0
|
|
43925
|
+
}
|
|
43926
|
+
};
|
|
43927
|
+
}
|
|
43928
|
+
const metrics = await computeQualityMetrics(changedFiles, thresholds, directory);
|
|
43929
|
+
const errorsCount = metrics.violations.filter((v) => v.severity === "error").length;
|
|
43930
|
+
const warningsCount = metrics.violations.filter((v) => v.severity === "warning").length;
|
|
43931
|
+
const verdict = errorsCount > 0 ? "fail" : "pass";
|
|
43932
|
+
await saveEvidence(directory, "quality_budget", {
|
|
43933
|
+
task_id: "quality_budget",
|
|
43934
|
+
type: "quality_budget",
|
|
43935
|
+
timestamp: new Date().toISOString(),
|
|
43936
|
+
agent: "quality_budget",
|
|
43937
|
+
verdict,
|
|
43938
|
+
summary: `Quality budget check: ${metrics.files_analyzed.length} files analyzed, ${metrics.violations.length} violation(s) found (${errorsCount} errors, ${warningsCount} warnings)`,
|
|
43939
|
+
metrics: {
|
|
43940
|
+
complexity_delta: metrics.complexity_delta,
|
|
43941
|
+
public_api_delta: metrics.public_api_delta,
|
|
43942
|
+
duplication_ratio: metrics.duplication_ratio,
|
|
43943
|
+
test_to_code_ratio: metrics.test_to_code_ratio
|
|
43944
|
+
},
|
|
43945
|
+
thresholds: {
|
|
43946
|
+
max_complexity_delta: thresholds.max_complexity_delta,
|
|
43947
|
+
max_public_api_delta: thresholds.max_public_api_delta,
|
|
43948
|
+
max_duplication_ratio: thresholds.max_duplication_ratio,
|
|
43949
|
+
min_test_to_code_ratio: thresholds.min_test_to_code_ratio
|
|
43950
|
+
},
|
|
43951
|
+
violations: metrics.violations.map((v) => ({
|
|
43952
|
+
type: v.type,
|
|
43953
|
+
message: v.message,
|
|
43954
|
+
severity: v.severity,
|
|
43955
|
+
files: v.files
|
|
43956
|
+
})),
|
|
43957
|
+
files_analyzed: metrics.files_analyzed
|
|
43958
|
+
});
|
|
43959
|
+
return {
|
|
43960
|
+
verdict,
|
|
43961
|
+
metrics,
|
|
43962
|
+
violations: metrics.violations,
|
|
43963
|
+
summary: {
|
|
43964
|
+
files_analyzed: metrics.files_analyzed.length,
|
|
43965
|
+
violations_count: metrics.violations.length,
|
|
43966
|
+
errors_count: errorsCount,
|
|
43967
|
+
warnings_count: warningsCount
|
|
43968
|
+
}
|
|
43969
|
+
};
|
|
43970
|
+
}
|
|
43971
|
+
|
|
43972
|
+
// src/tools/sast-scan.ts
|
|
43973
|
+
init_manager();
|
|
43974
|
+
import * as fs20 from "fs";
|
|
43975
|
+
import * as path25 from "path";
|
|
43976
|
+
import { extname as extname7 } from "path";
|
|
43977
|
+
|
|
43978
|
+
// src/sast/rules/c.ts
|
|
43979
|
+
var cRules = [
|
|
43980
|
+
{
|
|
43981
|
+
id: "sast/c-buffer-overflow",
|
|
43982
|
+
name: "Buffer overflow vulnerability",
|
|
43983
|
+
severity: "critical",
|
|
43984
|
+
languages: ["c", "cpp"],
|
|
43985
|
+
description: "strcpy/strcat to fixed-size buffer without bounds checking",
|
|
43986
|
+
remediation: "Use strncpy, snprintf, or safer alternatives that include size limits.",
|
|
43094
43987
|
pattern: /\b(?:strcpy|strcat)\s*\(\s*[a-zA-Z_]/
|
|
43095
43988
|
},
|
|
43096
43989
|
{
|
|
@@ -43697,20 +44590,768 @@ var allRules = [
|
|
|
43697
44590
|
...cRules,
|
|
43698
44591
|
...csharpRules
|
|
43699
44592
|
];
|
|
44593
|
+
function getRulesForLanguage(language) {
|
|
44594
|
+
const normalized = language.toLowerCase();
|
|
44595
|
+
return allRules.filter((rule) => rule.languages.some((lang) => lang.toLowerCase() === normalized));
|
|
44596
|
+
}
|
|
44597
|
+
function findPatternMatches(content, pattern) {
|
|
44598
|
+
const matches = [];
|
|
44599
|
+
const lines = content.split(`
|
|
44600
|
+
`);
|
|
44601
|
+
for (let lineNum = 0;lineNum < lines.length; lineNum++) {
|
|
44602
|
+
const line = lines[lineNum];
|
|
44603
|
+
const workPattern = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g");
|
|
44604
|
+
let match;
|
|
44605
|
+
while ((match = workPattern.exec(line)) !== null) {
|
|
44606
|
+
matches.push({
|
|
44607
|
+
text: match[0],
|
|
44608
|
+
line: lineNum + 1,
|
|
44609
|
+
column: match.index + 1
|
|
44610
|
+
});
|
|
44611
|
+
if (match[0].length === 0) {
|
|
44612
|
+
workPattern.lastIndex++;
|
|
44613
|
+
}
|
|
44614
|
+
}
|
|
44615
|
+
}
|
|
44616
|
+
return matches;
|
|
44617
|
+
}
|
|
44618
|
+
function executeRulesSync(filePath, content, language) {
|
|
44619
|
+
const findings = [];
|
|
44620
|
+
const normalizedLang = language.toLowerCase();
|
|
44621
|
+
const rules = getRulesForLanguage(normalizedLang);
|
|
44622
|
+
for (const rule of rules) {
|
|
44623
|
+
if (!rule.pattern)
|
|
44624
|
+
continue;
|
|
44625
|
+
const matches = findPatternMatches(content, rule.pattern);
|
|
44626
|
+
for (const match of matches) {
|
|
44627
|
+
if (rule.validate) {
|
|
44628
|
+
const context = {
|
|
44629
|
+
filePath,
|
|
44630
|
+
content,
|
|
44631
|
+
language: normalizedLang
|
|
44632
|
+
};
|
|
44633
|
+
if (!rule.validate(match, context)) {
|
|
44634
|
+
continue;
|
|
44635
|
+
}
|
|
44636
|
+
}
|
|
44637
|
+
const lines = content.split(`
|
|
44638
|
+
`);
|
|
44639
|
+
const excerpt = lines[match.line - 1]?.trim() || "";
|
|
44640
|
+
findings.push({
|
|
44641
|
+
rule_id: rule.id,
|
|
44642
|
+
severity: rule.severity,
|
|
44643
|
+
message: rule.description,
|
|
44644
|
+
location: {
|
|
44645
|
+
file: filePath,
|
|
44646
|
+
line: match.line,
|
|
44647
|
+
column: match.column
|
|
44648
|
+
},
|
|
44649
|
+
remediation: rule.remediation,
|
|
44650
|
+
excerpt
|
|
44651
|
+
});
|
|
44652
|
+
}
|
|
44653
|
+
}
|
|
44654
|
+
return findings;
|
|
44655
|
+
}
|
|
43700
44656
|
|
|
43701
44657
|
// src/sast/semgrep.ts
|
|
43702
44658
|
import { execFile, execFileSync, spawn } from "child_process";
|
|
43703
44659
|
import { promisify } from "util";
|
|
43704
44660
|
var execFileAsync = promisify(execFile);
|
|
44661
|
+
var semgrepAvailableCache = null;
|
|
44662
|
+
var DEFAULT_RULES_DIR = ".swarm/semgrep-rules";
|
|
44663
|
+
var DEFAULT_TIMEOUT_MS3 = 30000;
|
|
44664
|
+
function isSemgrepAvailable() {
|
|
44665
|
+
if (semgrepAvailableCache !== null) {
|
|
44666
|
+
return semgrepAvailableCache;
|
|
44667
|
+
}
|
|
44668
|
+
try {
|
|
44669
|
+
execFileSync("semgrep", ["--version"], {
|
|
44670
|
+
encoding: "utf-8",
|
|
44671
|
+
stdio: "pipe"
|
|
44672
|
+
});
|
|
44673
|
+
semgrepAvailableCache = true;
|
|
44674
|
+
return true;
|
|
44675
|
+
} catch {
|
|
44676
|
+
semgrepAvailableCache = false;
|
|
44677
|
+
return false;
|
|
44678
|
+
}
|
|
44679
|
+
}
|
|
44680
|
+
function parseSemgrepResults(semgrepOutput) {
|
|
44681
|
+
const findings = [];
|
|
44682
|
+
try {
|
|
44683
|
+
const parsed = JSON.parse(semgrepOutput);
|
|
44684
|
+
const results = parsed.results || parsed;
|
|
44685
|
+
for (const result of results) {
|
|
44686
|
+
const severity = mapSemgrepSeverity(result.extra?.severity || result.severity);
|
|
44687
|
+
findings.push({
|
|
44688
|
+
rule_id: result.check_id || result.rule_id || "unknown",
|
|
44689
|
+
severity,
|
|
44690
|
+
message: result.extra?.message || result.message || "Security issue detected",
|
|
44691
|
+
location: {
|
|
44692
|
+
file: result.path || result.start?.filename || result.file || "unknown",
|
|
44693
|
+
line: result.start?.line || result.line || 1,
|
|
44694
|
+
column: result.start?.col || result.column
|
|
44695
|
+
},
|
|
44696
|
+
remediation: result.extra?.fix,
|
|
44697
|
+
excerpt: result.extra?.lines || result.lines || ""
|
|
44698
|
+
});
|
|
44699
|
+
}
|
|
44700
|
+
} catch {
|
|
44701
|
+
return [];
|
|
44702
|
+
}
|
|
44703
|
+
return findings;
|
|
44704
|
+
}
|
|
44705
|
+
function mapSemgrepSeverity(severity) {
|
|
44706
|
+
const severityLower = (severity || "").toLowerCase();
|
|
44707
|
+
switch (severityLower) {
|
|
44708
|
+
case "error":
|
|
44709
|
+
case "critical":
|
|
44710
|
+
return "critical";
|
|
44711
|
+
case "warning":
|
|
44712
|
+
case "high":
|
|
44713
|
+
return "high";
|
|
44714
|
+
case "info":
|
|
44715
|
+
case "low":
|
|
44716
|
+
return "low";
|
|
44717
|
+
default:
|
|
44718
|
+
return "medium";
|
|
44719
|
+
}
|
|
44720
|
+
}
|
|
44721
|
+
async function executeWithTimeout(command, args2, options) {
|
|
44722
|
+
return new Promise((resolve10) => {
|
|
44723
|
+
const child = spawn(command, args2, {
|
|
44724
|
+
shell: false,
|
|
44725
|
+
cwd: options.cwd
|
|
44726
|
+
});
|
|
44727
|
+
let stdout = "";
|
|
44728
|
+
let stderr = "";
|
|
44729
|
+
const timeout = setTimeout(() => {
|
|
44730
|
+
child.kill("SIGTERM");
|
|
44731
|
+
resolve10({
|
|
44732
|
+
stdout,
|
|
44733
|
+
stderr: "Process timed out",
|
|
44734
|
+
exitCode: 124
|
|
44735
|
+
});
|
|
44736
|
+
}, options.timeoutMs);
|
|
44737
|
+
child.stdout?.on("data", (data) => {
|
|
44738
|
+
stdout += data.toString();
|
|
44739
|
+
});
|
|
44740
|
+
child.stderr?.on("data", (data) => {
|
|
44741
|
+
stderr += data.toString();
|
|
44742
|
+
});
|
|
44743
|
+
child.on("close", (code) => {
|
|
44744
|
+
clearTimeout(timeout);
|
|
44745
|
+
resolve10({
|
|
44746
|
+
stdout,
|
|
44747
|
+
stderr,
|
|
44748
|
+
exitCode: code ?? 0
|
|
44749
|
+
});
|
|
44750
|
+
});
|
|
44751
|
+
child.on("error", (err2) => {
|
|
44752
|
+
clearTimeout(timeout);
|
|
44753
|
+
resolve10({
|
|
44754
|
+
stdout,
|
|
44755
|
+
stderr: err2.message,
|
|
44756
|
+
exitCode: 1
|
|
44757
|
+
});
|
|
44758
|
+
});
|
|
44759
|
+
});
|
|
44760
|
+
}
|
|
44761
|
+
async function runSemgrep(options) {
|
|
44762
|
+
const files = options.files || [];
|
|
44763
|
+
const rulesDir = options.rulesDir || DEFAULT_RULES_DIR;
|
|
44764
|
+
const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS3;
|
|
44765
|
+
if (files.length === 0) {
|
|
44766
|
+
return {
|
|
44767
|
+
available: isSemgrepAvailable(),
|
|
44768
|
+
findings: [],
|
|
44769
|
+
engine: "tier_a"
|
|
44770
|
+
};
|
|
44771
|
+
}
|
|
44772
|
+
if (!isSemgrepAvailable()) {
|
|
44773
|
+
return {
|
|
44774
|
+
available: false,
|
|
44775
|
+
findings: [],
|
|
44776
|
+
error: "Semgrep is not installed or not available on PATH",
|
|
44777
|
+
engine: "tier_a"
|
|
44778
|
+
};
|
|
44779
|
+
}
|
|
44780
|
+
const args2 = [
|
|
44781
|
+
"--config=./" + rulesDir,
|
|
44782
|
+
"--json",
|
|
44783
|
+
"--quiet",
|
|
44784
|
+
...files
|
|
44785
|
+
];
|
|
44786
|
+
try {
|
|
44787
|
+
const result = await executeWithTimeout("semgrep", args2, {
|
|
44788
|
+
timeoutMs,
|
|
44789
|
+
cwd: options.cwd
|
|
44790
|
+
});
|
|
44791
|
+
if (result.exitCode !== 0) {
|
|
44792
|
+
if (result.exitCode === 1 && result.stdout) {
|
|
44793
|
+
const findings2 = parseSemgrepResults(result.stdout);
|
|
44794
|
+
return {
|
|
44795
|
+
available: true,
|
|
44796
|
+
findings: findings2,
|
|
44797
|
+
engine: "tier_a+tier_b"
|
|
44798
|
+
};
|
|
44799
|
+
}
|
|
44800
|
+
return {
|
|
44801
|
+
available: true,
|
|
44802
|
+
findings: [],
|
|
44803
|
+
error: result.stderr || `Semgrep exited with code ${result.exitCode}`,
|
|
44804
|
+
engine: "tier_a"
|
|
44805
|
+
};
|
|
44806
|
+
}
|
|
44807
|
+
const findings = parseSemgrepResults(result.stdout);
|
|
44808
|
+
return {
|
|
44809
|
+
available: true,
|
|
44810
|
+
findings,
|
|
44811
|
+
engine: "tier_a+tier_b"
|
|
44812
|
+
};
|
|
44813
|
+
} catch (error93) {
|
|
44814
|
+
const errorMessage = error93 instanceof Error ? error93.message : "Unknown error running Semgrep";
|
|
44815
|
+
return {
|
|
44816
|
+
available: true,
|
|
44817
|
+
findings: [],
|
|
44818
|
+
error: errorMessage,
|
|
44819
|
+
engine: "tier_a"
|
|
44820
|
+
};
|
|
44821
|
+
}
|
|
44822
|
+
}
|
|
43705
44823
|
|
|
43706
44824
|
// src/tools/sast-scan.ts
|
|
43707
44825
|
init_utils();
|
|
43708
44826
|
var MAX_FILE_SIZE_BYTES6 = 512 * 1024;
|
|
44827
|
+
var MAX_FILES_SCANNED2 = 1000;
|
|
44828
|
+
var MAX_FINDINGS2 = 100;
|
|
44829
|
+
var SEVERITY_ORDER = {
|
|
44830
|
+
low: 0,
|
|
44831
|
+
medium: 1,
|
|
44832
|
+
high: 2,
|
|
44833
|
+
critical: 3
|
|
44834
|
+
};
|
|
44835
|
+
function shouldSkipFile(filePath) {
|
|
44836
|
+
try {
|
|
44837
|
+
const stats = fs20.statSync(filePath);
|
|
44838
|
+
if (stats.size > MAX_FILE_SIZE_BYTES6) {
|
|
44839
|
+
return { skip: true, reason: "file too large" };
|
|
44840
|
+
}
|
|
44841
|
+
if (stats.size === 0) {
|
|
44842
|
+
return { skip: true, reason: "empty file" };
|
|
44843
|
+
}
|
|
44844
|
+
const fd = fs20.openSync(filePath, "r");
|
|
44845
|
+
const buffer = Buffer.alloc(8192);
|
|
44846
|
+
const bytesRead = fs20.readSync(fd, buffer, 0, 8192, 0);
|
|
44847
|
+
fs20.closeSync(fd);
|
|
44848
|
+
if (bytesRead > 0) {
|
|
44849
|
+
let nullCount = 0;
|
|
44850
|
+
for (let i2 = 0;i2 < bytesRead; i2++) {
|
|
44851
|
+
if (buffer[i2] === 0) {
|
|
44852
|
+
nullCount++;
|
|
44853
|
+
}
|
|
44854
|
+
}
|
|
44855
|
+
if (nullCount / bytesRead > 0.1) {
|
|
44856
|
+
return { skip: true, reason: "binary file" };
|
|
44857
|
+
}
|
|
44858
|
+
}
|
|
44859
|
+
return { skip: false };
|
|
44860
|
+
} catch {
|
|
44861
|
+
return { skip: true, reason: "cannot read file" };
|
|
44862
|
+
}
|
|
44863
|
+
}
|
|
44864
|
+
function meetsThreshold(severity, threshold) {
|
|
44865
|
+
const severityLevel = SEVERITY_ORDER[severity] ?? 0;
|
|
44866
|
+
const thresholdLevel = SEVERITY_ORDER[threshold] ?? 1;
|
|
44867
|
+
return severityLevel >= thresholdLevel;
|
|
44868
|
+
}
|
|
44869
|
+
function countBySeverity(findings) {
|
|
44870
|
+
const counts = {
|
|
44871
|
+
critical: 0,
|
|
44872
|
+
high: 0,
|
|
44873
|
+
medium: 0,
|
|
44874
|
+
low: 0
|
|
44875
|
+
};
|
|
44876
|
+
for (const finding of findings) {
|
|
44877
|
+
const severity = finding.severity.toLowerCase();
|
|
44878
|
+
if (severity in counts) {
|
|
44879
|
+
counts[severity]++;
|
|
44880
|
+
}
|
|
44881
|
+
}
|
|
44882
|
+
return counts;
|
|
44883
|
+
}
|
|
44884
|
+
function scanFileWithTierA(filePath, language) {
|
|
44885
|
+
try {
|
|
44886
|
+
const content = fs20.readFileSync(filePath, "utf-8");
|
|
44887
|
+
const findings = executeRulesSync(filePath, content, language);
|
|
44888
|
+
return findings.map((f) => ({
|
|
44889
|
+
rule_id: f.rule_id,
|
|
44890
|
+
severity: f.severity,
|
|
44891
|
+
message: f.message,
|
|
44892
|
+
location: {
|
|
44893
|
+
file: f.location.file,
|
|
44894
|
+
line: f.location.line,
|
|
44895
|
+
column: f.location.column
|
|
44896
|
+
},
|
|
44897
|
+
remediation: f.remediation
|
|
44898
|
+
}));
|
|
44899
|
+
} catch {
|
|
44900
|
+
return [];
|
|
44901
|
+
}
|
|
44902
|
+
}
|
|
44903
|
+
async function sastScan(input, directory, config3) {
|
|
44904
|
+
const { changed_files, severity_threshold = "medium" } = input;
|
|
44905
|
+
if (config3?.gates?.sast_scan?.enabled === false) {
|
|
44906
|
+
return {
|
|
44907
|
+
verdict: "pass",
|
|
44908
|
+
findings: [],
|
|
44909
|
+
summary: {
|
|
44910
|
+
engine: "tier_a",
|
|
44911
|
+
files_scanned: 0,
|
|
44912
|
+
findings_count: 0,
|
|
44913
|
+
findings_by_severity: {
|
|
44914
|
+
critical: 0,
|
|
44915
|
+
high: 0,
|
|
44916
|
+
medium: 0,
|
|
44917
|
+
low: 0
|
|
44918
|
+
}
|
|
44919
|
+
}
|
|
44920
|
+
};
|
|
44921
|
+
}
|
|
44922
|
+
const allFindings = [];
|
|
44923
|
+
let filesScanned = 0;
|
|
44924
|
+
let filesSkipped = 0;
|
|
44925
|
+
const semgrepAvailable = isSemgrepAvailable();
|
|
44926
|
+
const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
|
|
44927
|
+
const filesByLanguage = new Map;
|
|
44928
|
+
for (const filePath of changed_files) {
|
|
44929
|
+
const resolvedPath = path25.isAbsolute(filePath) ? filePath : path25.resolve(directory, filePath);
|
|
44930
|
+
if (!fs20.existsSync(resolvedPath)) {
|
|
44931
|
+
filesSkipped++;
|
|
44932
|
+
continue;
|
|
44933
|
+
}
|
|
44934
|
+
const skipResult = shouldSkipFile(resolvedPath);
|
|
44935
|
+
if (skipResult.skip) {
|
|
44936
|
+
filesSkipped++;
|
|
44937
|
+
continue;
|
|
44938
|
+
}
|
|
44939
|
+
const ext = extname7(resolvedPath).toLowerCase();
|
|
44940
|
+
const langDef = getLanguageForExtension(ext);
|
|
44941
|
+
if (!langDef) {
|
|
44942
|
+
filesSkipped++;
|
|
44943
|
+
continue;
|
|
44944
|
+
}
|
|
44945
|
+
const language = langDef.id;
|
|
44946
|
+
const tierAFindings = scanFileWithTierA(resolvedPath, language);
|
|
44947
|
+
allFindings.push(...tierAFindings);
|
|
44948
|
+
if (semgrepAvailable) {
|
|
44949
|
+
const existing = filesByLanguage.get(language) || [];
|
|
44950
|
+
existing.push(resolvedPath);
|
|
44951
|
+
filesByLanguage.set(language, existing);
|
|
44952
|
+
}
|
|
44953
|
+
filesScanned++;
|
|
44954
|
+
if (filesScanned >= MAX_FILES_SCANNED2) {
|
|
44955
|
+
warn(`SAST Scan: Reached maximum files limit (${MAX_FILES_SCANNED2}), stopping`);
|
|
44956
|
+
break;
|
|
44957
|
+
}
|
|
44958
|
+
}
|
|
44959
|
+
if (semgrepAvailable && filesByLanguage.size > 0) {
|
|
44960
|
+
try {
|
|
44961
|
+
const allFilesForSemgrep = Array.from(filesByLanguage.values()).flat();
|
|
44962
|
+
if (allFilesForSemgrep.length > 0) {
|
|
44963
|
+
const semgrepResult = await runSemgrep({
|
|
44964
|
+
files: allFilesForSemgrep
|
|
44965
|
+
});
|
|
44966
|
+
if (semgrepResult.findings.length > 0) {
|
|
44967
|
+
const semgrepFindings = semgrepResult.findings.map((f) => ({
|
|
44968
|
+
rule_id: f.rule_id,
|
|
44969
|
+
severity: f.severity,
|
|
44970
|
+
message: f.message,
|
|
44971
|
+
location: {
|
|
44972
|
+
file: f.location.file,
|
|
44973
|
+
line: f.location.line,
|
|
44974
|
+
column: f.location.column
|
|
44975
|
+
},
|
|
44976
|
+
remediation: f.remediation
|
|
44977
|
+
}));
|
|
44978
|
+
const existingKeys = new Set(allFindings.map((f) => `${f.rule_id}:${f.location.file}:${f.location.line}`));
|
|
44979
|
+
for (const finding of semgrepFindings) {
|
|
44980
|
+
const key = `${finding.rule_id}:${finding.location.file}:${finding.location.line}`;
|
|
44981
|
+
if (!existingKeys.has(key)) {
|
|
44982
|
+
allFindings.push(finding);
|
|
44983
|
+
existingKeys.add(key);
|
|
44984
|
+
}
|
|
44985
|
+
}
|
|
44986
|
+
}
|
|
44987
|
+
}
|
|
44988
|
+
} catch (error93) {
|
|
44989
|
+
warn(`SAST Scan: Semgrep failed, falling back to Tier A: ${error93}`);
|
|
44990
|
+
}
|
|
44991
|
+
}
|
|
44992
|
+
let finalFindings = allFindings;
|
|
44993
|
+
if (allFindings.length > MAX_FINDINGS2) {
|
|
44994
|
+
finalFindings = allFindings.slice(0, MAX_FINDINGS2);
|
|
44995
|
+
warn(`SAST Scan: Found ${allFindings.length} findings, limiting to ${MAX_FINDINGS2}`);
|
|
44996
|
+
}
|
|
44997
|
+
const findingsBySeverity = countBySeverity(finalFindings);
|
|
44998
|
+
let verdict = "pass";
|
|
44999
|
+
for (const finding of finalFindings) {
|
|
45000
|
+
if (meetsThreshold(finding.severity, severity_threshold)) {
|
|
45001
|
+
verdict = "fail";
|
|
45002
|
+
break;
|
|
45003
|
+
}
|
|
45004
|
+
}
|
|
45005
|
+
const summary = {
|
|
45006
|
+
engine,
|
|
45007
|
+
files_scanned: filesScanned,
|
|
45008
|
+
findings_count: finalFindings.length,
|
|
45009
|
+
findings_by_severity: findingsBySeverity
|
|
45010
|
+
};
|
|
45011
|
+
await saveEvidence(directory, "sast_scan", {
|
|
45012
|
+
task_id: "sast_scan",
|
|
45013
|
+
type: "sast",
|
|
45014
|
+
timestamp: new Date().toISOString(),
|
|
45015
|
+
agent: "sast_scan",
|
|
45016
|
+
verdict,
|
|
45017
|
+
summary: `Scanned ${filesScanned} files, found ${finalFindings.length} finding(s) using ${engine}`,
|
|
45018
|
+
...summary,
|
|
45019
|
+
findings: finalFindings
|
|
45020
|
+
});
|
|
45021
|
+
return {
|
|
45022
|
+
verdict,
|
|
45023
|
+
findings: finalFindings,
|
|
45024
|
+
summary
|
|
45025
|
+
};
|
|
45026
|
+
}
|
|
45027
|
+
|
|
45028
|
+
// src/tools/pre-check-batch.ts
|
|
45029
|
+
init_secretscan();
|
|
45030
|
+
var TOOL_TIMEOUT_MS = 60000;
|
|
45031
|
+
var MAX_COMBINED_BYTES = 500000;
|
|
45032
|
+
var MAX_CONCURRENT = 4;
|
|
45033
|
+
var MAX_FILES = 100;
|
|
45034
|
+
function validatePath(inputPath, baseDir) {
|
|
45035
|
+
if (!inputPath || inputPath.length === 0) {
|
|
45036
|
+
return "path is required";
|
|
45037
|
+
}
|
|
45038
|
+
const resolved = path26.resolve(baseDir, inputPath);
|
|
45039
|
+
const baseResolved = path26.resolve(baseDir);
|
|
45040
|
+
const relative2 = path26.relative(baseResolved, resolved);
|
|
45041
|
+
if (relative2.startsWith("..") || path26.isAbsolute(relative2)) {
|
|
45042
|
+
return "path traversal detected";
|
|
45043
|
+
}
|
|
45044
|
+
return null;
|
|
45045
|
+
}
|
|
45046
|
+
function validateDirectory(dir) {
|
|
45047
|
+
if (!dir || dir.length === 0) {
|
|
45048
|
+
return "directory is required";
|
|
45049
|
+
}
|
|
45050
|
+
if (dir.length > 500) {
|
|
45051
|
+
return "directory path too long";
|
|
45052
|
+
}
|
|
45053
|
+
const traversalCheck = validatePath(dir, process.cwd());
|
|
45054
|
+
if (traversalCheck) {
|
|
45055
|
+
return traversalCheck;
|
|
45056
|
+
}
|
|
45057
|
+
return null;
|
|
45058
|
+
}
|
|
45059
|
+
async function runWithTimeout(promise3, timeoutMs) {
|
|
45060
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
45061
|
+
setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
45062
|
+
});
|
|
45063
|
+
return Promise.race([promise3, timeoutPromise]);
|
|
45064
|
+
}
|
|
45065
|
+
async function runLintWrapped(_config) {
|
|
45066
|
+
const start2 = process.hrtime.bigint();
|
|
45067
|
+
try {
|
|
45068
|
+
const linter = await detectAvailableLinter();
|
|
45069
|
+
if (!linter) {
|
|
45070
|
+
return {
|
|
45071
|
+
ran: false,
|
|
45072
|
+
error: "No linter found (biome or eslint)",
|
|
45073
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45074
|
+
};
|
|
45075
|
+
}
|
|
45076
|
+
const result = await runWithTimeout(runLint(linter, "check"), TOOL_TIMEOUT_MS);
|
|
45077
|
+
return {
|
|
45078
|
+
ran: true,
|
|
45079
|
+
result,
|
|
45080
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45081
|
+
};
|
|
45082
|
+
} catch (error93) {
|
|
45083
|
+
return {
|
|
45084
|
+
ran: true,
|
|
45085
|
+
error: error93 instanceof Error ? error93.message : "Unknown error",
|
|
45086
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45087
|
+
};
|
|
45088
|
+
}
|
|
45089
|
+
}
|
|
45090
|
+
async function runSecretscanWrapped(directory, _config) {
|
|
45091
|
+
const start2 = process.hrtime.bigint();
|
|
45092
|
+
try {
|
|
45093
|
+
const result = await runWithTimeout(runSecretscan(directory), TOOL_TIMEOUT_MS);
|
|
45094
|
+
return {
|
|
45095
|
+
ran: true,
|
|
45096
|
+
result,
|
|
45097
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45098
|
+
};
|
|
45099
|
+
} catch (error93) {
|
|
45100
|
+
return {
|
|
45101
|
+
ran: true,
|
|
45102
|
+
error: error93 instanceof Error ? error93.message : "Unknown error",
|
|
45103
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45104
|
+
};
|
|
45105
|
+
}
|
|
45106
|
+
}
|
|
45107
|
+
async function runSastScanWrapped(changedFiles, directory, severityThreshold, config3) {
|
|
45108
|
+
const start2 = process.hrtime.bigint();
|
|
45109
|
+
try {
|
|
45110
|
+
const result = await runWithTimeout(sastScan({ changed_files: changedFiles, severity_threshold: severityThreshold }, directory, config3), TOOL_TIMEOUT_MS);
|
|
45111
|
+
return {
|
|
45112
|
+
ran: true,
|
|
45113
|
+
result,
|
|
45114
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45115
|
+
};
|
|
45116
|
+
} catch (error93) {
|
|
45117
|
+
return {
|
|
45118
|
+
ran: true,
|
|
45119
|
+
error: error93 instanceof Error ? error93.message : "Unknown error",
|
|
45120
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45121
|
+
};
|
|
45122
|
+
}
|
|
45123
|
+
}
|
|
45124
|
+
async function runQualityBudgetWrapped(changedFiles, directory, _config) {
|
|
45125
|
+
const start2 = process.hrtime.bigint();
|
|
45126
|
+
try {
|
|
45127
|
+
const result = await runWithTimeout(qualityBudget({ changed_files: changedFiles }, directory), TOOL_TIMEOUT_MS);
|
|
45128
|
+
return {
|
|
45129
|
+
ran: true,
|
|
45130
|
+
result,
|
|
45131
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45132
|
+
};
|
|
45133
|
+
} catch (error93) {
|
|
45134
|
+
return {
|
|
45135
|
+
ran: true,
|
|
45136
|
+
error: error93 instanceof Error ? error93.message : "Unknown error",
|
|
45137
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45138
|
+
};
|
|
45139
|
+
}
|
|
45140
|
+
}
|
|
45141
|
+
async function runPreCheckBatch(input) {
|
|
45142
|
+
const { files, directory, sast_threshold = "medium", config: config3 } = input;
|
|
45143
|
+
const dirError = validateDirectory(directory);
|
|
45144
|
+
if (dirError) {
|
|
45145
|
+
warn(`pre_check_batch: Invalid directory: ${dirError}`);
|
|
45146
|
+
return {
|
|
45147
|
+
gates_passed: false,
|
|
45148
|
+
lint: { ran: false, error: dirError, duration_ms: 0 },
|
|
45149
|
+
secretscan: { ran: false, error: dirError, duration_ms: 0 },
|
|
45150
|
+
sast_scan: { ran: false, error: dirError, duration_ms: 0 },
|
|
45151
|
+
quality_budget: { ran: false, error: dirError, duration_ms: 0 },
|
|
45152
|
+
total_duration_ms: 0
|
|
45153
|
+
};
|
|
45154
|
+
}
|
|
45155
|
+
let changedFiles = [];
|
|
45156
|
+
if (files && files.length > 0) {
|
|
45157
|
+
for (const file3 of files) {
|
|
45158
|
+
const fileError = validatePath(file3, directory);
|
|
45159
|
+
if (fileError) {
|
|
45160
|
+
warn(`pre_check_batch: Invalid file path: ${file3}`);
|
|
45161
|
+
continue;
|
|
45162
|
+
}
|
|
45163
|
+
changedFiles.push(path26.resolve(directory, file3));
|
|
45164
|
+
}
|
|
45165
|
+
} else {
|
|
45166
|
+
changedFiles = [];
|
|
45167
|
+
}
|
|
45168
|
+
if (changedFiles.length === 0 && !files) {
|
|
45169
|
+
warn("pre_check_batch: No files provided, skipping SAST and quality_budget");
|
|
45170
|
+
return {
|
|
45171
|
+
gates_passed: true,
|
|
45172
|
+
lint: { ran: false, error: "No files provided", duration_ms: 0 },
|
|
45173
|
+
secretscan: { ran: false, error: "No files provided", duration_ms: 0 },
|
|
45174
|
+
sast_scan: { ran: false, error: "No files provided", duration_ms: 0 },
|
|
45175
|
+
quality_budget: {
|
|
45176
|
+
ran: false,
|
|
45177
|
+
error: "No files provided",
|
|
45178
|
+
duration_ms: 0
|
|
45179
|
+
},
|
|
45180
|
+
total_duration_ms: 0
|
|
45181
|
+
};
|
|
45182
|
+
}
|
|
45183
|
+
if (changedFiles.length > MAX_FILES) {
|
|
45184
|
+
throw new Error(`Input exceeds maximum file count: ${changedFiles.length} > ${MAX_FILES}`);
|
|
45185
|
+
}
|
|
45186
|
+
const limit = pLimit(MAX_CONCURRENT);
|
|
45187
|
+
const [lintResult, secretscanResult, sastScanResult, qualityBudgetResult] = await Promise.all([
|
|
45188
|
+
limit(() => runLintWrapped(config3)),
|
|
45189
|
+
limit(() => runSecretscanWrapped(directory, config3)),
|
|
45190
|
+
limit(() => runSastScanWrapped(changedFiles, directory, sast_threshold, config3)),
|
|
45191
|
+
limit(() => runQualityBudgetWrapped(changedFiles, directory, config3))
|
|
45192
|
+
]);
|
|
45193
|
+
const totalDuration = lintResult.duration_ms + secretscanResult.duration_ms + sastScanResult.duration_ms + qualityBudgetResult.duration_ms;
|
|
45194
|
+
let gatesPassed = true;
|
|
45195
|
+
if (lintResult.ran && lintResult.result) {
|
|
45196
|
+
const lintRes = lintResult.result;
|
|
45197
|
+
if ("success" in lintRes && lintRes.success === false) {
|
|
45198
|
+
gatesPassed = false;
|
|
45199
|
+
warn("pre_check_batch: Lint has errors - GATE FAILED");
|
|
45200
|
+
}
|
|
45201
|
+
} else if (lintResult.error) {
|
|
45202
|
+
gatesPassed = false;
|
|
45203
|
+
warn(`pre_check_batch: Lint error - GATE FAILED: ${lintResult.error}`);
|
|
45204
|
+
}
|
|
45205
|
+
if (secretscanResult.ran && secretscanResult.result) {
|
|
45206
|
+
const scanResult = secretscanResult.result;
|
|
45207
|
+
if ("findings" in scanResult && scanResult.findings.length > 0) {
|
|
45208
|
+
gatesPassed = false;
|
|
45209
|
+
warn("pre_check_batch: Secretscan found secrets - GATE FAILED");
|
|
45210
|
+
}
|
|
45211
|
+
} else if (secretscanResult.error) {
|
|
45212
|
+
gatesPassed = false;
|
|
45213
|
+
warn(`pre_check_batch: Secretscan error - GATE FAILED: ${secretscanResult.error}`);
|
|
45214
|
+
}
|
|
45215
|
+
if (sastScanResult.ran && sastScanResult.result) {
|
|
45216
|
+
if (sastScanResult.result.verdict === "fail") {
|
|
45217
|
+
gatesPassed = false;
|
|
45218
|
+
warn("pre_check_batch: SAST scan found vulnerabilities - GATE FAILED");
|
|
45219
|
+
}
|
|
45220
|
+
} else if (sastScanResult.error) {
|
|
45221
|
+
gatesPassed = false;
|
|
45222
|
+
warn(`pre_check_batch: SAST scan error - GATE FAILED: ${sastScanResult.error}`);
|
|
45223
|
+
}
|
|
45224
|
+
const result = {
|
|
45225
|
+
gates_passed: gatesPassed,
|
|
45226
|
+
lint: lintResult,
|
|
45227
|
+
secretscan: secretscanResult,
|
|
45228
|
+
sast_scan: sastScanResult,
|
|
45229
|
+
quality_budget: qualityBudgetResult,
|
|
45230
|
+
total_duration_ms: Math.round(totalDuration)
|
|
45231
|
+
};
|
|
45232
|
+
const outputSize = JSON.stringify(result).length;
|
|
45233
|
+
if (outputSize > MAX_COMBINED_BYTES) {
|
|
45234
|
+
warn(`pre_check_batch: Large output (${outputSize} bytes)`);
|
|
45235
|
+
}
|
|
45236
|
+
return result;
|
|
45237
|
+
}
|
|
45238
|
+
var pre_check_batch = tool({
|
|
45239
|
+
description: "Run multiple verification tools in parallel: lint, secretscan, SAST scan, and quality budget. Returns unified result with gates_passed status. Security tools (secretscan, sast_scan) are HARD GATES - failures block merging.",
|
|
45240
|
+
args: {
|
|
45241
|
+
files: tool.schema.array(tool.schema.string()).optional().describe("Specific files to check (optional, scans directory if not provided)"),
|
|
45242
|
+
directory: tool.schema.string().describe('Directory to run checks in (e.g., "." or "./src")'),
|
|
45243
|
+
sast_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity for SAST findings to cause failure (default: medium)")
|
|
45244
|
+
},
|
|
45245
|
+
async execute(args2) {
|
|
45246
|
+
if (!args2 || typeof args2 !== "object") {
|
|
45247
|
+
const errorResult = {
|
|
45248
|
+
gates_passed: false,
|
|
45249
|
+
lint: { ran: false, error: "Invalid arguments", duration_ms: 0 },
|
|
45250
|
+
secretscan: { ran: false, error: "Invalid arguments", duration_ms: 0 },
|
|
45251
|
+
sast_scan: { ran: false, error: "Invalid arguments", duration_ms: 0 },
|
|
45252
|
+
quality_budget: {
|
|
45253
|
+
ran: false,
|
|
45254
|
+
error: "Invalid arguments",
|
|
45255
|
+
duration_ms: 0
|
|
45256
|
+
},
|
|
45257
|
+
total_duration_ms: 0
|
|
45258
|
+
};
|
|
45259
|
+
return JSON.stringify(errorResult, null, 2);
|
|
45260
|
+
}
|
|
45261
|
+
const typedArgs = args2;
|
|
45262
|
+
if (!typedArgs.directory) {
|
|
45263
|
+
const errorResult = {
|
|
45264
|
+
gates_passed: false,
|
|
45265
|
+
lint: { ran: false, error: "directory is required", duration_ms: 0 },
|
|
45266
|
+
secretscan: {
|
|
45267
|
+
ran: false,
|
|
45268
|
+
error: "directory is required",
|
|
45269
|
+
duration_ms: 0
|
|
45270
|
+
},
|
|
45271
|
+
sast_scan: {
|
|
45272
|
+
ran: false,
|
|
45273
|
+
error: "directory is required",
|
|
45274
|
+
duration_ms: 0
|
|
45275
|
+
},
|
|
45276
|
+
quality_budget: {
|
|
45277
|
+
ran: false,
|
|
45278
|
+
error: "directory is required",
|
|
45279
|
+
duration_ms: 0
|
|
45280
|
+
},
|
|
45281
|
+
total_duration_ms: 0
|
|
45282
|
+
};
|
|
45283
|
+
return JSON.stringify(errorResult, null, 2);
|
|
45284
|
+
}
|
|
45285
|
+
const dirError = validateDirectory(typedArgs.directory);
|
|
45286
|
+
if (dirError) {
|
|
45287
|
+
const errorResult = {
|
|
45288
|
+
gates_passed: false,
|
|
45289
|
+
lint: { ran: false, error: dirError, duration_ms: 0 },
|
|
45290
|
+
secretscan: { ran: false, error: dirError, duration_ms: 0 },
|
|
45291
|
+
sast_scan: { ran: false, error: dirError, duration_ms: 0 },
|
|
45292
|
+
quality_budget: { ran: false, error: dirError, duration_ms: 0 },
|
|
45293
|
+
total_duration_ms: 0
|
|
45294
|
+
};
|
|
45295
|
+
return JSON.stringify(errorResult, null, 2);
|
|
45296
|
+
}
|
|
45297
|
+
try {
|
|
45298
|
+
const result = await runPreCheckBatch({
|
|
45299
|
+
files: typedArgs.files,
|
|
45300
|
+
directory: typedArgs.directory,
|
|
45301
|
+
sast_threshold: typedArgs.sast_threshold,
|
|
45302
|
+
config: typedArgs.config
|
|
45303
|
+
});
|
|
45304
|
+
return JSON.stringify(result, null, 2);
|
|
45305
|
+
} catch (error93) {
|
|
45306
|
+
const errorMessage = error93 instanceof Error ? error93.message : "Unknown error";
|
|
45307
|
+
const errorResult = {
|
|
45308
|
+
gates_passed: false,
|
|
45309
|
+
lint: { ran: false, error: errorMessage, duration_ms: 0 },
|
|
45310
|
+
secretscan: { ran: false, error: errorMessage, duration_ms: 0 },
|
|
45311
|
+
sast_scan: { ran: false, error: errorMessage, duration_ms: 0 },
|
|
45312
|
+
quality_budget: { ran: false, error: errorMessage, duration_ms: 0 },
|
|
45313
|
+
total_duration_ms: 0
|
|
45314
|
+
};
|
|
45315
|
+
return JSON.stringify(errorResult, null, 2);
|
|
45316
|
+
}
|
|
45317
|
+
}
|
|
45318
|
+
});
|
|
45319
|
+
// src/tools/retrieve-summary.ts
|
|
45320
|
+
init_dist();
|
|
45321
|
+
var RETRIEVE_MAX_BYTES = 10 * 1024 * 1024;
|
|
45322
|
+
var retrieve_summary = tool({
|
|
45323
|
+
description: "Retrieve the full content of a stored tool output summary by its ID (e.g. S1, S2). Use this when a prior tool output was summarized and you need the full content.",
|
|
45324
|
+
args: {
|
|
45325
|
+
id: tool.schema.string().describe("The summary ID to retrieve (e.g. S1, S2, S99). Must match pattern S followed by digits.")
|
|
45326
|
+
},
|
|
45327
|
+
async execute(args2, context) {
|
|
45328
|
+
const directory = context.directory;
|
|
45329
|
+
let sanitizedId;
|
|
45330
|
+
try {
|
|
45331
|
+
sanitizedId = sanitizeSummaryId(args2.id);
|
|
45332
|
+
} catch {
|
|
45333
|
+
return "Error: invalid summary ID format. Expected format: S followed by digits (e.g. S1, S2, S99).";
|
|
45334
|
+
}
|
|
45335
|
+
let fullOutput;
|
|
45336
|
+
try {
|
|
45337
|
+
fullOutput = await loadFullOutput(directory, sanitizedId);
|
|
45338
|
+
} catch {
|
|
45339
|
+
return "Error: failed to retrieve summary.";
|
|
45340
|
+
}
|
|
45341
|
+
if (fullOutput === null) {
|
|
45342
|
+
return `Summary \`${sanitizedId}\` not found. Use a valid summary ID (e.g. S1, S2).`;
|
|
45343
|
+
}
|
|
45344
|
+
if (fullOutput.length > RETRIEVE_MAX_BYTES) {
|
|
45345
|
+
return `Error: summary content exceeds maximum size limit (10 MB).`;
|
|
45346
|
+
}
|
|
45347
|
+
return fullOutput;
|
|
45348
|
+
}
|
|
45349
|
+
});
|
|
43709
45350
|
// src/tools/sbom-generate.ts
|
|
43710
45351
|
init_dist();
|
|
43711
45352
|
init_manager();
|
|
43712
|
-
import * as
|
|
43713
|
-
import * as
|
|
45353
|
+
import * as fs21 from "fs";
|
|
45354
|
+
import * as path27 from "path";
|
|
43714
45355
|
|
|
43715
45356
|
// src/sbom/detectors/dart.ts
|
|
43716
45357
|
function parsePubspecLock(content) {
|
|
@@ -44555,9 +46196,9 @@ function findManifestFiles(rootDir) {
|
|
|
44555
46196
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
44556
46197
|
function searchDir(dir) {
|
|
44557
46198
|
try {
|
|
44558
|
-
const entries =
|
|
46199
|
+
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
44559
46200
|
for (const entry of entries) {
|
|
44560
|
-
const fullPath =
|
|
46201
|
+
const fullPath = path27.join(dir, entry.name);
|
|
44561
46202
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
|
|
44562
46203
|
continue;
|
|
44563
46204
|
}
|
|
@@ -44567,7 +46208,7 @@ function findManifestFiles(rootDir) {
|
|
|
44567
46208
|
for (const pattern of patterns) {
|
|
44568
46209
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
44569
46210
|
if (new RegExp(regex, "i").test(entry.name)) {
|
|
44570
|
-
manifestFiles.push(
|
|
46211
|
+
manifestFiles.push(path27.relative(cwd, fullPath));
|
|
44571
46212
|
break;
|
|
44572
46213
|
}
|
|
44573
46214
|
}
|
|
@@ -44584,14 +46225,14 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
44584
46225
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
44585
46226
|
for (const dir of directories) {
|
|
44586
46227
|
try {
|
|
44587
|
-
const entries =
|
|
46228
|
+
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
44588
46229
|
for (const entry of entries) {
|
|
44589
|
-
const fullPath =
|
|
46230
|
+
const fullPath = path27.join(dir, entry.name);
|
|
44590
46231
|
if (entry.isFile()) {
|
|
44591
46232
|
for (const pattern of patterns) {
|
|
44592
46233
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
44593
46234
|
if (new RegExp(regex, "i").test(entry.name)) {
|
|
44594
|
-
found.push(
|
|
46235
|
+
found.push(path27.relative(workingDir, fullPath));
|
|
44595
46236
|
break;
|
|
44596
46237
|
}
|
|
44597
46238
|
}
|
|
@@ -44604,11 +46245,11 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
44604
46245
|
function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
44605
46246
|
const dirs = new Set;
|
|
44606
46247
|
for (const file3 of changedFiles) {
|
|
44607
|
-
let currentDir =
|
|
46248
|
+
let currentDir = path27.dirname(file3);
|
|
44608
46249
|
while (true) {
|
|
44609
|
-
if (currentDir && currentDir !== "." && currentDir !==
|
|
44610
|
-
dirs.add(
|
|
44611
|
-
const parent =
|
|
46250
|
+
if (currentDir && currentDir !== "." && currentDir !== path27.sep) {
|
|
46251
|
+
dirs.add(path27.join(workingDir, currentDir));
|
|
46252
|
+
const parent = path27.dirname(currentDir);
|
|
44612
46253
|
if (parent === currentDir)
|
|
44613
46254
|
break;
|
|
44614
46255
|
currentDir = parent;
|
|
@@ -44622,7 +46263,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
|
44622
46263
|
}
|
|
44623
46264
|
function ensureOutputDir(outputDir) {
|
|
44624
46265
|
try {
|
|
44625
|
-
|
|
46266
|
+
fs21.mkdirSync(outputDir, { recursive: true });
|
|
44626
46267
|
} catch (error93) {
|
|
44627
46268
|
if (!error93 || error93.code !== "EEXIST") {
|
|
44628
46269
|
throw error93;
|
|
@@ -44715,11 +46356,11 @@ var sbom_generate = tool({
|
|
|
44715
46356
|
const processedFiles = [];
|
|
44716
46357
|
for (const manifestFile of manifestFiles) {
|
|
44717
46358
|
try {
|
|
44718
|
-
const fullPath =
|
|
44719
|
-
if (!
|
|
46359
|
+
const fullPath = path27.isAbsolute(manifestFile) ? manifestFile : path27.join(workingDir, manifestFile);
|
|
46360
|
+
if (!fs21.existsSync(fullPath)) {
|
|
44720
46361
|
continue;
|
|
44721
46362
|
}
|
|
44722
|
-
const content =
|
|
46363
|
+
const content = fs21.readFileSync(fullPath, "utf-8");
|
|
44723
46364
|
const components = detectComponents(manifestFile, content);
|
|
44724
46365
|
processedFiles.push(manifestFile);
|
|
44725
46366
|
if (components.length > 0) {
|
|
@@ -44732,8 +46373,8 @@ var sbom_generate = tool({
|
|
|
44732
46373
|
const bom = generateCycloneDX(allComponents);
|
|
44733
46374
|
const bomJson = serializeCycloneDX(bom);
|
|
44734
46375
|
const filename = generateSbomFilename();
|
|
44735
|
-
const outputPath =
|
|
44736
|
-
|
|
46376
|
+
const outputPath = path27.join(outputDir, filename);
|
|
46377
|
+
fs21.writeFileSync(outputPath, bomJson, "utf-8");
|
|
44737
46378
|
const verdict = processedFiles.length > 0 ? "pass" : "pass";
|
|
44738
46379
|
try {
|
|
44739
46380
|
const timestamp = new Date().toISOString();
|
|
@@ -44774,8 +46415,8 @@ var sbom_generate = tool({
|
|
|
44774
46415
|
});
|
|
44775
46416
|
// src/tools/schema-drift.ts
|
|
44776
46417
|
init_dist();
|
|
44777
|
-
import * as
|
|
44778
|
-
import * as
|
|
46418
|
+
import * as fs22 from "fs";
|
|
46419
|
+
import * as path28 from "path";
|
|
44779
46420
|
var SPEC_CANDIDATES = [
|
|
44780
46421
|
"openapi.json",
|
|
44781
46422
|
"openapi.yaml",
|
|
@@ -44807,28 +46448,28 @@ function normalizePath(p) {
|
|
|
44807
46448
|
}
|
|
44808
46449
|
function discoverSpecFile(cwd, specFileArg) {
|
|
44809
46450
|
if (specFileArg) {
|
|
44810
|
-
const resolvedPath =
|
|
44811
|
-
const normalizedCwd = cwd.endsWith(
|
|
46451
|
+
const resolvedPath = path28.resolve(cwd, specFileArg);
|
|
46452
|
+
const normalizedCwd = cwd.endsWith(path28.sep) ? cwd : cwd + path28.sep;
|
|
44812
46453
|
if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
|
|
44813
46454
|
throw new Error("Invalid spec_file: path traversal detected");
|
|
44814
46455
|
}
|
|
44815
|
-
const ext =
|
|
46456
|
+
const ext = path28.extname(resolvedPath).toLowerCase();
|
|
44816
46457
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
44817
46458
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
44818
46459
|
}
|
|
44819
|
-
const stats =
|
|
46460
|
+
const stats = fs22.statSync(resolvedPath);
|
|
44820
46461
|
if (stats.size > MAX_SPEC_SIZE) {
|
|
44821
46462
|
throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
|
|
44822
46463
|
}
|
|
44823
|
-
if (!
|
|
46464
|
+
if (!fs22.existsSync(resolvedPath)) {
|
|
44824
46465
|
throw new Error(`Spec file not found: ${resolvedPath}`);
|
|
44825
46466
|
}
|
|
44826
46467
|
return resolvedPath;
|
|
44827
46468
|
}
|
|
44828
46469
|
for (const candidate of SPEC_CANDIDATES) {
|
|
44829
|
-
const candidatePath =
|
|
44830
|
-
if (
|
|
44831
|
-
const stats =
|
|
46470
|
+
const candidatePath = path28.resolve(cwd, candidate);
|
|
46471
|
+
if (fs22.existsSync(candidatePath)) {
|
|
46472
|
+
const stats = fs22.statSync(candidatePath);
|
|
44832
46473
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
44833
46474
|
return candidatePath;
|
|
44834
46475
|
}
|
|
@@ -44837,8 +46478,8 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
44837
46478
|
return null;
|
|
44838
46479
|
}
|
|
44839
46480
|
function parseSpec(specFile) {
|
|
44840
|
-
const content =
|
|
44841
|
-
const ext =
|
|
46481
|
+
const content = fs22.readFileSync(specFile, "utf-8");
|
|
46482
|
+
const ext = path28.extname(specFile).toLowerCase();
|
|
44842
46483
|
if (ext === ".json") {
|
|
44843
46484
|
return parseJsonSpec(content);
|
|
44844
46485
|
}
|
|
@@ -44904,12 +46545,12 @@ function extractRoutes(cwd) {
|
|
|
44904
46545
|
function walkDir(dir) {
|
|
44905
46546
|
let entries;
|
|
44906
46547
|
try {
|
|
44907
|
-
entries =
|
|
46548
|
+
entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
44908
46549
|
} catch {
|
|
44909
46550
|
return;
|
|
44910
46551
|
}
|
|
44911
46552
|
for (const entry of entries) {
|
|
44912
|
-
const fullPath =
|
|
46553
|
+
const fullPath = path28.join(dir, entry.name);
|
|
44913
46554
|
if (entry.isSymbolicLink()) {
|
|
44914
46555
|
continue;
|
|
44915
46556
|
}
|
|
@@ -44919,7 +46560,7 @@ function extractRoutes(cwd) {
|
|
|
44919
46560
|
}
|
|
44920
46561
|
walkDir(fullPath);
|
|
44921
46562
|
} else if (entry.isFile()) {
|
|
44922
|
-
const ext =
|
|
46563
|
+
const ext = path28.extname(entry.name).toLowerCase();
|
|
44923
46564
|
const baseName = entry.name.toLowerCase();
|
|
44924
46565
|
if (![".ts", ".js", ".mjs"].includes(ext)) {
|
|
44925
46566
|
continue;
|
|
@@ -44937,7 +46578,7 @@ function extractRoutes(cwd) {
|
|
|
44937
46578
|
}
|
|
44938
46579
|
function extractRoutesFromFile(filePath) {
|
|
44939
46580
|
const routes = [];
|
|
44940
|
-
const content =
|
|
46581
|
+
const content = fs22.readFileSync(filePath, "utf-8");
|
|
44941
46582
|
const lines = content.split(/\r?\n/);
|
|
44942
46583
|
const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
44943
46584
|
const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
|
|
@@ -45084,8 +46725,8 @@ init_secretscan();
|
|
|
45084
46725
|
|
|
45085
46726
|
// src/tools/symbols.ts
|
|
45086
46727
|
init_tool();
|
|
45087
|
-
import * as
|
|
45088
|
-
import * as
|
|
46728
|
+
import * as fs23 from "fs";
|
|
46729
|
+
import * as path29 from "path";
|
|
45089
46730
|
var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
|
|
45090
46731
|
var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
45091
46732
|
function containsControlCharacters(str) {
|
|
@@ -45114,11 +46755,11 @@ function containsWindowsAttacks(str) {
|
|
|
45114
46755
|
}
|
|
45115
46756
|
function isPathInWorkspace(filePath, workspace) {
|
|
45116
46757
|
try {
|
|
45117
|
-
const resolvedPath =
|
|
45118
|
-
const realWorkspace =
|
|
45119
|
-
const realResolvedPath =
|
|
45120
|
-
const relativePath =
|
|
45121
|
-
if (relativePath.startsWith("..") ||
|
|
46758
|
+
const resolvedPath = path29.resolve(workspace, filePath);
|
|
46759
|
+
const realWorkspace = fs23.realpathSync(workspace);
|
|
46760
|
+
const realResolvedPath = fs23.realpathSync(resolvedPath);
|
|
46761
|
+
const relativePath = path29.relative(realWorkspace, realResolvedPath);
|
|
46762
|
+
if (relativePath.startsWith("..") || path29.isAbsolute(relativePath)) {
|
|
45122
46763
|
return false;
|
|
45123
46764
|
}
|
|
45124
46765
|
return true;
|
|
@@ -45130,17 +46771,17 @@ function validatePathForRead(filePath, workspace) {
|
|
|
45130
46771
|
return isPathInWorkspace(filePath, workspace);
|
|
45131
46772
|
}
|
|
45132
46773
|
function extractTSSymbols(filePath, cwd) {
|
|
45133
|
-
const fullPath =
|
|
46774
|
+
const fullPath = path29.join(cwd, filePath);
|
|
45134
46775
|
if (!validatePathForRead(fullPath, cwd)) {
|
|
45135
46776
|
return [];
|
|
45136
46777
|
}
|
|
45137
46778
|
let content;
|
|
45138
46779
|
try {
|
|
45139
|
-
const stats =
|
|
46780
|
+
const stats = fs23.statSync(fullPath);
|
|
45140
46781
|
if (stats.size > MAX_FILE_SIZE_BYTES7) {
|
|
45141
46782
|
throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
|
|
45142
46783
|
}
|
|
45143
|
-
content =
|
|
46784
|
+
content = fs23.readFileSync(fullPath, "utf-8");
|
|
45144
46785
|
} catch {
|
|
45145
46786
|
return [];
|
|
45146
46787
|
}
|
|
@@ -45282,17 +46923,17 @@ function extractTSSymbols(filePath, cwd) {
|
|
|
45282
46923
|
});
|
|
45283
46924
|
}
|
|
45284
46925
|
function extractPythonSymbols(filePath, cwd) {
|
|
45285
|
-
const fullPath =
|
|
46926
|
+
const fullPath = path29.join(cwd, filePath);
|
|
45286
46927
|
if (!validatePathForRead(fullPath, cwd)) {
|
|
45287
46928
|
return [];
|
|
45288
46929
|
}
|
|
45289
46930
|
let content;
|
|
45290
46931
|
try {
|
|
45291
|
-
const stats =
|
|
46932
|
+
const stats = fs23.statSync(fullPath);
|
|
45292
46933
|
if (stats.size > MAX_FILE_SIZE_BYTES7) {
|
|
45293
46934
|
throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
|
|
45294
46935
|
}
|
|
45295
|
-
content =
|
|
46936
|
+
content = fs23.readFileSync(fullPath, "utf-8");
|
|
45296
46937
|
} catch {
|
|
45297
46938
|
return [];
|
|
45298
46939
|
}
|
|
@@ -45364,7 +47005,7 @@ var symbols = tool({
|
|
|
45364
47005
|
}, null, 2);
|
|
45365
47006
|
}
|
|
45366
47007
|
const cwd = process.cwd();
|
|
45367
|
-
const ext =
|
|
47008
|
+
const ext = path29.extname(file3);
|
|
45368
47009
|
if (containsControlCharacters(file3)) {
|
|
45369
47010
|
return JSON.stringify({
|
|
45370
47011
|
file: file3,
|
|
@@ -45432,8 +47073,8 @@ init_test_runner();
|
|
|
45432
47073
|
|
|
45433
47074
|
// src/tools/todo-extract.ts
|
|
45434
47075
|
init_dist();
|
|
45435
|
-
import * as
|
|
45436
|
-
import * as
|
|
47076
|
+
import * as fs24 from "fs";
|
|
47077
|
+
import * as path30 from "path";
|
|
45437
47078
|
var MAX_TEXT_LENGTH = 200;
|
|
45438
47079
|
var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
|
|
45439
47080
|
var SUPPORTED_EXTENSIONS2 = new Set([
|
|
@@ -45504,9 +47145,9 @@ function validatePathsInput(paths, cwd) {
|
|
|
45504
47145
|
return { error: "paths contains path traversal", resolvedPath: null };
|
|
45505
47146
|
}
|
|
45506
47147
|
try {
|
|
45507
|
-
const resolvedPath =
|
|
45508
|
-
const normalizedCwd =
|
|
45509
|
-
const normalizedResolved =
|
|
47148
|
+
const resolvedPath = path30.resolve(paths);
|
|
47149
|
+
const normalizedCwd = path30.resolve(cwd);
|
|
47150
|
+
const normalizedResolved = path30.resolve(resolvedPath);
|
|
45510
47151
|
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
45511
47152
|
return {
|
|
45512
47153
|
error: "paths must be within the current working directory",
|
|
@@ -45522,13 +47163,13 @@ function validatePathsInput(paths, cwd) {
|
|
|
45522
47163
|
}
|
|
45523
47164
|
}
|
|
45524
47165
|
function isSupportedExtension(filePath) {
|
|
45525
|
-
const ext =
|
|
47166
|
+
const ext = path30.extname(filePath).toLowerCase();
|
|
45526
47167
|
return SUPPORTED_EXTENSIONS2.has(ext);
|
|
45527
47168
|
}
|
|
45528
47169
|
function findSourceFiles3(dir, files = []) {
|
|
45529
47170
|
let entries;
|
|
45530
47171
|
try {
|
|
45531
|
-
entries =
|
|
47172
|
+
entries = fs24.readdirSync(dir);
|
|
45532
47173
|
} catch {
|
|
45533
47174
|
return files;
|
|
45534
47175
|
}
|
|
@@ -45537,10 +47178,10 @@ function findSourceFiles3(dir, files = []) {
|
|
|
45537
47178
|
if (SKIP_DIRECTORIES3.has(entry)) {
|
|
45538
47179
|
continue;
|
|
45539
47180
|
}
|
|
45540
|
-
const fullPath =
|
|
47181
|
+
const fullPath = path30.join(dir, entry);
|
|
45541
47182
|
let stat;
|
|
45542
47183
|
try {
|
|
45543
|
-
stat =
|
|
47184
|
+
stat = fs24.statSync(fullPath);
|
|
45544
47185
|
} catch {
|
|
45545
47186
|
continue;
|
|
45546
47187
|
}
|
|
@@ -45633,7 +47274,7 @@ var todo_extract = tool({
|
|
|
45633
47274
|
return JSON.stringify(errorResult, null, 2);
|
|
45634
47275
|
}
|
|
45635
47276
|
const scanPath = resolvedPath;
|
|
45636
|
-
if (!
|
|
47277
|
+
if (!fs24.existsSync(scanPath)) {
|
|
45637
47278
|
const errorResult = {
|
|
45638
47279
|
error: `path not found: ${pathsInput}`,
|
|
45639
47280
|
total: 0,
|
|
@@ -45643,13 +47284,13 @@ var todo_extract = tool({
|
|
|
45643
47284
|
return JSON.stringify(errorResult, null, 2);
|
|
45644
47285
|
}
|
|
45645
47286
|
const filesToScan = [];
|
|
45646
|
-
const stat =
|
|
47287
|
+
const stat = fs24.statSync(scanPath);
|
|
45647
47288
|
if (stat.isFile()) {
|
|
45648
47289
|
if (isSupportedExtension(scanPath)) {
|
|
45649
47290
|
filesToScan.push(scanPath);
|
|
45650
47291
|
} else {
|
|
45651
47292
|
const errorResult = {
|
|
45652
|
-
error: `unsupported file extension: ${
|
|
47293
|
+
error: `unsupported file extension: ${path30.extname(scanPath)}`,
|
|
45653
47294
|
total: 0,
|
|
45654
47295
|
byPriority: { high: 0, medium: 0, low: 0 },
|
|
45655
47296
|
entries: []
|
|
@@ -45662,11 +47303,11 @@ var todo_extract = tool({
|
|
|
45662
47303
|
const allEntries = [];
|
|
45663
47304
|
for (const filePath of filesToScan) {
|
|
45664
47305
|
try {
|
|
45665
|
-
const fileStat =
|
|
47306
|
+
const fileStat = fs24.statSync(filePath);
|
|
45666
47307
|
if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
|
|
45667
47308
|
continue;
|
|
45668
47309
|
}
|
|
45669
|
-
const content =
|
|
47310
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
45670
47311
|
const entries = parseTodoComments(content, filePath, tagsSet);
|
|
45671
47312
|
allEntries.push(...entries);
|
|
45672
47313
|
} catch {}
|
|
@@ -45737,7 +47378,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
45737
47378
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
45738
47379
|
preflightTriggerManager = new PTM(automationConfig);
|
|
45739
47380
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
45740
|
-
const swarmDir =
|
|
47381
|
+
const swarmDir = path31.resolve(ctx.directory, ".swarm");
|
|
45741
47382
|
statusArtifact = new ASA(swarmDir);
|
|
45742
47383
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
45743
47384
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|
|
@@ -45840,6 +47481,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
45840
47481
|
lint,
|
|
45841
47482
|
diff,
|
|
45842
47483
|
pkg_audit,
|
|
47484
|
+
pre_check_batch,
|
|
45843
47485
|
retrieve_summary,
|
|
45844
47486
|
schema_drift,
|
|
45845
47487
|
secretscan,
|