opencode-swarm 7.139.2 → 7.139.3

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.
Files changed (91) hide show
  1. package/.opencode/skills/phase-wrap/SKILL.md +2 -2
  2. package/dist/background/stage-b-gates.d.ts +5 -1
  3. package/dist/background/task-envelope.d.ts +4 -4
  4. package/dist/cli/{config-doctor-hrb61yqe.js → config-doctor-75qky762.js} +3 -3
  5. package/dist/cli/{core-g2dfh9fq.js → core-nn8ffy5h.js} +3 -3
  6. package/dist/cli/{curation-policy-4tp0mbcb.js → curation-policy-f46tf3dw.js} +5 -5
  7. package/dist/cli/{curator-drift-vfh07mqy.js → curator-drift-r52hjskk.js} +2 -2
  8. package/dist/cli/{curator-llm-factory-hvw13hmr.js → curator-llm-factory-1r9vz0g2.js} +28 -28
  9. package/dist/cli/{curator-vpcs1p1v.js → curator-m6mk2v1n.js} +28 -28
  10. package/dist/cli/{dispatch-b1fge4xk.js → dispatch-nbv0hezh.js} +2 -2
  11. package/dist/cli/{evidence-summary-service-kzxr71yd.js → evidence-summary-service-sxcq5nrt.js} +6 -6
  12. package/dist/cli/{gate-evidence-z2b1ymnw.js → gate-evidence-1982qswy.js} +3 -3
  13. package/dist/cli/{guardrail-explain-k62h3c92.js → guardrail-explain-y2wg00dp.js} +29 -29
  14. package/dist/cli/{guardrail-log-2ftew0p3.js → guardrail-log-320grggj.js} +5 -5
  15. package/dist/cli/{hive-promoter-mpf0cfkk.js → hive-promoter-v4w7cjvk.js} +28 -28
  16. package/dist/cli/{index-3eddtgxt.js → index-05mms0va.js} +1 -1
  17. package/dist/cli/{index-y64jv2pe.js → index-0mzz46we.js} +2 -2
  18. package/dist/cli/{index-ffpmqx30.js → index-1jbj7vzv.js} +4 -4
  19. package/dist/cli/{index-y47dwge1.js → index-1yd38ajy.js} +2 -2
  20. package/dist/cli/{index-gvhvz176.js → index-2eet124q.js} +1 -1
  21. package/dist/cli/{index-0wp67gqj.js → index-4fr94329.js} +11 -6
  22. package/dist/cli/{index-y111zefa.js → index-4nt52yr9.js} +80 -31
  23. package/dist/cli/{index-3jcyn8g6.js → index-4wjk296r.js} +1 -1
  24. package/dist/cli/{index-se8xc78k.js → index-5hyq2mw6.js} +30 -30
  25. package/dist/cli/{index-5zyk8qgv.js → index-5syvwd8g.js} +1 -1
  26. package/dist/cli/{index-ktagrqxw.js → index-6axm8q3s.js} +3 -3
  27. package/dist/cli/{index-668dpw5e.js → index-6mvbjqbt.js} +1 -1
  28. package/dist/cli/{index-wsdnttf4.js → index-74hpm6sh.js} +1 -1
  29. package/dist/cli/{index-wrt6m5ze.js → index-ajwmn8gp.js} +289 -73
  30. package/dist/cli/{index-6w1re1qn.js → index-ba5e5e3n.js} +2 -2
  31. package/dist/cli/{index-8gftfvpp.js → index-bcwx1335.js} +1 -1
  32. package/dist/cli/{index-a12906mj.js → index-gen82t10.js} +3 -3
  33. package/dist/cli/{index-s608d3hw.js → index-hckq2r5m.js} +1 -1
  34. package/dist/cli/{index-e9tx99ds.js → index-hwnpd4tw.js} +3 -3
  35. package/dist/cli/{index-3radty3f.js → index-jqn332jx.js} +2 -2
  36. package/dist/cli/{index-x9q4bbhd.js → index-k87hs2ff.js} +1 -1
  37. package/dist/cli/{index-8p9pasnp.js → index-kyz7259r.js} +2 -2
  38. package/dist/cli/{index-prmb5qfp.js → index-mv0czd9c.js} +2 -2
  39. package/dist/cli/{index-6c5f309q.js → index-mzck763x.js} +2 -2
  40. package/dist/cli/{index-a7nh382r.js → index-tkg6rapy.js} +1344 -682
  41. package/dist/cli/{index-yr6x707c.js → index-v7g8dnyh.js} +3 -3
  42. package/dist/cli/{index-w71cq27a.js → index-xeyr62x0.js} +5 -5
  43. package/dist/cli/{index-vm4xw9z3.js → index-xsxrhakg.js} +1 -1
  44. package/dist/cli/{index-5w7ekpy3.js → index-xzzcfwwm.js} +1 -1
  45. package/dist/cli/{index-q11t5erh.js → index-yawwwhmc.js} +2 -2
  46. package/dist/cli/{index-q7ezzgwq.js → index-yh0q21nj.js} +1 -1
  47. package/dist/cli/{index-cyw81ngh.js → index-z6qe8wft.js} +6 -6
  48. package/dist/cli/index.js +28 -28
  49. package/dist/cli/{knowledge-escalator-n9pmx4qr.js → knowledge-escalator-kkjsjtza.js} +6 -6
  50. package/dist/cli/{knowledge-events-raevm18w.js → knowledge-events-jbw9j94n.js} +4 -4
  51. package/dist/cli/{knowledge-link-3tq8w6gs.js → knowledge-link-f6akgbdn.js} +3 -3
  52. package/dist/cli/{knowledge-store-bcfqz8fr.js → knowledge-store-m7vnz62z.js} +4 -4
  53. package/dist/cli/{knowledge-validator-jxqj96cx.js → knowledge-validator-rqj7kech.js} +7 -7
  54. package/dist/cli/{pending-delegations-tmyf18p4.js → pending-delegations-14544jpr.js} +3 -3
  55. package/dist/cli/{pr-subscriptions-hhx6yk24.js → pr-subscriptions-qdwfm1a8.js} +3 -3
  56. package/dist/cli/{runner-w30gazew.js → runner-mzb3e2s6.js} +4 -4
  57. package/dist/cli/{scan-cursor-ah5swtyh.js → scan-cursor-8e6ryhjq.js} +5 -5
  58. package/dist/cli/{schema-f7jgrtxj.js → schema-g7rs20c6.js} +2 -2
  59. package/dist/cli/{scope-persistence-3f0d4fwv.js → scope-persistence-wy65zyha.js} +2 -2
  60. package/dist/cli/{skill-generator-hp7as46c.js → skill-generator-2rv6ftre.js} +8 -8
  61. package/dist/cli/{task-envelope-qn0qtnh0.js → task-envelope-jp2fdsvn.js} +11 -8
  62. package/dist/cli/{workspace-snapshot-h5rzw37b.js → workspace-snapshot-w3tg4ne5.js} +2 -2
  63. package/dist/cli/{worktree-collision-ownership-vexb0b6r.js → worktree-collision-ownership-24s0t62z.js} +4 -4
  64. package/dist/commands/approve-plan-critic.d.ts +2 -2
  65. package/dist/commands/registry.d.ts +1 -1
  66. package/dist/config/agent-model.d.ts +35 -0
  67. package/dist/db/qa-gate-profile.d.ts +11 -3
  68. package/dist/evidence/manager.d.ts +1 -1
  69. package/dist/evidence/phase-participation.d.ts +44 -0
  70. package/dist/hooks/compaction-customizer.d.ts +30 -2
  71. package/dist/hooks/context-budget.d.ts +1 -1
  72. package/dist/hooks/delegation-gate.d.ts +3 -3
  73. package/dist/hooks/guardrails/index.d.ts +5 -1
  74. package/dist/hooks/guardrails/messages-transform.d.ts +2 -0
  75. package/dist/hooks/guardrails/pre-check-result.d.ts +20 -0
  76. package/dist/index.js +420 -415
  77. package/dist/sast/semgrep.d.ts +15 -32
  78. package/dist/services/compaction-service.d.ts +3 -1
  79. package/dist/session/snapshot-writer.d.ts +1 -0
  80. package/dist/state.d.ts +70 -19
  81. package/dist/tools/context-status.d.ts +4 -1
  82. package/dist/tools/lint.d.ts +48 -22
  83. package/dist/tools/pre-check-batch.d.ts +37 -13
  84. package/dist/tools/quality-budget.d.ts +3 -2
  85. package/dist/tools/sast-baseline.d.ts +1 -0
  86. package/dist/tools/sast-scan.d.ts +4 -1
  87. package/dist/tools/secretscan.d.ts +2 -2
  88. package/dist/tools/set-qa-gates.d.ts +5 -4
  89. package/dist/utils/bun-compat.d.ts +20 -0
  90. package/dist/utils/external-tool-runner.d.ts +10 -0
  91. package/package.json +1 -1
