opencode-swarm 6.73.0 → 6.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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"),
@@ -16734,7 +16738,7 @@ async function updateTaskStatus(directory, taskId, status) {
16734
16738
  const updatedPlan = { ...plan, phases: updatedPhases };
16735
16739
  try {
16736
16740
  await savePlan(directory, updatedPlan, {
16737
- preserveCompletedStatuses: true
16741
+ preserveCompletedStatuses: false
16738
16742
  });
16739
16743
  return updatedPlan;
16740
16744
  } catch (error49) {
@@ -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();
@@ -76602,8 +76610,8 @@ var placeholder_scan = createSwarmTool({
76602
76610
  });
76603
76611
  // src/tools/pre-check-batch.ts
76604
76612
  init_dist();
76605
- import * as fs64 from "fs";
76606
- import * as path78 from "path";
76613
+ import * as fs65 from "fs";
76614
+ import * as path79 from "path";
76607
76615
  init_manager2();
76608
76616
  init_utils();
76609
76617
  init_create_tool();
@@ -76738,8 +76746,8 @@ var quality_budget = createSwarmTool({
76738
76746
  init_dist();
76739
76747
  init_manager2();
76740
76748
  init_detector();
76741
- import * as fs63 from "fs";
76742
- import * as path77 from "path";
76749
+ import * as fs64 from "fs";
76750
+ import * as path78 from "path";
76743
76751
  import { extname as extname18 } from "path";
76744
76752
 
76745
76753
  // src/sast/rules/c.ts
@@ -77628,6 +77636,303 @@ async function runSemgrep(options) {
77628
77636
  // src/tools/sast-scan.ts
77629
77637
  init_utils();
77630
77638
  init_create_tool();
77639
+
77640
+ // src/tools/sast-baseline.ts
77641
+ init_utils2();
77642
+ import * as crypto8 from "crypto";
77643
+ import * as fs63 from "fs";
77644
+ import * as path77 from "path";
77645
+ var BASELINE_SCHEMA_VERSION = "1.0.0";
77646
+ var MAX_BASELINE_FINDINGS = 2000;
77647
+ var MAX_BASELINE_BYTES = 2 * 1048576;
77648
+ var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
77649
+ function normalizeFindingPath(directory, file3) {
77650
+ const resolved = path77.isAbsolute(file3) ? file3 : path77.resolve(directory, file3);
77651
+ const rel = path77.relative(path77.resolve(directory), resolved);
77652
+ return rel.replace(/\\/g, "/");
77653
+ }
77654
+ function baselineRelPath(phase) {
77655
+ return path77.join("evidence", String(phase), "sast-baseline.json");
77656
+ }
77657
+ function tempRelPath(phase) {
77658
+ return path77.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
77659
+ }
77660
+ function lockRelPath(phase) {
77661
+ return path77.join("evidence", String(phase), "sast-baseline.json.lock");
77662
+ }
77663
+ function getLine(lines, idx) {
77664
+ if (idx < 0 || idx >= lines.length)
77665
+ return "";
77666
+ return (lines[idx] ?? "").trim();
77667
+ }
77668
+ function fingerprintFinding(finding, directory, occurrenceIndex) {
77669
+ const relFile = normalizeFindingPath(directory, finding.location.file);
77670
+ if (relFile.startsWith("..")) {
77671
+ return {
77672
+ fingerprint: `${relFile}|${finding.rule_id}|L${finding.location.line}|UNSTABLE|#${occurrenceIndex}`,
77673
+ stable: false
77674
+ };
77675
+ }
77676
+ const lineNum = finding.location.line;
77677
+ try {
77678
+ const content = fs63.readFileSync(finding.location.file, "utf-8");
77679
+ const lines = content.split(`
77680
+ `);
77681
+ const idx = lineNum - 1;
77682
+ const window2 = [
77683
+ getLine(lines, idx - 1),
77684
+ getLine(lines, idx),
77685
+ getLine(lines, idx + 1)
77686
+ ].join(`
77687
+ `);
77688
+ const hash3 = crypto8.createHash("sha256").update(window2).digest("hex").slice(0, 16);
77689
+ return {
77690
+ fingerprint: `${relFile}|${finding.rule_id}|${hash3}|#${occurrenceIndex}`,
77691
+ stable: true
77692
+ };
77693
+ } catch {
77694
+ return {
77695
+ fingerprint: `${relFile}|${finding.rule_id}|L${lineNum}|UNSTABLE|#${occurrenceIndex}`,
77696
+ stable: false
77697
+ };
77698
+ }
77699
+ }
77700
+ function assignOccurrenceIndices(findings, directory) {
77701
+ const countMap = new Map;
77702
+ return findings.map((finding) => {
77703
+ const relFile = normalizeFindingPath(directory, finding.location.file);
77704
+ const lineNum = finding.location.line;
77705
+ let baseKey;
77706
+ try {
77707
+ if (relFile.startsWith(".."))
77708
+ throw new Error("escapes workspace");
77709
+ const content = fs63.readFileSync(finding.location.file, "utf-8");
77710
+ const lines = content.split(`
77711
+ `);
77712
+ const idx = lineNum - 1;
77713
+ const window2 = [
77714
+ getLine(lines, idx - 1),
77715
+ getLine(lines, idx),
77716
+ getLine(lines, idx + 1)
77717
+ ].join(`
77718
+ `);
77719
+ const hash3 = crypto8.createHash("sha256").update(window2).digest("hex").slice(0, 16);
77720
+ baseKey = `${relFile}|${finding.rule_id}|${hash3}`;
77721
+ } catch {
77722
+ baseKey = `${relFile}|${finding.rule_id}|L${lineNum}|UNSTABLE`;
77723
+ }
77724
+ const occIdx = countMap.get(baseKey) ?? 0;
77725
+ countMap.set(baseKey, occIdx + 1);
77726
+ const fp = fingerprintFinding(finding, directory, occIdx);
77727
+ return {
77728
+ finding,
77729
+ index: occIdx,
77730
+ stable: fp.stable,
77731
+ fingerprint: fp.fingerprint
77732
+ };
77733
+ });
77734
+ }
77735
+ async function acquireLock(lockPath) {
77736
+ for (let attempt = 0;attempt <= LOCK_RETRY_DELAYS_MS.length; attempt++) {
77737
+ try {
77738
+ const fd = fs63.openSync(lockPath, "wx");
77739
+ fs63.closeSync(fd);
77740
+ return () => {
77741
+ try {
77742
+ fs63.unlinkSync(lockPath);
77743
+ } catch {}
77744
+ };
77745
+ } catch {
77746
+ if (attempt < LOCK_RETRY_DELAYS_MS.length) {
77747
+ await new Promise((resolve31) => setTimeout(resolve31, LOCK_RETRY_DELAYS_MS[attempt]));
77748
+ }
77749
+ }
77750
+ }
77751
+ return () => {};
77752
+ }
77753
+ function validatePhase(phase) {
77754
+ if (!Number.isInteger(phase) || phase < 1) {
77755
+ return "Invalid phase: must be a positive integer";
77756
+ }
77757
+ return null;
77758
+ }
77759
+ async function captureOrMergeBaseline(directory, phase, findings, engine, scannedFiles, opts) {
77760
+ const phaseError = validatePhase(phase);
77761
+ if (phaseError)
77762
+ return { status: "error", message: phaseError };
77763
+ if (!scannedFiles || scannedFiles.length === 0) {
77764
+ return {
77765
+ status: "error",
77766
+ message: "capture_baseline requires non-empty changed_files"
77767
+ };
77768
+ }
77769
+ let baselinePath;
77770
+ let tempPath;
77771
+ let lockPath;
77772
+ try {
77773
+ baselinePath = validateSwarmPath(directory, baselineRelPath(phase));
77774
+ tempPath = validateSwarmPath(directory, tempRelPath(phase));
77775
+ lockPath = validateSwarmPath(directory, lockRelPath(phase));
77776
+ } catch (e) {
77777
+ return {
77778
+ status: "error",
77779
+ message: e instanceof Error ? e.message : "Path validation failed"
77780
+ };
77781
+ }
77782
+ fs63.mkdirSync(path77.dirname(baselinePath), { recursive: true });
77783
+ const releaseLock = await acquireLock(lockPath);
77784
+ try {
77785
+ let existing = null;
77786
+ try {
77787
+ const raw = fs63.readFileSync(baselinePath, "utf-8");
77788
+ const parsed = JSON.parse(raw);
77789
+ if (parsed.schema_version === BASELINE_SCHEMA_VERSION) {
77790
+ existing = parsed;
77791
+ }
77792
+ } catch {}
77793
+ const scannedRelFiles = new Set(scannedFiles.map((f) => normalizeFindingPath(directory, f)));
77794
+ const indexed = assignOccurrenceIndices(findings, directory);
77795
+ if (existing && !opts?.force) {
77796
+ const prunedFingerprints = existing.fingerprints.filter((fp) => {
77797
+ const relFile = fp.slice(0, fp.indexOf("|"));
77798
+ return !scannedRelFiles.has(relFile);
77799
+ });
77800
+ const prunedSnapshot = existing.findings_snapshot.filter((f) => {
77801
+ return !scannedRelFiles.has(normalizeFindingPath(directory, f.location.file));
77802
+ });
77803
+ const prunedFilesIndexed = existing.files_indexed.filter((f) => !scannedRelFiles.has(f));
77804
+ const mergedFingerprints = [
77805
+ ...prunedFingerprints,
77806
+ ...indexed.map((i2) => i2.fingerprint)
77807
+ ];
77808
+ const mergedSnapshot = [
77809
+ ...prunedSnapshot,
77810
+ ...indexed.map((i2) => i2.finding)
77811
+ ];
77812
+ const mergedFilesIndexed = [
77813
+ ...prunedFilesIndexed,
77814
+ ...Array.from(scannedRelFiles)
77815
+ ];
77816
+ const truncated2 = mergedSnapshot.length > MAX_BASELINE_FINDINGS;
77817
+ const cappedSnapshot2 = truncated2 ? mergedSnapshot.slice(-MAX_BASELINE_FINDINGS) : mergedSnapshot;
77818
+ const cappedFingerprints2 = truncated2 ? mergedFingerprints.slice(-MAX_BASELINE_FINDINGS) : mergedFingerprints;
77819
+ let cappedFilesIndexed = mergedFilesIndexed;
77820
+ if (truncated2) {
77821
+ const survivingFiles = new Set;
77822
+ for (const finding of cappedSnapshot2) {
77823
+ const relFile = normalizeFindingPath(directory, finding.location.file);
77824
+ survivingFiles.add(relFile);
77825
+ }
77826
+ cappedFilesIndexed = Array.from(survivingFiles);
77827
+ }
77828
+ const now2 = new Date().toISOString();
77829
+ const bundle2 = {
77830
+ schema_version: BASELINE_SCHEMA_VERSION,
77831
+ phase,
77832
+ created_at: existing.created_at,
77833
+ updated_at: now2,
77834
+ engine,
77835
+ files_indexed: cappedFilesIndexed,
77836
+ fingerprints: cappedFingerprints2,
77837
+ findings_snapshot: cappedSnapshot2,
77838
+ truncated: truncated2
77839
+ };
77840
+ const json4 = JSON.stringify(bundle2, null, 2);
77841
+ if (json4.length > MAX_BASELINE_BYTES) {
77842
+ return {
77843
+ status: "error",
77844
+ message: `Baseline would exceed size cap (${json4.length} bytes > ${MAX_BASELINE_BYTES})`
77845
+ };
77846
+ }
77847
+ fs63.writeFileSync(tempPath, json4, "utf-8");
77848
+ fs63.renameSync(tempPath, baselinePath);
77849
+ return {
77850
+ status: "merged",
77851
+ path: baselinePath,
77852
+ fingerprint_count: cappedFingerprints2.length
77853
+ };
77854
+ }
77855
+ const newFingerprints = indexed.map((i2) => i2.fingerprint);
77856
+ const newSnapshot = indexed.map((i2) => i2.finding);
77857
+ const truncated = newSnapshot.length > MAX_BASELINE_FINDINGS;
77858
+ const cappedSnapshot = truncated ? newSnapshot.slice(0, MAX_BASELINE_FINDINGS) : newSnapshot;
77859
+ const cappedFingerprints = truncated ? newFingerprints.slice(0, MAX_BASELINE_FINDINGS) : newFingerprints;
77860
+ const now = new Date().toISOString();
77861
+ const bundle = {
77862
+ schema_version: BASELINE_SCHEMA_VERSION,
77863
+ phase,
77864
+ created_at: now,
77865
+ updated_at: now,
77866
+ engine,
77867
+ files_indexed: Array.from(scannedRelFiles),
77868
+ fingerprints: cappedFingerprints,
77869
+ findings_snapshot: cappedSnapshot,
77870
+ truncated
77871
+ };
77872
+ const json3 = JSON.stringify(bundle, null, 2);
77873
+ if (json3.length > MAX_BASELINE_BYTES) {
77874
+ return {
77875
+ status: "error",
77876
+ message: `Baseline would exceed size cap (${json3.length} bytes > ${MAX_BASELINE_BYTES})`
77877
+ };
77878
+ }
77879
+ fs63.writeFileSync(tempPath, json3, "utf-8");
77880
+ fs63.renameSync(tempPath, baselinePath);
77881
+ return {
77882
+ status: "written",
77883
+ path: baselinePath,
77884
+ fingerprint_count: cappedFingerprints.length
77885
+ };
77886
+ } finally {
77887
+ releaseLock();
77888
+ }
77889
+ }
77890
+ function loadBaseline(directory, phase) {
77891
+ const phaseError = validatePhase(phase);
77892
+ if (phaseError) {
77893
+ return { status: "invalid_schema", errors: [phaseError] };
77894
+ }
77895
+ let baselinePath;
77896
+ try {
77897
+ baselinePath = validateSwarmPath(directory, baselineRelPath(phase));
77898
+ } catch (e) {
77899
+ return {
77900
+ status: "invalid_schema",
77901
+ errors: [e instanceof Error ? e.message : "Path validation failed"]
77902
+ };
77903
+ }
77904
+ try {
77905
+ const raw = fs63.readFileSync(baselinePath, "utf-8");
77906
+ const parsed = JSON.parse(raw);
77907
+ if (parsed.schema_version !== BASELINE_SCHEMA_VERSION) {
77908
+ return {
77909
+ status: "invalid_schema",
77910
+ errors: [`Unknown schema version: ${String(parsed.schema_version)}`]
77911
+ };
77912
+ }
77913
+ if (!Array.isArray(parsed.fingerprints)) {
77914
+ return {
77915
+ status: "invalid_schema",
77916
+ errors: ["Missing or invalid fingerprints array"]
77917
+ };
77918
+ }
77919
+ return {
77920
+ status: "found",
77921
+ fingerprints: new Set(parsed.fingerprints),
77922
+ bundle: parsed
77923
+ };
77924
+ } catch (e) {
77925
+ if (e.code === "ENOENT") {
77926
+ return { status: "not_found" };
77927
+ }
77928
+ return {
77929
+ status: "invalid_schema",
77930
+ errors: [e instanceof Error ? e.message : "Failed to read baseline"]
77931
+ };
77932
+ }
77933
+ }
77934
+
77935
+ // src/tools/sast-scan.ts
77631
77936
  var MAX_FILE_SIZE_BYTES8 = 512 * 1024;
77632
77937
  var MAX_FILES_SCANNED2 = 1000;
77633
77938
  var MAX_FINDINGS2 = 100;
@@ -77639,17 +77944,17 @@ var SEVERITY_ORDER = {
77639
77944
  };
77640
77945
  function shouldSkipFile(filePath) {
77641
77946
  try {
77642
- const stats = fs63.statSync(filePath);
77947
+ const stats = fs64.statSync(filePath);
77643
77948
  if (stats.size > MAX_FILE_SIZE_BYTES8) {
77644
77949
  return { skip: true, reason: "file too large" };
77645
77950
  }
77646
77951
  if (stats.size === 0) {
77647
77952
  return { skip: true, reason: "empty file" };
77648
77953
  }
77649
- const fd = fs63.openSync(filePath, "r");
77954
+ const fd = fs64.openSync(filePath, "r");
77650
77955
  const buffer = Buffer.alloc(8192);
77651
- const bytesRead = fs63.readSync(fd, buffer, 0, 8192, 0);
77652
- fs63.closeSync(fd);
77956
+ const bytesRead = fs64.readSync(fd, buffer, 0, 8192, 0);
77957
+ fs64.closeSync(fd);
77653
77958
  if (bytesRead > 0) {
77654
77959
  let nullCount = 0;
77655
77960
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -77688,7 +77993,7 @@ function countBySeverity(findings) {
77688
77993
  }
77689
77994
  function scanFileWithTierA(filePath, language) {
77690
77995
  try {
77691
- const content = fs63.readFileSync(filePath, "utf-8");
77996
+ const content = fs64.readFileSync(filePath, "utf-8");
77692
77997
  const findings = executeRulesSync(filePath, content, language);
77693
77998
  return findings.map((f) => ({
77694
77999
  rule_id: f.rule_id,
@@ -77706,7 +78011,12 @@ function scanFileWithTierA(filePath, language) {
77706
78011
  }
77707
78012
  }
77708
78013
  async function sastScan(input, directory, config3) {
77709
- const { changed_files, severity_threshold = "medium" } = input;
78014
+ const {
78015
+ changed_files,
78016
+ severity_threshold = "medium",
78017
+ capture_baseline = false,
78018
+ phase
78019
+ } = input;
77710
78020
  if (config3?.gates?.sast_scan?.enabled === false) {
77711
78021
  return {
77712
78022
  verdict: "pass",
@@ -77727,6 +78037,7 @@ async function sastScan(input, directory, config3) {
77727
78037
  const allFindings = [];
77728
78038
  let filesScanned = 0;
77729
78039
  let _filesSkipped = 0;
78040
+ const scannedFilePaths = [];
77730
78041
  const semgrepAvailable = isSemgrepAvailable();
77731
78042
  const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
77732
78043
  const filesByLanguage = new Map;
@@ -77735,13 +78046,13 @@ async function sastScan(input, directory, config3) {
77735
78046
  _filesSkipped++;
77736
78047
  continue;
77737
78048
  }
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) {
78049
+ const resolvedPath = path78.isAbsolute(filePath) ? filePath : path78.resolve(directory, filePath);
78050
+ const resolvedDirectory = path78.resolve(directory);
78051
+ if (!resolvedPath.startsWith(resolvedDirectory + path78.sep) && resolvedPath !== resolvedDirectory) {
77741
78052
  _filesSkipped++;
77742
78053
  continue;
77743
78054
  }
77744
- if (!fs63.existsSync(resolvedPath)) {
78055
+ if (!fs64.existsSync(resolvedPath)) {
77745
78056
  _filesSkipped++;
77746
78057
  continue;
77747
78058
  }
@@ -77776,6 +78087,7 @@ async function sastScan(input, directory, config3) {
77776
78087
  }
77777
78088
  }
77778
78089
  filesScanned++;
78090
+ scannedFilePaths.push(resolvedPath);
77779
78091
  if (filesScanned >= MAX_FILES_SCANNED2) {
77780
78092
  warn(`SAST Scan: Reached maximum files limit (${MAX_FILES_SCANNED2}), stopping`);
77781
78093
  break;
@@ -77825,14 +78137,97 @@ async function sastScan(input, directory, config3) {
77825
78137
  warn(`SAST Scan: Semgrep failed, falling back to Tier A: ${error93}`);
77826
78138
  }
77827
78139
  }
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}`);
78140
+ if (capture_baseline) {
78141
+ if (phase === undefined || !Number.isInteger(phase) || phase < 1) {
78142
+ const errorResult = {
78143
+ verdict: "fail",
78144
+ findings: allFindings.slice(0, MAX_FINDINGS2),
78145
+ summary: {
78146
+ engine,
78147
+ files_scanned: filesScanned,
78148
+ findings_count: Math.min(allFindings.length, MAX_FINDINGS2),
78149
+ findings_by_severity: countBySeverity(allFindings.slice(0, MAX_FINDINGS2))
78150
+ }
78151
+ };
78152
+ return errorResult;
78153
+ }
78154
+ const captureFindings = allFindings.slice(0, MAX_BASELINE_FINDINGS);
78155
+ const captureResult = await captureOrMergeBaseline(directory, phase, captureFindings, engine, scannedFilePaths);
78156
+ const captureStatus = captureResult.status === "written" ? "baseline_captured" : captureResult.status === "merged" ? "baseline_merged" : undefined;
78157
+ if (captureResult.status === "error") {
78158
+ warn(`SAST Baseline: capture failed \u2014 ${captureResult.message}`);
78159
+ }
78160
+ const finalFindings2 = allFindings.slice(0, MAX_FINDINGS2);
78161
+ const summary2 = {
78162
+ engine,
78163
+ files_scanned: filesScanned,
78164
+ findings_count: finalFindings2.length,
78165
+ findings_by_severity: countBySeverity(finalFindings2)
78166
+ };
78167
+ await saveEvidence(directory, "sast_scan", {
78168
+ task_id: "sast_scan",
78169
+ type: "sast",
78170
+ timestamp: new Date().toISOString(),
78171
+ agent: "sast_scan",
78172
+ verdict: "pass",
78173
+ summary: `Baseline capture: scanned ${filesScanned} files, recorded ${captureFindings.length} finding(s)`,
78174
+ ...summary2,
78175
+ findings: finalFindings2,
78176
+ baseline_used: false
78177
+ });
78178
+ return {
78179
+ verdict: "pass",
78180
+ findings: finalFindings2,
78181
+ summary: summary2,
78182
+ status: captureStatus,
78183
+ finding_count: captureResult.status !== "error" ? captureResult.fingerprint_count : undefined,
78184
+ baseline_used: false
78185
+ };
78186
+ }
78187
+ let newFindings;
78188
+ let preExistingFindings;
78189
+ let baselineUsed = false;
78190
+ let truncatedPreExisting = false;
78191
+ if (phase !== undefined && Number.isInteger(phase) && phase >= 1) {
78192
+ const baselineResult = loadBaseline(directory, phase);
78193
+ if (baselineResult.status === "found") {
78194
+ baselineUsed = true;
78195
+ const baselineSet = baselineResult.fingerprints;
78196
+ const indexed = assignOccurrenceIndices(allFindings, directory);
78197
+ const rawNew = [];
78198
+ const rawPreExisting = [];
78199
+ for (const { finding, stable, fingerprint } of indexed) {
78200
+ if (!stable || !baselineSet.has(fingerprint)) {
78201
+ rawNew.push(finding);
78202
+ } else {
78203
+ rawPreExisting.push(finding);
78204
+ }
78205
+ }
78206
+ newFindings = rawNew.slice(0, MAX_FINDINGS2);
78207
+ const preExistingBudget = Math.max(0, MAX_FINDINGS2 - newFindings.length);
78208
+ preExistingFindings = rawPreExisting.slice(0, preExistingBudget);
78209
+ truncatedPreExisting = rawPreExisting.length > preExistingBudget;
78210
+ } else if (baselineResult.status === "invalid_schema") {
78211
+ warn(`SAST Baseline: could not load baseline for phase ${phase} \u2014 ${baselineResult.errors.join(", ")}. Falling back to legacy behavior.`);
78212
+ }
78213
+ }
78214
+ let finalFindings;
78215
+ if (!baselineUsed) {
78216
+ finalFindings = allFindings;
78217
+ if (allFindings.length > MAX_FINDINGS2) {
78218
+ finalFindings = allFindings.slice(0, MAX_FINDINGS2);
78219
+ warn(`SAST Scan: Found ${allFindings.length} findings, limiting to ${MAX_FINDINGS2}`);
78220
+ }
78221
+ } else {
78222
+ finalFindings = [
78223
+ ...newFindings ?? [],
78224
+ ...preExistingFindings ?? []
78225
+ ].slice(0, MAX_FINDINGS2);
77832
78226
  }
77833
78227
  const findingsBySeverity = countBySeverity(finalFindings);
78228
+ const verdictSource = baselineUsed ? newFindings ?? [] : finalFindings;
77834
78229
  let verdict = "pass";
77835
- for (const finding of finalFindings) {
78230
+ for (const finding of verdictSource) {
77836
78231
  if (meetsThreshold(finding.severity, severity_threshold)) {
77837
78232
  verdict = "fail";
77838
78233
  break;
@@ -77855,42 +78250,65 @@ async function sastScan(input, directory, config3) {
77855
78250
  verdict,
77856
78251
  summary: `Scanned ${filesScanned} files, found ${finalFindings.length} finding(s) using ${engine}`,
77857
78252
  ...summary,
77858
- findings: finalFindings
78253
+ findings: finalFindings,
78254
+ ...baselineUsed && {
78255
+ new_findings: newFindings,
78256
+ pre_existing_findings: preExistingFindings,
78257
+ baseline_used: true
78258
+ }
77859
78259
  });
77860
- return {
78260
+ const result = {
77861
78261
  verdict,
77862
78262
  findings: finalFindings,
77863
78263
  summary
77864
78264
  };
78265
+ if (baselineUsed) {
78266
+ result.new_findings = newFindings;
78267
+ result.pre_existing_findings = preExistingFindings;
78268
+ result.baseline_used = true;
78269
+ if (truncatedPreExisting)
78270
+ result.truncated_pre_existing = true;
78271
+ }
78272
+ return result;
77865
78273
  }
77866
78274
  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.",
78275
+ 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
78276
  args: {
77869
78277
  directory: tool.schema.string().describe("Directory to scan for security vulnerabilities"),
77870
78278
  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")
78279
+ severity_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().default("medium").describe("Minimum severity that causes failure"),
78280
+ 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."),
78281
+ 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
78282
  },
77873
78283
  execute: async (args2, directory) => {
77874
78284
  let safeArgs;
77875
78285
  try {
77876
78286
  if (args2 && typeof args2 === "object") {
78287
+ const rawPhase = args2.phase;
78288
+ const rawCapture = args2.capture_baseline;
77877
78289
  safeArgs = {
77878
78290
  directory: args2.directory,
77879
78291
  changed_files: args2.changed_files,
77880
- severity_threshold: args2.severity_threshold
78292
+ severity_threshold: args2.severity_threshold,
78293
+ phase: typeof rawPhase === "number" && Number.isInteger(rawPhase) && rawPhase >= 1 ? rawPhase : undefined,
78294
+ capture_baseline: typeof rawCapture === "boolean" ? rawCapture : undefined
77881
78295
  };
77882
78296
  } else {
77883
78297
  safeArgs = {
77884
78298
  directory: undefined,
77885
78299
  changed_files: undefined,
77886
- severity_threshold: undefined
78300
+ severity_threshold: undefined,
78301
+ capture_baseline: undefined,
78302
+ phase: undefined
77887
78303
  };
77888
78304
  }
77889
78305
  } catch {
77890
78306
  safeArgs = {
77891
78307
  directory: undefined,
77892
78308
  changed_files: undefined,
77893
- severity_threshold: undefined
78309
+ severity_threshold: undefined,
78310
+ capture_baseline: undefined,
78311
+ phase: undefined
77894
78312
  };
77895
78313
  }
77896
78314
  if (safeArgs.directory === undefined) {
@@ -77913,7 +78331,9 @@ var sast_scan = createSwarmTool({
77913
78331
  }
77914
78332
  const input = {
77915
78333
  changed_files: safeArgs.changed_files ?? [],
77916
- severity_threshold: safeArgs.severity_threshold ?? "medium"
78334
+ severity_threshold: safeArgs.severity_threshold ?? "medium",
78335
+ capture_baseline: safeArgs.capture_baseline,
78336
+ phase: safeArgs.phase
77917
78337
  };
77918
78338
  const result = await sastScan(input, directory);
77919
78339
  return JSON.stringify(result, null, 2);
@@ -77939,20 +78359,20 @@ function validatePath(inputPath, baseDir, workspaceDir) {
77939
78359
  let resolved;
77940
78360
  const isWinAbs = isWindowsAbsolutePath(inputPath);
77941
78361
  if (isWinAbs) {
77942
- resolved = path78.win32.resolve(inputPath);
77943
- } else if (path78.isAbsolute(inputPath)) {
77944
- resolved = path78.resolve(inputPath);
78362
+ resolved = path79.win32.resolve(inputPath);
78363
+ } else if (path79.isAbsolute(inputPath)) {
78364
+ resolved = path79.resolve(inputPath);
77945
78365
  } else {
77946
- resolved = path78.resolve(baseDir, inputPath);
78366
+ resolved = path79.resolve(baseDir, inputPath);
77947
78367
  }
77948
- const workspaceResolved = path78.resolve(workspaceDir);
77949
- let relative18;
78368
+ const workspaceResolved = path79.resolve(workspaceDir);
78369
+ let relative19;
77950
78370
  if (isWinAbs) {
77951
- relative18 = path78.win32.relative(workspaceResolved, resolved);
78371
+ relative19 = path79.win32.relative(workspaceResolved, resolved);
77952
78372
  } else {
77953
- relative18 = path78.relative(workspaceResolved, resolved);
78373
+ relative19 = path79.relative(workspaceResolved, resolved);
77954
78374
  }
77955
- if (relative18.startsWith("..")) {
78375
+ if (relative19.startsWith("..")) {
77956
78376
  return "path traversal detected";
77957
78377
  }
77958
78378
  return null;
@@ -78015,7 +78435,7 @@ async function runLintOnFiles(linter, files, workspaceDir) {
78015
78435
  if (typeof file3 !== "string") {
78016
78436
  continue;
78017
78437
  }
78018
- const resolvedPath = path78.resolve(file3);
78438
+ const resolvedPath = path79.resolve(file3);
78019
78439
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
78020
78440
  if (validationError) {
78021
78441
  continue;
@@ -78172,7 +78592,7 @@ async function runSecretscanWithFiles(files, directory) {
78172
78592
  skippedFiles++;
78173
78593
  continue;
78174
78594
  }
78175
- const resolvedPath = path78.resolve(file3);
78595
+ const resolvedPath = path79.resolve(file3);
78176
78596
  const validationError = validatePath(resolvedPath, directory, directory);
78177
78597
  if (validationError) {
78178
78598
  skippedFiles++;
@@ -78190,14 +78610,14 @@ async function runSecretscanWithFiles(files, directory) {
78190
78610
  };
78191
78611
  }
78192
78612
  for (const file3 of validatedFiles) {
78193
- const ext = path78.extname(file3).toLowerCase();
78613
+ const ext = path79.extname(file3).toLowerCase();
78194
78614
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
78195
78615
  skippedFiles++;
78196
78616
  continue;
78197
78617
  }
78198
78618
  let stat3;
78199
78619
  try {
78200
- stat3 = fs64.statSync(file3);
78620
+ stat3 = fs65.statSync(file3);
78201
78621
  } catch {
78202
78622
  skippedFiles++;
78203
78623
  continue;
@@ -78208,7 +78628,7 @@ async function runSecretscanWithFiles(files, directory) {
78208
78628
  }
78209
78629
  let content;
78210
78630
  try {
78211
- const buffer = fs64.readFileSync(file3);
78631
+ const buffer = fs65.readFileSync(file3);
78212
78632
  if (buffer.includes(0)) {
78213
78633
  skippedFiles++;
78214
78634
  continue;
@@ -78274,10 +78694,14 @@ async function runSecretscanWithFiles(files, directory) {
78274
78694
  return errorResult;
78275
78695
  }
78276
78696
  }
78277
- async function runSastScanWrapped(changedFiles, directory, severityThreshold, config3) {
78697
+ async function runSastScanWrapped(changedFiles, directory, severityThreshold, config3, phase) {
78278
78698
  const start2 = process.hrtime.bigint();
78279
78699
  try {
78280
- const result = await runWithTimeout2(sastScan({ changed_files: changedFiles, severity_threshold: severityThreshold }, directory, config3), TOOL_TIMEOUT_MS);
78700
+ const result = await runWithTimeout2(sastScan({
78701
+ changed_files: changedFiles,
78702
+ severity_threshold: severityThreshold,
78703
+ phase
78704
+ }, directory, config3), TOOL_TIMEOUT_MS);
78281
78705
  return {
78282
78706
  ran: true,
78283
78707
  result,
@@ -78309,6 +78733,15 @@ async function runQualityBudgetWrapped(changedFiles, directory, _config) {
78309
78733
  }
78310
78734
  }
78311
78735
  var GATE_SEVERITIES = new Set(["high", "critical"]);
78736
+ var SEVERITY_ORDER_PCB = {
78737
+ low: 0,
78738
+ medium: 1,
78739
+ high: 2,
78740
+ critical: 3
78741
+ };
78742
+ function meetsThresholdForTriage(severity, threshold) {
78743
+ return (SEVERITY_ORDER_PCB[severity] ?? 0) >= (SEVERITY_ORDER_PCB[threshold] ?? 1);
78744
+ }
78312
78745
  async function runGitDiff(args2, directory) {
78313
78746
  try {
78314
78747
  const proc = Bun.spawn(["git", "diff", ...args2], {
@@ -78396,7 +78829,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
78396
78829
  const preexistingFindings = [];
78397
78830
  for (const finding of findings) {
78398
78831
  const filePath = finding.location.file;
78399
- const normalised = path78.relative(directory, filePath).replace(/\\/g, "/");
78832
+ const normalised = path79.relative(directory, filePath).replace(/\\/g, "/");
78400
78833
  const changedLines = changedLineRanges.get(normalised);
78401
78834
  if (changedLines?.has(finding.location.line)) {
78402
78835
  newFindings.push(finding);
@@ -78408,7 +78841,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
78408
78841
  }
78409
78842
  async function runPreCheckBatch(input, workspaceDir, contextDir) {
78410
78843
  const effectiveWorkspaceDir = workspaceDir || input.directory || contextDir;
78411
- const { files, directory, sast_threshold = "medium", config: config3 } = input;
78844
+ const { files, directory, sast_threshold = "medium", config: config3, phase } = input;
78412
78845
  const dirError = validateDirectory2(directory, effectiveWorkspaceDir);
78413
78846
  if (dirError) {
78414
78847
  warn(`pre_check_batch: Invalid directory: ${dirError}`);
@@ -78447,7 +78880,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
78447
78880
  warn(`pre_check_batch: Invalid file path: ${file3}`);
78448
78881
  continue;
78449
78882
  }
78450
- changedFiles.push(path78.resolve(directory, file3));
78883
+ changedFiles.push(path79.resolve(directory, file3));
78451
78884
  }
78452
78885
  if (changedFiles.length === 0) {
78453
78886
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -78471,7 +78904,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
78471
78904
  const [lintResult, secretscanResult, sastScanResult, qualityBudgetResult] = await Promise.all([
78472
78905
  limit(() => runLintWrapped(changedFiles, directory, config3)),
78473
78906
  limit(() => runSecretscanWrapped(changedFiles, directory, config3)),
78474
- limit(() => runSastScanWrapped(changedFiles, directory, sast_threshold, config3)),
78907
+ limit(() => runSastScanWrapped(changedFiles, directory, sast_threshold, config3, phase)),
78475
78908
  limit(() => runQualityBudgetWrapped(changedFiles, directory, config3))
78476
78909
  ]);
78477
78910
  const totalDuration = lintResult.duration_ms + secretscanResult.duration_ms + sastScanResult.duration_ms + qualityBudgetResult.duration_ms;
@@ -78516,8 +78949,20 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
78516
78949
  }
78517
78950
  let sastPreexistingFindings;
78518
78951
  if (sastScanResult.ran && sastScanResult.result) {
78519
- if (sastScanResult.result.verdict === "fail") {
78520
- const gateFindings = sastScanResult.result.findings.filter((f) => GATE_SEVERITIES.has(f.severity));
78952
+ const sastResult = sastScanResult.result;
78953
+ if (sastResult.baseline_used) {
78954
+ if (sastResult.pre_existing_findings && sastResult.pre_existing_findings.length > 0) {
78955
+ sastPreexistingFindings = sastResult.pre_existing_findings.filter((f) => meetsThresholdForTriage(f.severity, sast_threshold));
78956
+ if (sastPreexistingFindings.length > 0) {
78957
+ warn(`pre_check_batch: SAST baseline diff found ${sastPreexistingFindings.length} pre-existing finding(s) - passing to reviewer for triage`);
78958
+ }
78959
+ }
78960
+ if (sastResult.verdict === "fail") {
78961
+ gatesPassed = false;
78962
+ warn(`pre_check_batch: SAST scan found new findings above threshold - GATE FAILED`);
78963
+ }
78964
+ } else if (sastResult.verdict === "fail") {
78965
+ const gateFindings = sastResult.findings.filter((f) => GATE_SEVERITIES.has(f.severity));
78521
78966
  if (gateFindings.length > 0) {
78522
78967
  const changedLineRanges = await getChangedLineRanges(directory);
78523
78968
  const { newFindings, preexistingFindings } = classifySastFindings(gateFindings, changedLineRanges, directory);
@@ -78566,7 +79011,8 @@ var pre_check_batch = createSwarmTool({
78566
79011
  args: {
78567
79012
  files: tool.schema.array(tool.schema.string()).optional().describe("Specific files to check (optional, scans directory if not provided)"),
78568
79013
  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)")
79014
+ sast_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity for SAST findings to cause failure (default: medium)"),
79015
+ 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
79016
  },
78571
79017
  async execute(args2, directory) {
78572
79018
  if (!args2 || typeof args2 !== "object") {
@@ -78635,7 +79081,7 @@ var pre_check_batch = createSwarmTool({
78635
79081
  };
78636
79082
  return JSON.stringify(errorResult, null, 2);
78637
79083
  }
78638
- const resolvedDirectory = path78.resolve(typedArgs.directory);
79084
+ const resolvedDirectory = path79.resolve(typedArgs.directory);
78639
79085
  const workspaceAnchor = resolvedDirectory;
78640
79086
  const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
78641
79087
  if (dirError) {
@@ -78650,11 +79096,14 @@ var pre_check_batch = createSwarmTool({
78650
79096
  return JSON.stringify(errorResult, null, 2);
78651
79097
  }
78652
79098
  try {
79099
+ const rawPhase = typedArgs.phase;
79100
+ const safePhase = typeof rawPhase === "number" && Number.isInteger(rawPhase) && rawPhase >= 1 ? rawPhase : undefined;
78653
79101
  const result = await runPreCheckBatch({
78654
79102
  files: typedArgs.files,
78655
79103
  directory: resolvedDirectory,
78656
79104
  sast_threshold: typedArgs.sast_threshold,
78657
- config: typedArgs.config
79105
+ config: typedArgs.config,
79106
+ phase: safePhase
78658
79107
  }, workspaceAnchor, directory);
78659
79108
  return JSON.stringify(result, null, 2);
78660
79109
  } catch (error93) {
@@ -78673,7 +79122,7 @@ var pre_check_batch = createSwarmTool({
78673
79122
  });
78674
79123
  // src/tools/repo-map.ts
78675
79124
  init_dist();
78676
- import * as path79 from "path";
79125
+ import * as path80 from "path";
78677
79126
  init_path_security();
78678
79127
  init_create_tool();
78679
79128
  var VALID_ACTIONS = [
@@ -78698,7 +79147,7 @@ function validateFile(p) {
78698
79147
  return "file contains control characters";
78699
79148
  if (containsPathTraversal(p))
78700
79149
  return "file contains path traversal";
78701
- if (path79.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
79150
+ if (path80.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
78702
79151
  return "file must be a workspace-relative path, not absolute";
78703
79152
  }
78704
79153
  return null;
@@ -78721,8 +79170,8 @@ function ok(action, payload) {
78721
79170
  }
78722
79171
  function toRelativeGraphPath(input, workspaceRoot) {
78723
79172
  const normalized = input.replace(/\\/g, "/");
78724
- if (path79.isAbsolute(normalized)) {
78725
- const rel = path79.relative(workspaceRoot, normalized).replace(/\\/g, "/");
79173
+ if (path80.isAbsolute(normalized)) {
79174
+ const rel = path80.relative(workspaceRoot, normalized).replace(/\\/g, "/");
78726
79175
  return normalizeGraphPath2(rel);
78727
79176
  }
78728
79177
  return normalizeGraphPath2(normalized);
@@ -78866,8 +79315,8 @@ var repo_map = createSwarmTool({
78866
79315
  // src/tools/req-coverage.ts
78867
79316
  init_dist();
78868
79317
  init_create_tool();
78869
- import * as fs65 from "fs";
78870
- import * as path80 from "path";
79318
+ import * as fs66 from "fs";
79319
+ import * as path81 from "path";
78871
79320
  var SPEC_FILE = ".swarm/spec.md";
78872
79321
  var EVIDENCE_DIR4 = ".swarm/evidence";
78873
79322
  var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
@@ -78926,19 +79375,19 @@ function extractObligationAndText(id, lineText) {
78926
79375
  var PHASE_TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
78927
79376
  function readTouchedFiles(evidenceDir, phase, cwd) {
78928
79377
  const touchedFiles = new Set;
78929
- if (!fs65.existsSync(evidenceDir) || !fs65.statSync(evidenceDir).isDirectory()) {
79378
+ if (!fs66.existsSync(evidenceDir) || !fs66.statSync(evidenceDir).isDirectory()) {
78930
79379
  return [];
78931
79380
  }
78932
79381
  let entries;
78933
79382
  try {
78934
- entries = fs65.readdirSync(evidenceDir);
79383
+ entries = fs66.readdirSync(evidenceDir);
78935
79384
  } catch {
78936
79385
  return [];
78937
79386
  }
78938
79387
  for (const entry of entries) {
78939
- const entryPath = path80.join(evidenceDir, entry);
79388
+ const entryPath = path81.join(evidenceDir, entry);
78940
79389
  try {
78941
- const stat3 = fs65.statSync(entryPath);
79390
+ const stat3 = fs66.statSync(entryPath);
78942
79391
  if (!stat3.isDirectory()) {
78943
79392
  continue;
78944
79393
  }
@@ -78952,14 +79401,14 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
78952
79401
  if (entryPhase !== String(phase)) {
78953
79402
  continue;
78954
79403
  }
78955
- const evidenceFilePath = path80.join(entryPath, "evidence.json");
79404
+ const evidenceFilePath = path81.join(entryPath, "evidence.json");
78956
79405
  try {
78957
- const resolvedPath = path80.resolve(evidenceFilePath);
78958
- const evidenceDirResolved = path80.resolve(evidenceDir);
78959
- if (!resolvedPath.startsWith(evidenceDirResolved + path80.sep)) {
79406
+ const resolvedPath = path81.resolve(evidenceFilePath);
79407
+ const evidenceDirResolved = path81.resolve(evidenceDir);
79408
+ if (!resolvedPath.startsWith(evidenceDirResolved + path81.sep)) {
78960
79409
  continue;
78961
79410
  }
78962
- const stat3 = fs65.lstatSync(evidenceFilePath);
79411
+ const stat3 = fs66.lstatSync(evidenceFilePath);
78963
79412
  if (!stat3.isFile()) {
78964
79413
  continue;
78965
79414
  }
@@ -78971,7 +79420,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
78971
79420
  }
78972
79421
  let content;
78973
79422
  try {
78974
- content = fs65.readFileSync(evidenceFilePath, "utf-8");
79423
+ content = fs66.readFileSync(evidenceFilePath, "utf-8");
78975
79424
  } catch {
78976
79425
  continue;
78977
79426
  }
@@ -78990,7 +79439,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
78990
79439
  if (Array.isArray(diffEntry.files_changed)) {
78991
79440
  for (const file3 of diffEntry.files_changed) {
78992
79441
  if (typeof file3 === "string") {
78993
- touchedFiles.add(path80.resolve(cwd, file3));
79442
+ touchedFiles.add(path81.resolve(cwd, file3));
78994
79443
  }
78995
79444
  }
78996
79445
  }
@@ -79003,12 +79452,12 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
79003
79452
  }
79004
79453
  function searchFileForKeywords(filePath, keywords, cwd) {
79005
79454
  try {
79006
- const resolvedPath = path80.resolve(filePath);
79007
- const cwdResolved = path80.resolve(cwd);
79455
+ const resolvedPath = path81.resolve(filePath);
79456
+ const cwdResolved = path81.resolve(cwd);
79008
79457
  if (!resolvedPath.startsWith(cwdResolved)) {
79009
79458
  return false;
79010
79459
  }
79011
- const content = fs65.readFileSync(resolvedPath, "utf-8");
79460
+ const content = fs66.readFileSync(resolvedPath, "utf-8");
79012
79461
  for (const keyword of keywords) {
79013
79462
  const regex = new RegExp(`\\b${keyword}\\b`, "i");
79014
79463
  if (regex.test(content)) {
@@ -79138,10 +79587,10 @@ var req_coverage = createSwarmTool({
79138
79587
  }, null, 2);
79139
79588
  }
79140
79589
  const cwd = inputDirectory || directory;
79141
- const specPath = path80.join(cwd, SPEC_FILE);
79590
+ const specPath = path81.join(cwd, SPEC_FILE);
79142
79591
  let specContent;
79143
79592
  try {
79144
- specContent = fs65.readFileSync(specPath, "utf-8");
79593
+ specContent = fs66.readFileSync(specPath, "utf-8");
79145
79594
  } catch (readError) {
79146
79595
  return JSON.stringify({
79147
79596
  success: false,
@@ -79165,7 +79614,7 @@ var req_coverage = createSwarmTool({
79165
79614
  message: "No FR requirements found in spec.md"
79166
79615
  }, null, 2);
79167
79616
  }
79168
- const evidenceDir = path80.join(cwd, EVIDENCE_DIR4);
79617
+ const evidenceDir = path81.join(cwd, EVIDENCE_DIR4);
79169
79618
  const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
79170
79619
  const analyzedRequirements = [];
79171
79620
  let coveredCount = 0;
@@ -79191,12 +79640,12 @@ var req_coverage = createSwarmTool({
79191
79640
  requirements: analyzedRequirements
79192
79641
  };
79193
79642
  const reportFilename = `req-coverage-phase-${phase}.json`;
79194
- const reportPath = path80.join(evidenceDir, reportFilename);
79643
+ const reportPath = path81.join(evidenceDir, reportFilename);
79195
79644
  try {
79196
- if (!fs65.existsSync(evidenceDir)) {
79197
- fs65.mkdirSync(evidenceDir, { recursive: true });
79645
+ if (!fs66.existsSync(evidenceDir)) {
79646
+ fs66.mkdirSync(evidenceDir, { recursive: true });
79198
79647
  }
79199
- fs65.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
79648
+ fs66.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
79200
79649
  } catch (writeError) {
79201
79650
  console.warn(`Failed to write coverage report: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
79202
79651
  }
@@ -79274,9 +79723,9 @@ ${paginatedContent}`;
79274
79723
  });
79275
79724
  // src/tools/save-plan.ts
79276
79725
  init_tool();
79277
- import * as crypto8 from "crypto";
79278
- import * as fs66 from "fs";
79279
- import * as path81 from "path";
79726
+ import * as crypto9 from "crypto";
79727
+ import * as fs67 from "fs";
79728
+ import * as path82 from "path";
79280
79729
  init_checkpoint3();
79281
79730
  init_ledger();
79282
79731
  init_manager();
@@ -79357,12 +79806,12 @@ async function executeSavePlan(args2, fallbackDir) {
79357
79806
  let specMtime;
79358
79807
  let specHash;
79359
79808
  if (process.env.SWARM_SKIP_SPEC_GATE !== "1") {
79360
- const specPath = path81.join(targetWorkspace, ".swarm", "spec.md");
79809
+ const specPath = path82.join(targetWorkspace, ".swarm", "spec.md");
79361
79810
  try {
79362
- const stat3 = await fs66.promises.stat(specPath);
79811
+ const stat3 = await fs67.promises.stat(specPath);
79363
79812
  specMtime = stat3.mtime.toISOString();
79364
- const content = await fs66.promises.readFile(specPath, "utf8");
79365
- specHash = crypto8.createHash("sha256").update(content).digest("hex");
79813
+ const content = await fs67.promises.readFile(specPath, "utf8");
79814
+ specHash = crypto9.createHash("sha256").update(content).digest("hex");
79366
79815
  } catch {
79367
79816
  return {
79368
79817
  success: false,
@@ -79374,16 +79823,18 @@ async function executeSavePlan(args2, fallbackDir) {
79374
79823
  }
79375
79824
  const dir = targetWorkspace;
79376
79825
  const existingStatusMap = new Map;
79377
- try {
79378
- const existing = await loadPlanJsonOnly(dir);
79379
- if (existing) {
79380
- for (const phase of existing.phases) {
79381
- for (const task of phase.tasks) {
79382
- existingStatusMap.set(task.id, task.status);
79826
+ if (!args2.reset_statuses) {
79827
+ try {
79828
+ const existing = await loadPlanJsonOnly(dir);
79829
+ if (existing) {
79830
+ for (const phase of existing.phases) {
79831
+ for (const task of phase.tasks) {
79832
+ existingStatusMap.set(task.id, task.status);
79833
+ }
79383
79834
  }
79384
79835
  }
79385
- }
79386
- } catch {}
79836
+ } catch {}
79837
+ }
79387
79838
  const plan = {
79388
79839
  schema_version: "1.0.0",
79389
79840
  title: args2.title,
@@ -79428,21 +79879,23 @@ async function executeSavePlan(args2, fallbackDir) {
79428
79879
  };
79429
79880
  }
79430
79881
  try {
79431
- await savePlan(dir, plan);
79882
+ await savePlan(dir, plan, {
79883
+ preserveCompletedStatuses: !args2.reset_statuses
79884
+ });
79432
79885
  const savedPlan = await loadPlanJsonOnly(dir);
79433
79886
  if (savedPlan) {
79434
79887
  await takeSnapshotEvent(dir, savedPlan).catch(() => {});
79435
79888
  }
79436
79889
  await writeCheckpoint(dir).catch(() => {});
79437
79890
  try {
79438
- const markerPath = path81.join(dir, ".swarm", ".plan-write-marker");
79891
+ const markerPath = path82.join(dir, ".swarm", ".plan-write-marker");
79439
79892
  const marker = JSON.stringify({
79440
79893
  source: "save_plan",
79441
79894
  timestamp: new Date().toISOString(),
79442
79895
  phases_count: plan.phases.length,
79443
79896
  tasks_count: tasksCount
79444
79897
  });
79445
- await fs66.promises.writeFile(markerPath, marker, "utf8");
79898
+ await fs67.promises.writeFile(markerPath, marker, "utf8");
79446
79899
  } catch {}
79447
79900
  const warnings = [];
79448
79901
  let criticReviewFound = false;
@@ -79458,7 +79911,7 @@ async function executeSavePlan(args2, fallbackDir) {
79458
79911
  return {
79459
79912
  success: true,
79460
79913
  message: "Plan saved successfully",
79461
- plan_path: path81.join(dir, ".swarm", "plan.json"),
79914
+ plan_path: path82.join(dir, ".swarm", "plan.json"),
79462
79915
  phases_count: plan.phases.length,
79463
79916
  tasks_count: tasksCount,
79464
79917
  ...warnings.length > 0 ? { warnings } : {}
@@ -79493,7 +79946,8 @@ var save_plan = createSwarmTool({
79493
79946
  acceptance: tool.schema.string().optional().describe("Acceptance criteria for this task")
79494
79947
  })).min(1).describe("Tasks in this phase")
79495
79948
  })).min(1).describe("Implementation phases"),
79496
- working_directory: tool.schema.string().optional().describe("Working directory (explicit path, required - no fallback)")
79949
+ working_directory: tool.schema.string().optional().describe("Working directory (explicit path, required - no fallback)"),
79950
+ reset_statuses: tool.schema.boolean().optional().describe("When true, reset ALL task statuses to pending regardless of prior completion state. " + "Use only when deliberately re-planning a phase from scratch. " + "Default false (preserves existing task statuses across plan revisions).")
79497
79951
  },
79498
79952
  execute: async (args2, _directory) => {
79499
79953
  return JSON.stringify(await executeSavePlan(args2, _directory), null, 2);
@@ -79502,8 +79956,8 @@ var save_plan = createSwarmTool({
79502
79956
  // src/tools/sbom-generate.ts
79503
79957
  init_dist();
79504
79958
  init_manager2();
79505
- import * as fs67 from "fs";
79506
- import * as path82 from "path";
79959
+ import * as fs68 from "fs";
79960
+ import * as path83 from "path";
79507
79961
 
79508
79962
  // src/sbom/detectors/index.ts
79509
79963
  init_utils();
@@ -80351,9 +80805,9 @@ function findManifestFiles(rootDir) {
80351
80805
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
80352
80806
  function searchDir(dir) {
80353
80807
  try {
80354
- const entries = fs67.readdirSync(dir, { withFileTypes: true });
80808
+ const entries = fs68.readdirSync(dir, { withFileTypes: true });
80355
80809
  for (const entry of entries) {
80356
- const fullPath = path82.join(dir, entry.name);
80810
+ const fullPath = path83.join(dir, entry.name);
80357
80811
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
80358
80812
  continue;
80359
80813
  }
@@ -80362,7 +80816,7 @@ function findManifestFiles(rootDir) {
80362
80816
  } else if (entry.isFile()) {
80363
80817
  for (const pattern of patterns) {
80364
80818
  if (simpleGlobToRegex(pattern).test(entry.name)) {
80365
- manifestFiles.push(path82.relative(rootDir, fullPath));
80819
+ manifestFiles.push(path83.relative(rootDir, fullPath));
80366
80820
  break;
80367
80821
  }
80368
80822
  }
@@ -80378,13 +80832,13 @@ function findManifestFilesInDirs(directories, workingDir) {
80378
80832
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
80379
80833
  for (const dir of directories) {
80380
80834
  try {
80381
- const entries = fs67.readdirSync(dir, { withFileTypes: true });
80835
+ const entries = fs68.readdirSync(dir, { withFileTypes: true });
80382
80836
  for (const entry of entries) {
80383
- const fullPath = path82.join(dir, entry.name);
80837
+ const fullPath = path83.join(dir, entry.name);
80384
80838
  if (entry.isFile()) {
80385
80839
  for (const pattern of patterns) {
80386
80840
  if (simpleGlobToRegex(pattern).test(entry.name)) {
80387
- found.push(path82.relative(workingDir, fullPath));
80841
+ found.push(path83.relative(workingDir, fullPath));
80388
80842
  break;
80389
80843
  }
80390
80844
  }
@@ -80397,11 +80851,11 @@ function findManifestFilesInDirs(directories, workingDir) {
80397
80851
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
80398
80852
  const dirs = new Set;
80399
80853
  for (const file3 of changedFiles) {
80400
- let currentDir = path82.dirname(file3);
80854
+ let currentDir = path83.dirname(file3);
80401
80855
  while (true) {
80402
- if (currentDir && currentDir !== "." && currentDir !== path82.sep) {
80403
- dirs.add(path82.join(workingDir, currentDir));
80404
- const parent = path82.dirname(currentDir);
80856
+ if (currentDir && currentDir !== "." && currentDir !== path83.sep) {
80857
+ dirs.add(path83.join(workingDir, currentDir));
80858
+ const parent = path83.dirname(currentDir);
80405
80859
  if (parent === currentDir)
80406
80860
  break;
80407
80861
  currentDir = parent;
@@ -80415,7 +80869,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
80415
80869
  }
80416
80870
  function ensureOutputDir(outputDir) {
80417
80871
  try {
80418
- fs67.mkdirSync(outputDir, { recursive: true });
80872
+ fs68.mkdirSync(outputDir, { recursive: true });
80419
80873
  } catch (error93) {
80420
80874
  if (!error93 || error93.code !== "EEXIST") {
80421
80875
  throw error93;
@@ -80485,7 +80939,7 @@ var sbom_generate = createSwarmTool({
80485
80939
  const changedFiles = obj.changed_files;
80486
80940
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
80487
80941
  const workingDir = directory;
80488
- const outputDir = path82.isAbsolute(relativeOutputDir) ? relativeOutputDir : path82.join(workingDir, relativeOutputDir);
80942
+ const outputDir = path83.isAbsolute(relativeOutputDir) ? relativeOutputDir : path83.join(workingDir, relativeOutputDir);
80489
80943
  let manifestFiles = [];
80490
80944
  if (scope === "all") {
80491
80945
  manifestFiles = findManifestFiles(workingDir);
@@ -80508,11 +80962,11 @@ var sbom_generate = createSwarmTool({
80508
80962
  const processedFiles = [];
80509
80963
  for (const manifestFile of manifestFiles) {
80510
80964
  try {
80511
- const fullPath = path82.isAbsolute(manifestFile) ? manifestFile : path82.join(workingDir, manifestFile);
80512
- if (!fs67.existsSync(fullPath)) {
80965
+ const fullPath = path83.isAbsolute(manifestFile) ? manifestFile : path83.join(workingDir, manifestFile);
80966
+ if (!fs68.existsSync(fullPath)) {
80513
80967
  continue;
80514
80968
  }
80515
- const content = fs67.readFileSync(fullPath, "utf-8");
80969
+ const content = fs68.readFileSync(fullPath, "utf-8");
80516
80970
  const components = detectComponents(manifestFile, content);
80517
80971
  processedFiles.push(manifestFile);
80518
80972
  if (components.length > 0) {
@@ -80525,8 +80979,8 @@ var sbom_generate = createSwarmTool({
80525
80979
  const bom = generateCycloneDX(allComponents);
80526
80980
  const bomJson = serializeCycloneDX(bom);
80527
80981
  const filename = generateSbomFilename();
80528
- const outputPath = path82.join(outputDir, filename);
80529
- fs67.writeFileSync(outputPath, bomJson, "utf-8");
80982
+ const outputPath = path83.join(outputDir, filename);
80983
+ fs68.writeFileSync(outputPath, bomJson, "utf-8");
80530
80984
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
80531
80985
  try {
80532
80986
  const timestamp = new Date().toISOString();
@@ -80568,8 +81022,8 @@ var sbom_generate = createSwarmTool({
80568
81022
  // src/tools/schema-drift.ts
80569
81023
  init_dist();
80570
81024
  init_create_tool();
80571
- import * as fs68 from "fs";
80572
- import * as path83 from "path";
81025
+ import * as fs69 from "fs";
81026
+ import * as path84 from "path";
80573
81027
  var SPEC_CANDIDATES = [
80574
81028
  "openapi.json",
80575
81029
  "openapi.yaml",
@@ -80601,28 +81055,28 @@ function normalizePath3(p) {
80601
81055
  }
80602
81056
  function discoverSpecFile(cwd, specFileArg) {
80603
81057
  if (specFileArg) {
80604
- const resolvedPath = path83.resolve(cwd, specFileArg);
80605
- const normalizedCwd = cwd.endsWith(path83.sep) ? cwd : cwd + path83.sep;
81058
+ const resolvedPath = path84.resolve(cwd, specFileArg);
81059
+ const normalizedCwd = cwd.endsWith(path84.sep) ? cwd : cwd + path84.sep;
80606
81060
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
80607
81061
  throw new Error("Invalid spec_file: path traversal detected");
80608
81062
  }
80609
- const ext = path83.extname(resolvedPath).toLowerCase();
81063
+ const ext = path84.extname(resolvedPath).toLowerCase();
80610
81064
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
80611
81065
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
80612
81066
  }
80613
- const stats = fs68.statSync(resolvedPath);
81067
+ const stats = fs69.statSync(resolvedPath);
80614
81068
  if (stats.size > MAX_SPEC_SIZE) {
80615
81069
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
80616
81070
  }
80617
- if (!fs68.existsSync(resolvedPath)) {
81071
+ if (!fs69.existsSync(resolvedPath)) {
80618
81072
  throw new Error(`Spec file not found: ${resolvedPath}`);
80619
81073
  }
80620
81074
  return resolvedPath;
80621
81075
  }
80622
81076
  for (const candidate of SPEC_CANDIDATES) {
80623
- const candidatePath = path83.resolve(cwd, candidate);
80624
- if (fs68.existsSync(candidatePath)) {
80625
- const stats = fs68.statSync(candidatePath);
81077
+ const candidatePath = path84.resolve(cwd, candidate);
81078
+ if (fs69.existsSync(candidatePath)) {
81079
+ const stats = fs69.statSync(candidatePath);
80626
81080
  if (stats.size <= MAX_SPEC_SIZE) {
80627
81081
  return candidatePath;
80628
81082
  }
@@ -80631,8 +81085,8 @@ function discoverSpecFile(cwd, specFileArg) {
80631
81085
  return null;
80632
81086
  }
80633
81087
  function parseSpec(specFile) {
80634
- const content = fs68.readFileSync(specFile, "utf-8");
80635
- const ext = path83.extname(specFile).toLowerCase();
81088
+ const content = fs69.readFileSync(specFile, "utf-8");
81089
+ const ext = path84.extname(specFile).toLowerCase();
80636
81090
  if (ext === ".json") {
80637
81091
  return parseJsonSpec(content);
80638
81092
  }
@@ -80703,12 +81157,12 @@ function extractRoutes(cwd) {
80703
81157
  function walkDir(dir) {
80704
81158
  let entries;
80705
81159
  try {
80706
- entries = fs68.readdirSync(dir, { withFileTypes: true });
81160
+ entries = fs69.readdirSync(dir, { withFileTypes: true });
80707
81161
  } catch {
80708
81162
  return;
80709
81163
  }
80710
81164
  for (const entry of entries) {
80711
- const fullPath = path83.join(dir, entry.name);
81165
+ const fullPath = path84.join(dir, entry.name);
80712
81166
  if (entry.isSymbolicLink()) {
80713
81167
  continue;
80714
81168
  }
@@ -80718,7 +81172,7 @@ function extractRoutes(cwd) {
80718
81172
  }
80719
81173
  walkDir(fullPath);
80720
81174
  } else if (entry.isFile()) {
80721
- const ext = path83.extname(entry.name).toLowerCase();
81175
+ const ext = path84.extname(entry.name).toLowerCase();
80722
81176
  const baseName = entry.name.toLowerCase();
80723
81177
  if (![".ts", ".js", ".mjs"].includes(ext)) {
80724
81178
  continue;
@@ -80736,7 +81190,7 @@ function extractRoutes(cwd) {
80736
81190
  }
80737
81191
  function extractRoutesFromFile(filePath) {
80738
81192
  const routes = [];
80739
- const content = fs68.readFileSync(filePath, "utf-8");
81193
+ const content = fs69.readFileSync(filePath, "utf-8");
80740
81194
  const lines = content.split(/\r?\n/);
80741
81195
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
80742
81196
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -80884,8 +81338,8 @@ var schema_drift = createSwarmTool({
80884
81338
  init_tool();
80885
81339
  init_path_security();
80886
81340
  init_create_tool();
80887
- import * as fs69 from "fs";
80888
- import * as path84 from "path";
81341
+ import * as fs70 from "fs";
81342
+ import * as path85 from "path";
80889
81343
  var DEFAULT_MAX_RESULTS = 100;
80890
81344
  var DEFAULT_MAX_LINES = 200;
80891
81345
  var REGEX_TIMEOUT_MS = 5000;
@@ -80921,11 +81375,11 @@ function containsWindowsAttacks3(str) {
80921
81375
  }
80922
81376
  function isPathInWorkspace3(filePath, workspace) {
80923
81377
  try {
80924
- const resolvedPath = path84.resolve(workspace, filePath);
80925
- const realWorkspace = fs69.realpathSync(workspace);
80926
- const realResolvedPath = fs69.realpathSync(resolvedPath);
80927
- const relativePath = path84.relative(realWorkspace, realResolvedPath);
80928
- if (relativePath.startsWith("..") || path84.isAbsolute(relativePath)) {
81378
+ const resolvedPath = path85.resolve(workspace, filePath);
81379
+ const realWorkspace = fs70.realpathSync(workspace);
81380
+ const realResolvedPath = fs70.realpathSync(resolvedPath);
81381
+ const relativePath = path85.relative(realWorkspace, realResolvedPath);
81382
+ if (relativePath.startsWith("..") || path85.isAbsolute(relativePath)) {
80929
81383
  return false;
80930
81384
  }
80931
81385
  return true;
@@ -80938,12 +81392,12 @@ function validatePathForRead2(filePath, workspace) {
80938
81392
  }
80939
81393
  function findRgInEnvPath() {
80940
81394
  const searchPath = process.env.PATH ?? "";
80941
- for (const dir of searchPath.split(path84.delimiter)) {
81395
+ for (const dir of searchPath.split(path85.delimiter)) {
80942
81396
  if (!dir)
80943
81397
  continue;
80944
81398
  const isWindows = process.platform === "win32";
80945
- const candidate = path84.join(dir, isWindows ? "rg.exe" : "rg");
80946
- if (fs69.existsSync(candidate))
81399
+ const candidate = path85.join(dir, isWindows ? "rg.exe" : "rg");
81400
+ if (fs70.existsSync(candidate))
80947
81401
  return candidate;
80948
81402
  }
80949
81403
  return null;
@@ -80997,7 +81451,7 @@ async function ripgrepSearch(opts) {
80997
81451
  stderr: "pipe",
80998
81452
  cwd: opts.workspace
80999
81453
  });
81000
- const timeout = new Promise((resolve35) => setTimeout(() => resolve35("timeout"), REGEX_TIMEOUT_MS));
81454
+ const timeout = new Promise((resolve36) => setTimeout(() => resolve36("timeout"), REGEX_TIMEOUT_MS));
81001
81455
  const exitPromise = proc.exited;
81002
81456
  const result = await Promise.race([exitPromise, timeout]);
81003
81457
  if (result === "timeout") {
@@ -81070,10 +81524,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
81070
81524
  return files;
81071
81525
  }
81072
81526
  try {
81073
- const entries = fs69.readdirSync(dir, { withFileTypes: true });
81527
+ const entries = fs70.readdirSync(dir, { withFileTypes: true });
81074
81528
  for (const entry of entries) {
81075
- const fullPath = path84.join(dir, entry.name);
81076
- const relativePath = path84.relative(workspace, fullPath);
81529
+ const fullPath = path85.join(dir, entry.name);
81530
+ const relativePath = path85.relative(workspace, fullPath);
81077
81531
  if (!validatePathForRead2(fullPath, workspace)) {
81078
81532
  continue;
81079
81533
  }
@@ -81114,13 +81568,13 @@ async function fallbackSearch(opts) {
81114
81568
  const matches = [];
81115
81569
  let total = 0;
81116
81570
  for (const file3 of files) {
81117
- const fullPath = path84.join(opts.workspace, file3);
81571
+ const fullPath = path85.join(opts.workspace, file3);
81118
81572
  if (!validatePathForRead2(fullPath, opts.workspace)) {
81119
81573
  continue;
81120
81574
  }
81121
81575
  let stats;
81122
81576
  try {
81123
- stats = fs69.statSync(fullPath);
81577
+ stats = fs70.statSync(fullPath);
81124
81578
  if (stats.size > MAX_FILE_SIZE_BYTES10) {
81125
81579
  continue;
81126
81580
  }
@@ -81129,7 +81583,7 @@ async function fallbackSearch(opts) {
81129
81583
  }
81130
81584
  let content;
81131
81585
  try {
81132
- content = fs69.readFileSync(fullPath, "utf-8");
81586
+ content = fs70.readFileSync(fullPath, "utf-8");
81133
81587
  } catch {
81134
81588
  continue;
81135
81589
  }
@@ -81241,7 +81695,7 @@ var search = createSwarmTool({
81241
81695
  message: "Exclude pattern contains invalid Windows-specific sequence"
81242
81696
  }, null, 2);
81243
81697
  }
81244
- if (!fs69.existsSync(directory)) {
81698
+ if (!fs70.existsSync(directory)) {
81245
81699
  return JSON.stringify({
81246
81700
  error: true,
81247
81701
  type: "unknown",
@@ -81364,8 +81818,8 @@ var set_qa_gates = createSwarmTool({
81364
81818
  init_tool();
81365
81819
  init_path_security();
81366
81820
  init_create_tool();
81367
- import * as fs70 from "fs";
81368
- import * as path85 from "path";
81821
+ import * as fs71 from "fs";
81822
+ import * as path86 from "path";
81369
81823
  var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
81370
81824
  function containsWindowsAttacks4(str) {
81371
81825
  if (/:[^\\/]/.test(str))
@@ -81379,14 +81833,14 @@ function containsWindowsAttacks4(str) {
81379
81833
  }
81380
81834
  function isPathInWorkspace4(filePath, workspace) {
81381
81835
  try {
81382
- const resolvedPath = path85.resolve(workspace, filePath);
81383
- if (!fs70.existsSync(resolvedPath)) {
81836
+ const resolvedPath = path86.resolve(workspace, filePath);
81837
+ if (!fs71.existsSync(resolvedPath)) {
81384
81838
  return true;
81385
81839
  }
81386
- const realWorkspace = fs70.realpathSync(workspace);
81387
- const realResolvedPath = fs70.realpathSync(resolvedPath);
81388
- const relativePath = path85.relative(realWorkspace, realResolvedPath);
81389
- if (relativePath.startsWith("..") || path85.isAbsolute(relativePath)) {
81840
+ const realWorkspace = fs71.realpathSync(workspace);
81841
+ const realResolvedPath = fs71.realpathSync(resolvedPath);
81842
+ const relativePath = path86.relative(realWorkspace, realResolvedPath);
81843
+ if (relativePath.startsWith("..") || path86.isAbsolute(relativePath)) {
81390
81844
  return false;
81391
81845
  }
81392
81846
  return true;
@@ -81558,7 +82012,7 @@ var suggestPatch = createSwarmTool({
81558
82012
  message: "changes cannot be empty"
81559
82013
  }, null, 2);
81560
82014
  }
81561
- if (!fs70.existsSync(directory)) {
82015
+ if (!fs71.existsSync(directory)) {
81562
82016
  return JSON.stringify({
81563
82017
  success: false,
81564
82018
  error: true,
@@ -81594,8 +82048,8 @@ var suggestPatch = createSwarmTool({
81594
82048
  });
81595
82049
  continue;
81596
82050
  }
81597
- const fullPath = path85.resolve(directory, change.file);
81598
- if (!fs70.existsSync(fullPath)) {
82051
+ const fullPath = path86.resolve(directory, change.file);
82052
+ if (!fs71.existsSync(fullPath)) {
81599
82053
  errors5.push({
81600
82054
  success: false,
81601
82055
  error: true,
@@ -81609,7 +82063,7 @@ var suggestPatch = createSwarmTool({
81609
82063
  }
81610
82064
  let content;
81611
82065
  try {
81612
- content = fs70.readFileSync(fullPath, "utf-8");
82066
+ content = fs71.readFileSync(fullPath, "utf-8");
81613
82067
  } catch (err3) {
81614
82068
  errors5.push({
81615
82069
  success: false,
@@ -81688,8 +82142,8 @@ var suggestPatch = createSwarmTool({
81688
82142
  // src/tools/lint-spec.ts
81689
82143
  init_spec_schema();
81690
82144
  init_create_tool();
81691
- import * as fs71 from "fs";
81692
- import * as path86 from "path";
82145
+ import * as fs72 from "fs";
82146
+ import * as path87 from "path";
81693
82147
  var SPEC_FILE_NAME = "spec.md";
81694
82148
  var SWARM_DIR2 = ".swarm";
81695
82149
  var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
@@ -81742,8 +82196,8 @@ var lint_spec = createSwarmTool({
81742
82196
  async execute(_args, directory) {
81743
82197
  const errors5 = [];
81744
82198
  const warnings = [];
81745
- const specPath = path86.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
81746
- if (!fs71.existsSync(specPath)) {
82199
+ const specPath = path87.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
82200
+ if (!fs72.existsSync(specPath)) {
81747
82201
  const result2 = {
81748
82202
  valid: false,
81749
82203
  specMtime: null,
@@ -81762,12 +82216,12 @@ var lint_spec = createSwarmTool({
81762
82216
  }
81763
82217
  let specMtime = null;
81764
82218
  try {
81765
- const stats = fs71.statSync(specPath);
82219
+ const stats = fs72.statSync(specPath);
81766
82220
  specMtime = stats.mtime.toISOString();
81767
82221
  } catch {}
81768
82222
  let content;
81769
82223
  try {
81770
- content = fs71.readFileSync(specPath, "utf-8");
82224
+ content = fs72.readFileSync(specPath, "utf-8");
81771
82225
  } catch (e) {
81772
82226
  const result2 = {
81773
82227
  valid: false,
@@ -81812,13 +82266,13 @@ var lint_spec = createSwarmTool({
81812
82266
  });
81813
82267
  // src/tools/mutation-test.ts
81814
82268
  init_dist();
81815
- import * as fs72 from "fs";
81816
- import * as path88 from "path";
82269
+ import * as fs73 from "fs";
82270
+ import * as path89 from "path";
81817
82271
 
81818
82272
  // src/mutation/engine.ts
81819
82273
  import { spawnSync as spawnSync3 } from "child_process";
81820
- import { unlinkSync as unlinkSync11, writeFileSync as writeFileSync17 } from "fs";
81821
- import * as path87 from "path";
82274
+ import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "fs";
82275
+ import * as path88 from "path";
81822
82276
 
81823
82277
  // src/mutation/equivalence.ts
81824
82278
  function isStaticallyEquivalent(originalCode, mutatedCode) {
@@ -81953,9 +82407,9 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
81953
82407
  let patchFile;
81954
82408
  try {
81955
82409
  const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
81956
- patchFile = path87.join(workingDir, `.mutation_patch_${safeId2}.diff`);
82410
+ patchFile = path88.join(workingDir, `.mutation_patch_${safeId2}.diff`);
81957
82411
  try {
81958
- writeFileSync17(patchFile, patch.patch);
82412
+ writeFileSync18(patchFile, patch.patch);
81959
82413
  } catch (writeErr) {
81960
82414
  error93 = `Failed to write patch file: ${writeErr}`;
81961
82415
  outcome = "error";
@@ -82051,7 +82505,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
82051
82505
  revertError = new Error(`Failed to revert mutation ${patch.id}: ${revertErr}. Working tree may be dirty.`);
82052
82506
  }
82053
82507
  try {
82054
- unlinkSync11(patchFile);
82508
+ unlinkSync12(patchFile);
82055
82509
  } catch (_unlinkErr) {}
82056
82510
  }
82057
82511
  }
@@ -82347,8 +82801,8 @@ var mutation_test = createSwarmTool({
82347
82801
  ];
82348
82802
  for (const filePath of uniquePaths) {
82349
82803
  try {
82350
- const resolvedPath = path88.resolve(cwd, filePath);
82351
- sourceFiles.set(filePath, fs72.readFileSync(resolvedPath, "utf-8"));
82804
+ const resolvedPath = path89.resolve(cwd, filePath);
82805
+ sourceFiles.set(filePath, fs73.readFileSync(resolvedPath, "utf-8"));
82352
82806
  } catch {}
82353
82807
  }
82354
82808
  const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
@@ -82366,8 +82820,8 @@ var mutation_test = createSwarmTool({
82366
82820
  init_dist();
82367
82821
  init_manager2();
82368
82822
  init_detector();
82369
- import * as fs73 from "fs";
82370
- import * as path89 from "path";
82823
+ import * as fs74 from "fs";
82824
+ import * as path90 from "path";
82371
82825
  init_create_tool();
82372
82826
  var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
82373
82827
  var BINARY_CHECK_BYTES = 8192;
@@ -82433,7 +82887,7 @@ async function syntaxCheck(input, directory, config3) {
82433
82887
  if (languages?.length) {
82434
82888
  const lowerLangs = languages.map((l) => l.toLowerCase());
82435
82889
  filesToCheck = filesToCheck.filter((file3) => {
82436
- const ext = path89.extname(file3.path).toLowerCase();
82890
+ const ext = path90.extname(file3.path).toLowerCase();
82437
82891
  const langDef = getLanguageForExtension(ext);
82438
82892
  const fileProfile = getProfileForFile(file3.path);
82439
82893
  const langId = fileProfile?.id || langDef?.id;
@@ -82446,7 +82900,7 @@ async function syntaxCheck(input, directory, config3) {
82446
82900
  let skippedCount = 0;
82447
82901
  for (const fileInfo of filesToCheck) {
82448
82902
  const { path: filePath } = fileInfo;
82449
- const fullPath = path89.isAbsolute(filePath) ? filePath : path89.join(directory, filePath);
82903
+ const fullPath = path90.isAbsolute(filePath) ? filePath : path90.join(directory, filePath);
82450
82904
  const result = {
82451
82905
  path: filePath,
82452
82906
  language: "",
@@ -82476,7 +82930,7 @@ async function syntaxCheck(input, directory, config3) {
82476
82930
  }
82477
82931
  let content;
82478
82932
  try {
82479
- content = fs73.readFileSync(fullPath, "utf8");
82933
+ content = fs74.readFileSync(fullPath, "utf8");
82480
82934
  } catch {
82481
82935
  result.skipped_reason = "file_read_error";
82482
82936
  skippedCount++;
@@ -82495,7 +82949,7 @@ async function syntaxCheck(input, directory, config3) {
82495
82949
  results.push(result);
82496
82950
  continue;
82497
82951
  }
82498
- const ext = path89.extname(filePath).toLowerCase();
82952
+ const ext = path90.extname(filePath).toLowerCase();
82499
82953
  const langDef = getLanguageForExtension(ext);
82500
82954
  result.language = profile?.id || langDef?.id || "unknown";
82501
82955
  const errors5 = extractSyntaxErrors(parser, content);
@@ -82587,8 +83041,8 @@ init_dist();
82587
83041
  init_utils();
82588
83042
  init_create_tool();
82589
83043
  init_path_security();
82590
- import * as fs74 from "fs";
82591
- import * as path90 from "path";
83044
+ import * as fs75 from "fs";
83045
+ import * as path91 from "path";
82592
83046
  var MAX_TEXT_LENGTH = 200;
82593
83047
  var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
82594
83048
  var SUPPORTED_EXTENSIONS4 = new Set([
@@ -82654,9 +83108,9 @@ function validatePathsInput(paths, cwd) {
82654
83108
  return { error: "paths contains path traversal", resolvedPath: null };
82655
83109
  }
82656
83110
  try {
82657
- const resolvedPath = path90.resolve(paths);
82658
- const normalizedCwd = path90.resolve(cwd);
82659
- const normalizedResolved = path90.resolve(resolvedPath);
83111
+ const resolvedPath = path91.resolve(paths);
83112
+ const normalizedCwd = path91.resolve(cwd);
83113
+ const normalizedResolved = path91.resolve(resolvedPath);
82660
83114
  if (!normalizedResolved.startsWith(normalizedCwd)) {
82661
83115
  return {
82662
83116
  error: "paths must be within the current working directory",
@@ -82672,13 +83126,13 @@ function validatePathsInput(paths, cwd) {
82672
83126
  }
82673
83127
  }
82674
83128
  function isSupportedExtension(filePath) {
82675
- const ext = path90.extname(filePath).toLowerCase();
83129
+ const ext = path91.extname(filePath).toLowerCase();
82676
83130
  return SUPPORTED_EXTENSIONS4.has(ext);
82677
83131
  }
82678
83132
  function findSourceFiles4(dir, files = []) {
82679
83133
  let entries;
82680
83134
  try {
82681
- entries = fs74.readdirSync(dir);
83135
+ entries = fs75.readdirSync(dir);
82682
83136
  } catch {
82683
83137
  return files;
82684
83138
  }
@@ -82687,10 +83141,10 @@ function findSourceFiles4(dir, files = []) {
82687
83141
  if (SKIP_DIRECTORIES5.has(entry)) {
82688
83142
  continue;
82689
83143
  }
82690
- const fullPath = path90.join(dir, entry);
83144
+ const fullPath = path91.join(dir, entry);
82691
83145
  let stat3;
82692
83146
  try {
82693
- stat3 = fs74.statSync(fullPath);
83147
+ stat3 = fs75.statSync(fullPath);
82694
83148
  } catch {
82695
83149
  continue;
82696
83150
  }
@@ -82783,7 +83237,7 @@ var todo_extract = createSwarmTool({
82783
83237
  return JSON.stringify(errorResult, null, 2);
82784
83238
  }
82785
83239
  const scanPath = resolvedPath;
82786
- if (!fs74.existsSync(scanPath)) {
83240
+ if (!fs75.existsSync(scanPath)) {
82787
83241
  const errorResult = {
82788
83242
  error: `path not found: ${pathsInput}`,
82789
83243
  total: 0,
@@ -82793,13 +83247,13 @@ var todo_extract = createSwarmTool({
82793
83247
  return JSON.stringify(errorResult, null, 2);
82794
83248
  }
82795
83249
  const filesToScan = [];
82796
- const stat3 = fs74.statSync(scanPath);
83250
+ const stat3 = fs75.statSync(scanPath);
82797
83251
  if (stat3.isFile()) {
82798
83252
  if (isSupportedExtension(scanPath)) {
82799
83253
  filesToScan.push(scanPath);
82800
83254
  } else {
82801
83255
  const errorResult = {
82802
- error: `unsupported file extension: ${path90.extname(scanPath)}`,
83256
+ error: `unsupported file extension: ${path91.extname(scanPath)}`,
82803
83257
  total: 0,
82804
83258
  byPriority: { high: 0, medium: 0, low: 0 },
82805
83259
  entries: []
@@ -82812,11 +83266,11 @@ var todo_extract = createSwarmTool({
82812
83266
  const allEntries = [];
82813
83267
  for (const filePath of filesToScan) {
82814
83268
  try {
82815
- const fileStat = fs74.statSync(filePath);
83269
+ const fileStat = fs75.statSync(filePath);
82816
83270
  if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
82817
83271
  continue;
82818
83272
  }
82819
- const content = fs74.readFileSync(filePath, "utf-8");
83273
+ const content = fs75.readFileSync(filePath, "utf-8");
82820
83274
  const entries = parseTodoComments(content, filePath, tagsSet);
82821
83275
  allEntries.push(...entries);
82822
83276
  } catch {}
@@ -82846,18 +83300,18 @@ init_tool();
82846
83300
  init_loader();
82847
83301
  init_schema();
82848
83302
  init_gate_evidence();
82849
- import * as fs76 from "fs";
82850
- import * as path92 from "path";
83303
+ import * as fs77 from "fs";
83304
+ import * as path93 from "path";
82851
83305
 
82852
83306
  // src/hooks/diff-scope.ts
82853
- import * as fs75 from "fs";
82854
- import * as path91 from "path";
83307
+ import * as fs76 from "fs";
83308
+ import * as path92 from "path";
82855
83309
  function getDeclaredScope(taskId, directory) {
82856
83310
  try {
82857
- const planPath = path91.join(directory, ".swarm", "plan.json");
82858
- if (!fs75.existsSync(planPath))
83311
+ const planPath = path92.join(directory, ".swarm", "plan.json");
83312
+ if (!fs76.existsSync(planPath))
82859
83313
  return null;
82860
- const raw = fs75.readFileSync(planPath, "utf-8");
83314
+ const raw = fs76.readFileSync(planPath, "utf-8");
82861
83315
  const plan = JSON.parse(raw);
82862
83316
  for (const phase of plan.phases ?? []) {
82863
83317
  for (const task of phase.tasks ?? []) {
@@ -82972,7 +83426,7 @@ var TIER_3_PATTERNS = [
82972
83426
  ];
82973
83427
  function matchesTier3Pattern(files) {
82974
83428
  for (const file3 of files) {
82975
- const fileName = path92.basename(file3);
83429
+ const fileName = path93.basename(file3);
82976
83430
  for (const pattern of TIER_3_PATTERNS) {
82977
83431
  if (pattern.test(fileName)) {
82978
83432
  return true;
@@ -82986,8 +83440,8 @@ function checkReviewerGate(taskId, workingDirectory) {
82986
83440
  if (hasActiveTurboMode()) {
82987
83441
  const resolvedDir2 = workingDirectory;
82988
83442
  try {
82989
- const planPath = path92.join(resolvedDir2, ".swarm", "plan.json");
82990
- const planRaw = fs76.readFileSync(planPath, "utf-8");
83443
+ const planPath = path93.join(resolvedDir2, ".swarm", "plan.json");
83444
+ const planRaw = fs77.readFileSync(planPath, "utf-8");
82991
83445
  const plan = JSON.parse(planRaw);
82992
83446
  for (const planPhase of plan.phases ?? []) {
82993
83447
  for (const task of planPhase.tasks ?? []) {
@@ -83053,8 +83507,8 @@ function checkReviewerGate(taskId, workingDirectory) {
83053
83507
  }
83054
83508
  try {
83055
83509
  const resolvedDir2 = workingDirectory;
83056
- const planPath = path92.join(resolvedDir2, ".swarm", "plan.json");
83057
- const planRaw = fs76.readFileSync(planPath, "utf-8");
83510
+ const planPath = path93.join(resolvedDir2, ".swarm", "plan.json");
83511
+ const planRaw = fs77.readFileSync(planPath, "utf-8");
83058
83512
  const plan = JSON.parse(planRaw);
83059
83513
  for (const planPhase of plan.phases ?? []) {
83060
83514
  for (const task of planPhase.tasks ?? []) {
@@ -83271,8 +83725,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83271
83725
  };
83272
83726
  }
83273
83727
  }
83274
- normalizedDir = path92.normalize(args2.working_directory);
83275
- const pathParts = normalizedDir.split(path92.sep);
83728
+ normalizedDir = path93.normalize(args2.working_directory);
83729
+ const pathParts = normalizedDir.split(path93.sep);
83276
83730
  if (pathParts.includes("..")) {
83277
83731
  return {
83278
83732
  success: false,
@@ -83282,11 +83736,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83282
83736
  ]
83283
83737
  };
83284
83738
  }
83285
- const resolvedDir = path92.resolve(normalizedDir);
83739
+ const resolvedDir = path93.resolve(normalizedDir);
83286
83740
  try {
83287
- const realPath = fs76.realpathSync(resolvedDir);
83288
- const planPath = path92.join(realPath, ".swarm", "plan.json");
83289
- if (!fs76.existsSync(planPath)) {
83741
+ const realPath = fs77.realpathSync(resolvedDir);
83742
+ const planPath = path93.join(realPath, ".swarm", "plan.json");
83743
+ if (!fs77.existsSync(planPath)) {
83290
83744
  return {
83291
83745
  success: false,
83292
83746
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -83317,12 +83771,12 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83317
83771
  }
83318
83772
  if (args2.status === "in_progress") {
83319
83773
  try {
83320
- const evidencePath = path92.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
83321
- fs76.mkdirSync(path92.dirname(evidencePath), { recursive: true });
83322
- const fd = fs76.openSync(evidencePath, "wx");
83774
+ const evidencePath = path93.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
83775
+ fs77.mkdirSync(path93.dirname(evidencePath), { recursive: true });
83776
+ const fd = fs77.openSync(evidencePath, "wx");
83323
83777
  let writeOk = false;
83324
83778
  try {
83325
- fs76.writeSync(fd, JSON.stringify({
83779
+ fs77.writeSync(fd, JSON.stringify({
83326
83780
  task_id: args2.task_id,
83327
83781
  required_gates: ["reviewer", "test_engineer"],
83328
83782
  gates: {},
@@ -83330,10 +83784,10 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83330
83784
  }, null, 2));
83331
83785
  writeOk = true;
83332
83786
  } finally {
83333
- fs76.closeSync(fd);
83787
+ fs77.closeSync(fd);
83334
83788
  if (!writeOk) {
83335
83789
  try {
83336
- fs76.unlinkSync(evidencePath);
83790
+ fs77.unlinkSync(evidencePath);
83337
83791
  } catch {}
83338
83792
  }
83339
83793
  }
@@ -83343,8 +83797,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
83343
83797
  recoverTaskStateFromDelegations(args2.task_id);
83344
83798
  let phaseRequiresReviewer = true;
83345
83799
  try {
83346
- const planPath = path92.join(directory, ".swarm", "plan.json");
83347
- const planRaw = fs76.readFileSync(planPath, "utf-8");
83800
+ const planPath = path93.join(directory, ".swarm", "plan.json");
83801
+ const planRaw = fs77.readFileSync(planPath, "utf-8");
83348
83802
  const plan = JSON.parse(planRaw);
83349
83803
  const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
83350
83804
  if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
@@ -83453,8 +83907,8 @@ init_utils2();
83453
83907
  init_ledger();
83454
83908
  init_manager();
83455
83909
  init_create_tool();
83456
- import fs77 from "fs";
83457
- import path93 from "path";
83910
+ import fs78 from "fs";
83911
+ import path94 from "path";
83458
83912
  function derivePlanId5(plan) {
83459
83913
  return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
83460
83914
  }
@@ -83505,7 +83959,7 @@ async function executeWriteDriftEvidence(args2, directory) {
83505
83959
  entries: [evidenceEntry]
83506
83960
  };
83507
83961
  const filename = "drift-verifier.json";
83508
- const relativePath = path93.join("evidence", String(phase), filename);
83962
+ const relativePath = path94.join("evidence", String(phase), filename);
83509
83963
  let validatedPath;
83510
83964
  try {
83511
83965
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -83516,12 +83970,12 @@ async function executeWriteDriftEvidence(args2, directory) {
83516
83970
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
83517
83971
  }, null, 2);
83518
83972
  }
83519
- const evidenceDir = path93.dirname(validatedPath);
83973
+ const evidenceDir = path94.dirname(validatedPath);
83520
83974
  try {
83521
- await fs77.promises.mkdir(evidenceDir, { recursive: true });
83522
- const tempPath = path93.join(evidenceDir, `.${filename}.tmp`);
83523
- await fs77.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
83524
- await fs77.promises.rename(tempPath, validatedPath);
83975
+ await fs78.promises.mkdir(evidenceDir, { recursive: true });
83976
+ const tempPath = path94.join(evidenceDir, `.${filename}.tmp`);
83977
+ await fs78.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
83978
+ await fs78.promises.rename(tempPath, validatedPath);
83525
83979
  let snapshotInfo;
83526
83980
  let snapshotError;
83527
83981
  let qaProfileLocked;
@@ -83615,8 +84069,8 @@ var write_drift_evidence = createSwarmTool({
83615
84069
  init_tool();
83616
84070
  init_utils2();
83617
84071
  init_create_tool();
83618
- import fs78 from "fs";
83619
- import path94 from "path";
84072
+ import fs79 from "fs";
84073
+ import path95 from "path";
83620
84074
  function normalizeVerdict2(verdict) {
83621
84075
  switch (verdict) {
83622
84076
  case "APPROVED":
@@ -83664,7 +84118,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
83664
84118
  entries: [evidenceEntry]
83665
84119
  };
83666
84120
  const filename = "hallucination-guard.json";
83667
- const relativePath = path94.join("evidence", String(phase), filename);
84121
+ const relativePath = path95.join("evidence", String(phase), filename);
83668
84122
  let validatedPath;
83669
84123
  try {
83670
84124
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -83675,12 +84129,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
83675
84129
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
83676
84130
  }, null, 2);
83677
84131
  }
83678
- const evidenceDir = path94.dirname(validatedPath);
84132
+ const evidenceDir = path95.dirname(validatedPath);
83679
84133
  try {
83680
- await fs78.promises.mkdir(evidenceDir, { recursive: true });
83681
- const tempPath = path94.join(evidenceDir, `.${filename}.tmp`);
83682
- await fs78.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
83683
- await fs78.promises.rename(tempPath, validatedPath);
84134
+ await fs79.promises.mkdir(evidenceDir, { recursive: true });
84135
+ const tempPath = path95.join(evidenceDir, `.${filename}.tmp`);
84136
+ await fs79.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
84137
+ await fs79.promises.rename(tempPath, validatedPath);
83684
84138
  return JSON.stringify({
83685
84139
  success: true,
83686
84140
  phase,
@@ -83889,7 +84343,7 @@ var OpenCodeSwarm = async (ctx) => {
83889
84343
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
83890
84344
  preflightTriggerManager = new PTM(automationConfig);
83891
84345
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
83892
- const swarmDir = path95.resolve(ctx.directory, ".swarm");
84346
+ const swarmDir = path96.resolve(ctx.directory, ".swarm");
83893
84347
  statusArtifact = new ASA(swarmDir);
83894
84348
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
83895
84349
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {