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/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 ? ["npx", "biome", "check", "--write", "."] : ["npx", "biome", "check", "--write", "."];
29218
+ return isWindows ? [biomeBin, "check", "--write", "."] : [biomeBin, "check", "--write", "."];
29215
29219
  }
29216
- return isWindows ? ["npx", "biome", "check", "."] : ["npx", "biome", "check", "."];
29220
+ return isWindows ? [biomeBin, "check", "."] : [biomeBin, "check", "."];
29217
29221
  case "eslint":
29218
29222
  if (mode === "fix") {
29219
- return isWindows ? ["npx", "eslint", ".", "--fix"] : ["npx", "eslint", ".", "--fix"];
29223
+ return isWindows ? [eslintBin, ".", "--fix"] : [eslintBin, ".", "--fix"];
29220
29224
  }
29221
- return isWindows ? ["npx", "eslint", "."] : ["npx", "eslint", "."];
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 path10 from "path";
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 = path10.normalize(str);
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 = path10.extname(filePath).toLowerCase();
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 = path10.resolve(scanDir);
29542
- const resolvedRealPath = path10.resolve(realPath);
29543
- return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path10.sep) || resolvedRealPath.startsWith(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 = path10.join(dir, entry);
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 = path10.extname(fullPath).toLowerCase();
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 = path10.resolve(directory);
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 path11 from "path";
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 = path11.join(process.cwd(), "package.json");
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(path11.join(process.cwd(), "bun.lockb")) || fs7.existsSync(path11.join(process.cwd(), "bun.lock"))) {
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 = path11.join(process.cwd(), "pyproject.toml");
30122
- const setupCfgPath = path11.join(process.cwd(), "setup.cfg");
30123
- const requirementsTxtPath = path11.join(process.cwd(), "requirements.txt");
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 = path11.join(process.cwd(), "Cargo.toml");
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 = path11.join(process.cwd(), "pester.config.ps1");
30155
- const pesterConfigJsonPath = path11.join(process.cwd(), "pester.config.ps1.json");
30156
- const pesterPs1Path = path11.join(process.cwd(), "tests.ps1");
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 = path11.basename(file3);
30171
- const dirname4 = path11.dirname(file3);
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 = path11.extname(basename2);
30184
+ const ext = path12.extname(basename2);
30181
30185
  const possibleTestFiles = [
30182
- path11.join(dirname4, `${nameWithoutExt}.spec${ext}`),
30183
- path11.join(dirname4, `${nameWithoutExt}.test${ext}`),
30184
- path11.join(dirname4, "__tests__", `${nameWithoutExt}${ext}`),
30185
- path11.join(dirname4, "tests", `${nameWithoutExt}${ext}`),
30186
- path11.join(dirname4, "test", `${nameWithoutExt}${ext}`)
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 = path11.dirname(testFile);
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 = path11.resolve(testDir, importPath);
30214
- const existingExt = path11.extname(resolvedImport);
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 = path11.basename(resolvedImport, path11.extname(resolvedImport));
30235
- const importDir = path11.dirname(resolvedImport);
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 = path11.dirname(sourceFile);
30238
- const sourceBasename = path11.basename(sourceFile, path11.extname(sourceFile));
30239
- const isRelatedDir = importDir === sourceDir || importDir === path11.join(sourceDir, "__tests__") || importDir === path11.join(sourceDir, "tests") || importDir === path11.join(sourceDir, "test");
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 = path11.resolve(testDir, importPath);
30253
- const existingExt = path11.extname(resolvedImport);
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 = path11.dirname(resolvedImport);
30271
- const importBasename = path11.basename(resolvedImport, path11.extname(resolvedImport));
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 = path11.dirname(sourceFile);
30274
- const sourceBasename = path11.basename(sourceFile, path11.extname(sourceFile));
30275
- const isRelatedDir = importDir === sourceDir || importDir === path11.join(sourceDir, "__tests__") || importDir === path11.join(sourceDir, "tests") || importDir === path11.join(sourceDir, "test");
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 = path11.join(dir, entry);
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 = path11.extname(fullPath).toLowerCase();
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 = path11.extname(f).toLowerCase();
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 = path11.extname(f).toLowerCase();
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 path12 from "path";
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 = path12.normalize(dir);
30736
- const absolutePath = path12.isAbsolute(normalized) ? normalized : path12.resolve(normalized);
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 = path12.join(dir, "package.json");
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 = path12.join(dir, "CHANGELOG.md");
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 = path12.join(dir, file3);
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 path27 from "path";
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 imports \u2192 lint fix \u2192 lint check \u2192 secretscan \u2192 sast_scan \u2192 build_check \u2192 quality_budget \u2192 reviewer \u2192 security review \u2192 security-only review \u2192 verification tests \u2192 adversarial tests \u2192 coverage check \u2192 next task.
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 \`secretscan\` tool. FINDINGS \u2192 return to coder. NO FINDINGS \u2192 proceed to sast_scan.
31969
- - Run \`sast_scan\` tool. SAST FINDINGS AT OR ABOVE THRESHOLD \u2192 return to coder. NO FINDINGS \u2192 proceed to reviewer.
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 \`secretscan\` tool. FINDINGS \u2192 return to coder. NO FINDINGS \u2192 proceed to sast_scan.
32154
- 5i. Run \`sast_scan\` tool. SAST FINDINGS AT OR ABOVE THRESHOLD \u2192 return to coder. NO FINDINGS \u2192 proceed to build_check.
32155
- 5j. Run \`build_check\` tool. BUILD FAILURES \u2192 return to coder. SKIPPED (no toolchain) \u2192 proceed. PASSED \u2192 proceed to quality_budget.
32156
- 5k. Run \`quality_budget\` tool. QUALITY VIOLATIONS \u2192 return to coder. WITHIN BUDGET \u2192 proceed to reviewer.
32157
- 5l. {{AGENT_PREFIX}}reviewer - General review. REJECTED (< {{QA_RETRY_LIMIT}}) \u2192 coder retry. REJECTED ({{QA_RETRY_LIMIT}}) \u2192 escalate.
32158
- 5m. 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.
32159
- 5n. {{AGENT_PREFIX}}test_engineer - Verification tests. FAIL \u2192 coder retry from 5g.
32160
- 5o. {{AGENT_PREFIX}}test_engineer - Adversarial tests. FAIL \u2192 coder retry from 5g.
32161
- 5p. 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.
32162
- 5q. Update plan.md [x], proceed to next task.
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 path13 from "path";
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 = path13.join("summaries", `${sanitizedId}.json`);
35191
+ const relativePath = path14.join("summaries", `${sanitizedId}.json`);
35170
35192
  const summaryPath = validateSwarmPath(directory, relativePath);
35171
- const summaryDir = path13.dirname(summaryPath);
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 = path13.join(summaryDir, `${sanitizedId}.json.tmp.${Date.now()}.${process.pid}`);
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 = path13.join("summaries", `${sanitizedId}.json`);
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 path14 = `${directory}/.swarm/context.md`;
35699
- await Bun.write(path14, updated);
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 path15 from "path";
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 path14 from "path";
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 = path14.join(directory, ".swarm", "context.md");
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 = path15.join(directory, ".swarm", "evidence");
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(path15.join(evidenceDir, file3), "utf-8"));
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 = path15.join(directory, ".swarm", "evidence");
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(path15.join(evidenceDir_b, file3), "utf-8"));
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 path16 from "path";
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 path16.join(dir, matches[0]);
37374
+ return path17.join(dir, matches[0]);
37329
37375
  }
37330
37376
  } catch {}
37331
37377
  } else {
37332
- const filePath = path16.join(workingDir, pattern);
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 = path16.join(workingDir, "package.json");
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 = path16.join(workingDir, pattern);
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 = path16.join(dir, entry.name);
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 path17 from "path";
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 path17.join(process.cwd(), CHECKPOINT_LOG_PATH);
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 = path17.dirname(logPath);
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 path18 from "path";
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 = path18.extname(file3).toLowerCase();
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 = path18.join(cwd, file3);
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 path19 of paths) {
38226
- if (!path19 || path19.length === 0) {
38271
+ for (const path20 of paths) {
38272
+ if (!path20 || path20.length === 0) {
38227
38273
  return "empty path not allowed";
38228
38274
  }
38229
- if (path19.length > MAX_PATH_LENGTH) {
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(path19)) {
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 path19 = parts2[2];
38296
- files.push({ path: path19, additions, deletions });
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 path19 from "path";
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 = path19.resolve(cwd);
38549
- const swarmPath = path19.join(normalizedCwd, ".swarm");
38550
- const normalizedPath = path19.resolve(filePath);
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 = path19.join(evidenceDir, filename);
38627
+ const filePath = path20.join(evidenceDir, filename);
38582
38628
  try {
38583
- const resolvedPath = path19.resolve(filePath);
38584
- const evidenceDirResolved = path19.resolve(evidenceDir);
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 = path19.join(cwd, PLAN_FILE);
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 = path19.join(cwd, EVIDENCE_DIR);
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 path20 from "path";
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 = path20.join(targetDir, filename);
38819
- const base = path20.basename(filepath, path20.extname(filepath));
38820
- const ext = path20.extname(filepath);
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 = path20.join(targetDir, `${base}_${counter}${ext}`);
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 path21 from "path";
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 = path21.extname(filePath).toLowerCase();
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 = path21.resolve(targetFile);
39060
+ resolvedTarget = path22.resolve(targetFile);
39015
39061
  } catch {
39016
39062
  resolvedTarget = targetFile;
39017
39063
  }
39018
- const targetBasename = path21.basename(targetFile, path21.extname(targetFile));
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 = path21.normalize(targetWithExt).replace(/\\/g, "/");
39022
- const normalizedTargetWithoutExt = path21.normalize(targetWithoutExt).replace(/\\/g, "/");
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 = path21.dirname(targetFile);
39047
- const targetExt = path21.extname(targetFile);
39048
- const targetBasenameNoExt = path21.basename(targetFile, targetExt);
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(path21.join(dir, entry));
39162
+ stats.skippedDirs.push(path22.join(dir, entry));
39117
39163
  continue;
39118
39164
  }
39119
- const fullPath = path21.join(dir, entry);
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 = path21.extname(fullPath).toLowerCase();
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 = path21.resolve(file3);
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 = path21.dirname(targetFile);
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 path22 from "path";
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(path22.join(cwd, "package.json"))) {
39365
+ if (fs18.existsSync(path23.join(cwd, "package.json"))) {
39320
39366
  ecosystems.push("npm");
39321
39367
  }
39322
- if (fs18.existsSync(path22.join(cwd, "pyproject.toml")) || fs18.existsSync(path22.join(cwd, "requirements.txt"))) {
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(path22.join(cwd, "Cargo.toml"))) {
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(path23) {
41279
+ function locateFile(path24) {
41234
41280
  if (Module["locateFile"]) {
41235
- return Module["locateFile"](path23, scriptDirectory);
41281
+ return Module["locateFile"](path24, scriptDirectory);
41236
41282
  }
41237
- return scriptDirectory + path23;
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
- // src/tools/retrieve-summary.ts
43052
- init_dist();
43053
- var RETRIEVE_MAX_BYTES = 10 * 1024 * 1024;
43054
- var retrieve_summary = tool({
43055
- 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.",
43056
- args: {
43057
- id: tool.schema.string().describe("The summary ID to retrieve (e.g. S1, S2, S99). Must match pattern S followed by digits.")
43058
- },
43059
- async execute(args2, context) {
43060
- const directory = context.directory;
43061
- let sanitizedId;
43062
- try {
43063
- sanitizedId = sanitizeSummaryId(args2.id);
43064
- } catch {
43065
- return "Error: invalid summary ID format. Expected format: S followed by digits (e.g. S1, S2, S99).";
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
- fullOutput = await loadFullOutput(directory, sanitizedId);
43070
- } catch {
43071
- return "Error: failed to retrieve summary.";
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
- if (fullOutput === null) {
43074
- return `Summary \`${sanitizedId}\` not found. Use a valid summary ID (e.g. S1, S2).`;
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
- if (fullOutput.length > RETRIEVE_MAX_BYTES) {
43077
- return `Error: summary content exceeds maximum size limit (10 MB).`;
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
- // src/tools/sast-scan.ts
43083
- init_manager();
43084
-
43085
- // src/sast/rules/c.ts
43086
- var cRules = [
43087
- {
43088
- id: "sast/c-buffer-overflow",
43089
- name: "Buffer overflow vulnerability",
43090
- severity: "critical",
43091
- languages: ["c", "cpp"],
43092
- description: "strcpy/strcat to fixed-size buffer without bounds checking",
43093
- remediation: "Use strncpy, snprintf, or safer alternatives that include size limits.",
43094
- pattern: /\b(?:strcpy|strcat)\s*\(\s*[a-zA-Z_]/
43095
- },
43096
- {
43097
- id: "sast/c-gets",
43098
- name: "Unsafe gets() usage",
43099
- severity: "critical",
43100
- languages: ["c", "cpp"],
43101
- description: "gets() does not check buffer bounds - removed from C11",
43102
- remediation: "Use fgets() with proper buffer size instead of gets().",
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 fs19 from "fs";
43713
- import * as path23 from "path";
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 = fs19.readdirSync(dir, { withFileTypes: true });
46100
+ const entries = fs21.readdirSync(dir, { withFileTypes: true });
44559
46101
  for (const entry of entries) {
44560
- const fullPath = path23.join(dir, entry.name);
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(path23.relative(cwd, fullPath));
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 = fs19.readdirSync(dir, { withFileTypes: true });
46129
+ const entries = fs21.readdirSync(dir, { withFileTypes: true });
44588
46130
  for (const entry of entries) {
44589
- const fullPath = path23.join(dir, entry.name);
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(path23.relative(workingDir, fullPath));
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 = path23.dirname(file3);
46149
+ let currentDir = path27.dirname(file3);
44608
46150
  while (true) {
44609
- if (currentDir && currentDir !== "." && currentDir !== path23.sep) {
44610
- dirs.add(path23.join(workingDir, currentDir));
44611
- const parent = path23.dirname(currentDir);
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
- fs19.mkdirSync(outputDir, { recursive: true });
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 = path23.isAbsolute(manifestFile) ? manifestFile : path23.join(workingDir, manifestFile);
44719
- if (!fs19.existsSync(fullPath)) {
46260
+ const fullPath = path27.isAbsolute(manifestFile) ? manifestFile : path27.join(workingDir, manifestFile);
46261
+ if (!fs21.existsSync(fullPath)) {
44720
46262
  continue;
44721
46263
  }
44722
- const content = fs19.readFileSync(fullPath, "utf-8");
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 = path23.join(outputDir, filename);
44736
- fs19.writeFileSync(outputPath, bomJson, "utf-8");
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 fs20 from "fs";
44778
- import * as path24 from "path";
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 = path24.resolve(cwd, specFileArg);
44811
- const normalizedCwd = cwd.endsWith(path24.sep) ? cwd : cwd + path24.sep;
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 = path24.extname(resolvedPath).toLowerCase();
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 = fs20.statSync(resolvedPath);
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 (!fs20.existsSync(resolvedPath)) {
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 = path24.resolve(cwd, candidate);
44830
- if (fs20.existsSync(candidatePath)) {
44831
- const stats = fs20.statSync(candidatePath);
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 = fs20.readFileSync(specFile, "utf-8");
44841
- const ext = path24.extname(specFile).toLowerCase();
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 = fs20.readdirSync(dir, { withFileTypes: true });
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 = path24.join(dir, entry.name);
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 = path24.extname(entry.name).toLowerCase();
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 = fs20.readFileSync(filePath, "utf-8");
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 fs21 from "fs";
45088
- import * as path25 from "path";
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 = path25.resolve(workspace, filePath);
45118
- const realWorkspace = fs21.realpathSync(workspace);
45119
- const realResolvedPath = fs21.realpathSync(resolvedPath);
45120
- const relativePath = path25.relative(realWorkspace, realResolvedPath);
45121
- if (relativePath.startsWith("..") || path25.isAbsolute(relativePath)) {
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 = path25.join(cwd, filePath);
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 = fs21.statSync(fullPath);
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 = fs21.readFileSync(fullPath, "utf-8");
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 = path25.join(cwd, filePath);
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 = fs21.statSync(fullPath);
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 = fs21.readFileSync(fullPath, "utf-8");
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 = path25.extname(file3);
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 fs22 from "fs";
45436
- import * as path26 from "path";
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 = path26.resolve(paths);
45508
- const normalizedCwd = path26.resolve(cwd);
45509
- const normalizedResolved = path26.resolve(resolvedPath);
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 = path26.extname(filePath).toLowerCase();
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 = fs22.readdirSync(dir);
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 = path26.join(dir, entry);
47082
+ const fullPath = path30.join(dir, entry);
45541
47083
  let stat;
45542
47084
  try {
45543
- stat = fs22.statSync(fullPath);
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 (!fs22.existsSync(scanPath)) {
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 = fs22.statSync(scanPath);
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: ${path26.extname(scanPath)}`,
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 = fs22.statSync(filePath);
47207
+ const fileStat = fs24.statSync(filePath);
45666
47208
  if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
45667
47209
  continue;
45668
47210
  }
45669
- const content = fs22.readFileSync(filePath, "utf-8");
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 = path27.resolve(ctx.directory, ".swarm");
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,