@@ -2,6 +2,7 @@
2
2
  * Semgrep Integration for Tier B SAST Enhancement
3
3
  * Provides optional Semgrep detection and invocation for advanced static analysis
4
4
  */
5
+ import { resolveExecutableFromPath, runExternalTool } from '../utils/external-tool-runner';
5
6
  import type { SastFinding } from './rules/index.js';
6
7
  /**
7
8
  * Semgrep CLI options
@@ -19,6 +20,8 @@ export interface SemgrepOptions {
19
20
  lang?: string;
20
21
  /** When true, use --config auto instead of local rulesDir (for profile-driven languages) */
21
22
  useAutoConfig?: boolean;
23
+ /** Host/tool cancellation propagated to the shared subprocess runner. */
24
+ abortSignal?: AbortSignal;
22
25
  }
23
26
  /**
24
27
  * Result from Semgrep execution
@@ -33,6 +36,8 @@ export interface SemgrepResult {
33
36
  /** Engine label for the findings */
34
37
  engine: 'tier_a' | 'tier_a+tier_b';
35
38
  }
39
+ declare function resolveSemgrepBinary(): string | null;
40
+ declare function getAvailabilityProbeCwd(): string;
36
41
  export declare const _internals: {
37
42
  isSemgrepAvailable: typeof isSemgrepAvailable;
38
43
  checkSemgrepAvailable: typeof checkSemgrepAvailable;
@@ -40,48 +45,26 @@ export declare const _internals: {
40
45
  runSemgrep: typeof runSemgrep;
41
46
  getRulesDirectory: typeof getRulesDirectory;
42
47
  hasBundledRules: typeof hasBundledRules;
43
- executeWithTimeout: typeof executeWithTimeout;
48
+ resolveExecutableFromPath: typeof resolveExecutableFromPath;
49
+ resolveSemgrepBinary: typeof resolveSemgrepBinary;
50
+ runExternalTool: typeof runExternalTool;
51
+ getAvailabilityProbeCwd: typeof getAvailabilityProbeCwd;
44
52
  };
45
53
  /**
46
- * Check if Semgrep CLI is available on the system
47
- * Uses caching to avoid shelling out on every check
48
- * @returns true if Semgrep is available, false otherwise
54
+ * Synchronous compatibility surface.
55
+ * Returns the last confirmed async availability result when cached; otherwise
56
+ * falls back to a pure PATH-resolution heuristic without spawning.
49
57
  */
50
58
  export declare function isSemgrepAvailable(): boolean;
51
59
  /**
52
- * Check if Semgrep is available (async version for consistency)
53
- * @returns Promise resolving to availability status
60
+ * Bounded async availability probe used by the live batch path.
61
+ * Caches only the result of the real shared-runner probe.
54
62
  */
55
- export declare function checkSemgrepAvailable(): Promise<boolean>;
63
+ export declare function checkSemgrepAvailable(cwd?: string, abortSignal?: AbortSignal): Promise<boolean>;
56
64
  /**
57
65
  * Reset the Semgrep availability cache (useful for testing)
58
66
  */
59
67
  export declare function resetSemgrepCache(): void;
60
- /**
61
- * Execute a command with timeout
62
- * @param command - Command to execute
63
- * @param args - Command arguments (safe array, no shell injection)
64
- * @param options - Execution options including timeout
65
- * @returns Promise resolving to command output
66
- */
67
- declare function executeWithTimeout(command: string, args: string[], options: {
68
- cwd?: string;
69
- timeoutMs: number;
70
- maxOutputBytes?: number;
71
- /**
72
- * Test-only hook invoked once with the spawned child's pid, immediately
73
- * after `spawn()` returns. Lets tests assert the child is actually
74
- * reaped (not just that a placeholder exit code was set) without
75
- * mocking `node:child_process` (which would leak across test files —
76
- * AGENTS.md invariant 7). No production caller passes this.
77
- */
78
- onSpawn?: (pid: number | undefined) => void;
79
- }): Promise<{
80
- stdout: string;
81
- stderr: string;
82
- exitCode: number;
83
- truncated: boolean;
84
- }>;
85
68
  /**
86
69
  * Run Semgrep on specified files
87
70
  * @param options - Semgrep options
@@ -7,11 +7,13 @@
7
7
  * - Reflection (default 60%): re-summarise into tighter format
8
8
  * - Emergency (default 80%): hard truncation to system + current task + last N turns
9
9
  *
10
- * Consumes `swarmState.lastBudgetPct` (set by system-enhancer.ts after each budget calc).
10
+ * Consumes the per-session budget pct recorded by system-enhancer.ts after
11
+ * each budget calc (`getSessionBudgetPct`).
11
12
  * Never throws. Advisory system message injection via callback.
12
13
  */
13
14
  import type { CompactionConfig } from '../config/schema';
14
15
  export type { CompactionConfig };
16
+ export declare const MAX_TRACKED_COMPACTION_SESSIONS = 500;
15
17
  export interface CompactionServiceHook {
16
18
  toolAfter: (input: {
17
19
  tool: string;
@@ -30,6 +30,7 @@ export interface SerializedAgentSession {
30
30
  tool: string;
31
31
  taskId: string;
32
32
  timestamp: number;
33
+ code?: string;
33
34
  } | null;
34
35
  partialGateWarningsIssuedForTask: string[];
35
36
  completionGateWarnedForTask: string[];
package/dist/state.d.ts CHANGED
@@ -166,6 +166,7 @@ export interface AgentSessionState {
166
166
  tool: string;
167
167
  taskId: string;
168
168
  timestamp: number;
169
+ code?: string;
169
170
  } | null;
170
171
  /** Task IDs for which partial gate warning has already been issued (prevents per-task spam) */
171
172
  partialGateWarningsIssuedForTask: Set<string>;
@@ -596,26 +597,44 @@ export declare const swarmState: {
596
597
  * matching). Populated by `src/index.ts` after `createAgents`.
597
598
  */
598
599
  generatedAgentNames: string[];
599
- /** Last known context budget percentage (0-100), updated by system-enhancer */
600
- lastBudgetPct: number;
601
600
  /**
602
- * The DENOMINATOR `lastBudgetPct` was computed against, in tokens. Written
603
- * by system-enhancer at the same two statements that write `lastBudgetPct`,
604
- * because a percentage without its denominator cannot be turned back into a
605
- * token estimate. `/swarm status` used to reconstruct the estimate from a
606
- * hardcoded constant, so a user whose real denominator differed saw a token
607
- * figure that did not match the percentage next to it. 0 means "no budget
608
- * report has run yet".
601
+ * Last known context budget per session keyed by sessionID.
602
+ *
603
+ * `pct` is the budget percentage (0-100); `tokens` is the DENOMINATOR it was
604
+ * computed against. They are stored together because a percentage without
605
+ * its denominator cannot be turned back into a token estimate, and
606
+ * `/swarm status` renders both reading them from different sources would
607
+ * show a token figure that does not match the percentage beside it.
608
+ *
609
+ * Keyed rather than global (AGENTS.md invariant 8). These were two bare
610
+ * module-level scalars, which was harmless only while the budget check threw
611
+ * on every call and left them pinned at 0. Now that the check runs, a bare
612
+ * global leaks across sessions: a busy session reporting 85% would trip the
613
+ * default-on compaction service's emergency tier — and the `>= 50%` CONTEXT
614
+ * PRESSURE advisory — inside an unrelated small session. compaction-service
615
+ * keys its *hysteresis* per session, so the wrong session's state machine
616
+ * would advance on another session's number.
617
+ *
618
+ * Write via `setSessionBudget`, read via `getSessionBudgetPct` /
619
+ * `getSessionBudgetTokens`; `/swarm status` uses `getDisplayBudget`.
609
620
  */
610
- lastBudgetTokens: number;
621
+ lastBudgetBySession: Map<string, {
622
+ pct: number;
623
+ tokens: number;
624
+ }>;
611
625
  /**
612
- * Live `model.limit.context` per session — keyed by sessionID, recorded by
613
- * the `experimental.chat.system.transform` hook (the only hook the host
626
+ * Live `model.limit.context` per session — keyed by sessionID and bound to
627
+ * the reporting model/provider identity. Recorded by the
628
+ * `experimental.chat.system.transform` hook (the only hook the host
614
629
  * gives a `Model` to) and read by the `experimental.chat.messages.transform`
615
630
  * consumers, which receive messages but no model object. Bounded via
616
631
  * {@link setLiveContextWindow} (AGENTS.md invariant 8).
617
632
  */
618
- liveContextWindows: Map<string, number>;
633
+ liveContextWindows: Map<string, {
634
+ tokens?: number;
635
+ modelID?: string;
636
+ providerID?: string;
637
+ }>;
619
638
  /** Per-session guardrail state — keyed by sessionID */
620
639
  agentSessions: Map<string, AgentSessionState>;
621
640
  /** In-flight rehydration promises — awaited by rehydrateState before clearing agentSessions */
@@ -1157,13 +1176,15 @@ export declare function getResolvedAutoProceed(session: AgentSessionState, planA
1157
1176
  export declare function setSessionEnvironment(sessionId: string, profile: EnvironmentProfile): void;
1158
1177
  export declare function getSessionEnvironment(sessionId: string): EnvironmentProfile | undefined;
1159
1178
  export declare function ensureSessionEnvironment(sessionId: string): EnvironmentProfile;
1179
+ export declare const MAX_TRACKED_BUDGET_SESSIONS = 500;
1160
1180
  export declare const MAX_TRACKED_CRITICAL_SHOWN = 500;
1161
1181
  export declare const MAX_TRACKED_KNOWLEDGE_ACKS = 5000;
1162
1182
  export declare const MAX_TRACKED_GATE_DENIALS = 500;
1163
1183
  export declare const MAX_TRACKED_CONTEXT_WINDOWS = 500;
1164
1184
  /**
1165
1185
  * Record the live `model.limit.context` the host reported for `sessionID`,
1166
- * FIFO-evicting the oldest entry past {@link MAX_TRACKED_CONTEXT_WINDOWS}.
1186
+ * bound to the reporting model/provider identity and FIFO-evicting the oldest
1187
+ * entry past {@link MAX_TRACKED_CONTEXT_WINDOWS}.
1167
1188
  *
1168
1189
  * Only accepts a finite number ≥ 1 — the caller (`src/config/context-window.ts`
1169
1190
  * `isUsableContextWindow`) applies the real plausibility floor, and storing a
@@ -1171,14 +1192,44 @@ export declare const MAX_TRACKED_CONTEXT_WINDOWS = 500;
1171
1192
  * rejected value leaves any previously recorded window in place rather than
1172
1193
  * clobbering it, so one malformed turn does not blank a good reading.
1173
1194
  */
1174
- export declare function setLiveContextWindow(sessionID: string | undefined, tokens: unknown): void;
1195
+ export declare function setLiveContextWindow(sessionID: string | undefined, tokens: unknown, identity?: {
1196
+ modelID?: string;
1197
+ providerID?: string;
1198
+ }): void;
1175
1199
  /**
1176
1200
  * Read the live context window recorded for `sessionID`, or `undefined` when
1177
- * no `system.transform` has run for it yet (first turn of a session, or a
1178
- * consumer that never sees a sessionID). Callers must degrade to the static
1179
- * resolution rungs rather than assume a value.
1201
+ * no `system.transform` has run for it yet or the requested model/provider
1202
+ * identity does not exactly match the reporter. Callers must degrade to the
1203
+ * static resolution rungs rather than reuse a stale handoff denominator.
1180
1204
  */
1181
- export declare function getLiveContextWindow(sessionID: string | undefined): number | undefined;
1205
+ export declare function getLiveContextWindow(sessionID: string | undefined, identity?: {
1206
+ modelID?: string;
1207
+ providerID?: string;
1208
+ }): number | undefined;
1209
+ export declare function getLiveContextModelIdentity(sessionID: string | undefined): {
1210
+ modelID?: string;
1211
+ providerID?: string;
1212
+ } | undefined;
1213
+ /** Record this session's budget percentage and the denominator it was computed
1214
+ * against, FIFO-evicting the oldest entry if the cap is exceeded. The two are
1215
+ * written together so `/swarm status` can never pair a pct from one report
1216
+ * with a denominator from another. */
1217
+ export declare function setSessionBudget(sessionID: string, pct: number, tokens: number): void;
1218
+ /** This session's last budget percentage, or 0 if it has not been measured.
1219
+ * Consumers that ACT on a session (compaction, the context-pressure advisory)
1220
+ * MUST use this rather than any cross-session aggregate. */
1221
+ export declare function getSessionBudgetPct(sessionID: string | undefined): number;
1222
+ /** The denominator this session's percentage was computed against, or 0. */
1223
+ export declare function getSessionBudgetTokens(sessionID: string | undefined): number;
1224
+ /** The most-pressured session's budget snapshot, for process-wide DISPLAY only
1225
+ * (`/swarm status` has no single session in scope). Returns the pct and its
1226
+ * OWN denominator as a pair, so the two figures shown side by side always come
1227
+ * from the same report. Null when nothing has been measured yet. Never use
1228
+ * this to decide whether to act on a particular session. */
1229
+ export declare function getDisplayBudget(): {
1230
+ pct: number;
1231
+ tokens: number;
1232
+ } | null;
1182
1233
  /** Set the critical shown ids for a session, FIFO-evicting the oldest entry
1183
1234
  * if the cap is exceeded. Re-setting an existing key keeps insertion order
1184
1235
  * fresh for that key (delete-then-set). */
@@ -83,7 +83,10 @@ export declare const _internals: {
83
83
  * by the system.transform hook; `undefined` when none has been seen yet
84
84
  * @returns Context status with token usage, limit, and threshold state
85
85
  */
86
- declare function computeContextHeadroom(messages: ContextMessage[], warnThreshold?: number, criticalThreshold?: number, modelLimitsConfig?: Record<string, number>, liveContextLimit?: unknown): ContextStatusResult;
86
+ declare function computeContextHeadroom(messages: ContextMessage[], warnThreshold?: number, criticalThreshold?: number, modelLimitsConfig?: Record<string, number>, liveContextLimit?: unknown, modelIdentity?: {
87
+ modelID?: string;
88
+ providerID?: string;
89
+ }): ContextStatusResult;
87
90
  /**
88
91
  * context_status tool — read-only context window headroom report.
89
92
  *
@@ -1,8 +1,19 @@
1
+ import * as fs from 'node:fs';
1
2
  import type { tool } from '@opencode-ai/plugin';
3
+ import { isCommandAvailable } from '../build/discovery';
4
+ import { type ExternalToolRunOptions, type ExternalToolRunResult } from '../utils/external-tool-runner';
2
5
  export declare const MAX_OUTPUT_BYTES = 512000;
3
6
  export declare const MAX_COMMAND_LENGTH = 500;
4
7
  export declare const SUPPORTED_LINTERS: readonly ["biome", "eslint"];
5
8
  export type SupportedLinter = (typeof SUPPORTED_LINTERS)[number];
9
+ type ResolvedLinterSource = 'local-native' | 'local-shim' | 'safe-package-bin' | 'path-native' | 'path-shim' | 'legacy-test-probe';
10
+ export interface ResolvedLinterCommand {
11
+ linter: SupportedLinter;
12
+ executable: string;
13
+ argsPrefix: string[];
14
+ displayPrefix: string[];
15
+ source: ResolvedLinterSource;
16
+ }
6
17
  export type AdditionalLinter = 'ruff' | 'clippy' | 'golangci-lint' | 'checkstyle' | 'ktlint' | 'dotnet-format' | 'cppcheck' | 'swiftlint' | 'dart-analyze' | 'rubocop' | 'phpstan' | 'pint' | 'php-cs-fixer' | 'phpcs';
7
18
  export interface LintSuccessResult {
8
19
  success: true;
@@ -28,46 +39,61 @@ export { containsControlChars, containsPathTraversal, } from '../utils/path-secu
28
39
  export declare function validateArgs(args: unknown): args is {
29
40
  mode: 'fix' | 'check';
30
41
  };
42
+ export declare function getBiomeBinPath(directory: string): string;
43
+ export declare function getEslintBinPath(directory: string): string;
31
44
  export declare function getLinterCommand(linter: SupportedLinter, mode: 'fix' | 'check', projectDir: string): string[];
32
- /**
33
- * Build the shell command for an additional (non-JS/TS) linter.
34
- * cppcheck has no --fix mode; csharp and some others behave differently.
35
- */
36
- export declare function getAdditionalLinterCommand(linter: AdditionalLinter, mode: 'fix' | 'check', cwd: string): string[];
45
+ export declare function resolveLinterBinPath(linter: SupportedLinter, projectDir: string): string;
46
+ export declare function resolveLinterCommand(linter: SupportedLinter, directory: string): Promise<ResolvedLinterCommand | null>;
47
+ export declare function detectResolvedLinter(directory?: string, abortSignal?: AbortSignal): Promise<ResolvedLinterCommand | null>;
37
48
  /**
38
49
  * Detect the first available additional (non-JS/TS) linter for the current project.
39
50
  * Returns null when no additional linter is detected or its binary is unavailable.
40
51
  */
41
52
  export declare function detectAdditionalLinter(cwd: string): 'ruff' | 'clippy' | 'golangci-lint' | 'checkstyle' | 'ktlint' | 'dotnet-format' | 'cppcheck' | 'swiftlint' | 'dart-analyze' | 'rubocop' | 'phpstan' | 'pint' | 'php-cs-fixer' | 'phpcs' | null;
42
- /** Compute the local biome binary path for a given project directory. */
43
- export declare function getBiomeBinPath(directory: string): string;
44
- /**
45
- * Resolve the binary path for a linter, using the same hierarchy as detectAvailableLinter:
46
- * 1. Local node_modules/.bin
47
- * 2. Ancestor node_modules/.bin (monorepo)
48
- * 3. process.env.PATH scan
49
- * 4. Local path as fallback (may not exist)
50
- */
51
- export declare function resolveLinterBinPath(linter: SupportedLinter, projectDir: string): string;
52
- /** Compute the local eslint binary path for a given project directory. */
53
- export declare function getEslintBinPath(directory: string): string;
54
53
  export declare function detectAvailableLinter(directory?: string): Promise<SupportedLinter | null>;
55
54
  /** Internal implementation — accepts pre-computed binary paths for testability. */
56
- export declare function _detectAvailableLinter(_projectDir: string, biomeBin: string, eslintBin: string): Promise<SupportedLinter | null>;
57
- export declare function runLint(linter: SupportedLinter, mode: 'fix' | 'check', directory: string): Promise<LintResult>;
55
+ export declare function _detectAvailableLinter(projectDir: string, biomeBin: string, eslintBin: string): Promise<SupportedLinter | null>;
56
+ declare function runResolvedLint(command: ResolvedLinterCommand, mode: 'fix' | 'check', directory: string, abortSignal?: AbortSignal): Promise<LintResult>;
57
+ /**
58
+ * Build the shell command for an additional (non-JS/TS) linter.
59
+ * cppcheck has no --fix mode; csharp and some others behave differently.
60
+ */
61
+ export declare function getAdditionalLinterCommand(linter: AdditionalLinter, mode: 'fix' | 'check', cwd: string): string[] | null;
62
+ export declare function runLint(linter: SupportedLinter, mode: 'fix' | 'check', directory: string, abortSignal?: AbortSignal): Promise<LintResult>;
58
63
  /**
59
64
  * Run an additional (non-JS/TS) linter.
60
65
  * Follows the same structure as runLint() but uses getAdditionalLinterCommand().
61
66
  */
62
- export declare function runAdditionalLint(linter: AdditionalLinter, mode: 'fix' | 'check', cwd: string): Promise<LintResult>;
67
+ export declare function runAdditionalLint(linter: AdditionalLinter, mode: 'fix' | 'check', cwd: string, abortSignal?: AbortSignal): Promise<LintResult>;
63
68
  export declare const lint: ReturnType<typeof tool>;
64
69
  /**
65
70
  * DI seam for testability. Contains all test-mocked exports.
66
71
  * Internal calls should use _internals.fn() instead of fn() directly.
67
72
  */
68
73
  export declare const _internals: {
69
- detectAvailableLinter: typeof detectAvailableLinter;
70
- runLint: typeof runLint;
74
+ MAX_PACKAGE_MANIFEST_BYTES: number;
75
+ MAX_PATH_SHIM_BYTES: number;
76
+ arch: () => string;
77
+ closeSync: typeof fs.closeSync;
78
+ comSpec: () => string | undefined;
71
79
  detectAdditionalLinter: typeof detectAdditionalLinter;
80
+ detectAvailableLinter: typeof detectAvailableLinter;
81
+ detectResolvedLinter: typeof detectResolvedLinter;
82
+ execPath: () => string;
83
+ existsSync: typeof fs.existsSync;
84
+ fstatSync: typeof fs.fstatSync;
85
+ isCommandAvailable: typeof isCommandAvailable;
86
+ openSync: typeof fs.openSync;
87
+ pathEnv: () => string;
88
+ platform: () => NodeJS.Platform;
89
+ readFileSync: typeof fs.readFileSync;
90
+ readSync: typeof fs.readSync;
91
+ readdirSync: typeof fs.readdirSync;
92
+ realpathSync: typeof fs.realpathSync;
93
+ resolveLinterCommand: typeof resolveLinterCommand;
72
94
  runAdditionalLint: typeof runAdditionalLint;
95
+ runExternalTool: (options: ExternalToolRunOptions) => Promise<ExternalToolRunResult>;
96
+ runLint: typeof runLint;
97
+ runResolvedLint: typeof runResolvedLint;
98
+ statSync: typeof fs.statSync;
73
99
  };
@@ -6,7 +6,9 @@
6
6
  import type { tool } from '@opencode-ai/plugin';
7
7
  import type { PluginConfig } from '../config';
8
8
  import { saveEvidence } from '../evidence/manager.js';
9
- import type { LintResult } from './lint';
9
+ import { runExternalTool } from '../utils/external-tool-runner';
10
+ import type { LintResult, ResolvedLinterCommand } from './lint';
11
+ import { detectResolvedLinter } from './lint';
10
12
  import type { QualityBudgetResult } from './quality-budget';
11
13
  import { qualityBudget } from './quality-budget';
12
14
  import type { SastScanFinding, SastScanResult } from './sast-scan';
@@ -17,8 +19,15 @@ export declare const _internals: {
17
19
  runSecretscanWrapped: typeof runSecretscanWrapped;
18
20
  runSastScanWrapped: typeof runSastScanWrapped;
19
21
  runQualityBudgetWrapped: typeof runQualityBudgetWrapped;
22
+ runWithTimeout: typeof runWithTimeout;
20
23
  getChangedLineRanges: typeof getChangedLineRanges;
21
24
  saveEvidence: typeof saveEvidence;
25
+ runExternalTool: typeof runExternalTool;
26
+ detectResolvedLinter: typeof detectResolvedLinter;
27
+ runLintOnFiles: typeof runLintOnFiles;
28
+ platform: () => NodeJS.Platform;
29
+ serializePreCheckResult: typeof serializePreCheckResult;
30
+ MAX_COMBINED_BYTES: number;
22
31
  };
23
32
  export interface PreCheckBatchInput {
24
33
  /** List of specific files to check (optional) */
@@ -45,10 +54,14 @@ export interface ToolResult<T> {
45
54
  result?: T;
46
55
  /** Error message if failed */
47
56
  error?: string;
57
+ /** Successful result payload was omitted from a compact serialized response. */
58
+ result_omitted?: boolean;
48
59
  /** Duration in milliseconds */
49
60
  duration_ms: number;
50
61
  }
51
62
  export interface PreCheckBatchResult {
63
+ /** Structured producer status. Optional only for legacy serialized results. */
64
+ batch_status?: 'completed' | 'skipped' | 'invalid';
52
65
  /** Overall gate status: true if all security gates pass */
53
66
  gates_passed: boolean;
54
67
  /** Lint tool result */
@@ -64,36 +77,47 @@ export interface PreCheckBatchResult {
64
77
  /** Pre-existing SAST findings on unchanged lines, requiring reviewer triage */
65
78
  sast_preexisting_findings?: SastScanFinding[];
66
79
  }
80
+ /**
81
+ * Serialize the public result below the decoder's stricter byte ceiling. When
82
+ * detailed diagnostics exceed the aggregate budget, preserve every control
83
+ * field and replace optional payloads with bounded, UTF-8-safe summaries.
84
+ */
85
+ declare function serializePreCheckResult(result: PreCheckBatchResult): string;
86
+ /**
87
+ * Run a function with timeout
88
+ */
89
+ declare function runWithTimeout<T>(operation: (abortSignal: AbortSignal) => Promise<T>, timeoutMs: number, parentSignal?: AbortSignal, waitForCancellationSettlement?: boolean): Promise<T>;
67
90
  /**
68
91
  * Run lint with detection and timeout
69
92
  */
70
- declare function runLintWrapped(files: string[] | undefined, directory: string, _config?: PluginConfig): Promise<ToolResult<LintResult>>;
93
+ declare function runLintWrapped(files: string[] | undefined, directory: string, _config?: PluginConfig, abortSignal?: AbortSignal): Promise<ToolResult<LintResult>>;
94
+ /**
95
+ * Run lint on specific files only
96
+ */
97
+ declare function runLintOnFiles(resolvedLinter: ResolvedLinterCommand, files: string[], workspaceDir: string, abortSignal?: AbortSignal): Promise<LintResult>;
71
98
  /**
72
99
  * Run secretscan with timeout
73
100
  */
74
- declare function runSecretscanWrapped(files: string[] | undefined, directory: string, _config?: PluginConfig): Promise<ToolResult<SecretscanResult | SecretscanErrorResult>>;
101
+ declare function runSecretscanWrapped(files: string[] | undefined, directory: string, _config?: PluginConfig, abortSignal?: AbortSignal): Promise<ToolResult<SecretscanResult | SecretscanErrorResult>>;
75
102
  /**
76
103
  * Run SAST scan with timeout
77
104
  */
78
- declare function runSastScanWrapped(changedFiles: string[], directory: string, severityThreshold: 'low' | 'medium' | 'high' | 'critical', config?: PluginConfig, phase?: number): Promise<ToolResult<SastScanResult>>;
105
+ declare function runSastScanWrapped(changedFiles: string[], directory: string, severityThreshold: 'low' | 'medium' | 'high' | 'critical', config?: PluginConfig, phase?: number, abortSignal?: AbortSignal): Promise<ToolResult<SastScanResult>>;
79
106
  /**
80
107
  * Run quality budget with timeout
81
108
  */
82
- declare function runQualityBudgetWrapped(changedFiles: string[], directory: string, config?: PluginConfig): Promise<ToolResult<QualityBudgetResult>>;
109
+ declare function runQualityBudgetWrapped(changedFiles: string[], directory: string, config?: PluginConfig, abortSignal?: AbortSignal): Promise<ToolResult<QualityBudgetResult>>;
83
110
  /**
84
111
  * Parse unified diff output (with -U0) to extract added/modified line numbers per file.
85
112
  * Returns a Map from normalised file path → Set of changed line numbers.
86
113
  */
87
114
  export declare function parseDiffLineRanges(diffOutput: string): Map<string, Set<number>>;
88
115
  /**
89
- * Get changed line ranges for the current branch vs its base.
90
- * Tries three strategies in order:
91
- * 1. merge-base diff against main/master (captures all branch changes, works after commit)
92
- * 2. HEAD~1 (single-commit diff, works after commit)
93
- * 3. HEAD (unstaged/staged changes, works before commit)
94
- * Returns null if git is unavailable or no changes found.
116
+ * Get the union of committed, staged, unstaged, and untracked changed lines.
117
+ * A known empty union is authoritative; null means a required Git source was
118
+ * unavailable or malformed and callers must classify findings fail-closed.
95
119
  */
96
- export declare function getChangedLineRanges(directory: string): Promise<Map<string, Set<number>> | null>;
120
+ export declare function getChangedLineRanges(directory: string, abortSignal?: AbortSignal, requestedFiles?: string[]): Promise<Map<string, Set<number>> | null>;
97
121
  /**
98
122
  * Classify SAST findings as "new" (on changed lines) or "pre-existing" (unchanged lines).
99
123
  * A finding is "new" if its file+line intersects the changed line ranges from git diff.
@@ -108,7 +132,7 @@ export declare function classifySastFindings(findings: SastScanFinding[], change
108
132
  * @param input - The pre-check batch input
109
133
  * @param workspaceDir - Optional workspace directory for traversal validation (injected project root from createSwarmTool, or input.directory)
110
134
  */
111
- export declare function runPreCheckBatch(input: PreCheckBatchInput, workspaceDir?: string, contextDir?: string): Promise<PreCheckBatchResult>;
135
+ export declare function runPreCheckBatch(input: PreCheckBatchInput, workspaceDir?: string, contextDir?: string, abortSignal?: AbortSignal): Promise<PreCheckBatchResult>;
112
136
  /**
113
137
  * Pre-check batch tool - runs 4 verification tools in parallel
114
138
  * Returns unified result with gates_passed status
@@ -1,6 +1,6 @@
1
1
  import type { tool } from '@opencode-ai/plugin';
2
2
  import type { QualityBudgetConfig } from '../config/schema';
3
- import { type QualityMetrics, type QualityViolation } from '../quality/metrics';
3
+ import { computeQualityMetrics, type QualityMetrics, type QualityViolation } from '../quality/metrics';
4
4
  export interface QualityBudgetInput {
5
5
  changed_files: string[];
6
6
  config?: Partial<QualityBudgetConfig>;
@@ -22,7 +22,7 @@ export interface QualityBudgetResult {
22
22
  * Computes quality metrics (complexity, API, duplication, test ratio)
23
23
  * and compares against configured thresholds to ensure code quality.
24
24
  */
25
- export declare function qualityBudget(input: QualityBudgetInput, directory: string): Promise<QualityBudgetResult>;
25
+ export declare function qualityBudget(input: QualityBudgetInput, directory: string, abortSignal?: AbortSignal): Promise<QualityBudgetResult>;
26
26
  export declare const quality_budget: ReturnType<typeof tool>;
27
27
  /**
28
28
  * DI seam for testability. Contains all test-mocked exports.
@@ -30,4 +30,5 @@ export declare const quality_budget: ReturnType<typeof tool>;
30
30
  */
31
31
  export declare const _internals: {
32
32
  qualityBudget: typeof qualityBudget;
33
+ computeQualityMetrics: typeof computeQualityMetrics;
33
34
  };
@@ -116,6 +116,7 @@ export declare function assignOccurrenceIndices(findings: SastScanFinding[], dir
116
116
  */
117
117
  export declare function captureOrMergeBaseline(directory: string, phase: number, findings: SastScanFinding[], engine: 'tier_a' | 'tier_a+tier_b', scannedFiles: string[], opts?: {
118
118
  force?: boolean;
119
+ abortSignal?: AbortSignal;
119
120
  }): Promise<CaptureResult>;
120
121
  /**
121
122
  * Load the SAST baseline for a given phase.
@@ -5,7 +5,7 @@
5
5
  import type { ToolDefinition } from '@opencode-ai/plugin/tool';
6
6
  import type { PluginConfig } from '../config';
7
7
  import type { EvidenceVerdict } from '../config/evidence-schema';
8
- import { isSemgrepAvailable, runSemgrep } from '../sast/semgrep';
8
+ import { checkSemgrepAvailable, isSemgrepAvailable, runSemgrep } from '../sast/semgrep';
9
9
  export interface SastScanInput {
10
10
  /** List of files to scan */
11
11
  changed_files: string[];
@@ -32,6 +32,8 @@ export interface SastScanInput {
32
32
  * baseline diff mode: only findings absent from the phase baseline fail.
33
33
  */
34
34
  phase?: number;
35
+ /** Host/tool cancellation propagated to optional external scanners. */
36
+ abort_signal?: AbortSignal;
35
37
  }
36
38
  export interface SastScanResult {
37
39
  /** Overall verdict: pass if no findings above threshold, fail otherwise */
@@ -101,6 +103,7 @@ export declare const sast_scan: ToolDefinition;
101
103
  export declare const _internals: {
102
104
  sastScan: typeof sastScan;
103
105
  sast_scan: typeof sast_scan;
106
+ checkSemgrepAvailable: typeof checkSemgrepAvailable;
104
107
  isSemgrepAvailable: typeof isSemgrepAvailable;
105
108
  runSemgrep: typeof runSemgrep;
106
109
  };
@@ -67,10 +67,10 @@ export declare const _internals: {
67
67
  now: () => number;
68
68
  yieldToEventLoop: typeof yieldToEventLoop;
69
69
  openFile: typeof fs.openSync;
70
- fstatFile: typeof fs.fstatSync;
70
+ fstatFile: (fd: number) => fs.BigIntStats;
71
71
  readFileChunk: typeof fs.readSync;
72
72
  closeFile: typeof fs.closeSync;
73
- lstatFile: typeof fs.lstatSync;
73
+ lstatFile: (path: fs.PathLike) => fs.BigIntStats;
74
74
  closeDirectory: (directory: fs.Dir) => void;
75
75
  };
76
76
  export {};
@@ -2,11 +2,12 @@
2
2
  * Tool to configure the QA gate profile for the current plan.
3
3
  *
4
4
  * Architect-only: invoked during the QA GATE SELECTION phase of brainstorm
5
- * mode (or equivalent). Ratchet-tighter only cannot disable gates that are
6
- * already enabled. Rejects all writes once the profile is locked.
5
+ * mode (or equivalent). The initial selection may override defaults; after a
6
+ * profile exists, updates are ratchet-tighter and cannot disable enabled gates.
7
+ * Rejects all writes once the profile is locked.
7
8
  *
8
- * Creates the profile with defaults if missing, then applies the requested
9
- * partial update.
9
+ * Creates the profile with defaults plus the initial selection if missing,
10
+ * then always applies the requested partial update through the normal ratchet.
10
11
  */
11
12
  import type { tool } from '@opencode-ai/plugin';
12
13
  export interface SetQaGatesArgs {
@@ -24,6 +24,7 @@
24
24
  * - `bunHash` uses Node's `xxhash` via `Bun.hash`'s default algorithm when
25
25
  * present and falls back to a stable djb2-derived 32-bit hash on Node.
26
26
  */
27
+ import { spawn as nodeSpawn } from 'node:child_process';
27
28
  /**
28
29
  * Whether the current runtime is Bun. Cached at first call — every subsequent
29
30
  * call is a single property access.
@@ -86,6 +87,8 @@ export interface BunCompatSpawnOptions {
86
87
  stdout?: 'inherit' | 'ignore' | 'pipe';
87
88
  stderr?: 'inherit' | 'ignore' | 'pipe';
88
89
  timeout?: number;
90
+ /** Preserve the caller's exact Windows argv quoting (required for cmd.exe /c). */
91
+ windowsVerbatimArguments?: boolean;
89
92
  /**
90
93
  * When true, spawn the child as its own process-group leader (Node path:
91
94
  * `detached`) and kill the entire descendant tree on `kill()`/timeout
@@ -113,8 +116,24 @@ export interface BunCompatSubprocess {
113
116
  readonly stderr: BunCompatStream;
114
117
  readonly exited: Promise<number>;
115
118
  exitCode: number | null;
119
+ readonly signalCode?: NodeJS.Signals | null;
120
+ /**
121
+ * Asynchronous process-creation failure reported by Node's ChildProcess
122
+ * `error` event (for example ENOENT). Bun reports equivalent failures by
123
+ * throwing from `Bun.spawn`, so this is populated only by the Node fallback.
124
+ */
125
+ readonly spawnError?: Error | null;
116
126
  kill(signal?: NodeJS.Signals | number): void;
127
+ /** Awaitable best-effort descendant-tree termination for bounded runners. */
128
+ killTree?(signal?: NodeJS.Signals | number): Promise<void>;
117
129
  }
130
+ declare function createProcessTreeKiller(pid: number | undefined, directKill: (signal?: NodeJS.Signals | number) => void, wasDetached: boolean): (signal?: NodeJS.Signals | number) => Promise<void>;
131
+ export declare const _internals: {
132
+ platform: () => NodeJS.Platform;
133
+ cwd: () => string;
134
+ spawnTaskkill: (args: string[], options: Parameters<typeof nodeSpawn>[2]) => import("node:child_process").ChildProcess;
135
+ createProcessTreeKiller: typeof createProcessTreeKiller;
136
+ };
118
137
  /**
119
138
  * Merges `envOverrides` into the base env (or `process.env` if no base env
120
139
  * is given) and returns the resulting record.
@@ -150,3 +169,4 @@ export declare function bunSpawnSync(cmd: string[] | {
150
169
  stdin?: string | Uint8Array;
151
170
  timeout?: number;
152
171
  }, options?: BunCompatSpawnOptions): BunCompatSyncResult;
172
+ export {};
@@ -8,6 +8,8 @@ export interface ExternalToolRunOptions {
8
8
  maxStderrBytes: number;
9
9
  env?: Record<string, string | undefined>;
10
10
  abortSignal?: AbortSignal;
11
+ /** Preserve an already-quoted Windows command line, such as constrained cmd.exe /c. */
12
+ windowsVerbatimArguments?: boolean;
11
13
  }
12
14
  export interface ExternalToolRunResult {
13
15
  status: 'completed' | 'timeout' | 'cancelled' | 'spawn-error';
@@ -19,8 +21,16 @@ export interface ExternalToolRunResult {
19
21
  message?: string;
20
22
  }
21
23
  export declare function resolveExecutableFromPath(names: string[], envPath?: string, platform?: NodeJS.Platform): string | null;
24
+ declare function clampTimeoutMs(timeoutMs: number): number;
25
+ declare function computeSpawnTimeoutMs(timeoutMs: number): number;
22
26
  export declare function runExternalTool(options: ExternalToolRunOptions): Promise<ExternalToolRunResult>;
23
27
  export declare const _internals: {
24
28
  bunSpawn: typeof bunSpawn;
25
29
  platform: () => NodeJS.Platform;
30
+ now: () => number;
31
+ setTimeout: typeof setTimeout;
32
+ clearTimeout: typeof clearTimeout;
33
+ clampTimeoutMs: typeof clampTimeoutMs;
34
+ computeSpawnTimeoutMs: typeof computeSpawnTimeoutMs;
26
35
  };
36
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.139.2",
3
+ "version": "7.139.3",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",