opencode-swarm 6.9.0 → 6.10.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 +72 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/schema.d.ts +7 -0
- package/dist/index.js +1790 -247
- 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(),
|
|
@@ -31961,12 +31969,21 @@ You THINK. Subagents DO. You have the largest context window and strongest reaso
|
|
|
31961
31969
|
- If NEEDS_REVISION: Revise plan and re-submit to critic (max 2 cycles)
|
|
31962
31970
|
- If REJECTED after 2 cycles: Escalate to user with explanation
|
|
31963
31971
|
- ONLY AFTER critic approval: Proceed to implementation (Phase 3+)
|
|
31964
|
-
7. **MANDATORY QA GATE (Execute AFTER every coder task)** \u2014 sequence: coder \u2192 diff \u2192 syntax_check \u2192 placeholder_scan \u2192
|
|
31972
|
+
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.
|
|
31965
31973
|
- 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
31974
|
- Run \`syntax_check\` tool. SYNTACTIC ERRORS \u2192 return to coder. NO ERRORS \u2192 proceed to placeholder_scan.
|
|
31967
31975
|
- Run \`placeholder_scan\` tool. PLACEHOLDER FINDINGS \u2192 return to coder. NO FINDINGS \u2192 proceed to imports check.
|
|
31968
|
-
- Run \`
|
|
31969
|
-
- Run \`
|
|
31976
|
+
- Run \`imports\` tool. Record results for dependency audit. Proceed to lint fix.
|
|
31977
|
+
- Run \`lint\` tool (mode: fix) \u2192 allow auto-corrections. LINT FIX FAILS \u2192 return to coder. SUCCESS \u2192 proceed to build_check.
|
|
31978
|
+
- Run \`build_check\` tool. BUILD FAILS \u2192 return to coder. SUCCESS \u2192 proceed to pre_check_batch.
|
|
31979
|
+
- Run \`pre_check_batch\` tool \u2192 runs four verification tools in parallel (max 4 concurrent):
|
|
31980
|
+
- lint:check (code quality verification)
|
|
31981
|
+
- secretscan (secret detection)
|
|
31982
|
+
- sast_scan (static security analysis)
|
|
31983
|
+
- quality_budget (maintainability metrics)
|
|
31984
|
+
\u2192 Returns { gates_passed, lint, secretscan, sast_scan, quality_budget, total_duration_ms }
|
|
31985
|
+
\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.
|
|
31986
|
+
\u2192 If gates_passed === true: proceed to @reviewer.
|
|
31970
31987
|
- Delegate {{AGENT_PREFIX}}reviewer with CHECK dimensions. REJECTED \u2192 return to coder (max {{QA_RETRY_LIMIT}} attempts). APPROVED \u2192 continue.
|
|
31971
31988
|
- 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
31989
|
- Delegate {{AGENT_PREFIX}}test_engineer for verification tests. FAIL \u2192 return to coder.
|
|
@@ -31996,7 +32013,7 @@ SECURITY_KEYWORDS: password, secret, token, credential, auth, login, encryption,
|
|
|
31996
32013
|
|
|
31997
32014
|
SMEs advise only. Reviewer and critic review only. None of them write code.
|
|
31998
32015
|
|
|
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)
|
|
32016
|
+
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
32017
|
|
|
32001
32018
|
## DELEGATION FORMAT
|
|
32002
32019
|
|
|
@@ -32150,16 +32167,21 @@ For each task (respecting dependencies):
|
|
|
32150
32167
|
5e. Run \`placeholder_scan\` tool. PLACEHOLDER FINDINGS \u2192 return to coder. NO FINDINGS \u2192 proceed to imports.
|
|
32151
32168
|
5f. Run \`imports\` tool for dependency audit. ISSUES \u2192 return to coder.
|
|
32152
32169
|
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
|
-
5h. Run \`
|
|
32154
|
-
5i. Run \`
|
|
32155
|
-
|
|
32156
|
-
|
|
32157
|
-
|
|
32158
|
-
|
|
32159
|
-
|
|
32160
|
-
|
|
32161
|
-
|
|
32162
|
-
|
|
32170
|
+
5h. Run \`build_check\` tool. BUILD FAILS \u2192 return to coder. SUCCESS \u2192 proceed to pre_check_batch.
|
|
32171
|
+
5i. Run \`pre_check_batch\` tool \u2192 runs four verification tools in parallel (max 4 concurrent):
|
|
32172
|
+
- lint:check (code quality verification)
|
|
32173
|
+
- secretscan (secret detection)
|
|
32174
|
+
- sast_scan (static security analysis)
|
|
32175
|
+
- quality_budget (maintainability metrics)
|
|
32176
|
+
\u2192 Returns { gates_passed, lint, secretscan, sast_scan, quality_budget, total_duration_ms }
|
|
32177
|
+
\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.
|
|
32178
|
+
\u2192 If gates_passed === true: proceed to @reviewer.
|
|
32179
|
+
5j. {{AGENT_PREFIX}}reviewer - General review. REJECTED (< {{QA_RETRY_LIMIT}}) \u2192 coder retry. REJECTED ({{QA_RETRY_LIMIT}}) \u2192 escalate.
|
|
32180
|
+
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.
|
|
32181
|
+
5l. {{AGENT_PREFIX}}test_engineer - Verification tests. FAIL \u2192 coder retry from 5g.
|
|
32182
|
+
5m. {{AGENT_PREFIX}}test_engineer - Adversarial tests. FAIL \u2192 coder retry from 5g.
|
|
32183
|
+
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.
|
|
32184
|
+
5o. Update plan.md [x], proceed to next task.
|
|
32163
32185
|
|
|
32164
32186
|
### Phase 6: Phase Complete
|
|
32165
32187
|
1. {{AGENT_PREFIX}}explorer - Rescan
|
|
@@ -35138,7 +35160,7 @@ async function handleResetCommand(directory, args2) {
|
|
|
35138
35160
|
init_utils2();
|
|
35139
35161
|
init_utils();
|
|
35140
35162
|
import { mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync6 } from "fs";
|
|
35141
|
-
import * as
|
|
35163
|
+
import * as path14 from "path";
|
|
35142
35164
|
var SUMMARY_ID_REGEX = /^S\d+$/;
|
|
35143
35165
|
function sanitizeSummaryId(id) {
|
|
35144
35166
|
if (!id || id.length === 0) {
|
|
@@ -35166,9 +35188,9 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
35166
35188
|
if (outputBytes > maxStoredBytes) {
|
|
35167
35189
|
throw new Error(`Summary fullOutput size (${outputBytes} bytes) exceeds maximum (${maxStoredBytes} bytes)`);
|
|
35168
35190
|
}
|
|
35169
|
-
const relativePath =
|
|
35191
|
+
const relativePath = path14.join("summaries", `${sanitizedId}.json`);
|
|
35170
35192
|
const summaryPath = validateSwarmPath(directory, relativePath);
|
|
35171
|
-
const summaryDir =
|
|
35193
|
+
const summaryDir = path14.dirname(summaryPath);
|
|
35172
35194
|
const entry = {
|
|
35173
35195
|
id: sanitizedId,
|
|
35174
35196
|
summaryText,
|
|
@@ -35178,7 +35200,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
35178
35200
|
};
|
|
35179
35201
|
const entryJson = JSON.stringify(entry);
|
|
35180
35202
|
mkdirSync5(summaryDir, { recursive: true });
|
|
35181
|
-
const tempPath =
|
|
35203
|
+
const tempPath = path14.join(summaryDir, `${sanitizedId}.json.tmp.${Date.now()}.${process.pid}`);
|
|
35182
35204
|
try {
|
|
35183
35205
|
await Bun.write(tempPath, entryJson);
|
|
35184
35206
|
renameSync2(tempPath, summaryPath);
|
|
@@ -35191,7 +35213,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
|
|
|
35191
35213
|
}
|
|
35192
35214
|
async function loadFullOutput(directory, id) {
|
|
35193
35215
|
const sanitizedId = sanitizeSummaryId(id);
|
|
35194
|
-
const relativePath =
|
|
35216
|
+
const relativePath = path14.join("summaries", `${sanitizedId}.json`);
|
|
35195
35217
|
validateSwarmPath(directory, relativePath);
|
|
35196
35218
|
const content = await readSwarmFileAsync(directory, relativePath);
|
|
35197
35219
|
if (content === null) {
|
|
@@ -35695,8 +35717,8 @@ async function doFlush(directory) {
|
|
|
35695
35717
|
const activitySection = renderActivitySection();
|
|
35696
35718
|
const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
|
|
35697
35719
|
const flushedCount = swarmState.pendingEvents;
|
|
35698
|
-
const
|
|
35699
|
-
await Bun.write(
|
|
35720
|
+
const path15 = `${directory}/.swarm/context.md`;
|
|
35721
|
+
await Bun.write(path15, updated);
|
|
35700
35722
|
swarmState.pendingEvents = Math.max(0, swarmState.pendingEvents - flushedCount);
|
|
35701
35723
|
} catch (error93) {
|
|
35702
35724
|
warn("Agent activity flush failed:", error93);
|
|
@@ -36252,14 +36274,14 @@ ${originalText}`;
|
|
|
36252
36274
|
}
|
|
36253
36275
|
// src/hooks/system-enhancer.ts
|
|
36254
36276
|
import * as fs11 from "fs";
|
|
36255
|
-
import * as
|
|
36277
|
+
import * as path16 from "path";
|
|
36256
36278
|
init_manager2();
|
|
36257
36279
|
|
|
36258
36280
|
// src/services/decision-drift-analyzer.ts
|
|
36259
36281
|
init_utils2();
|
|
36260
36282
|
init_manager2();
|
|
36261
36283
|
import * as fs10 from "fs";
|
|
36262
|
-
import * as
|
|
36284
|
+
import * as path15 from "path";
|
|
36263
36285
|
var DEFAULT_DRIFT_CONFIG = {
|
|
36264
36286
|
staleThresholdPhases: 1,
|
|
36265
36287
|
detectContradictions: true,
|
|
@@ -36413,7 +36435,7 @@ async function analyzeDecisionDrift(directory, config3 = {}) {
|
|
|
36413
36435
|
currentPhase = legacyPhase;
|
|
36414
36436
|
}
|
|
36415
36437
|
}
|
|
36416
|
-
const contextPath =
|
|
36438
|
+
const contextPath = path15.join(directory, ".swarm", "context.md");
|
|
36417
36439
|
let contextContent = "";
|
|
36418
36440
|
try {
|
|
36419
36441
|
if (fs10.existsSync(contextPath)) {
|
|
@@ -36674,18 +36696,28 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
36674
36696
|
if (config3.secretscan?.enabled === false) {
|
|
36675
36697
|
tryInject("[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.");
|
|
36676
36698
|
}
|
|
36699
|
+
const sessionId_preflight = _input.sessionID;
|
|
36700
|
+
const activeAgent_preflight = swarmState.activeAgent.get(sessionId_preflight ?? "");
|
|
36701
|
+
const isArchitectForPreflight = !activeAgent_preflight || stripKnownSwarmPrefix(activeAgent_preflight) === "architect";
|
|
36702
|
+
if (isArchitectForPreflight) {
|
|
36703
|
+
if (config3.pipeline?.parallel_precheck !== false) {
|
|
36704
|
+
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.");
|
|
36705
|
+
} else {
|
|
36706
|
+
tryInject("[SWARM HINT] Parallel pre-check disabled: run lint:check \u2192 secretscan \u2192 sast_scan \u2192 quality_budget sequentially.");
|
|
36707
|
+
}
|
|
36708
|
+
}
|
|
36677
36709
|
const sessionId_retro = _input.sessionID;
|
|
36678
36710
|
const activeAgent_retro = swarmState.activeAgent.get(sessionId_retro ?? "");
|
|
36679
36711
|
const isArchitect = !activeAgent_retro || stripKnownSwarmPrefix(activeAgent_retro) === "architect";
|
|
36680
36712
|
if (isArchitect) {
|
|
36681
36713
|
try {
|
|
36682
|
-
const evidenceDir =
|
|
36714
|
+
const evidenceDir = path16.join(directory, ".swarm", "evidence");
|
|
36683
36715
|
if (fs11.existsSync(evidenceDir)) {
|
|
36684
36716
|
const files = fs11.readdirSync(evidenceDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
36685
36717
|
for (const file3 of files.slice(0, 5)) {
|
|
36686
36718
|
let content;
|
|
36687
36719
|
try {
|
|
36688
|
-
content = JSON.parse(fs11.readFileSync(
|
|
36720
|
+
content = JSON.parse(fs11.readFileSync(path16.join(evidenceDir, file3), "utf-8"));
|
|
36689
36721
|
} catch {
|
|
36690
36722
|
continue;
|
|
36691
36723
|
}
|
|
@@ -36900,18 +36932,32 @@ function createSystemEnhancerHook(config3, directory) {
|
|
|
36900
36932
|
metadata: { contentType: "prose" }
|
|
36901
36933
|
});
|
|
36902
36934
|
}
|
|
36935
|
+
const sessionId_preflight_b = _input.sessionID;
|
|
36936
|
+
const activeAgent_preflight_b = swarmState.activeAgent.get(sessionId_preflight_b ?? "");
|
|
36937
|
+
const isArchitectForPreflight_b = !activeAgent_preflight_b || stripKnownSwarmPrefix(activeAgent_preflight_b) === "architect";
|
|
36938
|
+
if (isArchitectForPreflight_b) {
|
|
36939
|
+
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.";
|
|
36940
|
+
candidates.push({
|
|
36941
|
+
id: `candidate-${idCounter++}`,
|
|
36942
|
+
kind: "phase",
|
|
36943
|
+
text: hintText_b,
|
|
36944
|
+
tokens: estimateTokens(hintText_b),
|
|
36945
|
+
priority: 1,
|
|
36946
|
+
metadata: { contentType: "prose" }
|
|
36947
|
+
});
|
|
36948
|
+
}
|
|
36903
36949
|
const sessionId_retro_b = _input.sessionID;
|
|
36904
36950
|
const activeAgent_retro_b = swarmState.activeAgent.get(sessionId_retro_b ?? "");
|
|
36905
36951
|
const isArchitect_b = !activeAgent_retro_b || stripKnownSwarmPrefix(activeAgent_retro_b) === "architect";
|
|
36906
36952
|
if (isArchitect_b) {
|
|
36907
36953
|
try {
|
|
36908
|
-
const evidenceDir_b =
|
|
36954
|
+
const evidenceDir_b = path16.join(directory, ".swarm", "evidence");
|
|
36909
36955
|
if (fs11.existsSync(evidenceDir_b)) {
|
|
36910
36956
|
const files_b = fs11.readdirSync(evidenceDir_b).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
36911
36957
|
for (const file3 of files_b.slice(0, 5)) {
|
|
36912
36958
|
let content_b;
|
|
36913
36959
|
try {
|
|
36914
|
-
content_b = JSON.parse(fs11.readFileSync(
|
|
36960
|
+
content_b = JSON.parse(fs11.readFileSync(path16.join(evidenceDir_b, file3), "utf-8"));
|
|
36915
36961
|
} catch {
|
|
36916
36962
|
continue;
|
|
36917
36963
|
}
|
|
@@ -37207,7 +37253,7 @@ init_dist();
|
|
|
37207
37253
|
// src/build/discovery.ts
|
|
37208
37254
|
init_dist();
|
|
37209
37255
|
import * as fs12 from "fs";
|
|
37210
|
-
import * as
|
|
37256
|
+
import * as path17 from "path";
|
|
37211
37257
|
var ECOSYSTEMS = [
|
|
37212
37258
|
{
|
|
37213
37259
|
ecosystem: "node",
|
|
@@ -37325,11 +37371,11 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
37325
37371
|
return regex.test(f);
|
|
37326
37372
|
});
|
|
37327
37373
|
if (matches.length > 0) {
|
|
37328
|
-
return
|
|
37374
|
+
return path17.join(dir, matches[0]);
|
|
37329
37375
|
}
|
|
37330
37376
|
} catch {}
|
|
37331
37377
|
} else {
|
|
37332
|
-
const filePath =
|
|
37378
|
+
const filePath = path17.join(workingDir, pattern);
|
|
37333
37379
|
if (fs12.existsSync(filePath)) {
|
|
37334
37380
|
return filePath;
|
|
37335
37381
|
}
|
|
@@ -37338,7 +37384,7 @@ function findBuildFiles(workingDir, patterns) {
|
|
|
37338
37384
|
return null;
|
|
37339
37385
|
}
|
|
37340
37386
|
function getRepoDefinedScripts(workingDir, scripts) {
|
|
37341
|
-
const packageJsonPath =
|
|
37387
|
+
const packageJsonPath = path17.join(workingDir, "package.json");
|
|
37342
37388
|
if (!fs12.existsSync(packageJsonPath)) {
|
|
37343
37389
|
return [];
|
|
37344
37390
|
}
|
|
@@ -37379,7 +37425,7 @@ function findAllBuildFiles(workingDir) {
|
|
|
37379
37425
|
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
37380
37426
|
findFilesRecursive(workingDir, regex, allBuildFiles);
|
|
37381
37427
|
} else {
|
|
37382
|
-
const filePath =
|
|
37428
|
+
const filePath = path17.join(workingDir, pattern);
|
|
37383
37429
|
if (fs12.existsSync(filePath)) {
|
|
37384
37430
|
allBuildFiles.add(filePath);
|
|
37385
37431
|
}
|
|
@@ -37392,7 +37438,7 @@ function findFilesRecursive(dir, regex, results) {
|
|
|
37392
37438
|
try {
|
|
37393
37439
|
const entries = fs12.readdirSync(dir, { withFileTypes: true });
|
|
37394
37440
|
for (const entry of entries) {
|
|
37395
|
-
const fullPath =
|
|
37441
|
+
const fullPath = path17.join(dir, entry.name);
|
|
37396
37442
|
if (entry.isDirectory() && !["node_modules", ".git", "dist", "build", "target"].includes(entry.name)) {
|
|
37397
37443
|
findFilesRecursive(fullPath, regex, results);
|
|
37398
37444
|
} else if (entry.isFile() && regex.test(entry.name)) {
|
|
@@ -37634,7 +37680,7 @@ var build_check = tool({
|
|
|
37634
37680
|
init_tool();
|
|
37635
37681
|
import { spawnSync } from "child_process";
|
|
37636
37682
|
import * as fs13 from "fs";
|
|
37637
|
-
import * as
|
|
37683
|
+
import * as path18 from "path";
|
|
37638
37684
|
var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
|
|
37639
37685
|
var MAX_LABEL_LENGTH = 100;
|
|
37640
37686
|
var GIT_TIMEOUT_MS = 30000;
|
|
@@ -37685,7 +37731,7 @@ function validateLabel(label) {
|
|
|
37685
37731
|
return null;
|
|
37686
37732
|
}
|
|
37687
37733
|
function getCheckpointLogPath() {
|
|
37688
|
-
return
|
|
37734
|
+
return path18.join(process.cwd(), CHECKPOINT_LOG_PATH);
|
|
37689
37735
|
}
|
|
37690
37736
|
function readCheckpointLog() {
|
|
37691
37737
|
const logPath = getCheckpointLogPath();
|
|
@@ -37703,7 +37749,7 @@ function readCheckpointLog() {
|
|
|
37703
37749
|
}
|
|
37704
37750
|
function writeCheckpointLog(log2) {
|
|
37705
37751
|
const logPath = getCheckpointLogPath();
|
|
37706
|
-
const dir =
|
|
37752
|
+
const dir = path18.dirname(logPath);
|
|
37707
37753
|
if (!fs13.existsSync(dir)) {
|
|
37708
37754
|
fs13.mkdirSync(dir, { recursive: true });
|
|
37709
37755
|
}
|
|
@@ -37910,7 +37956,7 @@ var checkpoint = tool({
|
|
|
37910
37956
|
// src/tools/complexity-hotspots.ts
|
|
37911
37957
|
init_dist();
|
|
37912
37958
|
import * as fs14 from "fs";
|
|
37913
|
-
import * as
|
|
37959
|
+
import * as path19 from "path";
|
|
37914
37960
|
var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
|
|
37915
37961
|
var DEFAULT_DAYS = 90;
|
|
37916
37962
|
var DEFAULT_TOP_N = 20;
|
|
@@ -38053,7 +38099,7 @@ async function analyzeHotspots(days, topN, extensions) {
|
|
|
38053
38099
|
const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
|
|
38054
38100
|
const filteredChurn = new Map;
|
|
38055
38101
|
for (const [file3, count] of churnMap) {
|
|
38056
|
-
const ext =
|
|
38102
|
+
const ext = path19.extname(file3).toLowerCase();
|
|
38057
38103
|
if (extSet.has(ext)) {
|
|
38058
38104
|
filteredChurn.set(file3, count);
|
|
38059
38105
|
}
|
|
@@ -38064,7 +38110,7 @@ async function analyzeHotspots(days, topN, extensions) {
|
|
|
38064
38110
|
for (const [file3, churnCount] of filteredChurn) {
|
|
38065
38111
|
let fullPath = file3;
|
|
38066
38112
|
if (!fs14.existsSync(fullPath)) {
|
|
38067
|
-
fullPath =
|
|
38113
|
+
fullPath = path19.join(cwd, file3);
|
|
38068
38114
|
}
|
|
38069
38115
|
const complexity = getComplexityForFile(fullPath);
|
|
38070
38116
|
if (complexity !== null) {
|
|
@@ -38222,14 +38268,14 @@ function validateBase(base) {
|
|
|
38222
38268
|
function validatePaths(paths) {
|
|
38223
38269
|
if (!paths)
|
|
38224
38270
|
return null;
|
|
38225
|
-
for (const
|
|
38226
|
-
if (!
|
|
38271
|
+
for (const path20 of paths) {
|
|
38272
|
+
if (!path20 || path20.length === 0) {
|
|
38227
38273
|
return "empty path not allowed";
|
|
38228
38274
|
}
|
|
38229
|
-
if (
|
|
38275
|
+
if (path20.length > MAX_PATH_LENGTH) {
|
|
38230
38276
|
return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
|
|
38231
38277
|
}
|
|
38232
|
-
if (SHELL_METACHARACTERS2.test(
|
|
38278
|
+
if (SHELL_METACHARACTERS2.test(path20)) {
|
|
38233
38279
|
return "path contains shell metacharacters";
|
|
38234
38280
|
}
|
|
38235
38281
|
}
|
|
@@ -38292,8 +38338,8 @@ var diff = tool({
|
|
|
38292
38338
|
if (parts2.length >= 3) {
|
|
38293
38339
|
const additions = parseInt(parts2[0]) || 0;
|
|
38294
38340
|
const deletions = parseInt(parts2[1]) || 0;
|
|
38295
|
-
const
|
|
38296
|
-
files.push({ path:
|
|
38341
|
+
const path20 = parts2[2];
|
|
38342
|
+
files.push({ path: path20, additions, deletions });
|
|
38297
38343
|
}
|
|
38298
38344
|
}
|
|
38299
38345
|
const contractChanges = [];
|
|
@@ -38522,7 +38568,7 @@ Use these as DOMAIN values when delegating to @sme.`;
|
|
|
38522
38568
|
// src/tools/evidence-check.ts
|
|
38523
38569
|
init_dist();
|
|
38524
38570
|
import * as fs15 from "fs";
|
|
38525
|
-
import * as
|
|
38571
|
+
import * as path20 from "path";
|
|
38526
38572
|
var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
|
|
38527
38573
|
var MAX_EVIDENCE_FILES = 1000;
|
|
38528
38574
|
var EVIDENCE_DIR = ".swarm/evidence";
|
|
@@ -38545,9 +38591,9 @@ function validateRequiredTypes(input) {
|
|
|
38545
38591
|
return null;
|
|
38546
38592
|
}
|
|
38547
38593
|
function isPathWithinSwarm(filePath, cwd) {
|
|
38548
|
-
const normalizedCwd =
|
|
38549
|
-
const swarmPath =
|
|
38550
|
-
const normalizedPath =
|
|
38594
|
+
const normalizedCwd = path20.resolve(cwd);
|
|
38595
|
+
const swarmPath = path20.join(normalizedCwd, ".swarm");
|
|
38596
|
+
const normalizedPath = path20.resolve(filePath);
|
|
38551
38597
|
return normalizedPath.startsWith(swarmPath);
|
|
38552
38598
|
}
|
|
38553
38599
|
function parseCompletedTasks(planContent) {
|
|
@@ -38578,10 +38624,10 @@ function readEvidenceFiles(evidenceDir, cwd) {
|
|
|
38578
38624
|
if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
|
|
38579
38625
|
continue;
|
|
38580
38626
|
}
|
|
38581
|
-
const filePath =
|
|
38627
|
+
const filePath = path20.join(evidenceDir, filename);
|
|
38582
38628
|
try {
|
|
38583
|
-
const resolvedPath =
|
|
38584
|
-
const evidenceDirResolved =
|
|
38629
|
+
const resolvedPath = path20.resolve(filePath);
|
|
38630
|
+
const evidenceDirResolved = path20.resolve(evidenceDir);
|
|
38585
38631
|
if (!resolvedPath.startsWith(evidenceDirResolved)) {
|
|
38586
38632
|
continue;
|
|
38587
38633
|
}
|
|
@@ -38688,7 +38734,7 @@ var evidence_check = tool({
|
|
|
38688
38734
|
return JSON.stringify(errorResult, null, 2);
|
|
38689
38735
|
}
|
|
38690
38736
|
const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
38691
|
-
const planPath =
|
|
38737
|
+
const planPath = path20.join(cwd, PLAN_FILE);
|
|
38692
38738
|
if (!isPathWithinSwarm(planPath, cwd)) {
|
|
38693
38739
|
const errorResult = {
|
|
38694
38740
|
error: "plan file path validation failed",
|
|
@@ -38720,7 +38766,7 @@ var evidence_check = tool({
|
|
|
38720
38766
|
};
|
|
38721
38767
|
return JSON.stringify(result2, null, 2);
|
|
38722
38768
|
}
|
|
38723
|
-
const evidenceDir =
|
|
38769
|
+
const evidenceDir = path20.join(cwd, EVIDENCE_DIR);
|
|
38724
38770
|
const evidence = readEvidenceFiles(evidenceDir, cwd);
|
|
38725
38771
|
const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
|
|
38726
38772
|
const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
|
|
@@ -38737,7 +38783,7 @@ var evidence_check = tool({
|
|
|
38737
38783
|
// src/tools/file-extractor.ts
|
|
38738
38784
|
init_tool();
|
|
38739
38785
|
import * as fs16 from "fs";
|
|
38740
|
-
import * as
|
|
38786
|
+
import * as path21 from "path";
|
|
38741
38787
|
var EXT_MAP = {
|
|
38742
38788
|
python: ".py",
|
|
38743
38789
|
py: ".py",
|
|
@@ -38815,12 +38861,12 @@ var extract_code_blocks = tool({
|
|
|
38815
38861
|
if (prefix) {
|
|
38816
38862
|
filename = `${prefix}_${filename}`;
|
|
38817
38863
|
}
|
|
38818
|
-
let filepath =
|
|
38819
|
-
const base =
|
|
38820
|
-
const ext =
|
|
38864
|
+
let filepath = path21.join(targetDir, filename);
|
|
38865
|
+
const base = path21.basename(filepath, path21.extname(filepath));
|
|
38866
|
+
const ext = path21.extname(filepath);
|
|
38821
38867
|
let counter = 1;
|
|
38822
38868
|
while (fs16.existsSync(filepath)) {
|
|
38823
|
-
filepath =
|
|
38869
|
+
filepath = path21.join(targetDir, `${base}_${counter}${ext}`);
|
|
38824
38870
|
counter++;
|
|
38825
38871
|
}
|
|
38826
38872
|
try {
|
|
@@ -38933,7 +38979,7 @@ var gitingest = tool({
|
|
|
38933
38979
|
// src/tools/imports.ts
|
|
38934
38980
|
init_dist();
|
|
38935
38981
|
import * as fs17 from "fs";
|
|
38936
|
-
import * as
|
|
38982
|
+
import * as path22 from "path";
|
|
38937
38983
|
var MAX_FILE_PATH_LENGTH2 = 500;
|
|
38938
38984
|
var MAX_SYMBOL_LENGTH = 256;
|
|
38939
38985
|
var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
|
|
@@ -38987,7 +39033,7 @@ function validateSymbolInput(symbol3) {
|
|
|
38987
39033
|
return null;
|
|
38988
39034
|
}
|
|
38989
39035
|
function isBinaryFile2(filePath, buffer) {
|
|
38990
|
-
const ext =
|
|
39036
|
+
const ext = path22.extname(filePath).toLowerCase();
|
|
38991
39037
|
if (ext === ".json" || ext === ".md" || ext === ".txt") {
|
|
38992
39038
|
return false;
|
|
38993
39039
|
}
|
|
@@ -39011,15 +39057,15 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
39011
39057
|
const imports = [];
|
|
39012
39058
|
let resolvedTarget;
|
|
39013
39059
|
try {
|
|
39014
|
-
resolvedTarget =
|
|
39060
|
+
resolvedTarget = path22.resolve(targetFile);
|
|
39015
39061
|
} catch {
|
|
39016
39062
|
resolvedTarget = targetFile;
|
|
39017
39063
|
}
|
|
39018
|
-
const targetBasename =
|
|
39064
|
+
const targetBasename = path22.basename(targetFile, path22.extname(targetFile));
|
|
39019
39065
|
const targetWithExt = targetFile;
|
|
39020
39066
|
const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
39021
|
-
const normalizedTargetWithExt =
|
|
39022
|
-
const normalizedTargetWithoutExt =
|
|
39067
|
+
const normalizedTargetWithExt = path22.normalize(targetWithExt).replace(/\\/g, "/");
|
|
39068
|
+
const normalizedTargetWithoutExt = path22.normalize(targetWithoutExt).replace(/\\/g, "/");
|
|
39023
39069
|
const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
39024
39070
|
let match;
|
|
39025
39071
|
while ((match = importRegex.exec(content)) !== null) {
|
|
@@ -39043,9 +39089,9 @@ function parseImports(content, targetFile, targetSymbol) {
|
|
|
39043
39089
|
}
|
|
39044
39090
|
const normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
|
|
39045
39091
|
let isMatch = false;
|
|
39046
|
-
const targetDir =
|
|
39047
|
-
const targetExt =
|
|
39048
|
-
const targetBasenameNoExt =
|
|
39092
|
+
const targetDir = path22.dirname(targetFile);
|
|
39093
|
+
const targetExt = path22.extname(targetFile);
|
|
39094
|
+
const targetBasenameNoExt = path22.basename(targetFile, targetExt);
|
|
39049
39095
|
const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
39050
39096
|
const moduleName = modulePath.split(/[/\\]/).pop() || "";
|
|
39051
39097
|
const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
|
|
@@ -39113,10 +39159,10 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
39113
39159
|
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
39114
39160
|
for (const entry of entries) {
|
|
39115
39161
|
if (SKIP_DIRECTORIES2.has(entry)) {
|
|
39116
|
-
stats.skippedDirs.push(
|
|
39162
|
+
stats.skippedDirs.push(path22.join(dir, entry));
|
|
39117
39163
|
continue;
|
|
39118
39164
|
}
|
|
39119
|
-
const fullPath =
|
|
39165
|
+
const fullPath = path22.join(dir, entry);
|
|
39120
39166
|
let stat;
|
|
39121
39167
|
try {
|
|
39122
39168
|
stat = fs17.statSync(fullPath);
|
|
@@ -39130,7 +39176,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
|
|
|
39130
39176
|
if (stat.isDirectory()) {
|
|
39131
39177
|
findSourceFiles2(fullPath, files, stats);
|
|
39132
39178
|
} else if (stat.isFile()) {
|
|
39133
|
-
const ext =
|
|
39179
|
+
const ext = path22.extname(fullPath).toLowerCase();
|
|
39134
39180
|
if (SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
39135
39181
|
files.push(fullPath);
|
|
39136
39182
|
}
|
|
@@ -39186,7 +39232,7 @@ var imports = tool({
|
|
|
39186
39232
|
return JSON.stringify(errorResult, null, 2);
|
|
39187
39233
|
}
|
|
39188
39234
|
try {
|
|
39189
|
-
const targetFile =
|
|
39235
|
+
const targetFile = path22.resolve(file3);
|
|
39190
39236
|
if (!fs17.existsSync(targetFile)) {
|
|
39191
39237
|
const errorResult = {
|
|
39192
39238
|
error: `target file not found: ${file3}`,
|
|
@@ -39208,7 +39254,7 @@ var imports = tool({
|
|
|
39208
39254
|
};
|
|
39209
39255
|
return JSON.stringify(errorResult, null, 2);
|
|
39210
39256
|
}
|
|
39211
|
-
const baseDir =
|
|
39257
|
+
const baseDir = path22.dirname(targetFile);
|
|
39212
39258
|
const scanStats = {
|
|
39213
39259
|
skippedDirs: [],
|
|
39214
39260
|
skippedFiles: 0,
|
|
@@ -39298,7 +39344,7 @@ init_lint();
|
|
|
39298
39344
|
// src/tools/pkg-audit.ts
|
|
39299
39345
|
init_dist();
|
|
39300
39346
|
import * as fs18 from "fs";
|
|
39301
|
-
import * as
|
|
39347
|
+
import * as path23 from "path";
|
|
39302
39348
|
var MAX_OUTPUT_BYTES5 = 52428800;
|
|
39303
39349
|
var AUDIT_TIMEOUT_MS = 120000;
|
|
39304
39350
|
function isValidEcosystem(value) {
|
|
@@ -39316,13 +39362,13 @@ function validateArgs3(args2) {
|
|
|
39316
39362
|
function detectEcosystems() {
|
|
39317
39363
|
const ecosystems = [];
|
|
39318
39364
|
const cwd = process.cwd();
|
|
39319
|
-
if (fs18.existsSync(
|
|
39365
|
+
if (fs18.existsSync(path23.join(cwd, "package.json"))) {
|
|
39320
39366
|
ecosystems.push("npm");
|
|
39321
39367
|
}
|
|
39322
|
-
if (fs18.existsSync(
|
|
39368
|
+
if (fs18.existsSync(path23.join(cwd, "pyproject.toml")) || fs18.existsSync(path23.join(cwd, "requirements.txt"))) {
|
|
39323
39369
|
ecosystems.push("pip");
|
|
39324
39370
|
}
|
|
39325
|
-
if (fs18.existsSync(
|
|
39371
|
+
if (fs18.existsSync(path23.join(cwd, "Cargo.toml"))) {
|
|
39326
39372
|
ecosystems.push("cargo");
|
|
39327
39373
|
}
|
|
39328
39374
|
return ecosystems;
|
|
@@ -41230,11 +41276,11 @@ var Module2 = (() => {
|
|
|
41230
41276
|
throw toThrow;
|
|
41231
41277
|
}, "quit_");
|
|
41232
41278
|
var scriptDirectory = "";
|
|
41233
|
-
function locateFile(
|
|
41279
|
+
function locateFile(path24) {
|
|
41234
41280
|
if (Module["locateFile"]) {
|
|
41235
|
-
return Module["locateFile"](
|
|
41281
|
+
return Module["locateFile"](path24, scriptDirectory);
|
|
41236
41282
|
}
|
|
41237
|
-
return scriptDirectory +
|
|
41283
|
+
return scriptDirectory + path24;
|
|
41238
41284
|
}
|
|
41239
41285
|
__name(locateFile, "locateFile");
|
|
41240
41286
|
var readAsync, readBinary;
|
|
@@ -43023,6 +43069,9 @@ for (const definition of languageDefinitions) {
|
|
|
43023
43069
|
extensionMap.set(extension, definition);
|
|
43024
43070
|
}
|
|
43025
43071
|
}
|
|
43072
|
+
function getLanguageForExtension(extension) {
|
|
43073
|
+
return extensionMap.get(extension.toLowerCase());
|
|
43074
|
+
}
|
|
43026
43075
|
|
|
43027
43076
|
// src/tools/placeholder-scan.ts
|
|
43028
43077
|
var MAX_FILE_SIZE = 1024 * 1024;
|
|
@@ -43043,63 +43092,808 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
|
|
|
43043
43092
|
".php",
|
|
43044
43093
|
".rb"
|
|
43045
43094
|
]);
|
|
43095
|
+
// src/tools/pre-check-batch.ts
|
|
43096
|
+
init_dist();
|
|
43097
|
+
import * as path26 from "path";
|
|
43098
|
+
|
|
43099
|
+
// node_modules/yocto-queue/index.js
|
|
43100
|
+
class Node2 {
|
|
43101
|
+
value;
|
|
43102
|
+
next;
|
|
43103
|
+
constructor(value) {
|
|
43104
|
+
this.value = value;
|
|
43105
|
+
}
|
|
43106
|
+
}
|
|
43107
|
+
|
|
43108
|
+
class Queue {
|
|
43109
|
+
#head;
|
|
43110
|
+
#tail;
|
|
43111
|
+
#size;
|
|
43112
|
+
constructor() {
|
|
43113
|
+
this.clear();
|
|
43114
|
+
}
|
|
43115
|
+
enqueue(value) {
|
|
43116
|
+
const node = new Node2(value);
|
|
43117
|
+
if (this.#head) {
|
|
43118
|
+
this.#tail.next = node;
|
|
43119
|
+
this.#tail = node;
|
|
43120
|
+
} else {
|
|
43121
|
+
this.#head = node;
|
|
43122
|
+
this.#tail = node;
|
|
43123
|
+
}
|
|
43124
|
+
this.#size++;
|
|
43125
|
+
}
|
|
43126
|
+
dequeue() {
|
|
43127
|
+
const current = this.#head;
|
|
43128
|
+
if (!current) {
|
|
43129
|
+
return;
|
|
43130
|
+
}
|
|
43131
|
+
this.#head = this.#head.next;
|
|
43132
|
+
this.#size--;
|
|
43133
|
+
if (!this.#head) {
|
|
43134
|
+
this.#tail = undefined;
|
|
43135
|
+
}
|
|
43136
|
+
return current.value;
|
|
43137
|
+
}
|
|
43138
|
+
peek() {
|
|
43139
|
+
if (!this.#head) {
|
|
43140
|
+
return;
|
|
43141
|
+
}
|
|
43142
|
+
return this.#head.value;
|
|
43143
|
+
}
|
|
43144
|
+
clear() {
|
|
43145
|
+
this.#head = undefined;
|
|
43146
|
+
this.#tail = undefined;
|
|
43147
|
+
this.#size = 0;
|
|
43148
|
+
}
|
|
43149
|
+
get size() {
|
|
43150
|
+
return this.#size;
|
|
43151
|
+
}
|
|
43152
|
+
*[Symbol.iterator]() {
|
|
43153
|
+
let current = this.#head;
|
|
43154
|
+
while (current) {
|
|
43155
|
+
yield current.value;
|
|
43156
|
+
current = current.next;
|
|
43157
|
+
}
|
|
43158
|
+
}
|
|
43159
|
+
*drain() {
|
|
43160
|
+
while (this.#head) {
|
|
43161
|
+
yield this.dequeue();
|
|
43162
|
+
}
|
|
43163
|
+
}
|
|
43164
|
+
}
|
|
43165
|
+
|
|
43166
|
+
// node_modules/p-limit/index.js
|
|
43167
|
+
function pLimit(concurrency) {
|
|
43168
|
+
let rejectOnClear = false;
|
|
43169
|
+
if (typeof concurrency === "object") {
|
|
43170
|
+
({ concurrency, rejectOnClear = false } = concurrency);
|
|
43171
|
+
}
|
|
43172
|
+
validateConcurrency(concurrency);
|
|
43173
|
+
if (typeof rejectOnClear !== "boolean") {
|
|
43174
|
+
throw new TypeError("Expected `rejectOnClear` to be a boolean");
|
|
43175
|
+
}
|
|
43176
|
+
const queue = new Queue;
|
|
43177
|
+
let activeCount = 0;
|
|
43178
|
+
const resumeNext = () => {
|
|
43179
|
+
if (activeCount < concurrency && queue.size > 0) {
|
|
43180
|
+
activeCount++;
|
|
43181
|
+
queue.dequeue().run();
|
|
43182
|
+
}
|
|
43183
|
+
};
|
|
43184
|
+
const next = () => {
|
|
43185
|
+
activeCount--;
|
|
43186
|
+
resumeNext();
|
|
43187
|
+
};
|
|
43188
|
+
const run2 = async (function_, resolve10, arguments_2) => {
|
|
43189
|
+
const result = (async () => function_(...arguments_2))();
|
|
43190
|
+
resolve10(result);
|
|
43191
|
+
try {
|
|
43192
|
+
await result;
|
|
43193
|
+
} catch {}
|
|
43194
|
+
next();
|
|
43195
|
+
};
|
|
43196
|
+
const enqueue = (function_, resolve10, reject, arguments_2) => {
|
|
43197
|
+
const queueItem = { reject };
|
|
43198
|
+
new Promise((internalResolve) => {
|
|
43199
|
+
queueItem.run = internalResolve;
|
|
43200
|
+
queue.enqueue(queueItem);
|
|
43201
|
+
}).then(run2.bind(undefined, function_, resolve10, arguments_2));
|
|
43202
|
+
if (activeCount < concurrency) {
|
|
43203
|
+
resumeNext();
|
|
43204
|
+
}
|
|
43205
|
+
};
|
|
43206
|
+
const generator = (function_, ...arguments_2) => new Promise((resolve10, reject) => {
|
|
43207
|
+
enqueue(function_, resolve10, reject, arguments_2);
|
|
43208
|
+
});
|
|
43209
|
+
Object.defineProperties(generator, {
|
|
43210
|
+
activeCount: {
|
|
43211
|
+
get: () => activeCount
|
|
43212
|
+
},
|
|
43213
|
+
pendingCount: {
|
|
43214
|
+
get: () => queue.size
|
|
43215
|
+
},
|
|
43216
|
+
clearQueue: {
|
|
43217
|
+
value() {
|
|
43218
|
+
if (!rejectOnClear) {
|
|
43219
|
+
queue.clear();
|
|
43220
|
+
return;
|
|
43221
|
+
}
|
|
43222
|
+
const abortError = AbortSignal.abort().reason;
|
|
43223
|
+
while (queue.size > 0) {
|
|
43224
|
+
queue.dequeue().reject(abortError);
|
|
43225
|
+
}
|
|
43226
|
+
}
|
|
43227
|
+
},
|
|
43228
|
+
concurrency: {
|
|
43229
|
+
get: () => concurrency,
|
|
43230
|
+
set(newConcurrency) {
|
|
43231
|
+
validateConcurrency(newConcurrency);
|
|
43232
|
+
concurrency = newConcurrency;
|
|
43233
|
+
queueMicrotask(() => {
|
|
43234
|
+
while (activeCount < concurrency && queue.size > 0) {
|
|
43235
|
+
resumeNext();
|
|
43236
|
+
}
|
|
43237
|
+
});
|
|
43238
|
+
}
|
|
43239
|
+
},
|
|
43240
|
+
map: {
|
|
43241
|
+
async value(iterable, function_) {
|
|
43242
|
+
const promises = Array.from(iterable, (value, index) => this(function_, value, index));
|
|
43243
|
+
return Promise.all(promises);
|
|
43244
|
+
}
|
|
43245
|
+
}
|
|
43246
|
+
});
|
|
43247
|
+
return generator;
|
|
43248
|
+
}
|
|
43249
|
+
function validateConcurrency(concurrency) {
|
|
43250
|
+
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
|
43251
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
43252
|
+
}
|
|
43253
|
+
}
|
|
43254
|
+
|
|
43255
|
+
// src/tools/pre-check-batch.ts
|
|
43256
|
+
init_utils();
|
|
43257
|
+
init_lint();
|
|
43258
|
+
|
|
43046
43259
|
// src/tools/quality-budget.ts
|
|
43047
43260
|
init_manager();
|
|
43048
43261
|
|
|
43049
43262
|
// src/quality/metrics.ts
|
|
43263
|
+
import * as fs19 from "fs";
|
|
43264
|
+
import * as path24 from "path";
|
|
43050
43265
|
var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
|
|
43051
|
-
|
|
43052
|
-
|
|
43053
|
-
|
|
43054
|
-
|
|
43055
|
-
|
|
43056
|
-
|
|
43057
|
-
|
|
43058
|
-
|
|
43059
|
-
|
|
43060
|
-
|
|
43061
|
-
|
|
43062
|
-
|
|
43063
|
-
|
|
43064
|
-
|
|
43065
|
-
|
|
43266
|
+
var MIN_DUPLICATION_LINES = 10;
|
|
43267
|
+
function estimateCyclomaticComplexity(content) {
|
|
43268
|
+
let processed = content.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
43269
|
+
processed = processed.replace(/\/\/.*/g, "");
|
|
43270
|
+
processed = processed.replace(/#.*/g, "");
|
|
43271
|
+
processed = processed.replace(/'[^']*'/g, "");
|
|
43272
|
+
processed = processed.replace(/"[^"]*"/g, "");
|
|
43273
|
+
processed = processed.replace(/`[^`]*`/g, "");
|
|
43274
|
+
let complexity = 1;
|
|
43275
|
+
const decisionPatterns = [
|
|
43276
|
+
/\bif\b/g,
|
|
43277
|
+
/\belse\s+if\b/g,
|
|
43278
|
+
/\bfor\b/g,
|
|
43279
|
+
/\bwhile\b/g,
|
|
43280
|
+
/\bswitch\b/g,
|
|
43281
|
+
/\bcase\b/g,
|
|
43282
|
+
/\bcatch\b/g,
|
|
43283
|
+
/\?\./g,
|
|
43284
|
+
/\?\?/g,
|
|
43285
|
+
/&&/g,
|
|
43286
|
+
/\|\|/g
|
|
43287
|
+
];
|
|
43288
|
+
for (const pattern of decisionPatterns) {
|
|
43289
|
+
const matches = processed.match(pattern);
|
|
43290
|
+
if (matches) {
|
|
43291
|
+
complexity += matches.length;
|
|
43292
|
+
}
|
|
43293
|
+
}
|
|
43294
|
+
const ternaryMatches = processed.match(/\?[^:]/g);
|
|
43295
|
+
if (ternaryMatches) {
|
|
43296
|
+
complexity += ternaryMatches.length;
|
|
43297
|
+
}
|
|
43298
|
+
return complexity;
|
|
43299
|
+
}
|
|
43300
|
+
function getComplexityForFile2(filePath) {
|
|
43301
|
+
try {
|
|
43302
|
+
const stat = fs19.statSync(filePath);
|
|
43303
|
+
if (stat.size > MAX_FILE_SIZE_BYTES5) {
|
|
43304
|
+
return null;
|
|
43305
|
+
}
|
|
43306
|
+
const content = fs19.readFileSync(filePath, "utf-8");
|
|
43307
|
+
return estimateCyclomaticComplexity(content);
|
|
43308
|
+
} catch {
|
|
43309
|
+
return null;
|
|
43310
|
+
}
|
|
43311
|
+
}
|
|
43312
|
+
async function computeComplexityDelta(files, workingDir) {
|
|
43313
|
+
let totalComplexity = 0;
|
|
43314
|
+
const analyzedFiles = [];
|
|
43315
|
+
for (const file3 of files) {
|
|
43316
|
+
const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
|
|
43317
|
+
if (!fs19.existsSync(fullPath)) {
|
|
43318
|
+
continue;
|
|
43319
|
+
}
|
|
43320
|
+
const complexity = getComplexityForFile2(fullPath);
|
|
43321
|
+
if (complexity !== null) {
|
|
43322
|
+
totalComplexity += complexity;
|
|
43323
|
+
analyzedFiles.push(file3);
|
|
43324
|
+
}
|
|
43325
|
+
}
|
|
43326
|
+
return { delta: totalComplexity, analyzedFiles };
|
|
43327
|
+
}
|
|
43328
|
+
function countExportsInFile(content) {
|
|
43329
|
+
let count = 0;
|
|
43330
|
+
const exportFunctionMatches = content.match(/export\s+function\s+\w+/g);
|
|
43331
|
+
if (exportFunctionMatches)
|
|
43332
|
+
count += exportFunctionMatches.length;
|
|
43333
|
+
const exportClassMatches = content.match(/export\s+class\s+\w+/g);
|
|
43334
|
+
if (exportClassMatches)
|
|
43335
|
+
count += exportClassMatches.length;
|
|
43336
|
+
const exportConstMatches = content.match(/export\s+const\s+\w+/g);
|
|
43337
|
+
if (exportConstMatches)
|
|
43338
|
+
count += exportConstMatches.length;
|
|
43339
|
+
const exportLetMatches = content.match(/export\s+let\s+\w+/g);
|
|
43340
|
+
if (exportLetMatches)
|
|
43341
|
+
count += exportLetMatches.length;
|
|
43342
|
+
const exportVarMatches = content.match(/export\s+var\s+\w+/g);
|
|
43343
|
+
if (exportVarMatches)
|
|
43344
|
+
count += exportVarMatches.length;
|
|
43345
|
+
const exportNamedMatches = content.match(/export\s*\{[^}]+\}/g);
|
|
43346
|
+
if (exportNamedMatches) {
|
|
43347
|
+
for (const match of exportNamedMatches) {
|
|
43348
|
+
const names = match.match(/\b[a-zA-Z_$][a-zA-Z0-9_$]*\b/g);
|
|
43349
|
+
const filteredNames = names ? names.filter((n) => n !== "export") : [];
|
|
43350
|
+
count += filteredNames.length;
|
|
43351
|
+
}
|
|
43352
|
+
}
|
|
43353
|
+
const exportDefaultMatches = content.match(/export\s+default/g);
|
|
43354
|
+
if (exportDefaultMatches)
|
|
43355
|
+
count += exportDefaultMatches.length;
|
|
43356
|
+
const exportTypeMatches = content.match(/export\s+(type|interface)\s+\w+/g);
|
|
43357
|
+
if (exportTypeMatches)
|
|
43358
|
+
count += exportTypeMatches.length;
|
|
43359
|
+
const exportEnumMatches = content.match(/export\s+enum\s+\w+/g);
|
|
43360
|
+
if (exportEnumMatches)
|
|
43361
|
+
count += exportEnumMatches.length;
|
|
43362
|
+
const exportEqualsMatches = content.match(/export\s+=/g);
|
|
43363
|
+
if (exportEqualsMatches)
|
|
43364
|
+
count += exportEqualsMatches.length;
|
|
43365
|
+
return count;
|
|
43366
|
+
}
|
|
43367
|
+
function countPythonExports(content) {
|
|
43368
|
+
let count = 0;
|
|
43369
|
+
const noComments = content.replace(/#.*/g, "");
|
|
43370
|
+
const noStrings = noComments.replace(/"[^"]*"/g, "").replace(/'[^']*'/g, "");
|
|
43371
|
+
const functionMatches = noStrings.match(/^\s*def\s+\w+/gm);
|
|
43372
|
+
if (functionMatches)
|
|
43373
|
+
count += functionMatches.length;
|
|
43374
|
+
const classMatches = noStrings.match(/^\s*class\s+\w+/gm);
|
|
43375
|
+
if (classMatches)
|
|
43376
|
+
count += classMatches.length;
|
|
43377
|
+
const originalContent = content;
|
|
43378
|
+
const allMatchOriginal = originalContent.match(/__all__\s*=\s*\[([^\]]+)\]/);
|
|
43379
|
+
if (allMatchOriginal?.[1]) {
|
|
43380
|
+
const names = allMatchOriginal[1].match(/['"]?(\w+)['"]?/g);
|
|
43381
|
+
if (names) {
|
|
43382
|
+
const cleanNames = names.map((n) => n.replace(/['"]/g, ""));
|
|
43383
|
+
count += cleanNames.length;
|
|
43384
|
+
}
|
|
43385
|
+
}
|
|
43386
|
+
return count;
|
|
43387
|
+
}
|
|
43388
|
+
function countRustExports(content) {
|
|
43389
|
+
let count = 0;
|
|
43390
|
+
let processed = content.replace(/\/\/.*/g, "");
|
|
43391
|
+
processed = processed.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
43392
|
+
const pubFnMatches = processed.match(/pub\s+fn\s+\w+/g);
|
|
43393
|
+
if (pubFnMatches)
|
|
43394
|
+
count += pubFnMatches.length;
|
|
43395
|
+
const pubStructMatches = processed.match(/pub\s+struct\s+\w+/g);
|
|
43396
|
+
if (pubStructMatches)
|
|
43397
|
+
count += pubStructMatches.length;
|
|
43398
|
+
const pubEnumMatches = processed.match(/pub\s+enum\s+\w+/g);
|
|
43399
|
+
if (pubEnumMatches)
|
|
43400
|
+
count += pubEnumMatches.length;
|
|
43401
|
+
const pubUseMatches = processed.match(/pub\s+use\s+/g);
|
|
43402
|
+
if (pubUseMatches)
|
|
43403
|
+
count += pubUseMatches.length;
|
|
43404
|
+
const pubConstMatches = processed.match(/pub\s+const\s+\w+/g);
|
|
43405
|
+
if (pubConstMatches)
|
|
43406
|
+
count += pubConstMatches.length;
|
|
43407
|
+
const pubModMatches = processed.match(/pub\s+mod\s+\w+/g);
|
|
43408
|
+
if (pubModMatches)
|
|
43409
|
+
count += pubModMatches.length;
|
|
43410
|
+
const pubTypeMatches = processed.match(/pub\s+type\s+\w+/g);
|
|
43411
|
+
if (pubTypeMatches)
|
|
43412
|
+
count += pubTypeMatches.length;
|
|
43413
|
+
return count;
|
|
43414
|
+
}
|
|
43415
|
+
function countGoExports(content) {
|
|
43416
|
+
let count = 0;
|
|
43417
|
+
let processed = content.replace(/\/\/.*/gm, "");
|
|
43418
|
+
processed = processed.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
43419
|
+
const exportedFuncMatches = processed.match(/^\s*func\s+[A-Z]\w*/gm);
|
|
43420
|
+
if (exportedFuncMatches)
|
|
43421
|
+
count += exportedFuncMatches.length;
|
|
43422
|
+
const exportedVarMatches = processed.match(/^\s*var\s+[A-Z]\w*/gm);
|
|
43423
|
+
if (exportedVarMatches)
|
|
43424
|
+
count += exportedVarMatches.length;
|
|
43425
|
+
const exportedTypeMatches = processed.match(/^\s*type\s+[A-Z]\w*/gm);
|
|
43426
|
+
if (exportedTypeMatches)
|
|
43427
|
+
count += exportedTypeMatches.length;
|
|
43428
|
+
const exportedConstMatches = processed.match(/^\s*const\s+[A-Z]\w*/gm);
|
|
43429
|
+
if (exportedConstMatches)
|
|
43430
|
+
count += exportedConstMatches.length;
|
|
43431
|
+
const packageMatch = processed.match(/^\s*package\s+\w+/m);
|
|
43432
|
+
if (packageMatch)
|
|
43433
|
+
count += 1;
|
|
43434
|
+
return count;
|
|
43435
|
+
}
|
|
43436
|
+
function getExportCountForFile(filePath) {
|
|
43437
|
+
try {
|
|
43438
|
+
const content = fs19.readFileSync(filePath, "utf-8");
|
|
43439
|
+
const ext = path24.extname(filePath).toLowerCase();
|
|
43440
|
+
switch (ext) {
|
|
43441
|
+
case ".ts":
|
|
43442
|
+
case ".tsx":
|
|
43443
|
+
case ".js":
|
|
43444
|
+
case ".jsx":
|
|
43445
|
+
case ".mjs":
|
|
43446
|
+
case ".cjs":
|
|
43447
|
+
return countExportsInFile(content);
|
|
43448
|
+
case ".py":
|
|
43449
|
+
return countPythonExports(content);
|
|
43450
|
+
case ".rs":
|
|
43451
|
+
return countRustExports(content);
|
|
43452
|
+
case ".go":
|
|
43453
|
+
return countGoExports(content);
|
|
43454
|
+
default:
|
|
43455
|
+
return countExportsInFile(content);
|
|
43456
|
+
}
|
|
43457
|
+
} catch {
|
|
43458
|
+
return 0;
|
|
43459
|
+
}
|
|
43460
|
+
}
|
|
43461
|
+
async function computePublicApiDelta(files, workingDir) {
|
|
43462
|
+
let totalExports = 0;
|
|
43463
|
+
const analyzedFiles = [];
|
|
43464
|
+
for (const file3 of files) {
|
|
43465
|
+
const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
|
|
43466
|
+
if (!fs19.existsSync(fullPath)) {
|
|
43467
|
+
continue;
|
|
43468
|
+
}
|
|
43469
|
+
const exports = getExportCountForFile(fullPath);
|
|
43470
|
+
totalExports += exports;
|
|
43471
|
+
analyzedFiles.push(file3);
|
|
43472
|
+
}
|
|
43473
|
+
return { delta: totalExports, analyzedFiles };
|
|
43474
|
+
}
|
|
43475
|
+
function findDuplicateLines(content, minLines) {
|
|
43476
|
+
const lines = content.split(`
|
|
43477
|
+
`).filter((line) => line.trim().length > 0);
|
|
43478
|
+
if (lines.length < minLines) {
|
|
43479
|
+
return 0;
|
|
43480
|
+
}
|
|
43481
|
+
const lineCounts = new Map;
|
|
43482
|
+
for (const line of lines) {
|
|
43483
|
+
const normalized = line.trim();
|
|
43484
|
+
lineCounts.set(normalized, (lineCounts.get(normalized) || 0) + 1);
|
|
43485
|
+
}
|
|
43486
|
+
let duplicateCount = 0;
|
|
43487
|
+
for (const [_line, count] of lineCounts) {
|
|
43488
|
+
if (count > 1) {
|
|
43489
|
+
duplicateCount += count - 1;
|
|
43490
|
+
}
|
|
43491
|
+
}
|
|
43492
|
+
return duplicateCount;
|
|
43493
|
+
}
|
|
43494
|
+
async function computeDuplicationRatio(files, workingDir) {
|
|
43495
|
+
let totalLines = 0;
|
|
43496
|
+
let duplicateLines = 0;
|
|
43497
|
+
const analyzedFiles = [];
|
|
43498
|
+
for (const file3 of files) {
|
|
43499
|
+
const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
|
|
43500
|
+
if (!fs19.existsSync(fullPath)) {
|
|
43501
|
+
continue;
|
|
43066
43502
|
}
|
|
43067
|
-
let fullOutput;
|
|
43068
43503
|
try {
|
|
43069
|
-
|
|
43070
|
-
|
|
43071
|
-
|
|
43504
|
+
const stat = fs19.statSync(fullPath);
|
|
43505
|
+
if (stat.size > MAX_FILE_SIZE_BYTES5) {
|
|
43506
|
+
continue;
|
|
43507
|
+
}
|
|
43508
|
+
const content = fs19.readFileSync(fullPath, "utf-8");
|
|
43509
|
+
const lines = content.split(`
|
|
43510
|
+
`).filter((line) => line.trim().length > 0);
|
|
43511
|
+
if (lines.length < MIN_DUPLICATION_LINES) {
|
|
43512
|
+
analyzedFiles.push(file3);
|
|
43513
|
+
continue;
|
|
43514
|
+
}
|
|
43515
|
+
totalLines += lines.length;
|
|
43516
|
+
duplicateLines += findDuplicateLines(content, MIN_DUPLICATION_LINES);
|
|
43517
|
+
analyzedFiles.push(file3);
|
|
43518
|
+
} catch {}
|
|
43519
|
+
}
|
|
43520
|
+
const ratio = totalLines > 0 ? duplicateLines / totalLines : 0;
|
|
43521
|
+
return { ratio, analyzedFiles };
|
|
43522
|
+
}
|
|
43523
|
+
function countCodeLines(content) {
|
|
43524
|
+
let processed = content.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
43525
|
+
processed = processed.replace(/\/\/.*/g, "");
|
|
43526
|
+
processed = processed.replace(/#.*/g, "");
|
|
43527
|
+
const lines = processed.split(`
|
|
43528
|
+
`).filter((line) => line.trim().length > 0);
|
|
43529
|
+
return lines.length;
|
|
43530
|
+
}
|
|
43531
|
+
function isTestFile(filePath) {
|
|
43532
|
+
const basename5 = path24.basename(filePath);
|
|
43533
|
+
const _ext = path24.extname(filePath).toLowerCase();
|
|
43534
|
+
const testPatterns = [
|
|
43535
|
+
".test.",
|
|
43536
|
+
".spec.",
|
|
43537
|
+
".tests.",
|
|
43538
|
+
".test.ts",
|
|
43539
|
+
".test.js",
|
|
43540
|
+
".spec.ts",
|
|
43541
|
+
".spec.js",
|
|
43542
|
+
".test.tsx",
|
|
43543
|
+
".test.jsx",
|
|
43544
|
+
".spec.tsx",
|
|
43545
|
+
".spec.jsx"
|
|
43546
|
+
];
|
|
43547
|
+
for (const pattern of testPatterns) {
|
|
43548
|
+
if (basename5.includes(pattern)) {
|
|
43549
|
+
return true;
|
|
43072
43550
|
}
|
|
43073
|
-
|
|
43074
|
-
|
|
43551
|
+
}
|
|
43552
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
43553
|
+
if (normalizedPath.includes("/tests/") || normalizedPath.includes("/test/")) {
|
|
43554
|
+
return true;
|
|
43555
|
+
}
|
|
43556
|
+
if (normalizedPath.includes("/__tests__/")) {
|
|
43557
|
+
return true;
|
|
43558
|
+
}
|
|
43559
|
+
return false;
|
|
43560
|
+
}
|
|
43561
|
+
function shouldExcludeFile(filePath, excludeGlobs) {
|
|
43562
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
43563
|
+
for (const glob of excludeGlobs) {
|
|
43564
|
+
const pattern = glob.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
43565
|
+
const regex = new RegExp(pattern);
|
|
43566
|
+
if (regex.test(normalizedPath)) {
|
|
43567
|
+
return true;
|
|
43075
43568
|
}
|
|
43076
|
-
|
|
43077
|
-
|
|
43569
|
+
}
|
|
43570
|
+
return false;
|
|
43571
|
+
}
|
|
43572
|
+
async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
|
|
43573
|
+
let testLines = 0;
|
|
43574
|
+
let codeLines = 0;
|
|
43575
|
+
const srcDir = path24.join(workingDir, "src");
|
|
43576
|
+
if (fs19.existsSync(srcDir)) {
|
|
43577
|
+
await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
43578
|
+
codeLines += lines;
|
|
43579
|
+
});
|
|
43580
|
+
}
|
|
43581
|
+
const possibleSrcDirs = ["lib", "app", "source", "core"];
|
|
43582
|
+
for (const dir of possibleSrcDirs) {
|
|
43583
|
+
const dirPath = path24.join(workingDir, dir);
|
|
43584
|
+
if (fs19.existsSync(dirPath)) {
|
|
43585
|
+
await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
|
|
43586
|
+
codeLines += lines;
|
|
43587
|
+
});
|
|
43078
43588
|
}
|
|
43079
|
-
return fullOutput;
|
|
43080
43589
|
}
|
|
43081
|
-
|
|
43082
|
-
|
|
43083
|
-
|
|
43084
|
-
|
|
43085
|
-
|
|
43086
|
-
|
|
43087
|
-
|
|
43088
|
-
|
|
43089
|
-
|
|
43090
|
-
|
|
43091
|
-
|
|
43092
|
-
|
|
43093
|
-
|
|
43094
|
-
|
|
43095
|
-
}
|
|
43096
|
-
|
|
43097
|
-
|
|
43098
|
-
|
|
43099
|
-
|
|
43100
|
-
|
|
43101
|
-
|
|
43102
|
-
|
|
43590
|
+
const testsDir = path24.join(workingDir, "tests");
|
|
43591
|
+
if (fs19.existsSync(testsDir)) {
|
|
43592
|
+
await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
43593
|
+
testLines += lines;
|
|
43594
|
+
});
|
|
43595
|
+
}
|
|
43596
|
+
const possibleTestDirs = ["test", "__tests__", "specs"];
|
|
43597
|
+
for (const dir of possibleTestDirs) {
|
|
43598
|
+
const dirPath = path24.join(workingDir, dir);
|
|
43599
|
+
if (fs19.existsSync(dirPath) && dirPath !== testsDir) {
|
|
43600
|
+
await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
|
|
43601
|
+
testLines += lines;
|
|
43602
|
+
});
|
|
43603
|
+
}
|
|
43604
|
+
}
|
|
43605
|
+
const totalLines = testLines + codeLines;
|
|
43606
|
+
const ratio = totalLines > 0 ? testLines / totalLines : 0;
|
|
43607
|
+
return { ratio, testLines, codeLines };
|
|
43608
|
+
}
|
|
43609
|
+
async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTestScan, callback) {
|
|
43610
|
+
try {
|
|
43611
|
+
const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
|
|
43612
|
+
for (const entry of entries) {
|
|
43613
|
+
const fullPath = path24.join(dirPath, entry.name);
|
|
43614
|
+
if (entry.isDirectory()) {
|
|
43615
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
|
|
43616
|
+
continue;
|
|
43617
|
+
}
|
|
43618
|
+
await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
|
|
43619
|
+
} else if (entry.isFile()) {
|
|
43620
|
+
const relativePath = fullPath.replace(`${process.cwd()}/`, "");
|
|
43621
|
+
const ext = path24.extname(entry.name).toLowerCase();
|
|
43622
|
+
const validExts = [
|
|
43623
|
+
".ts",
|
|
43624
|
+
".tsx",
|
|
43625
|
+
".js",
|
|
43626
|
+
".jsx",
|
|
43627
|
+
".py",
|
|
43628
|
+
".rs",
|
|
43629
|
+
".go",
|
|
43630
|
+
".java",
|
|
43631
|
+
".cs"
|
|
43632
|
+
];
|
|
43633
|
+
if (!validExts.includes(ext)) {
|
|
43634
|
+
continue;
|
|
43635
|
+
}
|
|
43636
|
+
if (isTestScan && !isTestFile(fullPath)) {
|
|
43637
|
+
continue;
|
|
43638
|
+
}
|
|
43639
|
+
if (!isTestScan && isTestFile(fullPath)) {
|
|
43640
|
+
continue;
|
|
43641
|
+
}
|
|
43642
|
+
if (shouldExcludeFile(relativePath, excludeGlobs)) {
|
|
43643
|
+
continue;
|
|
43644
|
+
}
|
|
43645
|
+
if (!isTestScan && includeGlobs.length > 0 && !includeGlobs.includes("**")) {
|
|
43646
|
+
let matches = false;
|
|
43647
|
+
for (const glob of includeGlobs) {
|
|
43648
|
+
const pattern = glob.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
43649
|
+
const regex = new RegExp(pattern);
|
|
43650
|
+
if (regex.test(relativePath)) {
|
|
43651
|
+
matches = true;
|
|
43652
|
+
break;
|
|
43653
|
+
}
|
|
43654
|
+
}
|
|
43655
|
+
if (!matches)
|
|
43656
|
+
continue;
|
|
43657
|
+
}
|
|
43658
|
+
try {
|
|
43659
|
+
const content = fs19.readFileSync(fullPath, "utf-8");
|
|
43660
|
+
const lines = countCodeLines(content);
|
|
43661
|
+
callback(lines);
|
|
43662
|
+
} catch {}
|
|
43663
|
+
}
|
|
43664
|
+
}
|
|
43665
|
+
} catch {}
|
|
43666
|
+
}
|
|
43667
|
+
function detectViolations(metrics, thresholds) {
|
|
43668
|
+
const violations = [];
|
|
43669
|
+
if (metrics.complexity_delta > thresholds.max_complexity_delta) {
|
|
43670
|
+
violations.push({
|
|
43671
|
+
type: "complexity",
|
|
43672
|
+
message: `Complexity delta (${metrics.complexity_delta}) exceeds threshold (${thresholds.max_complexity_delta})`,
|
|
43673
|
+
severity: metrics.complexity_delta > thresholds.max_complexity_delta * 1.5 ? "error" : "warning",
|
|
43674
|
+
files: metrics.files_analyzed
|
|
43675
|
+
});
|
|
43676
|
+
}
|
|
43677
|
+
if (metrics.public_api_delta > thresholds.max_public_api_delta) {
|
|
43678
|
+
violations.push({
|
|
43679
|
+
type: "api",
|
|
43680
|
+
message: `Public API delta (${metrics.public_api_delta}) exceeds threshold (${thresholds.max_public_api_delta})`,
|
|
43681
|
+
severity: metrics.public_api_delta > thresholds.max_public_api_delta * 1.5 ? "error" : "warning",
|
|
43682
|
+
files: metrics.files_analyzed
|
|
43683
|
+
});
|
|
43684
|
+
}
|
|
43685
|
+
if (metrics.duplication_ratio > thresholds.max_duplication_ratio) {
|
|
43686
|
+
violations.push({
|
|
43687
|
+
type: "duplication",
|
|
43688
|
+
message: `Duplication ratio (${(metrics.duplication_ratio * 100).toFixed(1)}%) exceeds threshold (${(thresholds.max_duplication_ratio * 100).toFixed(1)}%)`,
|
|
43689
|
+
severity: metrics.duplication_ratio > thresholds.max_duplication_ratio * 1.5 ? "error" : "warning",
|
|
43690
|
+
files: metrics.files_analyzed
|
|
43691
|
+
});
|
|
43692
|
+
}
|
|
43693
|
+
if (metrics.files_analyzed.length > 0 && metrics.test_to_code_ratio < thresholds.min_test_to_code_ratio) {
|
|
43694
|
+
violations.push({
|
|
43695
|
+
type: "test_ratio",
|
|
43696
|
+
message: `Test-to-code ratio (${(metrics.test_to_code_ratio * 100).toFixed(1)}%) below threshold (${(thresholds.min_test_to_code_ratio * 100).toFixed(1)}%)`,
|
|
43697
|
+
severity: metrics.test_to_code_ratio < thresholds.min_test_to_code_ratio * 0.5 ? "error" : "warning",
|
|
43698
|
+
files: metrics.files_analyzed
|
|
43699
|
+
});
|
|
43700
|
+
}
|
|
43701
|
+
return violations;
|
|
43702
|
+
}
|
|
43703
|
+
async function computeQualityMetrics(changedFiles, thresholds, workingDir) {
|
|
43704
|
+
const config3 = {
|
|
43705
|
+
enabled: thresholds.enabled ?? true,
|
|
43706
|
+
max_complexity_delta: thresholds.max_complexity_delta ?? 5,
|
|
43707
|
+
max_public_api_delta: thresholds.max_public_api_delta ?? 10,
|
|
43708
|
+
max_duplication_ratio: thresholds.max_duplication_ratio ?? 0.05,
|
|
43709
|
+
min_test_to_code_ratio: thresholds.min_test_to_code_ratio ?? 0.3,
|
|
43710
|
+
enforce_on_globs: thresholds.enforce_on_globs ?? ["src/**"],
|
|
43711
|
+
exclude_globs: thresholds.exclude_globs ?? [
|
|
43712
|
+
"docs/**",
|
|
43713
|
+
"tests/**",
|
|
43714
|
+
"**/*.test.*"
|
|
43715
|
+
]
|
|
43716
|
+
};
|
|
43717
|
+
const filteredFiles = changedFiles.filter((file3) => {
|
|
43718
|
+
const normalizedPath = file3.replace(/\\/g, "/");
|
|
43719
|
+
for (const glob of config3.enforce_on_globs) {
|
|
43720
|
+
const pattern = glob.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
43721
|
+
const regex = new RegExp(pattern);
|
|
43722
|
+
if (regex.test(normalizedPath)) {
|
|
43723
|
+
for (const exclude of config3.exclude_globs) {
|
|
43724
|
+
const excludePattern = exclude.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
43725
|
+
const excludeRegex = new RegExp(excludePattern);
|
|
43726
|
+
if (excludeRegex.test(normalizedPath)) {
|
|
43727
|
+
return false;
|
|
43728
|
+
}
|
|
43729
|
+
}
|
|
43730
|
+
return true;
|
|
43731
|
+
}
|
|
43732
|
+
}
|
|
43733
|
+
return false;
|
|
43734
|
+
});
|
|
43735
|
+
const [complexityResult, apiResult, duplicationResult, testRatioResult] = await Promise.all([
|
|
43736
|
+
computeComplexityDelta(filteredFiles, workingDir),
|
|
43737
|
+
computePublicApiDelta(filteredFiles, workingDir),
|
|
43738
|
+
computeDuplicationRatio(filteredFiles, workingDir),
|
|
43739
|
+
computeTestToCodeRatio(workingDir, config3.enforce_on_globs, config3.exclude_globs)
|
|
43740
|
+
]);
|
|
43741
|
+
const allAnalyzedFiles = [
|
|
43742
|
+
...new Set([
|
|
43743
|
+
...complexityResult.analyzedFiles,
|
|
43744
|
+
...apiResult.analyzedFiles,
|
|
43745
|
+
...duplicationResult.analyzedFiles
|
|
43746
|
+
])
|
|
43747
|
+
];
|
|
43748
|
+
const violations = detectViolations({
|
|
43749
|
+
complexity_delta: complexityResult.delta,
|
|
43750
|
+
public_api_delta: apiResult.delta,
|
|
43751
|
+
duplication_ratio: duplicationResult.ratio,
|
|
43752
|
+
test_to_code_ratio: testRatioResult.ratio,
|
|
43753
|
+
files_analyzed: allAnalyzedFiles,
|
|
43754
|
+
thresholds: config3,
|
|
43755
|
+
violations: []
|
|
43756
|
+
}, config3);
|
|
43757
|
+
return {
|
|
43758
|
+
complexity_delta: complexityResult.delta,
|
|
43759
|
+
public_api_delta: apiResult.delta,
|
|
43760
|
+
duplication_ratio: duplicationResult.ratio,
|
|
43761
|
+
test_to_code_ratio: testRatioResult.ratio,
|
|
43762
|
+
files_analyzed: allAnalyzedFiles,
|
|
43763
|
+
thresholds: config3,
|
|
43764
|
+
violations
|
|
43765
|
+
};
|
|
43766
|
+
}
|
|
43767
|
+
|
|
43768
|
+
// src/tools/quality-budget.ts
|
|
43769
|
+
function validateInput(input) {
|
|
43770
|
+
if (!input || typeof input !== "object") {
|
|
43771
|
+
return { valid: false, error: "Input must be an object" };
|
|
43772
|
+
}
|
|
43773
|
+
const typedInput = input;
|
|
43774
|
+
if (!Array.isArray(typedInput.changed_files)) {
|
|
43775
|
+
return { valid: false, error: "changed_files must be an array" };
|
|
43776
|
+
}
|
|
43777
|
+
for (const file3 of typedInput.changed_files) {
|
|
43778
|
+
if (typeof file3 !== "string") {
|
|
43779
|
+
return { valid: false, error: "changed_files must contain strings" };
|
|
43780
|
+
}
|
|
43781
|
+
}
|
|
43782
|
+
if (typedInput.config !== undefined) {
|
|
43783
|
+
if (!typedInput.config || typeof typedInput.config !== "object") {
|
|
43784
|
+
return { valid: false, error: "config must be an object if provided" };
|
|
43785
|
+
}
|
|
43786
|
+
}
|
|
43787
|
+
return { valid: true };
|
|
43788
|
+
}
|
|
43789
|
+
async function qualityBudget(input, directory) {
|
|
43790
|
+
const validation = validateInput(input);
|
|
43791
|
+
if (!validation.valid) {
|
|
43792
|
+
throw new Error(`Invalid input: ${validation.error}`);
|
|
43793
|
+
}
|
|
43794
|
+
const { changed_files: changedFiles, config: config3 } = input;
|
|
43795
|
+
const thresholds = {
|
|
43796
|
+
enabled: config3?.enabled ?? true,
|
|
43797
|
+
max_complexity_delta: config3?.max_complexity_delta ?? 5,
|
|
43798
|
+
max_public_api_delta: config3?.max_public_api_delta ?? 10,
|
|
43799
|
+
max_duplication_ratio: config3?.max_duplication_ratio ?? 0.05,
|
|
43800
|
+
min_test_to_code_ratio: config3?.min_test_to_code_ratio ?? 0.3,
|
|
43801
|
+
enforce_on_globs: config3?.enforce_on_globs ?? ["src/**"],
|
|
43802
|
+
exclude_globs: config3?.exclude_globs ?? [
|
|
43803
|
+
"docs/**",
|
|
43804
|
+
"tests/**",
|
|
43805
|
+
"**/*.test.*"
|
|
43806
|
+
]
|
|
43807
|
+
};
|
|
43808
|
+
if (!thresholds.enabled) {
|
|
43809
|
+
return {
|
|
43810
|
+
verdict: "pass",
|
|
43811
|
+
metrics: {
|
|
43812
|
+
complexity_delta: 0,
|
|
43813
|
+
public_api_delta: 0,
|
|
43814
|
+
duplication_ratio: 0,
|
|
43815
|
+
test_to_code_ratio: 0,
|
|
43816
|
+
files_analyzed: [],
|
|
43817
|
+
thresholds,
|
|
43818
|
+
violations: []
|
|
43819
|
+
},
|
|
43820
|
+
violations: [],
|
|
43821
|
+
summary: {
|
|
43822
|
+
files_analyzed: 0,
|
|
43823
|
+
violations_count: 0,
|
|
43824
|
+
errors_count: 0,
|
|
43825
|
+
warnings_count: 0
|
|
43826
|
+
}
|
|
43827
|
+
};
|
|
43828
|
+
}
|
|
43829
|
+
const metrics = await computeQualityMetrics(changedFiles, thresholds, directory);
|
|
43830
|
+
const errorsCount = metrics.violations.filter((v) => v.severity === "error").length;
|
|
43831
|
+
const warningsCount = metrics.violations.filter((v) => v.severity === "warning").length;
|
|
43832
|
+
const verdict = errorsCount > 0 ? "fail" : "pass";
|
|
43833
|
+
await saveEvidence(directory, "quality_budget", {
|
|
43834
|
+
task_id: "quality_budget",
|
|
43835
|
+
type: "quality_budget",
|
|
43836
|
+
timestamp: new Date().toISOString(),
|
|
43837
|
+
agent: "quality_budget",
|
|
43838
|
+
verdict,
|
|
43839
|
+
summary: `Quality budget check: ${metrics.files_analyzed.length} files analyzed, ${metrics.violations.length} violation(s) found (${errorsCount} errors, ${warningsCount} warnings)`,
|
|
43840
|
+
metrics: {
|
|
43841
|
+
complexity_delta: metrics.complexity_delta,
|
|
43842
|
+
public_api_delta: metrics.public_api_delta,
|
|
43843
|
+
duplication_ratio: metrics.duplication_ratio,
|
|
43844
|
+
test_to_code_ratio: metrics.test_to_code_ratio
|
|
43845
|
+
},
|
|
43846
|
+
thresholds: {
|
|
43847
|
+
max_complexity_delta: thresholds.max_complexity_delta,
|
|
43848
|
+
max_public_api_delta: thresholds.max_public_api_delta,
|
|
43849
|
+
max_duplication_ratio: thresholds.max_duplication_ratio,
|
|
43850
|
+
min_test_to_code_ratio: thresholds.min_test_to_code_ratio
|
|
43851
|
+
},
|
|
43852
|
+
violations: metrics.violations.map((v) => ({
|
|
43853
|
+
type: v.type,
|
|
43854
|
+
message: v.message,
|
|
43855
|
+
severity: v.severity,
|
|
43856
|
+
files: v.files
|
|
43857
|
+
})),
|
|
43858
|
+
files_analyzed: metrics.files_analyzed
|
|
43859
|
+
});
|
|
43860
|
+
return {
|
|
43861
|
+
verdict,
|
|
43862
|
+
metrics,
|
|
43863
|
+
violations: metrics.violations,
|
|
43864
|
+
summary: {
|
|
43865
|
+
files_analyzed: metrics.files_analyzed.length,
|
|
43866
|
+
violations_count: metrics.violations.length,
|
|
43867
|
+
errors_count: errorsCount,
|
|
43868
|
+
warnings_count: warningsCount
|
|
43869
|
+
}
|
|
43870
|
+
};
|
|
43871
|
+
}
|
|
43872
|
+
|
|
43873
|
+
// src/tools/sast-scan.ts
|
|
43874
|
+
init_manager();
|
|
43875
|
+
import * as fs20 from "fs";
|
|
43876
|
+
import * as path25 from "path";
|
|
43877
|
+
import { extname as extname7 } from "path";
|
|
43878
|
+
|
|
43879
|
+
// src/sast/rules/c.ts
|
|
43880
|
+
var cRules = [
|
|
43881
|
+
{
|
|
43882
|
+
id: "sast/c-buffer-overflow",
|
|
43883
|
+
name: "Buffer overflow vulnerability",
|
|
43884
|
+
severity: "critical",
|
|
43885
|
+
languages: ["c", "cpp"],
|
|
43886
|
+
description: "strcpy/strcat to fixed-size buffer without bounds checking",
|
|
43887
|
+
remediation: "Use strncpy, snprintf, or safer alternatives that include size limits.",
|
|
43888
|
+
pattern: /\b(?:strcpy|strcat)\s*\(\s*[a-zA-Z_]/
|
|
43889
|
+
},
|
|
43890
|
+
{
|
|
43891
|
+
id: "sast/c-gets",
|
|
43892
|
+
name: "Unsafe gets() usage",
|
|
43893
|
+
severity: "critical",
|
|
43894
|
+
languages: ["c", "cpp"],
|
|
43895
|
+
description: "gets() does not check buffer bounds - removed from C11",
|
|
43896
|
+
remediation: "Use fgets() with proper buffer size instead of gets().",
|
|
43103
43897
|
pattern: /\bgets\s*\(/
|
|
43104
43898
|
},
|
|
43105
43899
|
{
|
|
@@ -43697,20 +44491,768 @@ var allRules = [
|
|
|
43697
44491
|
...cRules,
|
|
43698
44492
|
...csharpRules
|
|
43699
44493
|
];
|
|
44494
|
+
function getRulesForLanguage(language) {
|
|
44495
|
+
const normalized = language.toLowerCase();
|
|
44496
|
+
return allRules.filter((rule) => rule.languages.some((lang) => lang.toLowerCase() === normalized));
|
|
44497
|
+
}
|
|
44498
|
+
function findPatternMatches(content, pattern) {
|
|
44499
|
+
const matches = [];
|
|
44500
|
+
const lines = content.split(`
|
|
44501
|
+
`);
|
|
44502
|
+
for (let lineNum = 0;lineNum < lines.length; lineNum++) {
|
|
44503
|
+
const line = lines[lineNum];
|
|
44504
|
+
const workPattern = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g");
|
|
44505
|
+
let match;
|
|
44506
|
+
while ((match = workPattern.exec(line)) !== null) {
|
|
44507
|
+
matches.push({
|
|
44508
|
+
text: match[0],
|
|
44509
|
+
line: lineNum + 1,
|
|
44510
|
+
column: match.index + 1
|
|
44511
|
+
});
|
|
44512
|
+
if (match[0].length === 0) {
|
|
44513
|
+
workPattern.lastIndex++;
|
|
44514
|
+
}
|
|
44515
|
+
}
|
|
44516
|
+
}
|
|
44517
|
+
return matches;
|
|
44518
|
+
}
|
|
44519
|
+
function executeRulesSync(filePath, content, language) {
|
|
44520
|
+
const findings = [];
|
|
44521
|
+
const normalizedLang = language.toLowerCase();
|
|
44522
|
+
const rules = getRulesForLanguage(normalizedLang);
|
|
44523
|
+
for (const rule of rules) {
|
|
44524
|
+
if (!rule.pattern)
|
|
44525
|
+
continue;
|
|
44526
|
+
const matches = findPatternMatches(content, rule.pattern);
|
|
44527
|
+
for (const match of matches) {
|
|
44528
|
+
if (rule.validate) {
|
|
44529
|
+
const context = {
|
|
44530
|
+
filePath,
|
|
44531
|
+
content,
|
|
44532
|
+
language: normalizedLang
|
|
44533
|
+
};
|
|
44534
|
+
if (!rule.validate(match, context)) {
|
|
44535
|
+
continue;
|
|
44536
|
+
}
|
|
44537
|
+
}
|
|
44538
|
+
const lines = content.split(`
|
|
44539
|
+
`);
|
|
44540
|
+
const excerpt = lines[match.line - 1]?.trim() || "";
|
|
44541
|
+
findings.push({
|
|
44542
|
+
rule_id: rule.id,
|
|
44543
|
+
severity: rule.severity,
|
|
44544
|
+
message: rule.description,
|
|
44545
|
+
location: {
|
|
44546
|
+
file: filePath,
|
|
44547
|
+
line: match.line,
|
|
44548
|
+
column: match.column
|
|
44549
|
+
},
|
|
44550
|
+
remediation: rule.remediation,
|
|
44551
|
+
excerpt
|
|
44552
|
+
});
|
|
44553
|
+
}
|
|
44554
|
+
}
|
|
44555
|
+
return findings;
|
|
44556
|
+
}
|
|
43700
44557
|
|
|
43701
44558
|
// src/sast/semgrep.ts
|
|
43702
44559
|
import { execFile, execFileSync, spawn } from "child_process";
|
|
43703
44560
|
import { promisify } from "util";
|
|
43704
44561
|
var execFileAsync = promisify(execFile);
|
|
44562
|
+
var semgrepAvailableCache = null;
|
|
44563
|
+
var DEFAULT_RULES_DIR = ".swarm/semgrep-rules";
|
|
44564
|
+
var DEFAULT_TIMEOUT_MS3 = 30000;
|
|
44565
|
+
function isSemgrepAvailable() {
|
|
44566
|
+
if (semgrepAvailableCache !== null) {
|
|
44567
|
+
return semgrepAvailableCache;
|
|
44568
|
+
}
|
|
44569
|
+
try {
|
|
44570
|
+
execFileSync("semgrep", ["--version"], {
|
|
44571
|
+
encoding: "utf-8",
|
|
44572
|
+
stdio: "pipe"
|
|
44573
|
+
});
|
|
44574
|
+
semgrepAvailableCache = true;
|
|
44575
|
+
return true;
|
|
44576
|
+
} catch {
|
|
44577
|
+
semgrepAvailableCache = false;
|
|
44578
|
+
return false;
|
|
44579
|
+
}
|
|
44580
|
+
}
|
|
44581
|
+
function parseSemgrepResults(semgrepOutput) {
|
|
44582
|
+
const findings = [];
|
|
44583
|
+
try {
|
|
44584
|
+
const parsed = JSON.parse(semgrepOutput);
|
|
44585
|
+
const results = parsed.results || parsed;
|
|
44586
|
+
for (const result of results) {
|
|
44587
|
+
const severity = mapSemgrepSeverity(result.extra?.severity || result.severity);
|
|
44588
|
+
findings.push({
|
|
44589
|
+
rule_id: result.check_id || result.rule_id || "unknown",
|
|
44590
|
+
severity,
|
|
44591
|
+
message: result.extra?.message || result.message || "Security issue detected",
|
|
44592
|
+
location: {
|
|
44593
|
+
file: result.path || result.start?.filename || result.file || "unknown",
|
|
44594
|
+
line: result.start?.line || result.line || 1,
|
|
44595
|
+
column: result.start?.col || result.column
|
|
44596
|
+
},
|
|
44597
|
+
remediation: result.extra?.fix,
|
|
44598
|
+
excerpt: result.extra?.lines || result.lines || ""
|
|
44599
|
+
});
|
|
44600
|
+
}
|
|
44601
|
+
} catch {
|
|
44602
|
+
return [];
|
|
44603
|
+
}
|
|
44604
|
+
return findings;
|
|
44605
|
+
}
|
|
44606
|
+
function mapSemgrepSeverity(severity) {
|
|
44607
|
+
const severityLower = (severity || "").toLowerCase();
|
|
44608
|
+
switch (severityLower) {
|
|
44609
|
+
case "error":
|
|
44610
|
+
case "critical":
|
|
44611
|
+
return "critical";
|
|
44612
|
+
case "warning":
|
|
44613
|
+
case "high":
|
|
44614
|
+
return "high";
|
|
44615
|
+
case "info":
|
|
44616
|
+
case "low":
|
|
44617
|
+
return "low";
|
|
44618
|
+
default:
|
|
44619
|
+
return "medium";
|
|
44620
|
+
}
|
|
44621
|
+
}
|
|
44622
|
+
async function executeWithTimeout(command, args2, options) {
|
|
44623
|
+
return new Promise((resolve10) => {
|
|
44624
|
+
const child = spawn(command, args2, {
|
|
44625
|
+
shell: false,
|
|
44626
|
+
cwd: options.cwd
|
|
44627
|
+
});
|
|
44628
|
+
let stdout = "";
|
|
44629
|
+
let stderr = "";
|
|
44630
|
+
const timeout = setTimeout(() => {
|
|
44631
|
+
child.kill("SIGTERM");
|
|
44632
|
+
resolve10({
|
|
44633
|
+
stdout,
|
|
44634
|
+
stderr: "Process timed out",
|
|
44635
|
+
exitCode: 124
|
|
44636
|
+
});
|
|
44637
|
+
}, options.timeoutMs);
|
|
44638
|
+
child.stdout?.on("data", (data) => {
|
|
44639
|
+
stdout += data.toString();
|
|
44640
|
+
});
|
|
44641
|
+
child.stderr?.on("data", (data) => {
|
|
44642
|
+
stderr += data.toString();
|
|
44643
|
+
});
|
|
44644
|
+
child.on("close", (code) => {
|
|
44645
|
+
clearTimeout(timeout);
|
|
44646
|
+
resolve10({
|
|
44647
|
+
stdout,
|
|
44648
|
+
stderr,
|
|
44649
|
+
exitCode: code ?? 0
|
|
44650
|
+
});
|
|
44651
|
+
});
|
|
44652
|
+
child.on("error", (err2) => {
|
|
44653
|
+
clearTimeout(timeout);
|
|
44654
|
+
resolve10({
|
|
44655
|
+
stdout,
|
|
44656
|
+
stderr: err2.message,
|
|
44657
|
+
exitCode: 1
|
|
44658
|
+
});
|
|
44659
|
+
});
|
|
44660
|
+
});
|
|
44661
|
+
}
|
|
44662
|
+
async function runSemgrep(options) {
|
|
44663
|
+
const files = options.files || [];
|
|
44664
|
+
const rulesDir = options.rulesDir || DEFAULT_RULES_DIR;
|
|
44665
|
+
const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS3;
|
|
44666
|
+
if (files.length === 0) {
|
|
44667
|
+
return {
|
|
44668
|
+
available: isSemgrepAvailable(),
|
|
44669
|
+
findings: [],
|
|
44670
|
+
engine: "tier_a"
|
|
44671
|
+
};
|
|
44672
|
+
}
|
|
44673
|
+
if (!isSemgrepAvailable()) {
|
|
44674
|
+
return {
|
|
44675
|
+
available: false,
|
|
44676
|
+
findings: [],
|
|
44677
|
+
error: "Semgrep is not installed or not available on PATH",
|
|
44678
|
+
engine: "tier_a"
|
|
44679
|
+
};
|
|
44680
|
+
}
|
|
44681
|
+
const args2 = [
|
|
44682
|
+
"--config=./" + rulesDir,
|
|
44683
|
+
"--json",
|
|
44684
|
+
"--quiet",
|
|
44685
|
+
...files
|
|
44686
|
+
];
|
|
44687
|
+
try {
|
|
44688
|
+
const result = await executeWithTimeout("semgrep", args2, {
|
|
44689
|
+
timeoutMs,
|
|
44690
|
+
cwd: options.cwd
|
|
44691
|
+
});
|
|
44692
|
+
if (result.exitCode !== 0) {
|
|
44693
|
+
if (result.exitCode === 1 && result.stdout) {
|
|
44694
|
+
const findings2 = parseSemgrepResults(result.stdout);
|
|
44695
|
+
return {
|
|
44696
|
+
available: true,
|
|
44697
|
+
findings: findings2,
|
|
44698
|
+
engine: "tier_a+tier_b"
|
|
44699
|
+
};
|
|
44700
|
+
}
|
|
44701
|
+
return {
|
|
44702
|
+
available: true,
|
|
44703
|
+
findings: [],
|
|
44704
|
+
error: result.stderr || `Semgrep exited with code ${result.exitCode}`,
|
|
44705
|
+
engine: "tier_a"
|
|
44706
|
+
};
|
|
44707
|
+
}
|
|
44708
|
+
const findings = parseSemgrepResults(result.stdout);
|
|
44709
|
+
return {
|
|
44710
|
+
available: true,
|
|
44711
|
+
findings,
|
|
44712
|
+
engine: "tier_a+tier_b"
|
|
44713
|
+
};
|
|
44714
|
+
} catch (error93) {
|
|
44715
|
+
const errorMessage = error93 instanceof Error ? error93.message : "Unknown error running Semgrep";
|
|
44716
|
+
return {
|
|
44717
|
+
available: true,
|
|
44718
|
+
findings: [],
|
|
44719
|
+
error: errorMessage,
|
|
44720
|
+
engine: "tier_a"
|
|
44721
|
+
};
|
|
44722
|
+
}
|
|
44723
|
+
}
|
|
43705
44724
|
|
|
43706
44725
|
// src/tools/sast-scan.ts
|
|
43707
44726
|
init_utils();
|
|
43708
44727
|
var MAX_FILE_SIZE_BYTES6 = 512 * 1024;
|
|
44728
|
+
var MAX_FILES_SCANNED2 = 1000;
|
|
44729
|
+
var MAX_FINDINGS2 = 100;
|
|
44730
|
+
var SEVERITY_ORDER = {
|
|
44731
|
+
low: 0,
|
|
44732
|
+
medium: 1,
|
|
44733
|
+
high: 2,
|
|
44734
|
+
critical: 3
|
|
44735
|
+
};
|
|
44736
|
+
function shouldSkipFile(filePath) {
|
|
44737
|
+
try {
|
|
44738
|
+
const stats = fs20.statSync(filePath);
|
|
44739
|
+
if (stats.size > MAX_FILE_SIZE_BYTES6) {
|
|
44740
|
+
return { skip: true, reason: "file too large" };
|
|
44741
|
+
}
|
|
44742
|
+
if (stats.size === 0) {
|
|
44743
|
+
return { skip: true, reason: "empty file" };
|
|
44744
|
+
}
|
|
44745
|
+
const fd = fs20.openSync(filePath, "r");
|
|
44746
|
+
const buffer = Buffer.alloc(8192);
|
|
44747
|
+
const bytesRead = fs20.readSync(fd, buffer, 0, 8192, 0);
|
|
44748
|
+
fs20.closeSync(fd);
|
|
44749
|
+
if (bytesRead > 0) {
|
|
44750
|
+
let nullCount = 0;
|
|
44751
|
+
for (let i2 = 0;i2 < bytesRead; i2++) {
|
|
44752
|
+
if (buffer[i2] === 0) {
|
|
44753
|
+
nullCount++;
|
|
44754
|
+
}
|
|
44755
|
+
}
|
|
44756
|
+
if (nullCount / bytesRead > 0.1) {
|
|
44757
|
+
return { skip: true, reason: "binary file" };
|
|
44758
|
+
}
|
|
44759
|
+
}
|
|
44760
|
+
return { skip: false };
|
|
44761
|
+
} catch {
|
|
44762
|
+
return { skip: true, reason: "cannot read file" };
|
|
44763
|
+
}
|
|
44764
|
+
}
|
|
44765
|
+
function meetsThreshold(severity, threshold) {
|
|
44766
|
+
const severityLevel = SEVERITY_ORDER[severity] ?? 0;
|
|
44767
|
+
const thresholdLevel = SEVERITY_ORDER[threshold] ?? 1;
|
|
44768
|
+
return severityLevel >= thresholdLevel;
|
|
44769
|
+
}
|
|
44770
|
+
function countBySeverity(findings) {
|
|
44771
|
+
const counts = {
|
|
44772
|
+
critical: 0,
|
|
44773
|
+
high: 0,
|
|
44774
|
+
medium: 0,
|
|
44775
|
+
low: 0
|
|
44776
|
+
};
|
|
44777
|
+
for (const finding of findings) {
|
|
44778
|
+
const severity = finding.severity.toLowerCase();
|
|
44779
|
+
if (severity in counts) {
|
|
44780
|
+
counts[severity]++;
|
|
44781
|
+
}
|
|
44782
|
+
}
|
|
44783
|
+
return counts;
|
|
44784
|
+
}
|
|
44785
|
+
function scanFileWithTierA(filePath, language) {
|
|
44786
|
+
try {
|
|
44787
|
+
const content = fs20.readFileSync(filePath, "utf-8");
|
|
44788
|
+
const findings = executeRulesSync(filePath, content, language);
|
|
44789
|
+
return findings.map((f) => ({
|
|
44790
|
+
rule_id: f.rule_id,
|
|
44791
|
+
severity: f.severity,
|
|
44792
|
+
message: f.message,
|
|
44793
|
+
location: {
|
|
44794
|
+
file: f.location.file,
|
|
44795
|
+
line: f.location.line,
|
|
44796
|
+
column: f.location.column
|
|
44797
|
+
},
|
|
44798
|
+
remediation: f.remediation
|
|
44799
|
+
}));
|
|
44800
|
+
} catch {
|
|
44801
|
+
return [];
|
|
44802
|
+
}
|
|
44803
|
+
}
|
|
44804
|
+
async function sastScan(input, directory, config3) {
|
|
44805
|
+
const { changed_files, severity_threshold = "medium" } = input;
|
|
44806
|
+
if (config3?.gates?.sast_scan?.enabled === false) {
|
|
44807
|
+
return {
|
|
44808
|
+
verdict: "pass",
|
|
44809
|
+
findings: [],
|
|
44810
|
+
summary: {
|
|
44811
|
+
engine: "tier_a",
|
|
44812
|
+
files_scanned: 0,
|
|
44813
|
+
findings_count: 0,
|
|
44814
|
+
findings_by_severity: {
|
|
44815
|
+
critical: 0,
|
|
44816
|
+
high: 0,
|
|
44817
|
+
medium: 0,
|
|
44818
|
+
low: 0
|
|
44819
|
+
}
|
|
44820
|
+
}
|
|
44821
|
+
};
|
|
44822
|
+
}
|
|
44823
|
+
const allFindings = [];
|
|
44824
|
+
let filesScanned = 0;
|
|
44825
|
+
let filesSkipped = 0;
|
|
44826
|
+
const semgrepAvailable = isSemgrepAvailable();
|
|
44827
|
+
const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
|
|
44828
|
+
const filesByLanguage = new Map;
|
|
44829
|
+
for (const filePath of changed_files) {
|
|
44830
|
+
const resolvedPath = path25.isAbsolute(filePath) ? filePath : path25.resolve(directory, filePath);
|
|
44831
|
+
if (!fs20.existsSync(resolvedPath)) {
|
|
44832
|
+
filesSkipped++;
|
|
44833
|
+
continue;
|
|
44834
|
+
}
|
|
44835
|
+
const skipResult = shouldSkipFile(resolvedPath);
|
|
44836
|
+
if (skipResult.skip) {
|
|
44837
|
+
filesSkipped++;
|
|
44838
|
+
continue;
|
|
44839
|
+
}
|
|
44840
|
+
const ext = extname7(resolvedPath).toLowerCase();
|
|
44841
|
+
const langDef = getLanguageForExtension(ext);
|
|
44842
|
+
if (!langDef) {
|
|
44843
|
+
filesSkipped++;
|
|
44844
|
+
continue;
|
|
44845
|
+
}
|
|
44846
|
+
const language = langDef.id;
|
|
44847
|
+
const tierAFindings = scanFileWithTierA(resolvedPath, language);
|
|
44848
|
+
allFindings.push(...tierAFindings);
|
|
44849
|
+
if (semgrepAvailable) {
|
|
44850
|
+
const existing = filesByLanguage.get(language) || [];
|
|
44851
|
+
existing.push(resolvedPath);
|
|
44852
|
+
filesByLanguage.set(language, existing);
|
|
44853
|
+
}
|
|
44854
|
+
filesScanned++;
|
|
44855
|
+
if (filesScanned >= MAX_FILES_SCANNED2) {
|
|
44856
|
+
warn(`SAST Scan: Reached maximum files limit (${MAX_FILES_SCANNED2}), stopping`);
|
|
44857
|
+
break;
|
|
44858
|
+
}
|
|
44859
|
+
}
|
|
44860
|
+
if (semgrepAvailable && filesByLanguage.size > 0) {
|
|
44861
|
+
try {
|
|
44862
|
+
const allFilesForSemgrep = Array.from(filesByLanguage.values()).flat();
|
|
44863
|
+
if (allFilesForSemgrep.length > 0) {
|
|
44864
|
+
const semgrepResult = await runSemgrep({
|
|
44865
|
+
files: allFilesForSemgrep
|
|
44866
|
+
});
|
|
44867
|
+
if (semgrepResult.findings.length > 0) {
|
|
44868
|
+
const semgrepFindings = semgrepResult.findings.map((f) => ({
|
|
44869
|
+
rule_id: f.rule_id,
|
|
44870
|
+
severity: f.severity,
|
|
44871
|
+
message: f.message,
|
|
44872
|
+
location: {
|
|
44873
|
+
file: f.location.file,
|
|
44874
|
+
line: f.location.line,
|
|
44875
|
+
column: f.location.column
|
|
44876
|
+
},
|
|
44877
|
+
remediation: f.remediation
|
|
44878
|
+
}));
|
|
44879
|
+
const existingKeys = new Set(allFindings.map((f) => `${f.rule_id}:${f.location.file}:${f.location.line}`));
|
|
44880
|
+
for (const finding of semgrepFindings) {
|
|
44881
|
+
const key = `${finding.rule_id}:${finding.location.file}:${finding.location.line}`;
|
|
44882
|
+
if (!existingKeys.has(key)) {
|
|
44883
|
+
allFindings.push(finding);
|
|
44884
|
+
existingKeys.add(key);
|
|
44885
|
+
}
|
|
44886
|
+
}
|
|
44887
|
+
}
|
|
44888
|
+
}
|
|
44889
|
+
} catch (error93) {
|
|
44890
|
+
warn(`SAST Scan: Semgrep failed, falling back to Tier A: ${error93}`);
|
|
44891
|
+
}
|
|
44892
|
+
}
|
|
44893
|
+
let finalFindings = allFindings;
|
|
44894
|
+
if (allFindings.length > MAX_FINDINGS2) {
|
|
44895
|
+
finalFindings = allFindings.slice(0, MAX_FINDINGS2);
|
|
44896
|
+
warn(`SAST Scan: Found ${allFindings.length} findings, limiting to ${MAX_FINDINGS2}`);
|
|
44897
|
+
}
|
|
44898
|
+
const findingsBySeverity = countBySeverity(finalFindings);
|
|
44899
|
+
let verdict = "pass";
|
|
44900
|
+
for (const finding of finalFindings) {
|
|
44901
|
+
if (meetsThreshold(finding.severity, severity_threshold)) {
|
|
44902
|
+
verdict = "fail";
|
|
44903
|
+
break;
|
|
44904
|
+
}
|
|
44905
|
+
}
|
|
44906
|
+
const summary = {
|
|
44907
|
+
engine,
|
|
44908
|
+
files_scanned: filesScanned,
|
|
44909
|
+
findings_count: finalFindings.length,
|
|
44910
|
+
findings_by_severity: findingsBySeverity
|
|
44911
|
+
};
|
|
44912
|
+
await saveEvidence(directory, "sast_scan", {
|
|
44913
|
+
task_id: "sast_scan",
|
|
44914
|
+
type: "sast",
|
|
44915
|
+
timestamp: new Date().toISOString(),
|
|
44916
|
+
agent: "sast_scan",
|
|
44917
|
+
verdict,
|
|
44918
|
+
summary: `Scanned ${filesScanned} files, found ${finalFindings.length} finding(s) using ${engine}`,
|
|
44919
|
+
...summary,
|
|
44920
|
+
findings: finalFindings
|
|
44921
|
+
});
|
|
44922
|
+
return {
|
|
44923
|
+
verdict,
|
|
44924
|
+
findings: finalFindings,
|
|
44925
|
+
summary
|
|
44926
|
+
};
|
|
44927
|
+
}
|
|
44928
|
+
|
|
44929
|
+
// src/tools/pre-check-batch.ts
|
|
44930
|
+
init_secretscan();
|
|
44931
|
+
var TOOL_TIMEOUT_MS = 60000;
|
|
44932
|
+
var MAX_COMBINED_BYTES = 500000;
|
|
44933
|
+
var MAX_CONCURRENT = 4;
|
|
44934
|
+
var MAX_FILES = 100;
|
|
44935
|
+
function validatePath(inputPath, baseDir) {
|
|
44936
|
+
if (!inputPath || inputPath.length === 0) {
|
|
44937
|
+
return "path is required";
|
|
44938
|
+
}
|
|
44939
|
+
const resolved = path26.resolve(baseDir, inputPath);
|
|
44940
|
+
const baseResolved = path26.resolve(baseDir);
|
|
44941
|
+
const relative2 = path26.relative(baseResolved, resolved);
|
|
44942
|
+
if (relative2.startsWith("..") || path26.isAbsolute(relative2)) {
|
|
44943
|
+
return "path traversal detected";
|
|
44944
|
+
}
|
|
44945
|
+
return null;
|
|
44946
|
+
}
|
|
44947
|
+
function validateDirectory(dir) {
|
|
44948
|
+
if (!dir || dir.length === 0) {
|
|
44949
|
+
return "directory is required";
|
|
44950
|
+
}
|
|
44951
|
+
if (dir.length > 500) {
|
|
44952
|
+
return "directory path too long";
|
|
44953
|
+
}
|
|
44954
|
+
const traversalCheck = validatePath(dir, process.cwd());
|
|
44955
|
+
if (traversalCheck) {
|
|
44956
|
+
return traversalCheck;
|
|
44957
|
+
}
|
|
44958
|
+
return null;
|
|
44959
|
+
}
|
|
44960
|
+
async function runWithTimeout(promise3, timeoutMs) {
|
|
44961
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
44962
|
+
setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
44963
|
+
});
|
|
44964
|
+
return Promise.race([promise3, timeoutPromise]);
|
|
44965
|
+
}
|
|
44966
|
+
async function runLintWrapped(_config) {
|
|
44967
|
+
const start2 = process.hrtime.bigint();
|
|
44968
|
+
try {
|
|
44969
|
+
const linter = await detectAvailableLinter();
|
|
44970
|
+
if (!linter) {
|
|
44971
|
+
return {
|
|
44972
|
+
ran: false,
|
|
44973
|
+
error: "No linter found (biome or eslint)",
|
|
44974
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
44975
|
+
};
|
|
44976
|
+
}
|
|
44977
|
+
const result = await runWithTimeout(runLint(linter, "check"), TOOL_TIMEOUT_MS);
|
|
44978
|
+
return {
|
|
44979
|
+
ran: true,
|
|
44980
|
+
result,
|
|
44981
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
44982
|
+
};
|
|
44983
|
+
} catch (error93) {
|
|
44984
|
+
return {
|
|
44985
|
+
ran: true,
|
|
44986
|
+
error: error93 instanceof Error ? error93.message : "Unknown error",
|
|
44987
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
44988
|
+
};
|
|
44989
|
+
}
|
|
44990
|
+
}
|
|
44991
|
+
async function runSecretscanWrapped(directory, _config) {
|
|
44992
|
+
const start2 = process.hrtime.bigint();
|
|
44993
|
+
try {
|
|
44994
|
+
const result = await runWithTimeout(runSecretscan(directory), TOOL_TIMEOUT_MS);
|
|
44995
|
+
return {
|
|
44996
|
+
ran: true,
|
|
44997
|
+
result,
|
|
44998
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
44999
|
+
};
|
|
45000
|
+
} catch (error93) {
|
|
45001
|
+
return {
|
|
45002
|
+
ran: true,
|
|
45003
|
+
error: error93 instanceof Error ? error93.message : "Unknown error",
|
|
45004
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45005
|
+
};
|
|
45006
|
+
}
|
|
45007
|
+
}
|
|
45008
|
+
async function runSastScanWrapped(changedFiles, directory, severityThreshold, config3) {
|
|
45009
|
+
const start2 = process.hrtime.bigint();
|
|
45010
|
+
try {
|
|
45011
|
+
const result = await runWithTimeout(sastScan({ changed_files: changedFiles, severity_threshold: severityThreshold }, directory, config3), TOOL_TIMEOUT_MS);
|
|
45012
|
+
return {
|
|
45013
|
+
ran: true,
|
|
45014
|
+
result,
|
|
45015
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45016
|
+
};
|
|
45017
|
+
} catch (error93) {
|
|
45018
|
+
return {
|
|
45019
|
+
ran: true,
|
|
45020
|
+
error: error93 instanceof Error ? error93.message : "Unknown error",
|
|
45021
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45022
|
+
};
|
|
45023
|
+
}
|
|
45024
|
+
}
|
|
45025
|
+
async function runQualityBudgetWrapped(changedFiles, directory, _config) {
|
|
45026
|
+
const start2 = process.hrtime.bigint();
|
|
45027
|
+
try {
|
|
45028
|
+
const result = await runWithTimeout(qualityBudget({ changed_files: changedFiles }, directory), TOOL_TIMEOUT_MS);
|
|
45029
|
+
return {
|
|
45030
|
+
ran: true,
|
|
45031
|
+
result,
|
|
45032
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45033
|
+
};
|
|
45034
|
+
} catch (error93) {
|
|
45035
|
+
return {
|
|
45036
|
+
ran: true,
|
|
45037
|
+
error: error93 instanceof Error ? error93.message : "Unknown error",
|
|
45038
|
+
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
45039
|
+
};
|
|
45040
|
+
}
|
|
45041
|
+
}
|
|
45042
|
+
async function runPreCheckBatch(input) {
|
|
45043
|
+
const { files, directory, sast_threshold = "medium", config: config3 } = input;
|
|
45044
|
+
const dirError = validateDirectory(directory);
|
|
45045
|
+
if (dirError) {
|
|
45046
|
+
warn(`pre_check_batch: Invalid directory: ${dirError}`);
|
|
45047
|
+
return {
|
|
45048
|
+
gates_passed: false,
|
|
45049
|
+
lint: { ran: false, error: dirError, duration_ms: 0 },
|
|
45050
|
+
secretscan: { ran: false, error: dirError, duration_ms: 0 },
|
|
45051
|
+
sast_scan: { ran: false, error: dirError, duration_ms: 0 },
|
|
45052
|
+
quality_budget: { ran: false, error: dirError, duration_ms: 0 },
|
|
45053
|
+
total_duration_ms: 0
|
|
45054
|
+
};
|
|
45055
|
+
}
|
|
45056
|
+
let changedFiles = [];
|
|
45057
|
+
if (files && files.length > 0) {
|
|
45058
|
+
for (const file3 of files) {
|
|
45059
|
+
const fileError = validatePath(file3, directory);
|
|
45060
|
+
if (fileError) {
|
|
45061
|
+
warn(`pre_check_batch: Invalid file path: ${file3}`);
|
|
45062
|
+
continue;
|
|
45063
|
+
}
|
|
45064
|
+
changedFiles.push(path26.resolve(directory, file3));
|
|
45065
|
+
}
|
|
45066
|
+
} else {
|
|
45067
|
+
changedFiles = [];
|
|
45068
|
+
}
|
|
45069
|
+
if (changedFiles.length === 0 && !files) {
|
|
45070
|
+
warn("pre_check_batch: No files provided, skipping SAST and quality_budget");
|
|
45071
|
+
return {
|
|
45072
|
+
gates_passed: true,
|
|
45073
|
+
lint: { ran: false, error: "No files provided", duration_ms: 0 },
|
|
45074
|
+
secretscan: { ran: false, error: "No files provided", duration_ms: 0 },
|
|
45075
|
+
sast_scan: { ran: false, error: "No files provided", duration_ms: 0 },
|
|
45076
|
+
quality_budget: {
|
|
45077
|
+
ran: false,
|
|
45078
|
+
error: "No files provided",
|
|
45079
|
+
duration_ms: 0
|
|
45080
|
+
},
|
|
45081
|
+
total_duration_ms: 0
|
|
45082
|
+
};
|
|
45083
|
+
}
|
|
45084
|
+
if (changedFiles.length > MAX_FILES) {
|
|
45085
|
+
throw new Error(`Input exceeds maximum file count: ${changedFiles.length} > ${MAX_FILES}`);
|
|
45086
|
+
}
|
|
45087
|
+
const limit = pLimit(MAX_CONCURRENT);
|
|
45088
|
+
const [lintResult, secretscanResult, sastScanResult, qualityBudgetResult] = await Promise.all([
|
|
45089
|
+
limit(() => runLintWrapped(config3)),
|
|
45090
|
+
limit(() => runSecretscanWrapped(directory, config3)),
|
|
45091
|
+
limit(() => runSastScanWrapped(changedFiles, directory, sast_threshold, config3)),
|
|
45092
|
+
limit(() => runQualityBudgetWrapped(changedFiles, directory, config3))
|
|
45093
|
+
]);
|
|
45094
|
+
const totalDuration = lintResult.duration_ms + secretscanResult.duration_ms + sastScanResult.duration_ms + qualityBudgetResult.duration_ms;
|
|
45095
|
+
let gatesPassed = true;
|
|
45096
|
+
if (lintResult.ran && lintResult.result) {
|
|
45097
|
+
const lintRes = lintResult.result;
|
|
45098
|
+
if ("success" in lintRes && lintRes.success === false) {
|
|
45099
|
+
gatesPassed = false;
|
|
45100
|
+
warn("pre_check_batch: Lint has errors - GATE FAILED");
|
|
45101
|
+
}
|
|
45102
|
+
} else if (lintResult.error) {
|
|
45103
|
+
gatesPassed = false;
|
|
45104
|
+
warn(`pre_check_batch: Lint error - GATE FAILED: ${lintResult.error}`);
|
|
45105
|
+
}
|
|
45106
|
+
if (secretscanResult.ran && secretscanResult.result) {
|
|
45107
|
+
const scanResult = secretscanResult.result;
|
|
45108
|
+
if ("findings" in scanResult && scanResult.findings.length > 0) {
|
|
45109
|
+
gatesPassed = false;
|
|
45110
|
+
warn("pre_check_batch: Secretscan found secrets - GATE FAILED");
|
|
45111
|
+
}
|
|
45112
|
+
} else if (secretscanResult.error) {
|
|
45113
|
+
gatesPassed = false;
|
|
45114
|
+
warn(`pre_check_batch: Secretscan error - GATE FAILED: ${secretscanResult.error}`);
|
|
45115
|
+
}
|
|
45116
|
+
if (sastScanResult.ran && sastScanResult.result) {
|
|
45117
|
+
if (sastScanResult.result.verdict === "fail") {
|
|
45118
|
+
gatesPassed = false;
|
|
45119
|
+
warn("pre_check_batch: SAST scan found vulnerabilities - GATE FAILED");
|
|
45120
|
+
}
|
|
45121
|
+
} else if (sastScanResult.error) {
|
|
45122
|
+
gatesPassed = false;
|
|
45123
|
+
warn(`pre_check_batch: SAST scan error - GATE FAILED: ${sastScanResult.error}`);
|
|
45124
|
+
}
|
|
45125
|
+
const result = {
|
|
45126
|
+
gates_passed: gatesPassed,
|
|
45127
|
+
lint: lintResult,
|
|
45128
|
+
secretscan: secretscanResult,
|
|
45129
|
+
sast_scan: sastScanResult,
|
|
45130
|
+
quality_budget: qualityBudgetResult,
|
|
45131
|
+
total_duration_ms: Math.round(totalDuration)
|
|
45132
|
+
};
|
|
45133
|
+
const outputSize = JSON.stringify(result).length;
|
|
45134
|
+
if (outputSize > MAX_COMBINED_BYTES) {
|
|
45135
|
+
warn(`pre_check_batch: Large output (${outputSize} bytes)`);
|
|
45136
|
+
}
|
|
45137
|
+
return result;
|
|
45138
|
+
}
|
|
45139
|
+
var pre_check_batch = tool({
|
|
45140
|
+
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.",
|
|
45141
|
+
args: {
|
|
45142
|
+
files: tool.schema.array(tool.schema.string()).optional().describe("Specific files to check (optional, scans directory if not provided)"),
|
|
45143
|
+
directory: tool.schema.string().describe('Directory to run checks in (e.g., "." or "./src")'),
|
|
45144
|
+
sast_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity for SAST findings to cause failure (default: medium)")
|
|
45145
|
+
},
|
|
45146
|
+
async execute(args2) {
|
|
45147
|
+
if (!args2 || typeof args2 !== "object") {
|
|
45148
|
+
const errorResult = {
|
|
45149
|
+
gates_passed: false,
|
|
45150
|
+
lint: { ran: false, error: "Invalid arguments", duration_ms: 0 },
|
|
45151
|
+
secretscan: { ran: false, error: "Invalid arguments", duration_ms: 0 },
|
|
45152
|
+
sast_scan: { ran: false, error: "Invalid arguments", duration_ms: 0 },
|
|
45153
|
+
quality_budget: {
|
|
45154
|
+
ran: false,
|
|
45155
|
+
error: "Invalid arguments",
|
|
45156
|
+
duration_ms: 0
|
|
45157
|
+
},
|
|
45158
|
+
total_duration_ms: 0
|
|
45159
|
+
};
|
|
45160
|
+
return JSON.stringify(errorResult, null, 2);
|
|
45161
|
+
}
|
|
45162
|
+
const typedArgs = args2;
|
|
45163
|
+
if (!typedArgs.directory) {
|
|
45164
|
+
const errorResult = {
|
|
45165
|
+
gates_passed: false,
|
|
45166
|
+
lint: { ran: false, error: "directory is required", duration_ms: 0 },
|
|
45167
|
+
secretscan: {
|
|
45168
|
+
ran: false,
|
|
45169
|
+
error: "directory is required",
|
|
45170
|
+
duration_ms: 0
|
|
45171
|
+
},
|
|
45172
|
+
sast_scan: {
|
|
45173
|
+
ran: false,
|
|
45174
|
+
error: "directory is required",
|
|
45175
|
+
duration_ms: 0
|
|
45176
|
+
},
|
|
45177
|
+
quality_budget: {
|
|
45178
|
+
ran: false,
|
|
45179
|
+
error: "directory is required",
|
|
45180
|
+
duration_ms: 0
|
|
45181
|
+
},
|
|
45182
|
+
total_duration_ms: 0
|
|
45183
|
+
};
|
|
45184
|
+
return JSON.stringify(errorResult, null, 2);
|
|
45185
|
+
}
|
|
45186
|
+
const dirError = validateDirectory(typedArgs.directory);
|
|
45187
|
+
if (dirError) {
|
|
45188
|
+
const errorResult = {
|
|
45189
|
+
gates_passed: false,
|
|
45190
|
+
lint: { ran: false, error: dirError, duration_ms: 0 },
|
|
45191
|
+
secretscan: { ran: false, error: dirError, duration_ms: 0 },
|
|
45192
|
+
sast_scan: { ran: false, error: dirError, duration_ms: 0 },
|
|
45193
|
+
quality_budget: { ran: false, error: dirError, duration_ms: 0 },
|
|
45194
|
+
total_duration_ms: 0
|
|
45195
|
+
};
|
|
45196
|
+
return JSON.stringify(errorResult, null, 2);
|
|
45197
|
+
}
|
|
45198
|
+
try {
|
|
45199
|
+
const result = await runPreCheckBatch({
|
|
45200
|
+
files: typedArgs.files,
|
|
45201
|
+
directory: typedArgs.directory,
|
|
45202
|
+
sast_threshold: typedArgs.sast_threshold,
|
|
45203
|
+
config: typedArgs.config
|
|
45204
|
+
});
|
|
45205
|
+
return JSON.stringify(result, null, 2);
|
|
45206
|
+
} catch (error93) {
|
|
45207
|
+
const errorMessage = error93 instanceof Error ? error93.message : "Unknown error";
|
|
45208
|
+
const errorResult = {
|
|
45209
|
+
gates_passed: false,
|
|
45210
|
+
lint: { ran: false, error: errorMessage, duration_ms: 0 },
|
|
45211
|
+
secretscan: { ran: false, error: errorMessage, duration_ms: 0 },
|
|
45212
|
+
sast_scan: { ran: false, error: errorMessage, duration_ms: 0 },
|
|
45213
|
+
quality_budget: { ran: false, error: errorMessage, duration_ms: 0 },
|
|
45214
|
+
total_duration_ms: 0
|
|
45215
|
+
};
|
|
45216
|
+
return JSON.stringify(errorResult, null, 2);
|
|
45217
|
+
}
|
|
45218
|
+
}
|
|
45219
|
+
});
|
|
45220
|
+
// src/tools/retrieve-summary.ts
|
|
45221
|
+
init_dist();
|
|
45222
|
+
var RETRIEVE_MAX_BYTES = 10 * 1024 * 1024;
|
|
45223
|
+
var retrieve_summary = tool({
|
|
45224
|
+
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.",
|
|
45225
|
+
args: {
|
|
45226
|
+
id: tool.schema.string().describe("The summary ID to retrieve (e.g. S1, S2, S99). Must match pattern S followed by digits.")
|
|
45227
|
+
},
|
|
45228
|
+
async execute(args2, context) {
|
|
45229
|
+
const directory = context.directory;
|
|
45230
|
+
let sanitizedId;
|
|
45231
|
+
try {
|
|
45232
|
+
sanitizedId = sanitizeSummaryId(args2.id);
|
|
45233
|
+
} catch {
|
|
45234
|
+
return "Error: invalid summary ID format. Expected format: S followed by digits (e.g. S1, S2, S99).";
|
|
45235
|
+
}
|
|
45236
|
+
let fullOutput;
|
|
45237
|
+
try {
|
|
45238
|
+
fullOutput = await loadFullOutput(directory, sanitizedId);
|
|
45239
|
+
} catch {
|
|
45240
|
+
return "Error: failed to retrieve summary.";
|
|
45241
|
+
}
|
|
45242
|
+
if (fullOutput === null) {
|
|
45243
|
+
return `Summary \`${sanitizedId}\` not found. Use a valid summary ID (e.g. S1, S2).`;
|
|
45244
|
+
}
|
|
45245
|
+
if (fullOutput.length > RETRIEVE_MAX_BYTES) {
|
|
45246
|
+
return `Error: summary content exceeds maximum size limit (10 MB).`;
|
|
45247
|
+
}
|
|
45248
|
+
return fullOutput;
|
|
45249
|
+
}
|
|
45250
|
+
});
|
|
43709
45251
|
// src/tools/sbom-generate.ts
|
|
43710
45252
|
init_dist();
|
|
43711
45253
|
init_manager();
|
|
43712
|
-
import * as
|
|
43713
|
-
import * as
|
|
45254
|
+
import * as fs21 from "fs";
|
|
45255
|
+
import * as path27 from "path";
|
|
43714
45256
|
|
|
43715
45257
|
// src/sbom/detectors/dart.ts
|
|
43716
45258
|
function parsePubspecLock(content) {
|
|
@@ -44555,9 +46097,9 @@ function findManifestFiles(rootDir) {
|
|
|
44555
46097
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
44556
46098
|
function searchDir(dir) {
|
|
44557
46099
|
try {
|
|
44558
|
-
const entries =
|
|
46100
|
+
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
44559
46101
|
for (const entry of entries) {
|
|
44560
|
-
const fullPath =
|
|
46102
|
+
const fullPath = path27.join(dir, entry.name);
|
|
44561
46103
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
|
|
44562
46104
|
continue;
|
|
44563
46105
|
}
|
|
@@ -44567,7 +46109,7 @@ function findManifestFiles(rootDir) {
|
|
|
44567
46109
|
for (const pattern of patterns) {
|
|
44568
46110
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
44569
46111
|
if (new RegExp(regex, "i").test(entry.name)) {
|
|
44570
|
-
manifestFiles.push(
|
|
46112
|
+
manifestFiles.push(path27.relative(cwd, fullPath));
|
|
44571
46113
|
break;
|
|
44572
46114
|
}
|
|
44573
46115
|
}
|
|
@@ -44584,14 +46126,14 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
44584
46126
|
const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
|
|
44585
46127
|
for (const dir of directories) {
|
|
44586
46128
|
try {
|
|
44587
|
-
const entries =
|
|
46129
|
+
const entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
44588
46130
|
for (const entry of entries) {
|
|
44589
|
-
const fullPath =
|
|
46131
|
+
const fullPath = path27.join(dir, entry.name);
|
|
44590
46132
|
if (entry.isFile()) {
|
|
44591
46133
|
for (const pattern of patterns) {
|
|
44592
46134
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
44593
46135
|
if (new RegExp(regex, "i").test(entry.name)) {
|
|
44594
|
-
found.push(
|
|
46136
|
+
found.push(path27.relative(workingDir, fullPath));
|
|
44595
46137
|
break;
|
|
44596
46138
|
}
|
|
44597
46139
|
}
|
|
@@ -44604,11 +46146,11 @@ function findManifestFilesInDirs(directories, workingDir) {
|
|
|
44604
46146
|
function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
44605
46147
|
const dirs = new Set;
|
|
44606
46148
|
for (const file3 of changedFiles) {
|
|
44607
|
-
let currentDir =
|
|
46149
|
+
let currentDir = path27.dirname(file3);
|
|
44608
46150
|
while (true) {
|
|
44609
|
-
if (currentDir && currentDir !== "." && currentDir !==
|
|
44610
|
-
dirs.add(
|
|
44611
|
-
const parent =
|
|
46151
|
+
if (currentDir && currentDir !== "." && currentDir !== path27.sep) {
|
|
46152
|
+
dirs.add(path27.join(workingDir, currentDir));
|
|
46153
|
+
const parent = path27.dirname(currentDir);
|
|
44612
46154
|
if (parent === currentDir)
|
|
44613
46155
|
break;
|
|
44614
46156
|
currentDir = parent;
|
|
@@ -44622,7 +46164,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
|
|
|
44622
46164
|
}
|
|
44623
46165
|
function ensureOutputDir(outputDir) {
|
|
44624
46166
|
try {
|
|
44625
|
-
|
|
46167
|
+
fs21.mkdirSync(outputDir, { recursive: true });
|
|
44626
46168
|
} catch (error93) {
|
|
44627
46169
|
if (!error93 || error93.code !== "EEXIST") {
|
|
44628
46170
|
throw error93;
|
|
@@ -44715,11 +46257,11 @@ var sbom_generate = tool({
|
|
|
44715
46257
|
const processedFiles = [];
|
|
44716
46258
|
for (const manifestFile of manifestFiles) {
|
|
44717
46259
|
try {
|
|
44718
|
-
const fullPath =
|
|
44719
|
-
if (!
|
|
46260
|
+
const fullPath = path27.isAbsolute(manifestFile) ? manifestFile : path27.join(workingDir, manifestFile);
|
|
46261
|
+
if (!fs21.existsSync(fullPath)) {
|
|
44720
46262
|
continue;
|
|
44721
46263
|
}
|
|
44722
|
-
const content =
|
|
46264
|
+
const content = fs21.readFileSync(fullPath, "utf-8");
|
|
44723
46265
|
const components = detectComponents(manifestFile, content);
|
|
44724
46266
|
processedFiles.push(manifestFile);
|
|
44725
46267
|
if (components.length > 0) {
|
|
@@ -44732,8 +46274,8 @@ var sbom_generate = tool({
|
|
|
44732
46274
|
const bom = generateCycloneDX(allComponents);
|
|
44733
46275
|
const bomJson = serializeCycloneDX(bom);
|
|
44734
46276
|
const filename = generateSbomFilename();
|
|
44735
|
-
const outputPath =
|
|
44736
|
-
|
|
46277
|
+
const outputPath = path27.join(outputDir, filename);
|
|
46278
|
+
fs21.writeFileSync(outputPath, bomJson, "utf-8");
|
|
44737
46279
|
const verdict = processedFiles.length > 0 ? "pass" : "pass";
|
|
44738
46280
|
try {
|
|
44739
46281
|
const timestamp = new Date().toISOString();
|
|
@@ -44774,8 +46316,8 @@ var sbom_generate = tool({
|
|
|
44774
46316
|
});
|
|
44775
46317
|
// src/tools/schema-drift.ts
|
|
44776
46318
|
init_dist();
|
|
44777
|
-
import * as
|
|
44778
|
-
import * as
|
|
46319
|
+
import * as fs22 from "fs";
|
|
46320
|
+
import * as path28 from "path";
|
|
44779
46321
|
var SPEC_CANDIDATES = [
|
|
44780
46322
|
"openapi.json",
|
|
44781
46323
|
"openapi.yaml",
|
|
@@ -44807,28 +46349,28 @@ function normalizePath(p) {
|
|
|
44807
46349
|
}
|
|
44808
46350
|
function discoverSpecFile(cwd, specFileArg) {
|
|
44809
46351
|
if (specFileArg) {
|
|
44810
|
-
const resolvedPath =
|
|
44811
|
-
const normalizedCwd = cwd.endsWith(
|
|
46352
|
+
const resolvedPath = path28.resolve(cwd, specFileArg);
|
|
46353
|
+
const normalizedCwd = cwd.endsWith(path28.sep) ? cwd : cwd + path28.sep;
|
|
44812
46354
|
if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
|
|
44813
46355
|
throw new Error("Invalid spec_file: path traversal detected");
|
|
44814
46356
|
}
|
|
44815
|
-
const ext =
|
|
46357
|
+
const ext = path28.extname(resolvedPath).toLowerCase();
|
|
44816
46358
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
44817
46359
|
throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
|
|
44818
46360
|
}
|
|
44819
|
-
const stats =
|
|
46361
|
+
const stats = fs22.statSync(resolvedPath);
|
|
44820
46362
|
if (stats.size > MAX_SPEC_SIZE) {
|
|
44821
46363
|
throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
|
|
44822
46364
|
}
|
|
44823
|
-
if (!
|
|
46365
|
+
if (!fs22.existsSync(resolvedPath)) {
|
|
44824
46366
|
throw new Error(`Spec file not found: ${resolvedPath}`);
|
|
44825
46367
|
}
|
|
44826
46368
|
return resolvedPath;
|
|
44827
46369
|
}
|
|
44828
46370
|
for (const candidate of SPEC_CANDIDATES) {
|
|
44829
|
-
const candidatePath =
|
|
44830
|
-
if (
|
|
44831
|
-
const stats =
|
|
46371
|
+
const candidatePath = path28.resolve(cwd, candidate);
|
|
46372
|
+
if (fs22.existsSync(candidatePath)) {
|
|
46373
|
+
const stats = fs22.statSync(candidatePath);
|
|
44832
46374
|
if (stats.size <= MAX_SPEC_SIZE) {
|
|
44833
46375
|
return candidatePath;
|
|
44834
46376
|
}
|
|
@@ -44837,8 +46379,8 @@ function discoverSpecFile(cwd, specFileArg) {
|
|
|
44837
46379
|
return null;
|
|
44838
46380
|
}
|
|
44839
46381
|
function parseSpec(specFile) {
|
|
44840
|
-
const content =
|
|
44841
|
-
const ext =
|
|
46382
|
+
const content = fs22.readFileSync(specFile, "utf-8");
|
|
46383
|
+
const ext = path28.extname(specFile).toLowerCase();
|
|
44842
46384
|
if (ext === ".json") {
|
|
44843
46385
|
return parseJsonSpec(content);
|
|
44844
46386
|
}
|
|
@@ -44904,12 +46446,12 @@ function extractRoutes(cwd) {
|
|
|
44904
46446
|
function walkDir(dir) {
|
|
44905
46447
|
let entries;
|
|
44906
46448
|
try {
|
|
44907
|
-
entries =
|
|
46449
|
+
entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
44908
46450
|
} catch {
|
|
44909
46451
|
return;
|
|
44910
46452
|
}
|
|
44911
46453
|
for (const entry of entries) {
|
|
44912
|
-
const fullPath =
|
|
46454
|
+
const fullPath = path28.join(dir, entry.name);
|
|
44913
46455
|
if (entry.isSymbolicLink()) {
|
|
44914
46456
|
continue;
|
|
44915
46457
|
}
|
|
@@ -44919,7 +46461,7 @@ function extractRoutes(cwd) {
|
|
|
44919
46461
|
}
|
|
44920
46462
|
walkDir(fullPath);
|
|
44921
46463
|
} else if (entry.isFile()) {
|
|
44922
|
-
const ext =
|
|
46464
|
+
const ext = path28.extname(entry.name).toLowerCase();
|
|
44923
46465
|
const baseName = entry.name.toLowerCase();
|
|
44924
46466
|
if (![".ts", ".js", ".mjs"].includes(ext)) {
|
|
44925
46467
|
continue;
|
|
@@ -44937,7 +46479,7 @@ function extractRoutes(cwd) {
|
|
|
44937
46479
|
}
|
|
44938
46480
|
function extractRoutesFromFile(filePath) {
|
|
44939
46481
|
const routes = [];
|
|
44940
|
-
const content =
|
|
46482
|
+
const content = fs22.readFileSync(filePath, "utf-8");
|
|
44941
46483
|
const lines = content.split(/\r?\n/);
|
|
44942
46484
|
const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
44943
46485
|
const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
|
|
@@ -45084,8 +46626,8 @@ init_secretscan();
|
|
|
45084
46626
|
|
|
45085
46627
|
// src/tools/symbols.ts
|
|
45086
46628
|
init_tool();
|
|
45087
|
-
import * as
|
|
45088
|
-
import * as
|
|
46629
|
+
import * as fs23 from "fs";
|
|
46630
|
+
import * as path29 from "path";
|
|
45089
46631
|
var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
|
|
45090
46632
|
var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
|
|
45091
46633
|
function containsControlCharacters(str) {
|
|
@@ -45114,11 +46656,11 @@ function containsWindowsAttacks(str) {
|
|
|
45114
46656
|
}
|
|
45115
46657
|
function isPathInWorkspace(filePath, workspace) {
|
|
45116
46658
|
try {
|
|
45117
|
-
const resolvedPath =
|
|
45118
|
-
const realWorkspace =
|
|
45119
|
-
const realResolvedPath =
|
|
45120
|
-
const relativePath =
|
|
45121
|
-
if (relativePath.startsWith("..") ||
|
|
46659
|
+
const resolvedPath = path29.resolve(workspace, filePath);
|
|
46660
|
+
const realWorkspace = fs23.realpathSync(workspace);
|
|
46661
|
+
const realResolvedPath = fs23.realpathSync(resolvedPath);
|
|
46662
|
+
const relativePath = path29.relative(realWorkspace, realResolvedPath);
|
|
46663
|
+
if (relativePath.startsWith("..") || path29.isAbsolute(relativePath)) {
|
|
45122
46664
|
return false;
|
|
45123
46665
|
}
|
|
45124
46666
|
return true;
|
|
@@ -45130,17 +46672,17 @@ function validatePathForRead(filePath, workspace) {
|
|
|
45130
46672
|
return isPathInWorkspace(filePath, workspace);
|
|
45131
46673
|
}
|
|
45132
46674
|
function extractTSSymbols(filePath, cwd) {
|
|
45133
|
-
const fullPath =
|
|
46675
|
+
const fullPath = path29.join(cwd, filePath);
|
|
45134
46676
|
if (!validatePathForRead(fullPath, cwd)) {
|
|
45135
46677
|
return [];
|
|
45136
46678
|
}
|
|
45137
46679
|
let content;
|
|
45138
46680
|
try {
|
|
45139
|
-
const stats =
|
|
46681
|
+
const stats = fs23.statSync(fullPath);
|
|
45140
46682
|
if (stats.size > MAX_FILE_SIZE_BYTES7) {
|
|
45141
46683
|
throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
|
|
45142
46684
|
}
|
|
45143
|
-
content =
|
|
46685
|
+
content = fs23.readFileSync(fullPath, "utf-8");
|
|
45144
46686
|
} catch {
|
|
45145
46687
|
return [];
|
|
45146
46688
|
}
|
|
@@ -45282,17 +46824,17 @@ function extractTSSymbols(filePath, cwd) {
|
|
|
45282
46824
|
});
|
|
45283
46825
|
}
|
|
45284
46826
|
function extractPythonSymbols(filePath, cwd) {
|
|
45285
|
-
const fullPath =
|
|
46827
|
+
const fullPath = path29.join(cwd, filePath);
|
|
45286
46828
|
if (!validatePathForRead(fullPath, cwd)) {
|
|
45287
46829
|
return [];
|
|
45288
46830
|
}
|
|
45289
46831
|
let content;
|
|
45290
46832
|
try {
|
|
45291
|
-
const stats =
|
|
46833
|
+
const stats = fs23.statSync(fullPath);
|
|
45292
46834
|
if (stats.size > MAX_FILE_SIZE_BYTES7) {
|
|
45293
46835
|
throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
|
|
45294
46836
|
}
|
|
45295
|
-
content =
|
|
46837
|
+
content = fs23.readFileSync(fullPath, "utf-8");
|
|
45296
46838
|
} catch {
|
|
45297
46839
|
return [];
|
|
45298
46840
|
}
|
|
@@ -45364,7 +46906,7 @@ var symbols = tool({
|
|
|
45364
46906
|
}, null, 2);
|
|
45365
46907
|
}
|
|
45366
46908
|
const cwd = process.cwd();
|
|
45367
|
-
const ext =
|
|
46909
|
+
const ext = path29.extname(file3);
|
|
45368
46910
|
if (containsControlCharacters(file3)) {
|
|
45369
46911
|
return JSON.stringify({
|
|
45370
46912
|
file: file3,
|
|
@@ -45432,8 +46974,8 @@ init_test_runner();
|
|
|
45432
46974
|
|
|
45433
46975
|
// src/tools/todo-extract.ts
|
|
45434
46976
|
init_dist();
|
|
45435
|
-
import * as
|
|
45436
|
-
import * as
|
|
46977
|
+
import * as fs24 from "fs";
|
|
46978
|
+
import * as path30 from "path";
|
|
45437
46979
|
var MAX_TEXT_LENGTH = 200;
|
|
45438
46980
|
var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
|
|
45439
46981
|
var SUPPORTED_EXTENSIONS2 = new Set([
|
|
@@ -45504,9 +47046,9 @@ function validatePathsInput(paths, cwd) {
|
|
|
45504
47046
|
return { error: "paths contains path traversal", resolvedPath: null };
|
|
45505
47047
|
}
|
|
45506
47048
|
try {
|
|
45507
|
-
const resolvedPath =
|
|
45508
|
-
const normalizedCwd =
|
|
45509
|
-
const normalizedResolved =
|
|
47049
|
+
const resolvedPath = path30.resolve(paths);
|
|
47050
|
+
const normalizedCwd = path30.resolve(cwd);
|
|
47051
|
+
const normalizedResolved = path30.resolve(resolvedPath);
|
|
45510
47052
|
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
45511
47053
|
return {
|
|
45512
47054
|
error: "paths must be within the current working directory",
|
|
@@ -45522,13 +47064,13 @@ function validatePathsInput(paths, cwd) {
|
|
|
45522
47064
|
}
|
|
45523
47065
|
}
|
|
45524
47066
|
function isSupportedExtension(filePath) {
|
|
45525
|
-
const ext =
|
|
47067
|
+
const ext = path30.extname(filePath).toLowerCase();
|
|
45526
47068
|
return SUPPORTED_EXTENSIONS2.has(ext);
|
|
45527
47069
|
}
|
|
45528
47070
|
function findSourceFiles3(dir, files = []) {
|
|
45529
47071
|
let entries;
|
|
45530
47072
|
try {
|
|
45531
|
-
entries =
|
|
47073
|
+
entries = fs24.readdirSync(dir);
|
|
45532
47074
|
} catch {
|
|
45533
47075
|
return files;
|
|
45534
47076
|
}
|
|
@@ -45537,10 +47079,10 @@ function findSourceFiles3(dir, files = []) {
|
|
|
45537
47079
|
if (SKIP_DIRECTORIES3.has(entry)) {
|
|
45538
47080
|
continue;
|
|
45539
47081
|
}
|
|
45540
|
-
const fullPath =
|
|
47082
|
+
const fullPath = path30.join(dir, entry);
|
|
45541
47083
|
let stat;
|
|
45542
47084
|
try {
|
|
45543
|
-
stat =
|
|
47085
|
+
stat = fs24.statSync(fullPath);
|
|
45544
47086
|
} catch {
|
|
45545
47087
|
continue;
|
|
45546
47088
|
}
|
|
@@ -45633,7 +47175,7 @@ var todo_extract = tool({
|
|
|
45633
47175
|
return JSON.stringify(errorResult, null, 2);
|
|
45634
47176
|
}
|
|
45635
47177
|
const scanPath = resolvedPath;
|
|
45636
|
-
if (!
|
|
47178
|
+
if (!fs24.existsSync(scanPath)) {
|
|
45637
47179
|
const errorResult = {
|
|
45638
47180
|
error: `path not found: ${pathsInput}`,
|
|
45639
47181
|
total: 0,
|
|
@@ -45643,13 +47185,13 @@ var todo_extract = tool({
|
|
|
45643
47185
|
return JSON.stringify(errorResult, null, 2);
|
|
45644
47186
|
}
|
|
45645
47187
|
const filesToScan = [];
|
|
45646
|
-
const stat =
|
|
47188
|
+
const stat = fs24.statSync(scanPath);
|
|
45647
47189
|
if (stat.isFile()) {
|
|
45648
47190
|
if (isSupportedExtension(scanPath)) {
|
|
45649
47191
|
filesToScan.push(scanPath);
|
|
45650
47192
|
} else {
|
|
45651
47193
|
const errorResult = {
|
|
45652
|
-
error: `unsupported file extension: ${
|
|
47194
|
+
error: `unsupported file extension: ${path30.extname(scanPath)}`,
|
|
45653
47195
|
total: 0,
|
|
45654
47196
|
byPriority: { high: 0, medium: 0, low: 0 },
|
|
45655
47197
|
entries: []
|
|
@@ -45662,11 +47204,11 @@ var todo_extract = tool({
|
|
|
45662
47204
|
const allEntries = [];
|
|
45663
47205
|
for (const filePath of filesToScan) {
|
|
45664
47206
|
try {
|
|
45665
|
-
const fileStat =
|
|
47207
|
+
const fileStat = fs24.statSync(filePath);
|
|
45666
47208
|
if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
|
|
45667
47209
|
continue;
|
|
45668
47210
|
}
|
|
45669
|
-
const content =
|
|
47211
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
45670
47212
|
const entries = parseTodoComments(content, filePath, tagsSet);
|
|
45671
47213
|
allEntries.push(...entries);
|
|
45672
47214
|
} catch {}
|
|
@@ -45737,7 +47279,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
45737
47279
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
45738
47280
|
preflightTriggerManager = new PTM(automationConfig);
|
|
45739
47281
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
45740
|
-
const swarmDir =
|
|
47282
|
+
const swarmDir = path31.resolve(ctx.directory, ".swarm");
|
|
45741
47283
|
statusArtifact = new ASA(swarmDir);
|
|
45742
47284
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
45743
47285
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|
|
@@ -45840,6 +47382,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
45840
47382
|
lint,
|
|
45841
47383
|
diff,
|
|
45842
47384
|
pkg_audit,
|
|
47385
|
+
pre_check_batch,
|
|
45843
47386
|
retrieve_summary,
|
|
45844
47387
|
schema_drift,
|
|
45845
47388
|
secretscan,
|