opencode-swarm 6.73.1 → 6.74.1

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
@@ -14365,7 +14365,7 @@ var init_zod = __esm(() => {
14365
14365
  });
14366
14366
 
14367
14367
  // src/config/evidence-schema.ts
14368
- var EVIDENCE_MAX_JSON_BYTES, EVIDENCE_MAX_PATCH_BYTES, EVIDENCE_MAX_TASK_BYTES, EvidenceTypeSchema, EvidenceVerdictSchema, BaseEvidenceSchema, ReviewEvidenceSchema, TestEvidenceSchema, DiffEvidenceSchema, ApprovalEvidenceSchema, NoteEvidenceSchema, RetrospectiveEvidenceSchema, SyntaxEvidenceSchema, PlaceholderEvidenceSchema, SastEvidenceSchema, SbomEvidenceSchema, BuildEvidenceSchema, QualityBudgetEvidenceSchema, SecretscanEvidenceSchema, EvidenceSchema, EvidenceBundleSchema;
14368
+ var EVIDENCE_MAX_JSON_BYTES, EVIDENCE_MAX_PATCH_BYTES, EVIDENCE_MAX_TASK_BYTES, EvidenceTypeSchema, EvidenceVerdictSchema, BaseEvidenceSchema, ReviewEvidenceSchema, TestEvidenceSchema, DiffEvidenceSchema, ApprovalEvidenceSchema, NoteEvidenceSchema, RetrospectiveEvidenceSchema, SyntaxEvidenceSchema, PlaceholderEvidenceSchema, SastFindingSchema, SastEvidenceSchema, SbomEvidenceSchema, BuildEvidenceSchema, QualityBudgetEvidenceSchema, SecretscanEvidenceSchema, EvidenceSchema, EvidenceBundleSchema;
14369
14369
  var init_evidence_schema = __esm(() => {
14370
14370
  init_zod();
14371
14371
  EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
@@ -14504,19 +14504,20 @@ var init_evidence_schema = __esm(() => {
14504
14504
  files_with_findings: exports_external.number().int(),
14505
14505
  findings_count: exports_external.number().int()
14506
14506
  });
14507
+ SastFindingSchema = exports_external.object({
14508
+ rule_id: exports_external.string(),
14509
+ severity: exports_external.enum(["critical", "high", "medium", "low"]),
14510
+ message: exports_external.string(),
14511
+ location: exports_external.object({
14512
+ file: exports_external.string(),
14513
+ line: exports_external.number().int(),
14514
+ column: exports_external.number().int().optional()
14515
+ }),
14516
+ remediation: exports_external.string().optional()
14517
+ });
14507
14518
  SastEvidenceSchema = BaseEvidenceSchema.extend({
14508
14519
  type: exports_external.literal("sast"),
14509
- findings: exports_external.array(exports_external.object({
14510
- rule_id: exports_external.string(),
14511
- severity: exports_external.enum(["critical", "high", "medium", "low"]),
14512
- message: exports_external.string(),
14513
- location: exports_external.object({
14514
- file: exports_external.string(),
14515
- line: exports_external.number().int(),
14516
- column: exports_external.number().int().optional()
14517
- }),
14518
- remediation: exports_external.string().optional()
14519
- })).default([]),
14520
+ findings: exports_external.array(SastFindingSchema).default([]),
14520
14521
  engine: exports_external.enum(["tier_a", "tier_a+tier_b"]),
14521
14522
  files_scanned: exports_external.number().int(),
14522
14523
  findings_count: exports_external.number().int(),
@@ -14525,7 +14526,10 @@ var init_evidence_schema = __esm(() => {
14525
14526
  high: exports_external.number().int(),
14526
14527
  medium: exports_external.number().int(),
14527
14528
  low: exports_external.number().int()
14528
- })
14529
+ }),
14530
+ new_findings: exports_external.array(SastFindingSchema).optional(),
14531
+ pre_existing_findings: exports_external.array(SastFindingSchema).optional(),
14532
+ baseline_used: exports_external.boolean().optional()
14529
14533
  });
14530
14534
  SbomEvidenceSchema = BaseEvidenceSchema.extend({
14531
14535
  type: exports_external.literal("sbom"),
@@ -54529,6 +54533,9 @@ All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
54529
54533
  5a-bis. **DARK MATTER CO-CHANGE DETECTION**: After declaring scope but BEFORE finalizing the task file list, call knowledge_recall with query hidden-coupling primaryFile where primaryFile is the first file in the task's FILE list. Extract primaryFile from the task's FILE list (first file = primary). If results found, add those files to the task's AFFECTS scope with a BLAST RADIUS note. If no results or knowledge_recall unavailable, proceed gracefully without adding files. This is advisory \u2014 the architect may exclude files from scope if they are unrelated to the current task. Delegate to {{AGENT_PREFIX}}coder only after scope is declared.
54530
54534
 
54531
54535
  5b-PRE (required): Call \`declare_scope({ taskId, files })\` with the EXACT file list for this task \u2014 including any co-change files surfaced by 5a-bis. Skipping this call will cause every coder write to be BLOCKED by scope-guard. No \`declare_scope\` \u2192 no 5b delegation. See Rule 1a.
54536
+ 5b-BASE (required, once per task): Call \`sast_scan\` with \`{ capture_baseline: true, phase: <N>, changed_files: <files from 5b-PRE> }\` where \`<N>\` is the current phase number (extract from current task ID: task "3.2" \u2192 phase 3, task "1.5" \u2192 phase 1). The tool maintains \`.swarm/evidence/{phase}/sast-baseline.json\` as a phase-scoped, incrementally merged baseline of pre-existing SAST findings. Calling twice for the same files is safe (idempotent merge). Do NOT re-capture mid-task.
54537
+ \u2192 REQUIRED: Print "sast-baseline: [WRITTEN \u2014 N fingerprints | MERGED \u2014 N fingerprints | SKIPPED \u2014 gate disabled | ERROR \u2014 details]"
54538
+ \u2192 Subsequent \`pre_check_batch\` calls with \`phase: <N>\` will automatically diff against this baseline \u2014 only NEW findings (not in baseline) drive the fail verdict.
54532
54539
  5b. {{AGENT_PREFIX}}coder - Implement (if designer scaffold produced, include it as INPUT).
54533
54540
  5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. If COMPATIBILITY SIGNALS=INCOMPATIBLE or MIGRATION_SURFACE=yes \u2192 coder retry. If COMPATIBILITY SIGNALS=COMPATIBLE and MIGRATION_SURFACE=no \u2192 proceed.
54534
54541
  \u2192 REQUIRED: Print "diff: [PASS | CONTRACT CHANGE \u2014 details]"
@@ -54542,18 +54549,19 @@ All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
54542
54549
  \u2192 REQUIRED: Print "lint: [PASS | FAIL \u2014 details]"
54543
54550
  5h. Run \`build_check\` tool. BUILD FAILS \u2192 return to coder. SUCCESS \u2192 proceed to pre_check_batch.
54544
54551
  \u2192 REQUIRED: Print "buildcheck: [PASS | FAIL | SKIPPED \u2014 no toolchain]"
54545
- 5i. Run \`pre_check_batch\` tool \u2192 runs four verification tools in parallel (max 4 concurrent):
54552
+ 5i. Run \`pre_check_batch\` tool with \`phase: <N>\` (same phase number used in 5b-BASE) \u2192 runs four verification tools in parallel (max 4 concurrent):
54546
54553
  - lint:check (code quality verification)
54547
54554
  - secretscan (secret detection)
54548
- - sast_scan (static security analysis)
54555
+ - sast_scan (static security analysis \u2014 diffs against phase baseline when phase provided)
54549
54556
  - quality_budget (maintainability metrics)
54550
54557
  \u2192 Returns { gates_passed, lint, secretscan, sast_scan, quality_budget, total_duration_ms }
54558
+ \u2192 sast_scan result may include { new_findings, pre_existing_findings, baseline_used } when baseline diff is active.
54551
54559
  \u2192 If ALL FOUR tools have ran === false (lint.ran === false && secretscan.ran === false && sast_scan.ran === false && quality_budget.ran === false):
54552
54560
  \u2192 This is a SKIP - no tools actually ran. Print "pre_check_batch: SKIP \u2014 all tools ran===false (no files to check or tools not available)" and proceed to {{AGENT_PREFIX}}reviewer.
54553
54561
  \u2192 Else if gates_passed === false: read individual tool results, identify which tool(s) failed, return structured rejection to {{AGENT_PREFIX}}coder with specific tool failures. Do NOT call {{AGENT_PREFIX}}reviewer.
54554
- \u2192 If gates_passed === true AND sast_preexisting_findings is present: proceed to {{AGENT_PREFIX}}reviewer. Include the pre-existing SAST findings in the reviewer delegation context with instruction: "SAST TRIAGE REQUIRED: The following HIGH/CRITICAL SAST findings exist on unchanged lines in this changeset. Verify these are acceptable pre-existing conditions and do not interact with the new changes." Do NOT return to coder for pre-existing findings on unchanged code.
54562
+ \u2192 If gates_passed === true AND sast_preexisting_findings is present: proceed to {{AGENT_PREFIX}}reviewer. Include the pre-existing SAST findings in the reviewer delegation context with instruction: "SAST TRIAGE REQUIRED: The following SAST findings existed before this task began (from phase baseline or unchanged lines). Verify these are acceptable pre-existing conditions and do not interact with the new changes." Do NOT return to coder for pre-existing findings.
54555
54563
  \u2192 If gates_passed === true (no sast_preexisting_findings): proceed to {{AGENT_PREFIX}}reviewer.
54556
- \u2192 REQUIRED: Print "pre_check_batch: [PASS \u2014 all gates passed | PASS \u2014 pre-existing SAST findings on unchanged lines (N findings, reviewer triage) | FAIL \u2014 [gate]: [details]]"
54564
+ \u2192 REQUIRED: Print "pre_check_batch: [PASS \u2014 all gates passed | PASS \u2014 pre-existing SAST findings (N findings, reviewer triage) | FAIL \u2014 [gate]: [details]]"
54557
54565
 
54558
54566
  \u26A0\uFE0F pre_check_batch SCOPE BOUNDARY:
54559
54567
  pre_check_batch runs FOUR automated tools: lint:check, secretscan, sast_scan, quality_budget.
@@ -61583,7 +61591,7 @@ var init_runtime = __esm(() => {
61583
61591
 
61584
61592
  // src/index.ts
61585
61593
  init_agents();
61586
- import * as path95 from "path";
61594
+ import * as path96 from "path";
61587
61595
 
61588
61596
  // src/background/index.ts
61589
61597
  init_event_bus();
@@ -64037,7 +64045,8 @@ function validateGraphNode(node) {
64037
64045
  throw new Error("Invalid node: exports must be an array of strings");
64038
64046
  }
64039
64047
  if (containsControlChars(exp)) {
64040
- throw new Error("Invalid node: exports contains control characters");
64048
+ const preview = exp.slice(0, 120);
64049
+ throw new Error(`Invalid node: exports contains control characters (file=${node.filePath}, value="${preview}")`);
64041
64050
  }
64042
64051
  }
64043
64052
  if (!Array.isArray(node.imports)) {
@@ -64048,7 +64057,8 @@ function validateGraphNode(node) {
64048
64057
  throw new Error("Invalid node: imports must be an array of strings");
64049
64058
  }
64050
64059
  if (containsControlChars(imp)) {
64051
- throw new Error("Invalid node: imports contains control characters");
64060
+ const preview = imp.slice(0, 120);
64061
+ throw new Error(`Invalid node: imports contains control characters (file=${node.filePath}, value="${preview}")`);
64052
64062
  }
64053
64063
  }
64054
64064
  }
@@ -64405,11 +64415,13 @@ var EXTENSION_TO_LANGUAGE = {
64405
64415
  };
64406
64416
  function parseFileImports(content) {
64407
64417
  const imports = [];
64408
- const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)|export\s*\{[^}]*\}\s*from\s+['"`]([^'"`]+)['"`]|export\s+\*(?:\s+as\s+\w+)?\s+from\s+['"`]([^'"`]+)['"`]|import\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
64418
+ const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`\0\t\r\n]+)['"`]|import\s+['"`]([^'"`\0\t\r\n]+)['"`]|require\s*\(\s*['"`]([^'"`\0\t\r\n]+)['"`]\s*\)|export\s*\{[^}]*\}\s*from\s+['"`]([^'"`\0\t\r\n]+)['"`]|export\s+\*(?:\s+as\s+\w+)?\s+from\s+['"`]([^'"`\0\t\r\n]+)['"`]|import\s*\(\s*['"`]([^'"`\0\t\r\n]+)['"`]\s*\)/g;
64409
64419
  for (const match of content.matchAll(importRegex)) {
64410
64420
  const modulePath = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
64411
64421
  if (!modulePath)
64412
64422
  continue;
64423
+ if (containsControlChars(modulePath))
64424
+ continue;
64413
64425
  const matchedString = match[0];
64414
64426
  let importType = "named";
64415
64427
  if (matchedString.includes("* as")) {
@@ -76602,8 +76614,8 @@ var placeholder_scan = createSwarmTool({
76602
76614
  });
76603
76615
  // src/tools/pre-check-batch.ts
76604
76616
  init_dist();
76605
- import * as fs64 from "fs";
76606
- import * as path78 from "path";
76617
+ import * as fs65 from "fs";
76618
+ import * as path79 from "path";
76607
76619
  init_manager2();
76608
76620
  init_utils();
76609
76621
  init_create_tool();
@@ -76738,8 +76750,8 @@ var quality_budget = createSwarmTool({
76738
76750
  init_dist();
76739
76751
  init_manager2();
76740
76752
  init_detector();
76741
- import * as fs63 from "fs";
76742
- import * as path77 from "path";
76753
+ import * as fs64 from "fs";
76754
+ import * as path78 from "path";
76743
76755
  import { extname as extname18 } from "path";
76744
76756
 
76745
76757
  // src/sast/rules/c.ts
@@ -77628,6 +77640,303 @@ async function runSemgrep(options) {
77628
77640
  // src/tools/sast-scan.ts
77629
77641
  init_utils();
77630
77642
  init_create_tool();
77643
+
77644
+ // src/tools/sast-baseline.ts
77645
+ init_utils2();
77646
+ import * as crypto8 from "crypto";
77647
+ import * as fs63 from "fs";
77648
+ import * as path77 from "path";
77649
+ var BASELINE_SCHEMA_VERSION = "1.0.0";
77650
+ var MAX_BASELINE_FINDINGS = 2000;
77651
+ var MAX_BASELINE_BYTES = 2 * 1048576;
77652
+ var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
77653
+ function normalizeFindingPath(directory, file3) {
77654
+ const resolved = path77.isAbsolute(file3) ? file3 : path77.resolve(directory, file3);
77655
+ const rel = path77.relative(path77.resolve(directory), resolved);
77656
+ return rel.replace(/\\/g, "/");
77657
+ }
77658
+ function baselineRelPath(phase) {
77659
+ return path77.join("evidence", String(phase), "sast-baseline.json");
77660
+ }
77661
+ function tempRelPath(phase) {
77662
+ return path77.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
77663
+ }
77664
+ function lockRelPath(phase) {
77665
+ return path77.join("evidence", String(phase), "sast-baseline.json.lock");
77666
+ }
77667
+ function getLine(lines, idx) {
77668
+ if (idx < 0 || idx >= lines.length)
77669
+ return "";
77670
+ return (lines[idx] ?? "").trim();
77671
+ }
77672
+ function fingerprintFinding(finding, directory, occurrenceIndex) {
77673
+ const relFile = normalizeFindingPath(directory, finding.location.file);
77674
+ if (relFile.startsWith("..")) {
77675
+ return {
77676
+ fingerprint: `${relFile}|${finding.rule_id}|L${finding.location.line}|UNSTABLE|#${occurrenceIndex}`,
77677
+ stable: false
77678
+ };
77679
+ }
77680
+ const lineNum = finding.location.line;
77681
+ try {
77682
+ const content = fs63.readFileSync(finding.location.file, "utf-8");
77683
+ const lines = content.split(`
77684
+ `);
77685
+ const idx = lineNum - 1;
77686
+ const window2 = [
77687
+ getLine(lines, idx - 1),
77688
+ getLine(lines, idx),
77689
+ getLine(lines, idx + 1)
77690
+ ].join(`
77691
+ `);
77692
+ const hash3 = crypto8.createHash("sha256").update(window2).digest("hex").slice(0, 16);
77693
+ return {
77694
+ fingerprint: `${relFile}|${finding.rule_id}|${hash3}|#${occurrenceIndex}`,
77695
+ stable: true
77696
+ };
77697
+ } catch {
77698
+ return {
77699
+ fingerprint: `${relFile}|${finding.rule_id}|L${lineNum}|UNSTABLE|#${occurrenceIndex}`,
77700
+ stable: false
77701
+ };
77702
+ }
77703
+ }
77704
+ function assignOccurrenceIndices(findings, directory) {
77705
+ const countMap = new Map;
77706
+ return findings.map((finding) => {
77707
+ const relFile = normalizeFindingPath(directory, finding.location.file);
77708
+ const lineNum = finding.location.line;
77709
+ let baseKey;
77710
+ try {
77711
+ if (relFile.startsWith(".."))
77712
+ throw new Error("escapes workspace");
77713
+ const content = fs63.readFileSync(finding.location.file, "utf-8");
77714
+ const lines = content.split(`
77715
+ `);
77716
+ const idx = lineNum - 1;
77717
+ const window2 = [
77718
+ getLine(lines, idx - 1),
77719
+ getLine(lines, idx),
77720
+ getLine(lines, idx + 1)
77721
+ ].join(`
77722
+ `);
77723
+ const hash3 = crypto8.createHash("sha256").update(window2).digest("hex").slice(0, 16);
77724
+ baseKey = `${relFile}|${finding.rule_id}|${hash3}`;
77725
+ } catch {
77726
+ baseKey = `${relFile}|${finding.rule_id}|L${lineNum}|UNSTABLE`;
77727
+ }
77728
+ const occIdx = countMap.get(baseKey) ?? 0;
77729
+ countMap.set(baseKey, occIdx + 1);
77730
+ const fp = fingerprintFinding(finding, directory, occIdx);
77731
+ return {
77732
+ finding,
77733
+ index: occIdx,
77734
+ stable: fp.stable,
77735
+ fingerprint: fp.fingerprint
77736
+ };
77737
+ });
77738
+ }
77739
+ async function acquireLock(lockPath) {
77740
+ for (let attempt = 0;attempt <= LOCK_RETRY_DELAYS_MS.length; attempt++) {
77741
+ try {
77742
+ const fd = fs63.openSync(lockPath, "wx");
77743
+ fs63.closeSync(fd);
77744
+ return () => {
77745
+ try {
77746
+ fs63.unlinkSync(lockPath);
77747
+ } catch {}
77748
+ };
77749
+ } catch {
77750
+ if (attempt < LOCK_RETRY_DELAYS_MS.length) {
77751
+ await new Promise((resolve31) => setTimeout(resolve31, LOCK_RETRY_DELAYS_MS[attempt]));
77752
+ }
77753
+ }
77754
+ }
77755
+ return () => {};
77756
+ }
77757
+ function validatePhase(phase) {
77758
+ if (!Number.isInteger(phase) || phase < 1) {
77759
+ return "Invalid phase: must be a positive integer";
77760
+ }
77761
+ return null;
77762
+ }
77763
+ async function captureOrMergeBaseline(directory, phase, findings, engine, scannedFiles, opts) {
77764
+ const phaseError = validatePhase(phase);
77765
+ if (phaseError)
77766
+ return { status: "error", message: phaseError };
77767
+ if (!scannedFiles || scannedFiles.length === 0) {
77768
+ return {
77769
+ status: "error",
77770
+ message: "capture_baseline requires non-empty changed_files"
77771
+ };
77772
+ }
77773
+ let baselinePath;
77774
+ let tempPath;
77775
+ let lockPath;
77776
+ try {
77777
+ baselinePath = validateSwarmPath(directory, baselineRelPath(phase));
77778
+ tempPath = validateSwarmPath(directory, tempRelPath(phase));
77779
+ lockPath = validateSwarmPath(directory, lockRelPath(phase));
77780
+ } catch (e) {
77781
+ return {
77782
+ status: "error",
77783
+ message: e instanceof Error ? e.message : "Path validation failed"
77784
+ };
77785
+ }
77786
+ fs63.mkdirSync(path77.dirname(baselinePath), { recursive: true });
77787
+ const releaseLock = await acquireLock(lockPath);
77788
+ try {
77789
+ let existing = null;
77790
+ try {
77791
+ const raw = fs63.readFileSync(baselinePath, "utf-8");
77792
+ const parsed = JSON.parse(raw);
77793
+ if (parsed.schema_version === BASELINE_SCHEMA_VERSION) {
77794
+ existing = parsed;
77795
+ }
77796
+ } catch {}
77797
+ const scannedRelFiles = new Set(scannedFiles.map((f) => normalizeFindingPath(directory, f)));
77798
+ const indexed = assignOccurrenceIndices(findings, directory);
77799
+ if (existing && !opts?.force) {
77800
+ const prunedFingerprints = existing.fingerprints.filter((fp) => {
77801
+ const relFile = fp.slice(0, fp.indexOf("|"));
77802
+ return !scannedRelFiles.has(relFile);
77803
+ });
77804
+ const prunedSnapshot = existing.findings_snapshot.filter((f) => {
77805
+ return !scannedRelFiles.has(normalizeFindingPath(directory, f.location.file));
77806
+ });
77807
+ const prunedFilesIndexed = existing.files_indexed.filter((f) => !scannedRelFiles.has(f));
77808
+ const mergedFingerprints = [
77809
+ ...prunedFingerprints,
77810
+ ...indexed.map((i2) => i2.fingerprint)
77811
+ ];
77812
+ const mergedSnapshot = [
77813
+ ...prunedSnapshot,
77814
+ ...indexed.map((i2) => i2.finding)
77815
+ ];
77816
+ const mergedFilesIndexed = [
77817
+ ...prunedFilesIndexed,
77818
+ ...Array.from(scannedRelFiles)
77819
+ ];
77820
+ const truncated2 = mergedSnapshot.length > MAX_BASELINE_FINDINGS;
77821
+ const cappedSnapshot2 = truncated2 ? mergedSnapshot.slice(-MAX_BASELINE_FINDINGS) : mergedSnapshot;
77822
+ const cappedFingerprints2 = truncated2 ? mergedFingerprints.slice(-MAX_BASELINE_FINDINGS) : mergedFingerprints;
77823
+ let cappedFilesIndexed = mergedFilesIndexed;
77824
+ if (truncated2) {
77825
+ const survivingFiles = new Set;
77826
+ for (const finding of cappedSnapshot2) {
77827
+ const relFile = normalizeFindingPath(directory, finding.location.file);
77828
+ survivingFiles.add(relFile);
77829
+ }
77830
+ cappedFilesIndexed = Array.from(survivingFiles);
77831
+ }
77832
+ const now2 = new Date().toISOString();
77833
+ const bundle2 = {
77834
+ schema_version: BASELINE_SCHEMA_VERSION,
77835
+ phase,
77836
+ created_at: existing.created_at,
77837
+ updated_at: now2,
77838
+ engine,
77839
+ files_indexed: cappedFilesIndexed,
77840
+ fingerprints: cappedFingerprints2,
77841
+ findings_snapshot: cappedSnapshot2,
77842
+ truncated: truncated2
77843
+ };
77844
+ const json4 = JSON.stringify(bundle2, null, 2);
77845
+ if (json4.length > MAX_BASELINE_BYTES) {
77846
+ return {
77847
+ status: "error",
77848
+ message: `Baseline would exceed size cap (${json4.length} bytes > ${MAX_BASELINE_BYTES})`
77849
+ };
77850
+ }
77851
+ fs63.writeFileSync(tempPath, json4, "utf-8");
77852
+ fs63.renameSync(tempPath, baselinePath);
77853
+ return {
77854
+ status: "merged",
77855
+ path: baselinePath,
77856
+ fingerprint_count: cappedFingerprints2.length
77857
+ };
77858
+ }
77859
+ const newFingerprints = indexed.map((i2) => i2.fingerprint);
77860
+ const newSnapshot = indexed.map((i2) => i2.finding);
77861
+ const truncated = newSnapshot.length > MAX_BASELINE_FINDINGS;
77862
+ const cappedSnapshot = truncated ? newSnapshot.slice(0, MAX_BASELINE_FINDINGS) : newSnapshot;
77863
+ const cappedFingerprints = truncated ? newFingerprints.slice(0, MAX_BASELINE_FINDINGS) : newFingerprints;
77864
+ const now = new Date().toISOString();
77865
+ const bundle = {
77866
+ schema_version: BASELINE_SCHEMA_VERSION,
77867
+ phase,
77868
+ created_at: now,
77869
+ updated_at: now,
77870
+ engine,
77871
+ files_indexed: Array.from(scannedRelFiles),
77872
+ fingerprints: cappedFingerprints,
77873
+ findings_snapshot: cappedSnapshot,
77874
+ truncated
77875
+ };
77876
+ const json3 = JSON.stringify(bundle, null, 2);
77877
+ if (json3.length > MAX_BASELINE_BYTES) {
77878
+ return {
77879
+ status: "error",
77880
+ message: `Baseline would exceed size cap (${json3.length} bytes > ${MAX_BASELINE_BYTES})`
77881
+ };
77882
+ }
77883
+ fs63.writeFileSync(tempPath, json3, "utf-8");
77884
+ fs63.renameSync(tempPath, baselinePath);
77885
+ return {
77886
+ status: "written",
77887
+ path: baselinePath,
77888
+ fingerprint_count: cappedFingerprints.length
77889
+ };
77890
+ } finally {
77891
+ releaseLock();
77892
+ }
77893
+ }
77894
+ function loadBaseline(directory, phase) {
77895
+ const phaseError = validatePhase(phase);
77896
+ if (phaseError) {
77897
+ return { status: "invalid_schema", errors: [phaseError] };
77898
+ }
77899
+ let baselinePath;
77900
+ try {
77901
+ baselinePath = validateSwarmPath(directory, baselineRelPath(phase));
77902
+ } catch (e) {
77903
+ return {
77904
+ status: "invalid_schema",
77905
+ errors: [e instanceof Error ? e.message : "Path validation failed"]
77906
+ };
77907
+ }
77908
+ try {
77909
+ const raw = fs63.readFileSync(baselinePath, "utf-8");
77910
+ const parsed = JSON.parse(raw);
77911
+ if (parsed.schema_version !== BASELINE_SCHEMA_VERSION) {
77912
+ return {
77913
+ status: "invalid_schema",
77914
+ errors: [`Unknown schema version: ${String(parsed.schema_version)}`]
77915
+ };
77916
+ }
77917
+ if (!Array.isArray(parsed.fingerprints)) {
77918
+ return {
77919
+ status: "invalid_schema",
77920
+ errors: ["Missing or invalid fingerprints array"]
77921
+ };
77922
+ }
77923
+ return {
77924
+ status: "found",
77925
+ fingerprints: new Set(parsed.fingerprints),
77926
+ bundle: parsed
77927
+ };
77928
+ } catch (e) {
77929
+ if (e.code === "ENOENT") {
77930
+ return { status: "not_found" };
77931
+ }
77932
+ return {
77933
+ status: "invalid_schema",
77934
+ errors: [e instanceof Error ? e.message : "Failed to read baseline"]
77935
+ };
77936
+ }
77937
+ }
77938
+
77939
+ // src/tools/sast-scan.ts
77631
77940
  var MAX_FILE_SIZE_BYTES8 = 512 * 1024;
77632
77941
  var MAX_FILES_SCANNED2 = 1000;
77633
77942
  var MAX_FINDINGS2 = 100;
@@ -77639,17 +77948,17 @@ var SEVERITY_ORDER = {
77639
77948
  };
77640
77949
  function shouldSkipFile(filePath) {
77641
77950
  try {
77642
- const stats = fs63.statSync(filePath);
77951
+ const stats = fs64.statSync(filePath);
77643
77952
  if (stats.size > MAX_FILE_SIZE_BYTES8) {
77644
77953
  return { skip: true, reason: "file too large" };
77645
77954
  }
77646
77955
  if (stats.size === 0) {
77647
77956
  return { skip: true, reason: "empty file" };
77648
77957
  }
77649
- const fd = fs63.openSync(filePath, "r");
77958
+ const fd = fs64.openSync(filePath, "r");
77650
77959
  const buffer = Buffer.alloc(8192);
77651
- const bytesRead = fs63.readSync(fd, buffer, 0, 8192, 0);
77652
- fs63.closeSync(fd);
77960
+ const bytesRead = fs64.readSync(fd, buffer, 0, 8192, 0);
77961
+ fs64.closeSync(fd);
77653
77962
  if (bytesRead > 0) {
77654
77963
  let nullCount = 0;
77655
77964
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -77688,7 +77997,7 @@ function countBySeverity(findings) {
77688
77997
  }
77689
77998
  function scanFileWithTierA(filePath, language) {
77690
77999
  try {
77691
- const content = fs63.readFileSync(filePath, "utf-8");
78000
+ const content = fs64.readFileSync(filePath, "utf-8");
77692
78001
  const findings = executeRulesSync(filePath, content, language);
77693
78002
  return findings.map((f) => ({
77694
78003
  rule_id: f.rule_id,
@@ -77706,7 +78015,12 @@ function scanFileWithTierA(filePath, language) {
77706
78015
  }
77707
78016
  }
77708
78017
  async function sastScan(input, directory, config3) {
77709
- const { changed_files, severity_threshold = "medium" } = input;
78018
+ const {
78019
+ changed_files,
78020
+ severity_threshold = "medium",
78021
+ capture_baseline = false,
78022
+ phase
78023
+ } = input;
77710
78024
  if (config3?.gates?.sast_scan?.enabled === false) {
77711
78025
  return {
77712
78026
  verdict: "pass",
@@ -77727,6 +78041,7 @@ async function sastScan(input, directory, config3) {
77727
78041
  const allFindings = [];
77728
78042
  let filesScanned = 0;
77729
78043
  let _filesSkipped = 0;
78044
+ const scannedFilePaths = [];
77730
78045
  const semgrepAvailable = isSemgrepAvailable();
77731
78046
  const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
77732
78047
  const filesByLanguage = new Map;
@@ -77735,13 +78050,13 @@ async function sastScan(input, directory, config3) {
77735
78050
  _filesSkipped++;
77736
78051
  continue;
77737
78052
  }
77738
- const resolvedPath = path77.isAbsolute(filePath) ? filePath : path77.resolve(directory, filePath);
77739
- const resolvedDirectory = path77.resolve(directory);
77740
- if (!resolvedPath.startsWith(resolvedDirectory + path77.sep) && resolvedPath !== resolvedDirectory) {
78053
+ const resolvedPath = path78.isAbsolute(filePath) ? filePath : path78.resolve(directory, filePath);
78054
+ const resolvedDirectory = path78.resolve(directory);
78055
+ if (!resolvedPath.startsWith(resolvedDirectory + path78.sep) && resolvedPath !== resolvedDirectory) {
77741
78056
  _filesSkipped++;
77742
78057
  continue;
77743
78058
  }
77744
- if (!fs63.existsSync(resolvedPath)) {
78059
+ if (!fs64.existsSync(resolvedPath)) {
77745
78060
  _filesSkipped++;
77746
78061
  continue;
77747
78062
  }
@@ -77776,6 +78091,7 @@ async function sastScan(input, directory, config3) {
77776
78091
  }
77777
78092
  }
77778
78093
  filesScanned++;
78094
+ scannedFilePaths.push(resolvedPath);
77779
78095
  if (filesScanned >= MAX_FILES_SCANNED2) {
77780
78096
  warn(`SAST Scan: Reached maximum files limit (${MAX_FILES_SCANNED2}), stopping`);
77781
78097
  break;
@@ -77825,14 +78141,97 @@ async function sastScan(input, directory, config3) {
77825
78141
  warn(`SAST Scan: Semgrep failed, falling back to Tier A: ${error93}`);
77826
78142
  }
77827
78143
  }
77828
- let finalFindings = allFindings;
77829
- if (allFindings.length > MAX_FINDINGS2) {
77830
- finalFindings = allFindings.slice(0, MAX_FINDINGS2);
77831
- warn(`SAST Scan: Found ${allFindings.length} findings, limiting to ${MAX_FINDINGS2}`);
78144
+ if (capture_baseline) {
78145
+ if (phase === undefined || !Number.isInteger(phase) || phase < 1) {
78146
+ const errorResult = {
78147
+ verdict: "fail",
78148
+ findings: allFindings.slice(0, MAX_FINDINGS2),
78149
+ summary: {
78150
+ engine,
78151
+ files_scanned: filesScanned,
78152
+ findings_count: Math.min(allFindings.length, MAX_FINDINGS2),
78153
+ findings_by_severity: countBySeverity(allFindings.slice(0, MAX_FINDINGS2))
78154
+ }
78155
+ };
78156
+ return errorResult;
78157
+ }
78158
+ const captureFindings = allFindings.slice(0, MAX_BASELINE_FINDINGS);
78159
+ const captureResult = await captureOrMergeBaseline(directory, phase, captureFindings, engine, scannedFilePaths);
78160
+ const captureStatus = captureResult.status === "written" ? "baseline_captured" : captureResult.status === "merged" ? "baseline_merged" : undefined;
78161
+ if (captureResult.status === "error") {
78162
+ warn(`SAST Baseline: capture failed \u2014 ${captureResult.message}`);
78163
+ }
78164
+ const finalFindings2 = allFindings.slice(0, MAX_FINDINGS2);
78165
+ const summary2 = {
78166
+ engine,
78167
+ files_scanned: filesScanned,
78168
+ findings_count: finalFindings2.length,
78169
+ findings_by_severity: countBySeverity(finalFindings2)
78170
+ };
78171
+ await saveEvidence(directory, "sast_scan", {
78172
+ task_id: "sast_scan",
78173
+ type: "sast",
78174
+ timestamp: new Date().toISOString(),
78175
+ agent: "sast_scan",
78176
+ verdict: "pass",
78177
+ summary: `Baseline capture: scanned ${filesScanned} files, recorded ${captureFindings.length} finding(s)`,
78178
+ ...summary2,
78179
+ findings: finalFindings2,
78180
+ baseline_used: false
78181
+ });
78182
+ return {
78183
+ verdict: "pass",
78184
+ findings: finalFindings2,
78185
+ summary: summary2,
78186
+ status: captureStatus,
78187
+ finding_count: captureResult.status !== "error" ? captureResult.fingerprint_count : undefined,
78188
+ baseline_used: false
78189
+ };
78190
+ }
78191
+ let newFindings;
78192
+ let preExistingFindings;
78193
+ let baselineUsed = false;
78194
+ let truncatedPreExisting = false;
78195
+ if (phase !== undefined && Number.isInteger(phase) && phase >= 1) {
78196
+ const baselineResult = loadBaseline(directory, phase);
78197
+ if (baselineResult.status === "found") {
78198
+ baselineUsed = true;
78199
+ const baselineSet = baselineResult.fingerprints;
78200
+ const indexed = assignOccurrenceIndices(allFindings, directory);
78201
+ const rawNew = [];
78202
+ const rawPreExisting = [];
78203
+ for (const { finding, stable, fingerprint } of indexed) {
78204
+ if (!stable || !baselineSet.has(fingerprint)) {
78205
+ rawNew.push(finding);
78206
+ } else {
78207
+ rawPreExisting.push(finding);
78208
+ }
78209
+ }
78210
+ newFindings = rawNew.slice(0, MAX_FINDINGS2);
78211
+ const preExistingBudget = Math.max(0, MAX_FINDINGS2 - newFindings.length);
78212
+ preExistingFindings = rawPreExisting.slice(0, preExistingBudget);
78213
+ truncatedPreExisting = rawPreExisting.length > preExistingBudget;
78214
+ } else if (baselineResult.status === "invalid_schema") {
78215
+ warn(`SAST Baseline: could not load baseline for phase ${phase} \u2014 ${baselineResult.errors.join(", ")}. Falling back to legacy behavior.`);
78216
+ }
78217
+ }
78218
+ let finalFindings;
78219
+ if (!baselineUsed) {
78220
+ finalFindings = allFindings;
78221
+ if (allFindings.length > MAX_FINDINGS2) {
78222
+ finalFindings = allFindings.slice(0, MAX_FINDINGS2);
78223
+ warn(`SAST Scan: Found ${allFindings.length} findings, limiting to ${MAX_FINDINGS2}`);
78224
+ }
78225
+ } else {
78226
+ finalFindings = [
78227
+ ...newFindings ?? [],
78228
+ ...preExistingFindings ?? []
78229
+ ].slice(0, MAX_FINDINGS2);
77832
78230
  }
77833
78231
  const findingsBySeverity = countBySeverity(finalFindings);
78232
+ const verdictSource = baselineUsed ? newFindings ?? [] : finalFindings;
77834
78233
  let verdict = "pass";
77835
- for (const finding of finalFindings) {
78234
+ for (const finding of verdictSource) {
77836
78235
  if (meetsThreshold(finding.severity, severity_threshold)) {
77837
78236
  verdict = "fail";
77838
78237
  break;
@@ -77855,42 +78254,65 @@ async function sastScan(input, directory, config3) {
77855
78254
  verdict,
77856
78255
  summary: `Scanned ${filesScanned} files, found ${finalFindings.length} finding(s) using ${engine}`,
77857
78256
  ...summary,
77858
- findings: finalFindings
78257
+ findings: finalFindings,
78258
+ ...baselineUsed && {
78259
+ new_findings: newFindings,
78260
+ pre_existing_findings: preExistingFindings,
78261
+ baseline_used: true
78262
+ }
77859
78263
  });
77860
- return {
78264
+ const result = {
77861
78265
  verdict,
77862
78266
  findings: finalFindings,
77863
78267
  summary
77864
78268
  };
78269
+ if (baselineUsed) {
78270
+ result.new_findings = newFindings;
78271
+ result.pre_existing_findings = preExistingFindings;
78272
+ result.baseline_used = true;
78273
+ if (truncatedPreExisting)
78274
+ result.truncated_pre_existing = true;
78275
+ }
78276
+ return result;
77865
78277
  }
77866
78278
  var sast_scan = createSwarmTool({
77867
- description: "Static Application Security Testing (SAST) scan. Scans files for security vulnerabilities using built-in rules (Tier A) and optional Semgrep (Tier B). Returns structured findings with severity levels.",
78279
+ description: "Static Application Security Testing (SAST) scan. Scans files for security vulnerabilities using built-in rules (Tier A) and optional Semgrep (Tier B). Supports phase-scoped baseline diffing: set capture_baseline:true before first coder delegation to snapshot pre-existing findings; subsequent scans with the same phase only fail on NEW findings.",
77868
78280
  args: {
77869
78281
  directory: tool.schema.string().describe("Directory to scan for security vulnerabilities"),
77870
78282
  changed_files: tool.schema.array(tool.schema.string()).optional().describe("List of files to scan (leave empty to scan none)"),
77871
- severity_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().default("medium").describe("Minimum severity that causes failure")
78283
+ severity_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().default("medium").describe("Minimum severity that causes failure"),
78284
+ capture_baseline: tool.schema.boolean().optional().describe("When true, capture/merge a phase-scoped baseline of pre-existing findings. Requires phase. Subsequent scans with phase only fail on NEW findings."),
78285
+ phase: tool.schema.number().int().min(1).optional().describe("Current phase number (positive integer >= 1). Required with capture_baseline. Enables baseline diff when provided on non-capture scans.")
77872
78286
  },
77873
78287
  execute: async (args2, directory) => {
77874
78288
  let safeArgs;
77875
78289
  try {
77876
78290
  if (args2 && typeof args2 === "object") {
78291
+ const rawPhase = args2.phase;
78292
+ const rawCapture = args2.capture_baseline;
77877
78293
  safeArgs = {
77878
78294
  directory: args2.directory,
77879
78295
  changed_files: args2.changed_files,
77880
- severity_threshold: args2.severity_threshold
78296
+ severity_threshold: args2.severity_threshold,
78297
+ phase: typeof rawPhase === "number" && Number.isInteger(rawPhase) && rawPhase >= 1 ? rawPhase : undefined,
78298
+ capture_baseline: typeof rawCapture === "boolean" ? rawCapture : undefined
77881
78299
  };
77882
78300
  } else {
77883
78301
  safeArgs = {
77884
78302
  directory: undefined,
77885
78303
  changed_files: undefined,
77886
- severity_threshold: undefined
78304
+ severity_threshold: undefined,
78305
+ capture_baseline: undefined,
78306
+ phase: undefined
77887
78307
  };
77888
78308
  }
77889
78309
  } catch {
77890
78310
  safeArgs = {
77891
78311
  directory: undefined,
77892
78312
  changed_files: undefined,
77893
- severity_threshold: undefined
78313
+ severity_threshold: undefined,
78314
+ capture_baseline: undefined,
78315
+ phase: undefined
77894
78316
  };
77895
78317
  }
77896
78318
  if (safeArgs.directory === undefined) {
@@ -77913,7 +78335,9 @@ var sast_scan = createSwarmTool({
77913
78335
  }
77914
78336
  const input = {
77915
78337
  changed_files: safeArgs.changed_files ?? [],
77916
- severity_threshold: safeArgs.severity_threshold ?? "medium"
78338
+ severity_threshold: safeArgs.severity_threshold ?? "medium",
78339
+ capture_baseline: safeArgs.capture_baseline,
78340
+ phase: safeArgs.phase
77917
78341
  };
77918
78342
  const result = await sastScan(input, directory);
77919
78343
  return JSON.stringify(result, null, 2);
@@ -77939,20 +78363,20 @@ function validatePath(inputPath, baseDir, workspaceDir) {
77939
78363
  let resolved;
77940
78364
  const isWinAbs = isWindowsAbsolutePath(inputPath);
77941
78365
  if (isWinAbs) {
77942
- resolved = path78.win32.resolve(inputPath);
77943
- } else if (path78.isAbsolute(inputPath)) {
77944
- resolved = path78.resolve(inputPath);
78366
+ resolved = path79.win32.resolve(inputPath);
78367
+ } else if (path79.isAbsolute(inputPath)) {
78368
+ resolved = path79.resolve(inputPath);
77945
78369
  } else {
77946
- resolved = path78.resolve(baseDir, inputPath);
78370
+ resolved = path79.resolve(baseDir, inputPath);
77947
78371
  }
77948
- const workspaceResolved = path78.resolve(workspaceDir);
77949
- let relative18;
78372
+ const workspaceResolved = path79.resolve(workspaceDir);
78373
+ let relative19;
77950
78374
  if (isWinAbs) {
77951
- relative18 = path78.win32.relative(workspaceResolved, resolved);
78375
+ relative19 = path79.win32.relative(workspaceResolved, resolved);
77952
78376
  } else {
77953
- relative18 = path78.relative(workspaceResolved, resolved);
78377
+ relative19 = path79.relative(workspaceResolved, resolved);
77954
78378
  }
77955
- if (relative18.startsWith("..")) {
78379
+ if (relative19.startsWith("..")) {
77956
78380
  return "path traversal detected";
77957
78381
  }
77958
78382
  return null;
@@ -78015,7 +78439,7 @@ async function runLintOnFiles(linter, files, workspaceDir) {
78015
78439
  if (typeof file3 !== "string") {
78016
78440
  continue;
78017
78441
  }
78018
- const resolvedPath = path78.resolve(file3);
78442
+ const resolvedPath = path79.resolve(file3);
78019
78443
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
78020
78444
  if (validationError) {
78021
78445
  continue;
@@ -78172,7 +78596,7 @@ async function runSecretscanWithFiles(files, directory) {
78172
78596
  skippedFiles++;
78173
78597
  continue;
78174
78598
  }
78175
- const resolvedPath = path78.resolve(file3);
78599
+ const resolvedPath = path79.resolve(file3);
78176
78600
  const validationError = validatePath(resolvedPath, directory, directory);
78177
78601
  if (validationError) {
78178
78602
  skippedFiles++;
@@ -78190,14 +78614,14 @@ async function runSecretscanWithFiles(files, directory) {
78190
78614
  };
78191
78615
  }
78192
78616
  for (const file3 of validatedFiles) {
78193
- const ext = path78.extname(file3).toLowerCase();
78617
+ const ext = path79.extname(file3).toLowerCase();
78194
78618
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
78195
78619
  skippedFiles++;
78196
78620
  continue;
78197
78621
  }
78198
78622
  let stat3;
78199
78623
  try {
78200
- stat3 = fs64.statSync(file3);
78624
+ stat3 = fs65.statSync(file3);
78201
78625
  } catch {
78202
78626
  skippedFiles++;
78203
78627
  continue;
@@ -78208,7 +78632,7 @@ async function runSecretscanWithFiles(files, directory) {
78208
78632
  }
78209
78633
  let content;
78210
78634
  try {
78211
- const buffer = fs64.readFileSync(file3);
78635
+ const buffer = fs65.readFileSync(file3);
78212
78636
  if (buffer.includes(0)) {
78213
78637
  skippedFiles++;
78214
78638
  continue;
@@ -78274,10 +78698,14 @@ async function runSecretscanWithFiles(files, directory) {
78274
78698
  return errorResult;
78275
78699
  }
78276
78700
  }
78277
- async function runSastScanWrapped(changedFiles, directory, severityThreshold, config3) {
78701
+ async function runSastScanWrapped(changedFiles, directory, severityThreshold, config3, phase) {
78278
78702
  const start2 = process.hrtime.bigint();
78279
78703
  try {
78280
- const result = await runWithTimeout2(sastScan({ changed_files: changedFiles, severity_threshold: severityThreshold }, directory, config3), TOOL_TIMEOUT_MS);
78704
+ const result = await runWithTimeout2(sastScan({
78705
+ changed_files: changedFiles,
78706
+ severity_threshold: severityThreshold,
78707
+ phase
78708
+ }, directory, config3), TOOL_TIMEOUT_MS);
78281
78709
  return {
78282
78710
  ran: true,
78283
78711
  result,
@@ -78309,6 +78737,15 @@ async function runQualityBudgetWrapped(changedFiles, directory, _config) {
78309
78737
  }
78310
78738
  }
78311
78739
  var GATE_SEVERITIES = new Set(["high", "critical"]);
78740
+ var SEVERITY_ORDER_PCB = {
78741
+ low: 0,
78742
+ medium: 1,
78743
+ high: 2,
78744
+ critical: 3
78745
+ };
78746
+ function meetsThresholdForTriage(severity, threshold) {
78747
+ return (SEVERITY_ORDER_PCB[severity] ?? 0) >= (SEVERITY_ORDER_PCB[threshold] ?? 1);
78748
+ }
78312
78749
  async function runGitDiff(args2, directory) {
78313
78750
  try {
78314
78751
  const proc = Bun.spawn(["git", "diff", ...args2], {
@@ -78396,7 +78833,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
78396
78833
  const preexistingFindings = [];
78397
78834
  for (const finding of findings) {
78398
78835
  const filePath = finding.location.file;
78399
- const normalised = path78.relative(directory, filePath).replace(/\\/g, "/");
78836
+ const normalised = path79.relative(directory, filePath).replace(/\\/g, "/");
78400
78837
  const changedLines = changedLineRanges.get(normalised);
78401
78838
  if (changedLines?.has(finding.location.line)) {
78402
78839
  newFindings.push(finding);
@@ -78408,7 +78845,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
78408
78845
  }
78409
78846
  async function runPreCheckBatch(input, workspaceDir, contextDir) {
78410
78847
  const effectiveWorkspaceDir = workspaceDir || input.directory || contextDir;
78411
- const { files, directory, sast_threshold = "medium", config: config3 } = input;
78848
+ const { files, directory, sast_threshold = "medium", config: config3, phase } = input;
78412
78849
  const dirError = validateDirectory2(directory, effectiveWorkspaceDir);
78413
78850
  if (dirError) {
78414
78851
  warn(`pre_check_batch: Invalid directory: ${dirError}`);
@@ -78447,7 +78884,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
78447
78884
  warn(`pre_check_batch: Invalid file path: ${file3}`);
78448
78885
  continue;
78449
78886
  }
78450
- changedFiles.push(path78.resolve(directory, file3));
78887
+ changedFiles.push(path79.resolve(directory, file3));
78451
78888
  }
78452
78889
  if (changedFiles.length === 0) {
78453
78890
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -78471,7 +78908,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
78471
78908
  const [lintResult, secretscanResult, sastScanResult, qualityBudgetResult] = await Promise.all([
78472
78909
  limit(() => runLintWrapped(changedFiles, directory, config3)),
78473
78910
  limit(() => runSecretscanWrapped(changedFiles, directory, config3)),
78474
- limit(() => runSastScanWrapped(changedFiles, directory, sast_threshold, config3)),
78911
+ limit(() => runSastScanWrapped(changedFiles, directory, sast_threshold, config3, phase)),
78475
78912
  limit(() => runQualityBudgetWrapped(changedFiles, directory, config3))
78476
78913
  ]);
78477
78914
  const totalDuration = lintResult.duration_ms + secretscanResult.duration_ms + sastScanResult.duration_ms + qualityBudgetResult.duration_ms;
@@ -78516,8 +78953,20 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
78516
78953
  }
78517
78954
  let sastPreexistingFindings;
78518
78955
  if (sastScanResult.ran && sastScanResult.result) {
78519
- if (sastScanResult.result.verdict === "fail") {
78520
- const gateFindings = sastScanResult.result.findings.filter((f) => GATE_SEVERITIES.has(f.severity));
78956
+ const sastResult = sastScanResult.result;
78957
+ if (sastResult.baseline_used) {
78958
+ if (sastResult.pre_existing_findings && sastResult.pre_existing_findings.length > 0) {
78959
+ sastPreexistingFindings = sastResult.pre_existing_findings.filter((f) => meetsThresholdForTriage(f.severity, sast_threshold));
78960
+ if (sastPreexistingFindings.length > 0) {
78961
+ warn(`pre_check_batch: SAST baseline diff found ${sastPreexistingFindings.length} pre-existing finding(s) - passing to reviewer for triage`);
78962
+ }
78963
+ }
78964
+ if (sastResult.verdict === "fail") {
78965
+ gatesPassed = false;
78966
+ warn(`pre_check_batch: SAST scan found new findings above threshold - GATE FAILED`);
78967
+ }
78968
+ } else if (sastResult.verdict === "fail") {
78969
+ const gateFindings = sastResult.findings.filter((f) => GATE_SEVERITIES.has(f.severity));
78521
78970
  if (gateFindings.length > 0) {
78522
78971
  const changedLineRanges = await getChangedLineRanges(directory);
78523
78972
  const { newFindings, preexistingFindings } = classifySastFindings(gateFindings, changedLineRanges, directory);
@@ -78566,7 +79015,8 @@ var pre_check_batch = createSwarmTool({
78566
79015
  args: {
78567
79016
  files: tool.schema.array(tool.schema.string()).optional().describe("Specific files to check (optional, scans directory if not provided)"),
78568
79017
  directory: tool.schema.string().describe('Directory to run checks in (e.g., "." or "./src")'),
78569
- sast_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity for SAST findings to cause failure (default: medium)")
79018
+ sast_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity for SAST findings to cause failure (default: medium)"),
79019
+ phase: tool.schema.number().int().min(1).optional().describe("Current phase number (positive integer >= 1). When provided, enables SAST baseline diffing: only findings absent from the phase-scoped baseline fail the gate.")
78570
79020
  },
78571
79021
  async execute(args2, directory) {
78572
79022
  if (!args2 || typeof args2 !== "object") {
@@ -78635,7 +79085,7 @@ var pre_check_batch = createSwarmTool({
78635
79085
  };
78636
79086
  return JSON.stringify(errorResult, null, 2);
78637
79087
  }
78638
- const resolvedDirectory = path78.resolve(typedArgs.directory);
79088
+ const resolvedDirectory = path79.resolve(typedArgs.directory);
78639
79089
  const workspaceAnchor = resolvedDirectory;
78640
79090
  const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
78641
79091
  if (dirError) {
@@ -78650,11 +79100,14 @@ var pre_check_batch = createSwarmTool({
78650
79100
  return JSON.stringify(errorResult, null, 2);
78651
79101
  }
78652
79102
  try {
79103
+ const rawPhase = typedArgs.phase;
79104
+ const safePhase = typeof rawPhase === "number" && Number.isInteger(rawPhase) && rawPhase >= 1 ? rawPhase : undefined;
78653
79105
  const result = await runPreCheckBatch({
78654
79106
  files: typedArgs.files,
78655
79107
  directory: resolvedDirectory,
78656
79108
  sast_threshold: typedArgs.sast_threshold,
78657
- config: typedArgs.config
79109
+ config: typedArgs.config,
79110
+ phase: safePhase
78658
79111
  }, workspaceAnchor, directory);
78659
79112
  return JSON.stringify(result, null, 2);
78660
79113
  } catch (error93) {
@@ -78673,7 +79126,7 @@ var pre_check_batch = createSwarmTool({
78673
79126
  });
78674
79127
  // src/tools/repo-map.ts
78675
79128
  init_dist();
78676
- import * as path79 from "path";
79129
+ import * as path80 from "path";
78677
79130
  init_path_security();
78678
79131
  init_create_tool();
78679
79132
  var VALID_ACTIONS = [
@@ -78698,7 +79151,7 @@ function validateFile(p) {
78698
79151
  return "file contains control characters";
78699
79152
  if (containsPathTraversal(p))
78700
79153
  return "file contains path traversal";
78701
- if (path79.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
79154
+ if (path80.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
78702
79155
  return "file must be a workspace-relative path, not absolute";
78703
79156
  }
78704
79157
  return null;
@@ -78721,8 +79174,8 @@ function ok(action, payload) {
78721
79174
  }
78722
79175
  function toRelativeGraphPath(input, workspaceRoot) {
78723
79176
  const normalized = input.replace(/\\/g, "/");
78724
- if (path79.isAbsolute(normalized)) {
78725
- const rel = path79.relative(workspaceRoot, normalized).replace(/\\/g, "/");
79177
+ if (path80.isAbsolute(normalized)) {
79178
+ const rel = path80.relative(workspaceRoot, normalized).replace(/\\/g, "/");
78726
79179
  return normalizeGraphPath2(rel);
78727
79180
  }
78728
79181
  return normalizeGraphPath2(normalized);
@@ -78866,8 +79319,8 @@ var repo_map = createSwarmTool({
78866
79319
  // src/tools/req-coverage.ts
78867
79320
  init_dist();
78868
79321
  init_create_tool();
78869
- import * as fs65 from "fs";
78870
- import * as path80 from "path";
79322
+ import * as fs66 from "fs";
79323
+ import * as path81 from "path";
78871
79324
  var SPEC_FILE = ".swarm/spec.md";
78872
79325
  var EVIDENCE_DIR4 = ".swarm/evidence";
78873
79326
  var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
@@ -78926,19 +79379,19 @@ function extractObligationAndText(id, lineText) {
78926
79379
  var PHASE_TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
78927
79380
  function readTouchedFiles(evidenceDir, phase, cwd) {
78928
79381
  const touchedFiles = new Set;
78929
- if (!fs65.existsSync(evidenceDir) || !fs65.statSync(evidenceDir).isDirectory()) {
79382
+ if (!fs66.existsSync(evidenceDir) || !fs66.statSync(evidenceDir).isDirectory()) {
78930
79383
  return [];
78931
79384
  }
78932
79385
  let entries;
78933
79386
  try {
78934
- entries = fs65.readdirSync(evidenceDir);
79387
+ entries = fs66.readdirSync(evidenceDir);
78935
79388
  } catch {
78936
79389
  return [];
78937
79390
  }
78938
79391
  for (const entry of entries) {
78939
- const entryPath = path80.join(evidenceDir, entry);
79392
+ const entryPath = path81.join(evidenceDir, entry);
78940
79393
  try {
78941
- const stat3 = fs65.statSync(entryPath);
79394
+ const stat3 = fs66.statSync(entryPath);
78942
79395
  if (!stat3.isDirectory()) {
78943
79396
  continue;
78944
79397
  }
@@ -78952,14 +79405,14 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
78952
79405
  if (entryPhase !== String(phase)) {
78953
79406
  continue;
78954
79407
  }
78955
- const evidenceFilePath = path80.join(entryPath, "evidence.json");
79408
+ const evidenceFilePath = path81.join(entryPath, "evidence.json");
78956
79409
  try {
78957
- const resolvedPath = path80.resolve(evidenceFilePath);
78958
- const evidenceDirResolved = path80.resolve(evidenceDir);
78959
- if (!resolvedPath.startsWith(evidenceDirResolved + path80.sep)) {
79410
+ const resolvedPath = path81.resolve(evidenceFilePath);
79411
+ const evidenceDirResolved = path81.resolve(evidenceDir);
79412
+ if (!resolvedPath.startsWith(evidenceDirResolved + path81.sep)) {
78960
79413
  continue;
78961
79414
  }
78962
- const stat3 = fs65.lstatSync(evidenceFilePath);
79415
+ const stat3 = fs66.lstatSync(evidenceFilePath);
78963
79416
  if (!stat3.isFile()) {
78964
79417
  continue;
78965
79418
  }
@@ -78971,7 +79424,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
78971
79424
  }
78972
79425
  let content;
78973
79426
  try {
78974
- content = fs65.readFileSync(evidenceFilePath, "utf-8");
79427
+ content = fs66.readFileSync(evidenceFilePath, "utf-8");
78975
79428
  } catch {
78976
79429
  continue;
78977
79430
  }
@@ -78990,7 +79443,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
78990
79443
  if (Array.isArray(diffEntry.files_changed)) {
78991
79444
  for (const file3 of diffEntry.files_changed) {
78992
79445
  if (typeof file3 === "string") {
78993
- touchedFiles.add(path80.resolve(cwd, file3));
79446
+ touchedFiles.add(path81.resolve(cwd, file3));
78994
79447
  }
78995
79448
  }
78996
79449
  }
@@ -79003,12 +79456,12 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
79003
79456
  }
79004
79457
  function searchFileForKeywords(filePath, keywords, cwd) {
79005
79458
  try {
79006
- const resolvedPath = path80.resolve(filePath);
79007
- const cwdResolved = path80.resolve(cwd);
79459
+ const resolvedPath = path81.resolve(filePath);
79460
+ const cwdResolved = path81.resolve(cwd);
79008
79461
  if (!resolvedPath.startsWith(cwdResolved)) {
79009
79462
  return false;
79010
79463
  }
79011
- const content = fs65.readFileSync(resolvedPath, "utf-8");
79464
+ const content = fs66.readFileSync(resolvedPath, "utf-8");
79012
79465
  for (const keyword of keywords) {
79013
79466
  const regex = new RegExp(`\\b${keyword}\\b`, "i");
79014
79467
  if (regex.test(content)) {
@@ -79138,10 +79591,10 @@ var req_coverage = createSwarmTool({
79138
79591
  }, null, 2);
79139
79592
  }
79140
79593
  const cwd = inputDirectory || directory;
79141
- const specPath = path80.join(cwd, SPEC_FILE);
79594
+ const specPath = path81.join(cwd, SPEC_FILE);
79142
79595
  let specContent;
79143
79596
  try {
79144
- specContent = fs65.readFileSync(specPath, "utf-8");
79597
+ specContent = fs66.readFileSync(specPath, "utf-8");
79145
79598
  } catch (readError) {
79146
79599
  return JSON.stringify({
79147
79600
  success: false,
@@ -79165,7 +79618,7 @@ var req_coverage = createSwarmTool({
79165
79618
  message: "No FR requirements found in spec.md"
79166
79619
  }, null, 2);
79167
79620
  }
79168
- const evidenceDir = path80.join(cwd, EVIDENCE_DIR4);
79621
+ const evidenceDir = path81.join(cwd, EVIDENCE_DIR4);
79169
79622
  const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
79170
79623
  const analyzedRequirements = [];
79171
79624
  let coveredCount = 0;
@@ -79191,12 +79644,12 @@ var req_coverage = createSwarmTool({
79191
79644
  requirements: analyzedRequirements
79192
79645
  };
79193
79646
  const reportFilename = `req-coverage-phase-${phase}.json`;
79194
- const reportPath = path80.join(evidenceDir, reportFilename);
79647
+ const reportPath = path81.join(evidenceDir, reportFilename);
79195
79648
  try {
79196
- if (!fs65.existsSync(evidenceDir)) {
79197
- fs65.mkdirSync(evidenceDir, { recursive: true });
79649
+ if (!fs66.existsSync(evidenceDir)) {
79650
+ fs66.mkdirSync(evidenceDir, { recursive: true });
79198
79651
  }
79199
- fs65.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
79652
+ fs66.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
79200
79653
  } catch (writeError) {
79201
79654
  console.warn(`Failed to write coverage report: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
79202
79655
  }
@@ -79274,9 +79727,9 @@ ${paginatedContent}`;
79274
79727
  });
79275
79728
  // src/tools/save-plan.ts
79276
79729
  init_tool();
79277
- import * as crypto8 from "crypto";
79278
- import * as fs66 from "fs";
79279
- import * as path81 from "path";
79730
+ import * as crypto9 from "crypto";
79731
+ import * as fs67 from "fs";
79732
+ import * as path82 from "path";
79280
79733
  init_checkpoint3();
79281
79734
  init_ledger();
79282
79735
  init_manager();
@@ -79357,12 +79810,12 @@ async function executeSavePlan(args2, fallbackDir) {
79357
79810
  let specMtime;
79358
79811
  let specHash;
79359
79812
  if (process.env.SWARM_SKIP_SPEC_GATE !== "1") {
79360
- const specPath = path81.join(targetWorkspace, ".swarm", "spec.md");
79813
+ const specPath = path82.join(targetWorkspace, ".swarm", "spec.md");
79361
79814
  try {
79362
- const stat3 = await fs66.promises.stat(specPath);
79815
+ const stat3 = await fs67.promises.stat(specPath);
79363
79816
  specMtime = stat3.mtime.toISOString();
79364
- const content = await fs66.promises.readFile(specPath, "utf8");
79365
- specHash = crypto8.createHash("sha256").update(content).digest("hex");
79817
+ const content = await fs67.promises.readFile(specPath, "utf8");
79818
+ specHash = crypto9.createHash("sha256").update(content).digest("hex");
79366
79819
  } catch {
79367
79820
  return {
79368
79821
  success: false,
@@ -79439,14 +79892,14 @@ async function executeSavePlan(args2, fallbackDir) {
79439
79892
  }
79440
79893
  await writeCheckpoint(dir).catch(() => {});
79441
79894
  try {
79442
- const markerPath = path81.join(dir, ".swarm", ".plan-write-marker");
79895
+ const markerPath = path82.join(dir, ".swarm", ".plan-write-marker");
79443
79896
  const marker = JSON.stringify({
79444
79897
  source: "save_plan",
79445
79898
  timestamp: new Date().toISOString(),
79446
79899
  phases_count: plan.phases.length,
79447
79900
  tasks_count: tasksCount
79448
79901
  });
79449
- await fs66.promises.writeFile(markerPath, marker, "utf8");
79902
+ await fs67.promises.writeFile(markerPath, marker, "utf8");
79450
79903
  } catch {}
79451
79904
  const warnings = [];
79452
79905
  let criticReviewFound = false;
@@ -79462,7 +79915,7 @@ async function executeSavePlan(args2, fallbackDir) {
79462
79915
  return {
79463
79916
  success: true,
79464
79917
  message: "Plan saved successfully",
79465
- plan_path: path81.join(dir, ".swarm", "plan.json"),
79918
+ plan_path: path82.join(dir, ".swarm", "plan.json"),
79466
79919
  phases_count: plan.phases.length,
79467
79920
  tasks_count: tasksCount,
79468
79921
  ...warnings.length > 0 ? { warnings } : {}
@@ -79507,8 +79960,8 @@ var save_plan = createSwarmTool({
79507
79960
  // src/tools/sbom-generate.ts
79508
79961
  init_dist();
79509
79962
  init_manager2();
79510
- import * as fs67 from "fs";
79511
- import * as path82 from "path";
79963
+ import * as fs68 from "fs";
79964
+ import * as path83 from "path";
79512
79965
 
79513
79966
  // src/sbom/detectors/index.ts
79514
79967
  init_utils();
@@ -80356,9 +80809,9 @@ function findManifestFiles(rootDir) {
80356
80809
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
80357
80810
  function searchDir(dir) {
80358
80811
  try {
80359
- const entries = fs67.readdirSync(dir, { withFileTypes: true });
80812
+ const entries = fs68.readdirSync(dir, { withFileTypes: true });
80360
80813
  for (const entry of entries) {
80361
- const fullPath = path82.join(dir, entry.name);
80814
+ const fullPath = path83.join(dir, entry.name);
80362
80815
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
80363
80816
  continue;
80364
80817
  }
@@ -80367,7 +80820,7 @@ function findManifestFiles(rootDir) {
80367
80820
  } else if (entry.isFile()) {
80368
80821
  for (const pattern of patterns) {
80369
80822
  if (simpleGlobToRegex(pattern).test(entry.name)) {
80370
- manifestFiles.push(path82.relative(rootDir, fullPath));
80823
+ manifestFiles.push(path83.relative(rootDir, fullPath));
80371
80824
  break;
80372
80825
  }
80373
80826
  }
@@ -80383,13 +80836,13 @@ function findManifestFilesInDirs(directories, workingDir) {
80383
80836
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
80384
80837
  for (const dir of directories) {
80385
80838
  try {
80386
- const entries = fs67.readdirSync(dir, { withFileTypes: true });
80839
+ const entries = fs68.readdirSync(dir, { withFileTypes: true });
80387
80840
  for (const entry of entries) {
80388
- const fullPath = path82.join(dir, entry.name);
80841
+ const fullPath = path83.join(dir, entry.name);
80389
80842
  if (entry.isFile()) {
80390
80843
  for (const pattern of patterns) {
80391
80844
  if (simpleGlobToRegex(pattern).test(entry.name)) {
80392
- found.push(path82.relative(workingDir, fullPath));
80845
+ found.push(path83.relative(workingDir, fullPath));
80393
80846
  break;
80394
80847
  }
80395
80848
  }
@@ -80402,11 +80855,11 @@ function findManifestFilesInDirs(directories, workingDir) {
80402
80855
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
80403
80856
  const dirs = new Set;
80404
80857
  for (const file3 of changedFiles) {
80405
- let currentDir = path82.dirname(file3);
80858
+ let currentDir = path83.dirname(file3);
80406
80859
  while (true) {
80407
- if (currentDir && currentDir !== "." && currentDir !== path82.sep) {
80408
- dirs.add(path82.join(workingDir, currentDir));
80409
- const parent = path82.dirname(currentDir);
80860
+ if (currentDir && currentDir !== "." && currentDir !== path83.sep) {
80861
+ dirs.add(path83.join(workingDir, currentDir));
80862
+ const parent = path83.dirname(currentDir);
80410
80863
  if (parent === currentDir)
80411
80864
  break;
80412
80865
  currentDir = parent;
@@ -80420,7 +80873,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
80420
80873
  }
80421
80874
  function ensureOutputDir(outputDir) {
80422
80875
  try {
80423
- fs67.mkdirSync(outputDir, { recursive: true });
80876
+ fs68.mkdirSync(outputDir, { recursive: true });
80424
80877
  } catch (error93) {
80425
80878
  if (!error93 || error93.code !== "EEXIST") {
80426
80879
  throw error93;
@@ -80490,7 +80943,7 @@ var sbom_generate = createSwarmTool({
80490
80943
  const changedFiles = obj.changed_files;
80491
80944
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
80492
80945
  const workingDir = directory;
80493
- const outputDir = path82.isAbsolute(relativeOutputDir) ? relativeOutputDir : path82.join(workingDir, relativeOutputDir);
80946
+ const outputDir = path83.isAbsolute(relativeOutputDir) ? relativeOutputDir : path83.join(workingDir, relativeOutputDir);
80494
80947
  let manifestFiles = [];
80495
80948
  if (scope === "all") {
80496
80949
  manifestFiles = findManifestFiles(workingDir);
@@ -80513,11 +80966,11 @@ var sbom_generate = createSwarmTool({
80513
80966
  const processedFiles = [];
80514
80967
  for (const manifestFile of manifestFiles) {
80515
80968
  try {
80516
- const fullPath = path82.isAbsolute(manifestFile) ? manifestFile : path82.join(workingDir, manifestFile);
80517
- if (!fs67.existsSync(fullPath)) {
80969
+ const fullPath = path83.isAbsolute(manifestFile) ? manifestFile : path83.join(workingDir, manifestFile);
80970
+ if (!fs68.existsSync(fullPath)) {
80518
80971
  continue;
80519
80972
  }
80520
- const content = fs67.readFileSync(fullPath, "utf-8");
80973
+ const content = fs68.readFileSync(fullPath, "utf-8");
80521
80974
  const components = detectComponents(manifestFile, content);
80522
80975
  processedFiles.push(manifestFile);
80523
80976
  if (components.length > 0) {
@@ -80530,8 +80983,8 @@ var sbom_generate = createSwarmTool({
80530
80983
  const bom = generateCycloneDX(allComponents);
80531
80984
  const bomJson = serializeCycloneDX(bom);
80532
80985
  const filename = generateSbomFilename();
80533
- const outputPath = path82.join(outputDir, filename);
80534
- fs67.writeFileSync(outputPath, bomJson, "utf-8");
80986
+ const outputPath = path83.join(outputDir, filename);
80987
+ fs68.writeFileSync(outputPath, bomJson, "utf-8");
80535
80988
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
80536
80989
  try {
80537
80990
  const timestamp = new Date().toISOString();
@@ -80573,8 +81026,8 @@ var sbom_generate = createSwarmTool({
80573
81026
  // src/tools/schema-drift.ts
80574
81027
  init_dist();
80575
81028
  init_create_tool();
80576
- import * as fs68 from "fs";
80577
- import * as path83 from "path";
81029
+ import * as fs69 from "fs";
81030
+ import * as path84 from "path";
80578
81031
  var SPEC_CANDIDATES = [
80579
81032
  "openapi.json",
80580
81033
  "openapi.yaml",
@@ -80606,28 +81059,28 @@ function normalizePath3(p) {
80606
81059
  }
80607
81060
  function discoverSpecFile(cwd, specFileArg) {
80608
81061
  if (specFileArg) {
80609
- const resolvedPath = path83.resolve(cwd, specFileArg);
80610
- const normalizedCwd = cwd.endsWith(path83.sep) ? cwd : cwd + path83.sep;
81062
+ const resolvedPath = path84.resolve(cwd, specFileArg);
81063
+ const normalizedCwd = cwd.endsWith(path84.sep) ? cwd : cwd + path84.sep;
80611
81064
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
80612
81065
  throw new Error("Invalid spec_file: path traversal detected");
80613
81066
  }
80614
- const ext = path83.extname(resolvedPath).toLowerCase();
81067
+ const ext = path84.extname(resolvedPath).toLowerCase();
80615
81068
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
80616
81069
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
80617
81070
  }
80618
- const stats = fs68.statSync(resolvedPath);
81071
+ const stats = fs69.statSync(resolvedPath);
80619
81072
  if (stats.size > MAX_SPEC_SIZE) {
80620
81073
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
80621
81074
  }
80622
- if (!fs68.existsSync(resolvedPath)) {
81075
+ if (!fs69.existsSync(resolvedPath)) {
80623
81076
  throw new Error(`Spec file not found: ${resolvedPath}`);
80624
81077
  }
80625
81078
  return resolvedPath;
80626
81079
  }
80627
81080
  for (const candidate of SPEC_CANDIDATES) {
80628
- const candidatePath = path83.resolve(cwd, candidate);
80629
- if (fs68.existsSync(candidatePath)) {
80630
- const stats = fs68.statSync(candidatePath);
81081
+ const candidatePath = path84.resolve(cwd, candidate);
81082
+ if (fs69.existsSync(candidatePath)) {
81083
+ const stats = fs69.statSync(candidatePath);
80631
81084
  if (stats.size <= MAX_SPEC_SIZE) {
80632
81085
  return candidatePath;
80633
81086
  }
@@ -80636,8 +81089,8 @@ function discoverSpecFile(cwd, specFileArg) {
80636
81089
  return null;
80637
81090
  }
80638
81091
  function parseSpec(specFile) {
80639
- const content = fs68.readFileSync(specFile, "utf-8");
80640
- const ext = path83.extname(specFile).toLowerCase();
81092
+ const content = fs69.readFileSync(specFile, "utf-8");
81093
+ const ext = path84.extname(specFile).toLowerCase();
80641
81094
  if (ext === ".json") {
80642
81095
  return parseJsonSpec(content);
80643
81096
  }
@@ -80708,12 +81161,12 @@ function extractRoutes(cwd) {
80708
81161
  function walkDir(dir) {
80709
81162
  let entries;
80710
81163
  try {
80711
- entries = fs68.readdirSync(dir, { withFileTypes: true });
81164
+ entries = fs69.readdirSync(dir, { withFileTypes: true });
80712
81165
  } catch {
80713
81166
  return;
80714
81167
  }
80715
81168
  for (const entry of entries) {
80716
- const fullPath = path83.join(dir, entry.name);
81169
+ const fullPath = path84.join(dir, entry.name);
80717
81170
  if (entry.isSymbolicLink()) {
80718
81171
  continue;
80719
81172
  }
@@ -80723,7 +81176,7 @@ function extractRoutes(cwd) {
80723
81176
  }
80724
81177
  walkDir(fullPath);
80725
81178
  } else if (entry.isFile()) {
80726
- const ext = path83.extname(entry.name).toLowerCase();
81179
+ const ext = path84.extname(entry.name).toLowerCase();
80727
81180
  const baseName = entry.name.toLowerCase();
80728
81181
  if (![".ts", ".js", ".mjs"].includes(ext)) {
80729
81182
  continue;
@@ -80741,7 +81194,7 @@ function extractRoutes(cwd) {
80741
81194
  }
80742
81195
  function extractRoutesFromFile(filePath) {
80743
81196
  const routes = [];
80744
- const content = fs68.readFileSync(filePath, "utf-8");
81197
+ const content = fs69.readFileSync(filePath, "utf-8");
80745
81198
  const lines = content.split(/\r?\n/);
80746
81199
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
80747
81200
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -80889,8 +81342,8 @@ var schema_drift = createSwarmTool({
80889
81342
  init_tool();
80890
81343
  init_path_security();
80891
81344
  init_create_tool();
80892
- import * as fs69 from "fs";
80893
- import * as path84 from "path";
81345
+ import * as fs70 from "fs";
81346
+ import * as path85 from "path";
80894
81347
  var DEFAULT_MAX_RESULTS = 100;
80895
81348
  var DEFAULT_MAX_LINES = 200;
80896
81349
  var REGEX_TIMEOUT_MS = 5000;
@@ -80926,11 +81379,11 @@ function containsWindowsAttacks3(str) {
80926
81379
  }
80927
81380
  function isPathInWorkspace3(filePath, workspace) {
80928
81381
  try {
80929
- const resolvedPath = path84.resolve(workspace, filePath);
80930
- const realWorkspace = fs69.realpathSync(workspace);
80931
- const realResolvedPath = fs69.realpathSync(resolvedPath);
80932
- const relativePath = path84.relative(realWorkspace, realResolvedPath);
80933
- if (relativePath.startsWith("..") || path84.isAbsolute(relativePath)) {
81382
+ const resolvedPath = path85.resolve(workspace, filePath);
81383
+ const realWorkspace = fs70.realpathSync(workspace);
81384
+ const realResolvedPath = fs70.realpathSync(resolvedPath);
81385
+ const relativePath = path85.relative(realWorkspace, realResolvedPath);
81386
+ if (relativePath.startsWith("..") || path85.isAbsolute(relativePath)) {
80934
81387
  return false;
80935
81388
  }
80936
81389
  return true;
@@ -80943,12 +81396,12 @@ function validatePathForRead2(filePath, workspace) {
80943
81396
  }
80944
81397
  function findRgInEnvPath() {
80945
81398
  const searchPath = process.env.PATH ?? "";
80946
- for (const dir of searchPath.split(path84.delimiter)) {
81399
+ for (const dir of searchPath.split(path85.delimiter)) {
80947
81400
  if (!dir)
80948
81401
  continue;
80949
81402
  const isWindows = process.platform === "win32";
80950
- const candidate = path84.join(dir, isWindows ? "rg.exe" : "rg");
80951
- if (fs69.existsSync(candidate))
81403
+ const candidate = path85.join(dir, isWindows ? "rg.exe" : "rg");
81404
+ if (fs70.existsSync(candidate))
80952
81405
  return candidate;
80953
81406
  }
80954
81407
  return null;
@@ -81002,7 +81455,7 @@ async function ripgrepSearch(opts) {
81002
81455
  stderr: "pipe",
81003
81456
  cwd: opts.workspace
81004
81457
  });
81005
- const timeout = new Promise((resolve35) => setTimeout(() => resolve35("timeout"), REGEX_TIMEOUT_MS));
81458
+ const timeout = new Promise((resolve36) => setTimeout(() => resolve36("timeout"), REGEX_TIMEOUT_MS));
81006
81459
  const exitPromise = proc.exited;
81007
81460
  const result = await Promise.race([exitPromise, timeout]);
81008
81461
  if (result === "timeout") {
@@ -81075,10 +81528,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
81075
81528
  return files;
81076
81529
  }
81077
81530
  try {
81078
- const entries = fs69.readdirSync(dir, { withFileTypes: true });
81531
+ const entries = fs70.readdirSync(dir, { withFileTypes: true });
81079
81532
  for (const entry of entries) {
81080
- const fullPath = path84.join(dir, entry.name);
81081
- const relativePath = path84.relative(workspace, fullPath);
81533
+ const fullPath = path85.join(dir, entry.name);
81534
+ const relativePath = path85.relative(workspace, fullPath);
81082
81535
  if (!validatePathForRead2(fullPath, workspace)) {
81083
81536
  continue;
81084
81537
  }
@@ -81119,13 +81572,13 @@ async function fallbackSearch(opts) {
81119
81572
  const matches = [];
81120
81573
  let total = 0;
81121
81574
  for (const file3 of files) {
81122
- const fullPath = path84.join(opts.workspace, file3);
81575
+ const fullPath = path85.join(opts.workspace, file3);
81123
81576
  if (!validatePathForRead2(fullPath, opts.workspace)) {
81124
81577
  continue;
81125
81578
  }
81126
81579
  let stats;
81127
81580
  try {
81128
- stats = fs69.statSync(fullPath);
81581
+ stats = fs70.statSync(fullPath);
81129
81582
  if (stats.size > MAX_FILE_SIZE_BYTES10) {
81130
81583
  continue;
81131
81584
  }
@@ -81134,7 +81587,7 @@ async function fallbackSearch(opts) {
81134
81587
  }
81135
81588
  let content;
81136
81589
  try {
81137
- content = fs69.readFileSync(fullPath, "utf-8");
81590
+ content = fs70.readFileSync(fullPath, "utf-8");
81138
81591
  } catch {
81139
81592
  continue;
81140
81593
  }
@@ -81246,7 +81699,7 @@ var search = createSwarmTool({
81246
81699
  message: "Exclude pattern contains invalid Windows-specific sequence"
81247
81700
  }, null, 2);
81248
81701
  }
81249
- if (!fs69.existsSync(directory)) {
81702
+ if (!fs70.existsSync(directory)) {
81250
81703
  return JSON.stringify({
81251
81704
  error: true,
81252
81705
  type: "unknown",
@@ -81369,8 +81822,8 @@ var set_qa_gates = createSwarmTool({
81369
81822
  init_tool();
81370
81823
  init_path_security();
81371
81824
  init_create_tool();
81372
- import * as fs70 from "fs";
81373
- import * as path85 from "path";
81825
+ import * as fs71 from "fs";
81826
+ import * as path86 from "path";
81374
81827
  var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
81375
81828
  function containsWindowsAttacks4(str) {
81376
81829
  if (/:[^\\/]/.test(str))
@@ -81384,14 +81837,14 @@ function containsWindowsAttacks4(str) {
81384
81837
  }
81385
81838
  function isPathInWorkspace4(filePath, workspace) {
81386
81839
  try {
81387
- const resolvedPath = path85.resolve(workspace, filePath);
81388
- if (!fs70.existsSync(resolvedPath)) {
81840
+ const resolvedPath = path86.resolve(workspace, filePath);
81841
+ if (!fs71.existsSync(resolvedPath)) {
81389
81842
  return true;
81390
81843
  }
81391
- const realWorkspace = fs70.realpathSync(workspace);
81392
- const realResolvedPath = fs70.realpathSync(resolvedPath);
81393
- const relativePath = path85.relative(realWorkspace, realResolvedPath);
81394
- if (relativePath.startsWith("..") || path85.isAbsolute(relativePath)) {
81844
+ const realWorkspace = fs71.realpathSync(workspace);
81845
+ const realResolvedPath = fs71.realpathSync(resolvedPath);
81846
+ const relativePath = path86.relative(realWorkspace, realResolvedPath);
81847
+ if (relativePath.startsWith("..") || path86.isAbsolute(relativePath)) {
81395
81848
  return false;
81396
81849
  }
81397
81850
  return true;
@@ -81563,7 +82016,7 @@ var suggestPatch = createSwarmTool({
81563
82016
  message: "changes cannot be empty"
81564
82017
  }, null, 2);
81565
82018
  }
81566
- if (!fs70.existsSync(directory)) {
82019
+ if (!fs71.existsSync(directory)) {
81567
82020
  return JSON.stringify({
81568
82021
  success: false,
81569
82022
  error: true,
@@ -81599,8 +82052,8 @@ var suggestPatch = createSwarmTool({
81599
82052
  });
81600
82053
  continue;
81601
82054
  }
81602
- const fullPath = path85.resolve(directory, change.file);
81603
- if (!fs70.existsSync(fullPath)) {
82055
+ const fullPath = path86.resolve(directory, change.file);
82056
+ if (!fs71.existsSync(fullPath)) {
81604
82057
  errors5.push({
81605
82058
  success: false,
81606
82059
  error: true,
@@ -81614,7 +82067,7 @@ var suggestPatch = createSwarmTool({
81614
82067
  }
81615
82068
  let content;
81616
82069
  try {
81617
- content = fs70.readFileSync(fullPath, "utf-8");
82070
+ content = fs71.readFileSync(fullPath, "utf-8");
81618
82071
  } catch (err3) {
81619
82072
  errors5.push({
81620
82073
  success: false,
@@ -81693,8 +82146,8 @@ var suggestPatch = createSwarmTool({
81693
82146
  // src/tools/lint-spec.ts
81694
82147
  init_spec_schema();
81695
82148
  init_create_tool();
81696
- import * as fs71 from "fs";
81697
- import * as path86 from "path";
82149
+ import * as fs72 from "fs";
82150
+ import * as path87 from "path";
81698
82151
  var SPEC_FILE_NAME = "spec.md";
81699
82152
  var SWARM_DIR2 = ".swarm";
81700
82153
  var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
@@ -81747,8 +82200,8 @@ var lint_spec = createSwarmTool({
81747
82200
  async execute(_args, directory) {
81748
82201
  const errors5 = [];
81749
82202
  const warnings = [];
81750
- const specPath = path86.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
81751
- if (!fs71.existsSync(specPath)) {
82203
+ const specPath = path87.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
82204
+ if (!fs72.existsSync(specPath)) {
81752
82205
  const result2 = {
81753
82206
  valid: false,
81754
82207
  specMtime: null,
@@ -81767,12 +82220,12 @@ var lint_spec = createSwarmTool({
81767
82220
  }
81768
82221
  let specMtime = null;
81769
82222
  try {
81770
- const stats = fs71.statSync(specPath);
82223
+ const stats = fs72.statSync(specPath);
81771
82224
  specMtime = stats.mtime.toISOString();
81772
82225
  } catch {}
81773
82226
  let content;
81774
82227
  try {
81775
- content = fs71.readFileSync(specPath, "utf-8");
82228
+ content = fs72.readFileSync(specPath, "utf-8");
81776
82229
  } catch (e) {
81777
82230
  const result2 = {
81778
82231
  valid: false,
@@ -81817,13 +82270,13 @@ var lint_spec = createSwarmTool({
81817
82270
  });
81818
82271
  // src/tools/mutation-test.ts
81819
82272
  init_dist();
81820
- import * as fs72 from "fs";
81821
- import * as path88 from "path";
82273
+ import * as fs73 from "fs";
82274
+ import * as path89 from "path";
81822
82275
 
81823
82276
  // src/mutation/engine.ts
81824
82277
  import { spawnSync as spawnSync3 } from "child_process";
81825
- import { unlinkSync as unlinkSync11, writeFileSync as writeFileSync17 } from "fs";
81826
- import * as path87 from "path";
82278
+ import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "fs";
82279
+ import * as path88 from "path";
81827
82280
 
81828
82281
  // src/mutation/equivalence.ts
81829
82282
  function isStaticallyEquivalent(originalCode, mutatedCode) {
@@ -81958,9 +82411,9 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
81958
82411
  let patchFile;
81959
82412
  try {
81960
82413
  const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
81961
- patchFile = path87.join(workingDir, `.mutation_patch_${safeId2}.diff`);
82414
+ patchFile = path88.join(workingDir, `.mutation_patch_${safeId2}.diff`);
81962
82415
  try {
81963
- writeFileSync17(patchFile, patch.patch);
82416
+ writeFileSync18(patchFile, patch.patch);
81964
82417
  } catch (writeErr) {
81965
82418
  error93 = `Failed to write patch file: ${writeErr}`;
81966
82419
  outcome = "error";
@@ -82056,7 +82509,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
82056
82509
  revertError = new Error(`Failed to revert mutation ${patch.id}: ${revertErr}. Working tree may be dirty.`);
82057
82510
  }
82058
82511
  try {
82059
- unlinkSync11(patchFile);
82512
+ unlinkSync12(patchFile);
82060
82513
  } catch (_unlinkErr) {}
82061
82514
  }
82062
82515
  }
@@ -82352,8 +82805,8 @@ var mutation_test = createSwarmTool({
82352
82805
  ];
82353
82806
  for (const filePath of uniquePaths) {
82354
82807
  try {
82355
- const resolvedPath = path88.resolve(cwd, filePath);
82356
- sourceFiles.set(filePath, fs72.readFileSync(resolvedPath, "utf-8"));
82808
+ const resolvedPath = path89.resolve(cwd, filePath);
82809
+ sourceFiles.set(filePath, fs73.readFileSync(resolvedPath, "utf-8"));
82357
82810
  } catch {}
82358
82811
  }
82359
82812
  const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
@@ -82371,8 +82824,8 @@ var mutation_test = createSwarmTool({
82371
82824
  init_dist();
82372
82825
  init_manager2();
82373
82826
  init_detector();
82374
- import * as fs73 from "fs";
82375
- import * as path89 from "path";
82827
+ import * as fs74 from "fs";
82828
+ import * as path90 from "path";
82376
82829
  init_create_tool();
82377
82830
  var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
82378
82831
  var BINARY_CHECK_BYTES = 8192;
@@ -82438,7 +82891,7 @@ async function syntaxCheck(input, directory, config3) {
82438
82891
  if (languages?.length) {
82439
82892
  const lowerLangs = languages.map((l) => l.toLowerCase());
82440
82893
  filesToCheck = filesToCheck.filter((file3) => {
82441
- const ext = path89.extname(file3.path).toLowerCase();
82894
+ const ext = path90.extname(file3.path).toLowerCase();
82442
82895
  const langDef = getLanguageForExtension(ext);
82443
82896
  const fileProfile = getProfileForFile(file3.path);
82444
82897
  const langId = fileProfile?.id || langDef?.id;
@@ -82451,7 +82904,7 @@ async function syntaxCheck(input, directory, config3) {
82451
82904
  let skippedCount = 0;
82452
82905
  for (const fileInfo of filesToCheck) {
82453
82906
  const { path: filePath } = fileInfo;
82454
- const fullPath = path89.isAbsolute(filePath) ? filePath : path89.join(directory, filePath);
82907
+ const fullPath = path90.isAbsolute(filePath) ? filePath : path90.join(directory, filePath);
82455
82908
  const result = {
82456
82909
  path: filePath,
82457
82910
  language: "",
@@ -82481,7 +82934,7 @@ async function syntaxCheck(input, directory, config3) {
82481
82934
  }
82482
82935
  let content;
82483
82936
  try {
82484
- content = fs73.readFileSync(fullPath, "utf8");
82937
+ content = fs74.readFileSync(fullPath, "utf8");
82485
82938
  } catch {
82486
82939
  result.skipped_reason = "file_read_error";
82487
82940
  skippedCount++;
@@ -82500,7 +82953,7 @@ async function syntaxCheck(input, directory, config3) {
82500
82953
  results.push(result);
82501
82954
  continue;
82502
82955
  }
82503
- const ext = path89.extname(filePath).toLowerCase();
82956
+ const ext = path90.extname(filePath).toLowerCase();
82504
82957
  const langDef = getLanguageForExtension(ext);
82505
82958
  result.language = profile?.id || langDef?.id || "unknown";
82506
82959
  const errors5 = extractSyntaxErrors(parser, content);
@@ -82592,8 +83045,8 @@ init_dist();
82592
83045
  init_utils();
82593
83046
  init_create_tool();
82594
83047
  init_path_security();
82595
- import * as fs74 from "fs";
82596
- import * as path90 from "path";
83048
+ import * as fs75 from "fs";
83049
+ import * as path91 from "path";
82597
83050
  var MAX_TEXT_LENGTH = 200;
82598
83051
  var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
82599
83052
  var SUPPORTED_EXTENSIONS4 = new Set([
@@ -82659,9 +83112,9 @@ function validatePathsInput(paths, cwd) {
82659
83112
  return { error: "paths contains path traversal", resolvedPath: null };
82660
83113
  }
82661
83114
  try {
82662
- const resolvedPath = path90.resolve(paths);
82663
- const normalizedCwd = path90.resolve(cwd);
82664
- const normalizedResolved = path90.resolve(resolvedPath);
83115
+ const resolvedPath = path91.resolve(paths);
83116
+ const normalizedCwd = path91.resolve(cwd);
83117
+ const normalizedResolved = path91.resolve(resolvedPath);
82665
83118
  if (!normalizedResolved.startsWith(normalizedCwd)) {
82666
83119
  return {
82667
83120
  error: "paths must be within the current working directory",
@@ -82677,13 +83130,13 @@ function validatePathsInput(paths, cwd) {
82677
83130
  }
82678
83131
  }
82679
83132
  function isSupportedExtension(filePath) {
82680
- const ext = path90.extname(filePath).toLowerCase();
83133
+ const ext = path91.extname(filePath).toLowerCase();
82681
83134
  return SUPPORTED_EXTENSIONS4.has(ext);
82682
83135
  }
82683
83136
  function findSourceFiles4(dir, files = []) {
82684
83137
  let entries;
82685
83138
  try {
82686
- entries = fs74.readdirSync(dir);
83139
+ entries = fs75.readdirSync(dir);
82687
83140
  } catch {
82688
83141
  return files;
82689
83142
  }
@@ -82692,10 +83145,10 @@ function findSourceFiles4(dir, files = []) {
82692
83145
  if (SKIP_DIRECTORIES5.has(entry)) {
82693
83146
  continue;
82694
83147
  }
82695
- const fullPath = path90.join(dir, entry);
83148
+ const fullPath = path91.join(dir, entry);
82696
83149
  let stat3;
82697
83150
  try {
82698
- stat3 = fs74.statSync(fullPath);
83151
+ stat3 = fs75.statSync(fullPath);
82699
83152
  } catch {
82700
83153
  continue;
82701
83154
  }
@@ -82788,7 +83241,7 @@ var todo_extract = createSwarmTool({
82788
83241
  return JSON.stringify(errorResult, null, 2);
82789
83242
  }
82790
83243
  const scanPath = resolvedPath;
82791
- if (!fs74.existsSync(scanPath)) {
83244
+ if (!fs75.existsSync(scanPath)) {
82792
83245
  const errorResult = {
82793
83246
  error: `path not found: ${pathsInput}`,
82794
83247
  total: 0,
@@ -82798,13 +83251,13 @@ var todo_extract = createSwarmTool({
82798
83251
  return JSON.stringify(errorResult, null, 2);
82799
83252
  }
82800
83253
  const filesToScan = [];
82801
- const stat3 = fs74.statSync(scanPath);
83254
+ const stat3 = fs75.statSync(scanPath);
82802
83255
  if (stat3.isFile()) {
82803
83256
  if (isSupportedExtension(scanPath)) {
82804
83257
  filesToScan.push(scanPath);
82805
83258
  } else {
82806
83259
  const errorResult = {
82807
- error: `unsupported file extension: ${path90.extname(scanPath)}`,
83260
+ error: `unsupported file extension: ${path91.extname(scanPath)}`,
82808
83261
  total: 0,
82809
83262
  byPriority: { high: 0, medium: 0, low: 0 },
82810
83263
  entries: []
@@ -82817,11 +83270,11 @@ var todo_extract = createSwarmTool({
82817
83270
  const allEntries = [];
82818
83271
  for (const filePath of filesToScan) {
82819
83272
  try {
82820
- const fileStat = fs74.statSync(filePath);
83273
+ const fileStat = fs75.statSync(filePath);
82821
83274
  if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
82822
83275
  continue;
82823
83276
  }
82824
- const content = fs74.readFileSync(filePath, "utf-8");
83277
+ const content = fs75.readFileSync(filePath, "utf-8");
82825
83278
  const entries = parseTodoComments(content, filePath, tagsSet);
82826
83279
  allEntries.push(...entries);
82827
83280
  } catch {}
@@ -82851,18 +83304,18 @@ init_tool();
82851
83304
  init_loader();
82852
83305
  init_schema();
82853
83306
  init_gate_evidence();
82854
- import * as fs76 from "fs";
82855
- import * as path92 from "path";
83307
+ import * as fs77 from "fs";
83308
+ import * as path93 from "path";
82856
83309
 
82857
83310
  // src/hooks/diff-scope.ts
82858
- import * as fs75 from "fs";
82859
- import * as path91 from "path";
83311
+ import * as fs76 from "fs";
83312
+ import * as path92 from "path";
82860
83313
  function getDeclaredScope(taskId, directory) {
82861
83314
  try {
82862
- const planPath = path91.join(directory, ".swarm", "plan.json");
82863
- if (!fs75.existsSync(planPath))
83315
+ const planPath = path92.join(directory, ".swarm", "plan.json");
83316
+ if (!fs76.existsSync(planPath))
82864
83317
  return null;
82865
- const raw = fs75.readFileSync(planPath, "utf-8");
83318
+ const raw = fs76.readFileSync(planPath, "utf-8");
82866
83319
  const plan = JSON.parse(raw);
82867
83320
  for (const phase of plan.phases ?? []) {
82868
83321
  for (const task of phase.tasks ?? []) {
@@ -82977,7 +83430,7 @@ var TIER_3_PATTERNS = [
82977
83430
  ];
82978
83431
  function matchesTier3Pattern(files) {
82979
83432
  for (const file3 of files) {
82980
- const fileName = path92.basename(file3);
83433
+ const fileName = path93.basename(file3);
82981
83434
  for (const pattern of TIER_3_PATTERNS) {
82982
83435
  if (pattern.test(fileName)) {
82983
83436
  return true;
@@ -82991,8 +83444,8 @@ function checkReviewerGate(taskId, workingDirectory) {
82991
83444
  if (hasActiveTurboMode()) {
82992
83445
  const resolvedDir2 = workingDirectory;
82993
83446
  try {
82994
- const planPath = path92.join(resolvedDir2, ".swarm", "plan.json");
82995
- const planRaw = fs76.readFileSync(planPath, "utf-8");
83447
+ const planPath = path93.join(resolvedDir2, ".swarm", "plan.json");
83448
+ const planRaw = fs77.readFileSync(planPath, "utf-8");
82996
83449
  const plan = JSON.parse(planRaw);
82997
83450
  for (const planPhase of plan.phases ?? []) {
82998
83451
  for (const task of planPhase.tasks ?? []) {
@@ -83058,8 +83511,8 @@ function checkReviewerGate(taskId, workingDirectory) {
83058
83511
  }
83059
83512
  try {
83060
83513
  const resolvedDir2 = workingDirectory;
83061
- const planPath = path92.join(resolvedDir2, ".swarm", "plan.json");
83062
- const planRaw = fs76.readFileSync(planPath, "utf-8");
83514
+ const planPath = path93.join(resolvedDir2, ".swarm", "plan.json");
83515
+ const planRaw = fs77.readFileSync(planPath, "utf-8");
83063
83516
  const plan = JSON.parse(planRaw);
83064
83517
  for (const planPhase of plan.phases ?? []) {
83065
83518
  for (const task of planPhase.tasks ?? []) {
@@ -83276,8 +83729,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83276
83729
  };
83277
83730
  }
83278
83731
  }
83279
- normalizedDir = path92.normalize(args2.working_directory);
83280
- const pathParts = normalizedDir.split(path92.sep);
83732
+ normalizedDir = path93.normalize(args2.working_directory);
83733
+ const pathParts = normalizedDir.split(path93.sep);
83281
83734
  if (pathParts.includes("..")) {
83282
83735
  return {
83283
83736
  success: false,
@@ -83287,11 +83740,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83287
83740
  ]
83288
83741
  };
83289
83742
  }
83290
- const resolvedDir = path92.resolve(normalizedDir);
83743
+ const resolvedDir = path93.resolve(normalizedDir);
83291
83744
  try {
83292
- const realPath = fs76.realpathSync(resolvedDir);
83293
- const planPath = path92.join(realPath, ".swarm", "plan.json");
83294
- if (!fs76.existsSync(planPath)) {
83745
+ const realPath = fs77.realpathSync(resolvedDir);
83746
+ const planPath = path93.join(realPath, ".swarm", "plan.json");
83747
+ if (!fs77.existsSync(planPath)) {
83295
83748
  return {
83296
83749
  success: false,
83297
83750
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -83322,12 +83775,12 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83322
83775
  }
83323
83776
  if (args2.status === "in_progress") {
83324
83777
  try {
83325
- const evidencePath = path92.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
83326
- fs76.mkdirSync(path92.dirname(evidencePath), { recursive: true });
83327
- const fd = fs76.openSync(evidencePath, "wx");
83778
+ const evidencePath = path93.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
83779
+ fs77.mkdirSync(path93.dirname(evidencePath), { recursive: true });
83780
+ const fd = fs77.openSync(evidencePath, "wx");
83328
83781
  let writeOk = false;
83329
83782
  try {
83330
- fs76.writeSync(fd, JSON.stringify({
83783
+ fs77.writeSync(fd, JSON.stringify({
83331
83784
  task_id: args2.task_id,
83332
83785
  required_gates: ["reviewer", "test_engineer"],
83333
83786
  gates: {},
@@ -83335,10 +83788,10 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83335
83788
  }, null, 2));
83336
83789
  writeOk = true;
83337
83790
  } finally {
83338
- fs76.closeSync(fd);
83791
+ fs77.closeSync(fd);
83339
83792
  if (!writeOk) {
83340
83793
  try {
83341
- fs76.unlinkSync(evidencePath);
83794
+ fs77.unlinkSync(evidencePath);
83342
83795
  } catch {}
83343
83796
  }
83344
83797
  }
@@ -83348,8 +83801,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83348
83801
  recoverTaskStateFromDelegations(args2.task_id);
83349
83802
  let phaseRequiresReviewer = true;
83350
83803
  try {
83351
- const planPath = path92.join(directory, ".swarm", "plan.json");
83352
- const planRaw = fs76.readFileSync(planPath, "utf-8");
83804
+ const planPath = path93.join(directory, ".swarm", "plan.json");
83805
+ const planRaw = fs77.readFileSync(planPath, "utf-8");
83353
83806
  const plan = JSON.parse(planRaw);
83354
83807
  const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
83355
83808
  if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
@@ -83458,8 +83911,8 @@ init_utils2();
83458
83911
  init_ledger();
83459
83912
  init_manager();
83460
83913
  init_create_tool();
83461
- import fs77 from "fs";
83462
- import path93 from "path";
83914
+ import fs78 from "fs";
83915
+ import path94 from "path";
83463
83916
  function derivePlanId5(plan) {
83464
83917
  return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
83465
83918
  }
@@ -83510,7 +83963,7 @@ async function executeWriteDriftEvidence(args2, directory) {
83510
83963
  entries: [evidenceEntry]
83511
83964
  };
83512
83965
  const filename = "drift-verifier.json";
83513
- const relativePath = path93.join("evidence", String(phase), filename);
83966
+ const relativePath = path94.join("evidence", String(phase), filename);
83514
83967
  let validatedPath;
83515
83968
  try {
83516
83969
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -83521,12 +83974,12 @@ async function executeWriteDriftEvidence(args2, directory) {
83521
83974
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
83522
83975
  }, null, 2);
83523
83976
  }
83524
- const evidenceDir = path93.dirname(validatedPath);
83977
+ const evidenceDir = path94.dirname(validatedPath);
83525
83978
  try {
83526
- await fs77.promises.mkdir(evidenceDir, { recursive: true });
83527
- const tempPath = path93.join(evidenceDir, `.${filename}.tmp`);
83528
- await fs77.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
83529
- await fs77.promises.rename(tempPath, validatedPath);
83979
+ await fs78.promises.mkdir(evidenceDir, { recursive: true });
83980
+ const tempPath = path94.join(evidenceDir, `.${filename}.tmp`);
83981
+ await fs78.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
83982
+ await fs78.promises.rename(tempPath, validatedPath);
83530
83983
  let snapshotInfo;
83531
83984
  let snapshotError;
83532
83985
  let qaProfileLocked;
@@ -83620,8 +84073,8 @@ var write_drift_evidence = createSwarmTool({
83620
84073
  init_tool();
83621
84074
  init_utils2();
83622
84075
  init_create_tool();
83623
- import fs78 from "fs";
83624
- import path94 from "path";
84076
+ import fs79 from "fs";
84077
+ import path95 from "path";
83625
84078
  function normalizeVerdict2(verdict) {
83626
84079
  switch (verdict) {
83627
84080
  case "APPROVED":
@@ -83669,7 +84122,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
83669
84122
  entries: [evidenceEntry]
83670
84123
  };
83671
84124
  const filename = "hallucination-guard.json";
83672
- const relativePath = path94.join("evidence", String(phase), filename);
84125
+ const relativePath = path95.join("evidence", String(phase), filename);
83673
84126
  let validatedPath;
83674
84127
  try {
83675
84128
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -83680,12 +84133,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
83680
84133
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
83681
84134
  }, null, 2);
83682
84135
  }
83683
- const evidenceDir = path94.dirname(validatedPath);
84136
+ const evidenceDir = path95.dirname(validatedPath);
83684
84137
  try {
83685
- await fs78.promises.mkdir(evidenceDir, { recursive: true });
83686
- const tempPath = path94.join(evidenceDir, `.${filename}.tmp`);
83687
- await fs78.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
83688
- await fs78.promises.rename(tempPath, validatedPath);
84138
+ await fs79.promises.mkdir(evidenceDir, { recursive: true });
84139
+ const tempPath = path95.join(evidenceDir, `.${filename}.tmp`);
84140
+ await fs79.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
84141
+ await fs79.promises.rename(tempPath, validatedPath);
83689
84142
  return JSON.stringify({
83690
84143
  success: true,
83691
84144
  phase,
@@ -83894,7 +84347,7 @@ var OpenCodeSwarm = async (ctx) => {
83894
84347
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
83895
84348
  preflightTriggerManager = new PTM(automationConfig);
83896
84349
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
83897
- const swarmDir = path95.resolve(ctx.directory, ".swarm");
84350
+ const swarmDir = path96.resolve(ctx.directory, ".swarm");
83898
84351
  statusArtifact = new ASA(swarmDir);
83899
84352
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
83900
84353
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {