opencode-swarm 7.160.1 → 7.161.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.
Files changed (94) hide show
  1. package/.opencode/skills/critic-gate/SKILL.md +8 -1
  2. package/.opencode/skills/execute/SKILL.md +4 -3
  3. package/.opencode/skills/phase-wrap/SKILL.md +2 -0
  4. package/.opencode/skills/running-tests/SKILL.md +6 -0
  5. package/.opencode/skills/swarm-implement/SKILL.md +2 -0
  6. package/.opencode/skills/swarm-pr-review/SKILL.md +1 -0
  7. package/dist/agents/architect.d.ts +1 -1
  8. package/dist/background/delegation-lifecycle.d.ts +374 -0
  9. package/dist/background/pending-delegations.d.ts +6 -0
  10. package/dist/build/command-resolution.d.ts +13 -0
  11. package/dist/build/discovery.d.ts +11 -4
  12. package/dist/cli/{coder-settlement-678ccmnf.js → coder-settlement-nayk1bbf.js} +8 -8
  13. package/dist/cli/{config-doctor-fr8dfrt2.js → config-doctor-b1w7yvcs.js} +2 -2
  14. package/dist/cli/{core-4j1hbhey.js → core-whesnhpf.js} +1 -1
  15. package/dist/cli/{curation-policy-47przjwg.js → curation-policy-ej9xc0cd.js} +5 -5
  16. package/dist/cli/{curator-85d97240.js → curator-1rzb285s.js} +31 -31
  17. package/dist/cli/{curator-llm-factory-s9s4s0q3.js → curator-llm-factory-bqfkaq6y.js} +31 -31
  18. package/dist/cli/{dispatch-7r46tfr9.js → dispatch-q4v8x2pk.js} +1 -1
  19. package/dist/cli/{evidence-summary-service-9dyt2kzh.js → evidence-summary-service-ma8drpv5.js} +13 -13
  20. package/dist/cli/{gate-evidence-a6paafg8.js → gate-evidence-7eh82zs0.js} +5 -5
  21. package/dist/cli/{guardrail-explain-4a2xdtew.js → guardrail-explain-em2mqz9a.js} +32 -32
  22. package/dist/cli/{guardrail-log-27nkntrx.js → guardrail-log-845tfrwb.js} +6 -6
  23. package/dist/cli/{guardrail-reset-xn7k3hn8.js → guardrail-reset-091xw53a.js} +31 -31
  24. package/dist/cli/{hive-promoter-k57j3kp6.js → hive-promoter-kx8t5em3.js} +31 -31
  25. package/dist/cli/{index-zpzvpybn.js → index-14dgnwmh.js} +2 -2
  26. package/dist/cli/{index-nj6rmyeh.js → index-3146wmgv.js} +352 -149
  27. package/dist/cli/{index-wccdm7jv.js → index-3cxfqdpv.js} +4 -4
  28. package/dist/cli/{index-e1ygmd4q.js → index-52f67bbh.js} +33 -33
  29. package/dist/cli/{index-gjx53y3c.js → index-54jznpq7.js} +1 -1
  30. package/dist/cli/{index-7h2armwf.js → index-7619tk9a.js} +2 -2
  31. package/dist/cli/{index-7v0ex0jt.js → index-7qera0f6.js} +2 -2
  32. package/dist/cli/{index-v8dsmee1.js → index-93pf824c.js} +3 -3
  33. package/dist/cli/{index-drqvph47.js → index-ambz0ryy.js} +4 -4
  34. package/dist/cli/{index-prv20xq6.js → index-c6zyqgpq.js} +1 -1
  35. package/dist/cli/{index-nnzdayvk.js → index-d1h4sf8f.js} +6 -6
  36. package/dist/cli/{index-7ezv5j23.js → index-d8st72j7.js} +1 -1
  37. package/dist/cli/{index-nk498mx6.js → index-dq41vsf9.js} +2 -2
  38. package/dist/cli/{index-m84z3aa8.js → index-et1jpj6z.js} +3 -3
  39. package/dist/cli/{index-bqxffayb.js → index-etdtfhhv.js} +1 -1
  40. package/dist/cli/{index-m1cw9fsh.js → index-f4dmccpd.js} +8 -8
  41. package/dist/cli/{index-znf683sm.js → index-f5axf6vq.js} +1 -1
  42. package/dist/cli/{index-dke4wzkn.js → index-fx3em0dr.js} +17 -1
  43. package/dist/cli/{index-98jmggma.js → index-gkv7s4xy.js} +8 -8
  44. package/dist/cli/{index-f72612zc.js → index-h6wtxzvm.js} +1 -1
  45. package/dist/cli/{index-mmr96pwr.js → index-k55td019.js} +1 -1
  46. package/dist/cli/{index-61c3d769.js → index-mbktm1ah.js} +7 -4
  47. package/dist/cli/{index-jszabr0r.js → index-mj6ndvee.js} +4 -4
  48. package/dist/cli/{index-b5adxpy6.js → index-nqbqkx4c.js} +1 -1
  49. package/dist/cli/{index-8c9qzpy7.js → index-q8hm27yj.js} +7 -1
  50. package/dist/cli/{index-zdv914xa.js → index-qjge2m9r.js} +81 -74
  51. package/dist/cli/{index-j3ss86kf.js → index-s33ee2w3.js} +1 -1
  52. package/dist/cli/{index-z0bxbb9g.js → index-t81fmfjj.js} +4 -4
  53. package/dist/cli/{index-pkz91fwm.js → index-trmqq4wm.js} +2 -2
  54. package/dist/cli/{index-dq93ar40.js → index-w9g58t3j.js} +5 -5
  55. package/dist/cli/{index-ny0g8fdg.js → index-wrtgyvre.js} +5 -5
  56. package/dist/cli/{index-qc53dfzh.js → index-xy0e0r5f.js} +1 -1
  57. package/dist/cli/{index-pqjcyj0p.js → index-y0ra2v4w.js} +3 -3
  58. package/dist/cli/{index-497a3hhj.js → index-y14mwbk8.js} +3 -3
  59. package/dist/cli/{index-7npz9n4s.js → index-zkgrayya.js} +1 -1
  60. package/dist/cli/index.js +31 -31
  61. package/dist/cli/{knowledge-escalator-3q2x34cg.js → knowledge-escalator-9eqpsbw5.js} +12 -12
  62. package/dist/cli/{knowledge-events-5y34w84b.js → knowledge-events-a8r4ej12.js} +10 -10
  63. package/dist/cli/{knowledge-link-xezm8hr3.js → knowledge-link-8knefv8x.js} +4 -4
  64. package/dist/cli/{knowledge-store-fqv89q5k.js → knowledge-store-ydzeppv7.js} +5 -5
  65. package/dist/cli/{knowledge-validator-fsdx14tk.js → knowledge-validator-mnv58he9.js} +7 -7
  66. package/dist/cli/{pending-delegations-6ae7wy5q.js → pending-delegations-9mwmrwwm.js} +6 -4
  67. package/dist/cli/{pr-review-reentry-authorization-986tk381.js → pr-review-reentry-authorization-h1ssfq37.js} +31 -31
  68. package/dist/cli/{pr-subscriptions-asjevc5p.js → pr-subscriptions-3zwthnan.js} +4 -4
  69. package/dist/cli/{runner-wsdcy2d6.js → runner-fzgkd6m1.js} +5 -5
  70. package/dist/cli/{scan-cursor-shn926r6.js → scan-cursor-5rmaqw2t.js} +6 -6
  71. package/dist/cli/{schema-98tdb9h0.js → schema-72vpr9xy.js} +1 -1
  72. package/dist/cli/{scope-persistence-cn34xnwr.js → scope-persistence-39mwns79.js} +8 -8
  73. package/dist/cli/{skill-generator-kck7087c.js → skill-generator-9aay0t53.js} +14 -14
  74. package/dist/cli/{telemetry-gpr9s3sm.js → telemetry-cmbjv240.js} +1 -1
  75. package/dist/cli/{worktree-collision-ownership-6b3kshcv.js → worktree-collision-ownership-ybhktnq4.js} +4 -4
  76. package/dist/cli/{worktree-isolation-he01qhss.js → worktree-isolation-w8ett6bs.js} +31 -31
  77. package/dist/config/evidence-schema.d.ts +20 -2
  78. package/dist/config/schema.d.ts +7 -7
  79. package/dist/hooks/delegate-ack-collector.d.ts +74 -0
  80. package/dist/hooks/knowledge-injector.d.ts +12 -1
  81. package/dist/index.js +280 -279
  82. package/dist/lang/default-backend.d.ts +1 -13
  83. package/dist/learning/admission.d.ts +2 -2
  84. package/dist/memory/schema.d.ts +3 -3
  85. package/dist/observability/catalog.d.ts +2 -2
  86. package/dist/prm/types.d.ts +9 -0
  87. package/dist/telemetry.d.ts +9 -1
  88. package/dist/tools/build-check.d.ts +4 -0
  89. package/dist/tools/repo-graph/retrieval-router.d.ts +57 -0
  90. package/dist/tools/repo-graph.d.ts +2 -0
  91. package/dist/tools/repo-map.d.ts +202 -0
  92. package/dist/tools/search.d.ts +2 -2
  93. package/dist/tools/test-runner.d.ts +2 -0
  94. package/package.json +1 -1
@@ -41,13 +41,20 @@ one-line `reason` (or ask the user to run `/swarm approve-plan-critic
41
41
  <reason>`). This records a manual `plan_critic_gate` approval snapshot
42
42
  tagged `method: "manual_override"`, audited to `.swarm/events.jsonl`.
43
43
  Architect-only. Use ONLY when a legitimate APPROVED was lost — this is an
44
- escape hatch, not a substitute for running the critic review.
44
+ escape hatch, not a substitute for running the critic review. It is also the sanctioned recovery for a bookkeeping-grade hashed-field repair under PLAN FREEZE below.
45
45
 
46
46
  CRITIC-GATE TRIGGER: Run ONCE when you first write the complete .swarm/plan.md.
47
47
  Do NOT re-run CRITIC-GATE before every project phase.
48
48
  If resuming a project with an existing approved plan, CRITIC-GATE is already satisfied.
49
49
  Caveat: this assumption breaks if the plan lacks a `plan_critic_gate`-tagged approval snapshot (e.g. a plan approved before this mechanical gate existed, or one where the recording heuristic didn't fire) — in that case the first coder dispatch will fail with `PLAN_CRITIC_GATE_VIOLATION`. If that happens, do not assume CRITIC-GATE is satisfied; re-run it and get a fresh APPROVED verdict.
50
50
 
51
+ PLAN FREEZE AFTER APPROVAL (issue #1994 P1): once the critic returns APPROVED, the plan is frozen. The coder dispatch gate compares the plan against the approval snapshot via the structure hash (`computePlanStructureHash`), so classify post-approval changes by what that hash actually covers:
52
+ - STATUS-ONLY changes (task status transitions via `update_task_status`) are excluded from the hash and never invalidate the approval — no re-critic needed.
53
+ - MATERIAL (invalidates the approval): adding or removing tasks; changing any task's `id`, `phase`, `description`, `acceptance`, or `depends`; or enumerating `removed_task_ids` on a re-save. Re-run MODE: CRITIC-GATE exactly ONCE on the revised plan and get a fresh APPROVED before the next coder dispatch — the dispatch fails `PLAN_CRITIC_GATE_VIOLATION` against the stale snapshot otherwise.
54
+ - `fr_refs` changes are MATERIAL on process grounds (spec traceability feeds the critic's obligation check) even though the hash deliberately excludes `fr_refs` — the runtime will not catch this for you; re-critic is still required.
55
+ - BOOKKEEPING-GRADE hashed fields (`size`, `evidence_path`, `blocked_reason`, `title`, `current_phase`, `files_touched`) trip the gate mechanically even for pure bookkeeping edits. For a genuine bookkeeping repair — most commonly a `files_touched`-only reconciliation aligned with an active `declare_scope` binding (the sanctioned `SCOPE_CONFLICT` repair path in the execute skill) — use the gate's own recovery: `approve_plan_critic` with a truthful one-line reason (audited to `.swarm/events.jsonl`), not a full re-critic. Any substantive scope growth beyond reconciliation is MATERIAL: re-critic.
56
+ Batching rule: material changes accumulated across multiple `save_plan` calls since the last APPROVED count as ONE batch — re-critic that batch once, and never split material changes across separate calls to dodge the re-critic. The pre-change approval is never valid for the changed plan.
57
+
51
58
  6j. SPEC-GATE (Execute BEFORE any save_plan call):
52
59
  - An effective spec exists iff `/swarm sdd status` reports a resolved spec (it reflects `readEffectiveSpecSync`, which returns null — NO effective spec — for no sources, multiple competing sources (openspec+specify), multi-feature Spec-Kit without a selected feature, or any unresolvable state). `save_plan` rejects (SPEC_REQUIRED) when `/swarm sdd status` reports no resolved spec. The gate is overridable via `SWARM_SKIP_SPEC_GATE=1`.
53
60
  - Before calling save_plan, verify an effective spec exists (via `/swarm sdd status` or `lint_spec`).
@@ -73,8 +73,9 @@ All other gates: failure → return to coder. No self-fixes. No workarounds.
73
73
  → REQUIRED: Print "diff: [PASS | CONTRACT CHANGE — details]"
74
74
  5d. Run `syntax_check` tool. SYNTACTIC ERRORS → return to coder. NO ERRORS → proceed to placeholder_scan.
75
75
  → REQUIRED: Print "syntaxcheck: [PASS | FAIL — N errors]"
76
- 5e. Run `placeholder_scan` tool. PLACEHOLDER FINDINGS → return to coder. NO FINDINGS → proceed to imports.
77
- REQUIRED: Print "placeholderscan: [PASS | FAILN findings]"
76
+ 5e. Run `placeholder_scan` tool WITH DIFF SCOPING: pass `added_lines` — a map of workspace-relative file path → the line numbers ADDED by this task's uncommitted work (from `git diff -U0 HEAD -- <task files>` for tracked files; the coder's edits are not committed yet, so a commit-range diff like `<base>...HEAD` returns zero added lines here) — so only task-added lines drive the verdict. PLACEHOLDER FINDINGS (on added lines) → return to coder. NO FINDINGS → proceed to imports.
77
+ Diff-scope fallback: for a file whose added lines you cannot map (new/untracked file, no diff available) or whose computed added-line set is EMPTY, OMIT that file from `added_lines` entirely the tool then scans it unfiltered (fail-closed) — and manually cross-check that file's findings against the changed lines before returning to coder. NEVER pass an empty line array for a file (an empty array suppresses every finding in it) and NEVER hand-enumerate guessed line numbers (a wrong map silently suppresses findings). A pre-existing TODO/FIXME on an unchanged line is existing debt to surface to the reviewer, not a coder bounce.
78
+ → REQUIRED: Print "placeholderscan: [PASS | FAIL — N findings (diff-scoped) | FAIL — N findings (unscoped — cross-checked against changed lines)]"
78
79
  5f. Run `imports` tool for dependency audit. ISSUES → return to coder.
79
80
  → REQUIRED: Print "imports: [PASS | ISSUES — details]"
80
81
  5g. Run `lint` tool with fix mode for auto-fixes. If issues remain → run `lint` tool with check mode. FAIL → return to coder.
@@ -193,7 +194,7 @@ This step supplements (not replaces) the existing regression-sweep and test-drif
193
194
  5o. ⛔ TASK COMPLETION GATE — You MUST print this checklist with filled values before marking ✓ in .swarm/plan.md:
194
195
  [TOOL] diff: PASS / SKIP — value: ___
195
196
  [TOOL] syntax_check: PASS — value: ___
196
- [TOOL] placeholder_scan: PASS — value: ___
197
+ [TOOL] placeholder_scan: PASS — value: ___ (diff-scoped | unscoped — cross-checked)
197
198
  [TOOL] imports: PASS — value: ___
198
199
  [TOOL] lint: PASS — value: ___
199
200
  [TOOL] build_check: PASS / SKIPPED — value: ___
@@ -105,6 +105,8 @@ If any required agent is missing, `phase_complete` returns `{ success: false, st
105
105
 
106
106
  The `docs` agent requirement is controlled by `phase_complete.require_docs` in plugin configuration, not by the QA gate profile returned by `get_qa_gate_profile`. It defaults to `true`. Set it to `false` only when the phase genuinely has no documentation obligation. A successful docs completion is persisted as plan- and phase-bound participation proof so `phase_complete` can recover it after a session restart; unrelated task-gate evidence does not count as docs participation.
107
107
 
108
+ Docs-attestation integrity (issue #1994 P2): when the docs agent concludes that a phase — especially a doc-only phase — needs no further documentation edits, that attestation is valid ONLY if the docs agent actually inspected the phase's changed files and recorded what it checked (files read, per-file verdict) in its completion report. A mechanical "no edits needed" reply dispatched only to satisfy `agentsDispatched` — without inspecting the phase's changes — is a process violation: the gate exists to catch documentation drift, not to be acknowledged past.
109
+
108
110
  The `coder` and `test_engineer` agents are required because every phase that modifies source code or tests must have at least one implementation and one test-verification delegation. For pure documentation or retrospective phases, these may be waived by the user explicitly.
109
111
 
110
112
  This is a hard enforcement mechanism, not a suggestion. `phase_complete` will not return `status: success` if any required agent is missing from `agentsDispatched`.
@@ -232,6 +232,12 @@ git worktree remove "$env:TEMP\repro-check"
232
232
 
233
233
  ---
234
234
 
235
+ ## Placeholder Scans Without Diff Line Numbers
236
+
237
+ `placeholder_scan` is diff-aware: when you can supply `added_lines` (a map of workspace-relative file path → added line numbers from the task/PR diff), its verdict covers only the added lines, so a pre-existing TODO/FIXME on an unchanged line inside a changed file no longer fails the gate. When you CANNOT map a file's added lines (new/untracked file, no diff access) — or the computed added-line set is EMPTY — omit that file from `added_lines` entirely: the tool scans it unfiltered, fail-closed, and you manually cross-check that file's findings against the changed lines before treating a finding as introduced by the change. NEVER pass an empty line array for a file (an empty array suppresses every finding in it) and NEVER hand-enumerate guessed line numbers: a wrong `added_lines` map silently suppresses findings.
238
+
239
+ ---
240
+
235
241
  ## Failure Classification
236
242
 
237
243
  Not all failures are equal. Before deciding what to do, classify the failure:
@@ -117,6 +117,8 @@ Implement in coherent, reviewable chunks. Follow existing repository patterns.
117
117
 
118
118
  **`declare_scope` discipline.** Before every coder or test-engineer delegation (and before every retry), call `declare_scope({ taskId, files, replace_existing: true })` with the exact workspace-relative file list the delegated agent may modify — including generated or lockfile paths the change will produce (for example, `dist/*`, `package-lock.json`, or `bun.lock`). Direct and shell writes are both scope-enforced. If the file list is not completely obvious, declare the containing workspace-relative directories instead. Treat `SCOPE_CONFLICT`, `SCOPE_BINDING_EXPIRED`, `SCOPE_BINDING_AMBIGUOUS`, and `SCOPE_WORKSPACE_MISMATCH` as architect-owned recovery signals: reconcile the named scope sources or lane root, replace the declaration, then redispatch. Never instruct the delegated agent to bypass the gate or switch write mechanisms.
119
119
 
120
+ **Smallest justified scope on `SCOPE_CONFLICT` (issue #1994 S4).** The replacement declaration must be the SMALLEST justified scope: first reconcile what the delegation actually intends to write (the task's files vs the conflicting scope source named in the diagnostic), then narrow the conflicting lower-precedence source to a subset of that list — repair stale plan scope with `save_plan` and/or the delegation's `FILE:` lines — and only then re-declare exactly that file list; re-declaring alone leaves the non-subset relation and reproduces the same `SCOPE_CONFLICT`. Split the task into separate delegations if the conflict reveals two disjoint write sets. NEVER resolve a `SCOPE_CONFLICT` by widening to a plan-inferred superset: auto-widening over-authorizes writes and hides an inaccurate plan or delegation prompt behind a bigger scope. This matches the execute skill's scope-recovery contract ("Do not widen scope just to make the sets agree"); a `save_plan` scope repair is a bookkeeping change covered by the critic-gate plan-freeze rule.
121
+
120
122
  If the project exposes `sast_scan` with `capture_baseline`, capture the
121
123
  phase-scoped SAST baseline before first coder delegation so later scans fail only
122
124
  on new findings rather than pre-existing infrastructure noise.
@@ -832,6 +832,7 @@ Tool candidate rules:
832
832
 
833
833
  - Confirm reachability before reporting.
834
834
  - Confirm PR-introducedness before reporting as a PR blocker.
835
+ - When `placeholder_scan` output is used as a signal, pass `added_lines` (file path → PR-added line numbers from the merge-base diff) so only added lines drive the scan; a placeholder finding on an unchanged line is a pre-existing-debt candidate, not a PR blocker. If added-line mapping is unavailable, treat the findings as unscoped and cross-check them against the PR diff before recording the row.
835
836
  - Confirm that a framework, schema, middleware, caller guard, or test isolation rule does not already mitigate it.
836
837
  - Do not report scanner output verbatim without reviewer validation.
837
838
  - Redact secrets; never paste raw credentials into the final output.
@@ -20,7 +20,7 @@ export declare const ARCHITECT_MEMORY_OUTCOME_GUIDANCE = "After using recalled m
20
20
  * 5. Memory Rule (line ~101): Never store swarm identity in memory blocks
21
21
  * 6. CRITIC GATE (lines ~102-107): Plan review before implementation
22
22
  * 7. MANDATORY QA GATE (lines ~108-165):
23
- * - Stage A: Automated tool gates (diff → syntax_check → placeholder_scan → imports → lint → build_check → pre_check_batch)
23
+ * - Stage A: Automated tool gates (diff → syntax_check → placeholder_scan (diff-scoped via added_lines) → imports → lint → build_check → pre_check_batch)
24
24
  * - Stage B: Agent review gates (reviewer → security reviewer → test_engineer)
25
25
  * - ANTI-EXEMPTION RULES: 8 "WRONG thoughts" to ignore
26
26
  * - PARTIAL GATE RATIONALIZATIONS: 6 "WRONG thoughts" to ignore
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Shared Task/lane delegation lifecycle operations (issue #2045).
3
+ *
4
+ * Task-tool delegations and `dispatch_lanes` lanes both persist their state in
5
+ * the background-delegations ledger and both record their start through
6
+ * `recordPendingDelegationDetailed`; before issue #2045 only the Task side
7
+ * settled terminals through `claimTerminalResult`, leaving lanes with the
8
+ * weaker status-only `appendDelegationTransition` write. This module gives
9
+ * lanes the same exactly-once terminal semantics — `settleDelegationTerminal`
10
+ * wraps the SAME `claimTerminalResult` primitive the Task completion observer
11
+ * calls directly (one claim implementation, no second lifecycle state machine)
12
+ * — and emits the lane-side terminal observations the Task side produces
13
+ * through its `tool.execute.*` hooks (cost telemetry, trajectory, knowledge
14
+ * receipt reconciliation), so equivalent Task and lane dispatches leave
15
+ * equivalent lifecycle facts behind.
16
+ *
17
+ * Every observation is fail-open and split into two durability classes — see
18
+ * the `settleDelegationTerminal` docstring for the contract: AUTHORITATIVE
19
+ * ledger-committed receipts re-run on `duplicate` replays (crash recovery),
20
+ * while DIAGNOSTIC observations run only on `claimed` (exactly-once-at-emit).
21
+ */
22
+ import { type PricingConfig } from '../services/cost-accounting.js';
23
+ import { type BackgroundDelegationRecord, type BackgroundDelegationResult, type BackgroundTerminalResult, claimTerminalResult, findByCorrelationId } from './pending-delegations.js';
24
+ /** Terminal statuses a shared settle may establish (mirrors Task semantics). */
25
+ export type DelegationTerminalStatus = 'completed' | 'error' | 'cancelled';
26
+ /**
27
+ * Why `settleDelegationTerminal` returned without a fresh claim.
28
+ *
29
+ * `already_terminal_without_event` is deliberately distinct from `conflict`:
30
+ * a record terminalized WITHOUT an immutable event (the 30-minute stale sweep
31
+ * or a legacy status-only writer won the race) is a benign outcome that must
32
+ * not tick the late-terminal audit — the audit belongs to the writer that
33
+ * owns the conflicting *event*, and there is none.
34
+ */
35
+ export type DelegationSettleKind = 'claimed' | 'duplicate' | 'conflict' | 'already_terminal_without_event' | 'not_open' | 'missing';
36
+ export interface DelegationSettleOutcome {
37
+ kind: DelegationSettleKind;
38
+ record?: BackgroundDelegationRecord;
39
+ }
40
+ /**
41
+ * Settle outcomes that mean "the work item is already durably terminal; the
42
+ * caller's benign already-terminal handling applies" (no retry, no failure
43
+ * diagnostic — the race was routine).
44
+ */
45
+ export declare function isBenignSettleOutcome(kind: DelegationSettleKind): boolean;
46
+ export interface DelegationTerminalInput {
47
+ status: DelegationTerminalStatus;
48
+ result: BackgroundDelegationResult;
49
+ }
50
+ export interface DelegationObservationInput {
51
+ /** Full lane/delegate transcript text; drives knowledge reconciliation. */
52
+ transcript?: string;
53
+ /** Raw cost-evidence source (assistant info/metadata), when available. */
54
+ costRaw?: unknown;
55
+ /** Configured model for cost projection fallback. */
56
+ model?: string;
57
+ /** Pricing config for normalized cost estimation. */
58
+ pricing?: PricingConfig;
59
+ /** Wall-clock dispatch start (ms epoch), for trajectory elapsed_ms. */
60
+ startedAt?: number;
61
+ }
62
+ /**
63
+ * Build the immutable terminal event for a delegation record. The eventId is
64
+ * derived from trusted correlation + immutable result metadata (no timestamps,
65
+ * no process state), so a replayed observation of the same result claims
66
+ * idempotently and a different result is a distinct, auditable event.
67
+ */
68
+ export declare function buildDelegationTerminal(record: Pick<BackgroundDelegationRecord, 'correlationId' | 'jobId'>, terminal: DelegationTerminalInput, now: number): BackgroundTerminalResult;
69
+ /**
70
+ * Settle a delegation terminal exactly once through the shared claim, then —
71
+ * only on a fresh `claimed` disposition — run the terminal observations.
72
+ *
73
+ * Durability model (final-critic challenge, crash between claim and
74
+ * observations):
75
+ * - The AUTHORITATIVE receipts (validator/ledger-committed delegate ACK
76
+ * terminals, unacknowledged-critical violations, and reviewer verdicts) are
77
+ * re-run on `duplicate` replays: the receipt ledger's terminal authority is
78
+ * idempotent (same-outcome replays are `idempotent_skips`), so a replay
79
+ * after a crash closes the receipts exactly once — a lane whose observation
80
+ * pass died mid-flight recovers on any later settle replay (restart,
81
+ * concurrent collector).
82
+ * - The DIAGNOSTIC observations are exactly-once-at-emit on `claimed` only —
83
+ * they have no deduplicating sink, and re-emitting them on replay would
84
+ * duplicate records. This covers cost telemetry, trajectory, AND the
85
+ * audit-only non-critical `unacknowledged` knowledge observation (which
86
+ * bypasses the receipt ledger). It is the same crash window the Task
87
+ * transport's `tool.execute.*` hook emissions have always had (documented
88
+ * transport parity, not a regression).
89
+ *
90
+ * See {@link DelegationSettleKind} for the non-claim outcomes; callers map
91
+ * `duplicate`/`conflict`/`already_terminal_without_event` to their benign
92
+ * already-terminal handling and `missing`/`not_open` to their
93
+ * settle-failure diagnostics.
94
+ */
95
+ export declare function settleDelegationTerminal(directory: string, record: BackgroundDelegationRecord, terminal: DelegationTerminalInput, observations?: DelegationObservationInput, now?: number): Promise<DelegationSettleOutcome>;
96
+ /**
97
+ * Emit the `delegation_begin` pairing observation when a lane/delegation start
98
+ * record lands (issue #2045 Task/lane cost-event parity). Best-effort.
99
+ */
100
+ export declare function emitDelegationBegin(record: Pick<BackgroundDelegationRecord, 'parentSessionId' | 'swarmPrefixedAgent' | 'planTaskId'>): void;
101
+ /**
102
+ * Canonical cost-record identity material for a lane/delegation terminal.
103
+ * Joining back to the delegation record uses exactly these record fields —
104
+ * never agent-name matching. The `lane:` discriminator keeps lane identities
105
+ * disjoint from Task-tool records, whose material is `${sessionId}\0${callID}`.
106
+ */
107
+ export declare function delegationCostRecordMaterial(record: Pick<BackgroundDelegationRecord, 'parentSessionId' | 'callID' | 'laneId'>): string;
108
+ /** Bounded per-pass cap for terminal-lane receipt recovery (crash window). */
109
+ export declare const MAX_TERMINAL_LANE_RECEIPT_RECOVERY = 64;
110
+ /**
111
+ * Pre-record admission deadline for the directory-wide recovery pass
112
+ * (session-close hook): checked before EACH reconciliation, bounding how many
113
+ * records a pass may START — not the enumeration read/sort or an
114
+ * already-started reconciliation.
115
+ */
116
+ export declare const TERMINAL_LANE_RECEIPT_RECOVERY_DEADLINE_MS = 5000;
117
+ export interface TerminalLaneReceiptRecoveryResult {
118
+ /** Terminal lane records whose receipt reconciliation replay was attempted. */
119
+ recovered: number;
120
+ /** True when the pass ended before every candidate was attempted. */
121
+ exhaustedBudget: boolean;
122
+ }
123
+ export interface RecoverTerminalLaneReceiptsOptions {
124
+ /**
125
+ * Admission budget for STARTING a reconciliation. Default 5s; the pass
126
+ * stops cleanly before the next record when exhausted. Does not bound the
127
+ * enumeration or an in-flight reconciliation.
128
+ */
129
+ deadlineMs?: number;
130
+ /** Test seam for the clock. */
131
+ now?: () => number;
132
+ }
133
+ /**
134
+ * Durable crash-recovery pass for terminal lane records (final-critic
135
+ * challenge): a lane whose terminal claim landed but whose observation pass
136
+ * died is TERMINAL, so the collector's active-record filter skips it forever —
137
+ * the duplicate-replay path in {@link settleDelegationTerminal} would never be
138
+ * entered again on its own. This pass re-enters it: the durable transcript
139
+ * (`terminalResult.result.text`, persisted by the claim) is replayed through
140
+ * the ledger-idempotent receipt reconciliation in replay mode — authoritative
141
+ * receipts close exactly once; diagnostics never re-emit.
142
+ *
143
+ * Production wiring: (a) `collect_lane_results` runs it once per invocation
144
+ * over the batch's terminal records (the async-lane restart path — a batch is
145
+ * at most MAX_LANES records, so batch mode needs no cursor), and (b) the
146
+ * session-close maintenance trigger runs it directory-wide, which is what
147
+ * recovers BLOCKING lane records that have no collector.
148
+ *
149
+ * Forward progress (final-critic round 3): the directory-wide pass is bounded
150
+ * by a record cap and a pre-record admission deadline (checked before EACH
151
+ * reconciliation; it does not bound the candidate enumeration read/sort or an
152
+ * already-started reconciliation — those are bounded by the ledger read and
153
+ * the cap), and it persists an advancing
154
+ * cursor (`.swarm/lane-receipt-recovery-cursor.json`, ordered by
155
+ * `(updatedAt, correlationId)`), so each pass resumes after the last processed
156
+ * record and wraps to the oldest once the end is reached. Guarantees, stated
157
+ * exactly: under steady arrivals a candidate whose terminal commits with an
158
+ * ordering key OLDER than the cursor's position (a lock-contention straggler)
159
+ * is skipped until the next wrap — so a straggler is deferred, not lost, and
160
+ * an idle drain (a pass with nothing after the cursor) wraps immediately.
161
+ * Replays are ledger-idempotent, so a lost, stale, or wrapped cursor only
162
+ * causes harmless rework. Fail-open per record; never throws.
163
+ */
164
+ export declare function recoverTerminalLaneReceipts(directory: string, records?: readonly BackgroundDelegationRecord[], options?: RecoverTerminalLaneReceiptsOptions): Promise<TerminalLaneReceiptRecoveryResult>;
165
+ /**
166
+ * Test seam (repo convention): the telemetry sink and the claim are injectable
167
+ * so tests can stub them without `mock.module`.
168
+ */
169
+ export declare const _internals: {
170
+ claimTerminalResult: typeof claimTerminalResult;
171
+ findByCorrelationId: typeof findByCorrelationId;
172
+ telemetry: {
173
+ sessionStarted(sessionId: string, agentName: string): void;
174
+ sessionEnded(sessionId: string, reason: string): void;
175
+ agentActivated(sessionId: string, agentName: string, oldName?: string): void;
176
+ delegationBegin(sessionId: string, agentName: string, taskId: string): void;
177
+ delegationEnd(sessionId: string, agentName: string, taskId: string, result: string, costFields?: Partial<import("../services/cost-accounting.js").DelegationCostFields>): void;
178
+ taskStateChanged(sessionId: string, taskId: string, newState: string, oldState?: string): void;
179
+ gatePassed(sessionId: string, gate: string, taskId: string): void;
180
+ gateParseError(taskId: string, error: Error): void;
181
+ gateFailed(sessionId: string, gate: string, taskId: string, reason: string): void;
182
+ reviewerGateDecision(sessionId: string, taskId: string, blocked: boolean, reasonCode: import("../telemetry.js").ReviewerGateReasonCode, evidenceKind: import("../telemetry.js").ReviewerGateEvidenceKind): void;
183
+ phaseChanged(sessionId: string, oldPhase: number, newPhase: number): void;
184
+ budgetUpdated(sessionId: string, budgetPct: number, agentName: string): void;
185
+ contextPruned(data: {
186
+ sessionId: string;
187
+ agentName: string;
188
+ trigger: "agent_switch" | "critical_threshold";
189
+ usageSource: "provider" | "estimated";
190
+ beforeTokens: number;
191
+ afterTokens: number;
192
+ modelLimit: number;
193
+ maskedMessages: number;
194
+ maskedToolParts: number;
195
+ maskedTokensFreed: number;
196
+ prunedMessages: number;
197
+ prunedTextParts: number;
198
+ prunedToolParts: number;
199
+ prunedTokensFreed: number;
200
+ }): void;
201
+ modelFallback(sessionId: string, agentName: string, fromModel: string, toModel: string, reason: string): void;
202
+ modelUnresolved(agentName: string, model: string, detail: string): void;
203
+ hardLimitHit(sessionId: string, agentName: string, limitType: string, value: number): void;
204
+ revisionLimitHit(sessionId: string, agentName: string): void;
205
+ loopDetected(sessionId: string, agentName: string, loopType: string): void;
206
+ noOpStrongWarning(sessionId: string, agentName: string, count: number, threshold: number): void;
207
+ gateDenialLoop(sessionId: string, tool: string, code: string, count: number): void;
208
+ executionStallWarning(sessionId: string, count: number, threshold: number): void;
209
+ executionStallDenied(sessionId: string, tool: string, count: number, threshold: number): void;
210
+ swarmInternalsReadDenied(sessionId: string, tool: string, target: string): void;
211
+ scopeViolation(sessionId: string, agentName: string, file: string, reason: string): void;
212
+ qaSkipViolation(sessionId: string, agentName: string, skipCount: number): void;
213
+ heartbeat(sessionId: string): void;
214
+ turboModeChanged(sessionId: string, enabled: boolean, agentName: string): void;
215
+ autoOversightEscalation(sessionId: string, reason: string, interactionCount: number, deadlockCount: number, phase?: number): void;
216
+ environmentDetected(sessionId: string, hostOS: string, shellFamily: string, executionMode: string): void;
217
+ prmPatternDetected(sessionId: string, pattern: string, severity: string, category: string, stepRange: [number, number]): void;
218
+ prmCourseCorrectionInjected(sessionId: string, pattern: string, level: number): void;
219
+ prmEscalationTriggered(sessionId: string, pattern: string, level: number, occurrenceCount: number): void;
220
+ prmHardStop(sessionId: string, pattern: string, level: number, occurrenceCount: number): void;
221
+ closeArchiveResult(data: {
222
+ archive_valid: boolean;
223
+ archive_empty: boolean;
224
+ file_count: number;
225
+ bundle: string;
226
+ artifacts: Array<{
227
+ artifact: string;
228
+ requiredness: string;
229
+ attempt: string;
230
+ validation: string;
231
+ source_disposition: string;
232
+ method: string;
233
+ reason_code: string;
234
+ row_counts?: {
235
+ schema_migrations_max_version: number | null;
236
+ project_constraints: number;
237
+ qa_gate_profile: number;
238
+ };
239
+ }>;
240
+ }): void;
241
+ residueHealth(data: {
242
+ trigger: string;
243
+ scanned: number;
244
+ matched: number;
245
+ eligible: number;
246
+ ambiguous: number;
247
+ quarantined: number;
248
+ preserved: number;
249
+ total_bytes: number;
250
+ oldest_age_ms: number;
251
+ grammar_counts: Record<string, number>;
252
+ }): void;
253
+ contextTelemetryHealth(data: {
254
+ trigger: "compaction" | "close";
255
+ accepted_count: number;
256
+ compacted_count: number;
257
+ retained_count: number;
258
+ dropped_count: number;
259
+ corrupt_count: number;
260
+ oldest_timestamp: string | null;
261
+ newest_timestamp: string | null;
262
+ bytes: number;
263
+ limit_bytes: number;
264
+ }): void;
265
+ skillUsageHealth(data: {
266
+ trigger: "compaction" | "migration" | "consumption" | "pressure";
267
+ accepted: number;
268
+ compacted: number;
269
+ dropped: number;
270
+ skills_dropped: number;
271
+ corrupt: number;
272
+ pending_retained: number;
273
+ uncertain_retained: number;
274
+ uncertain_expired: number;
275
+ pending_evicted: number;
276
+ no_source_knowledge: number;
277
+ no_matching_knowledge: number;
278
+ bump_retry: number;
279
+ bump_unrecoverable: number;
280
+ bump_applied_zero: number;
281
+ pressure: number;
282
+ curator_skipped: number;
283
+ bytes: number;
284
+ limit_bytes: number;
285
+ oldest_timestamp: string | null;
286
+ newest_timestamp: string | null;
287
+ coverage: boolean;
288
+ }): void;
289
+ coreEventsHealth(data: {
290
+ trigger: "compaction" | "close";
291
+ accepted_count: number;
292
+ compacted_count: number;
293
+ retained_count: number;
294
+ dropped_count: number;
295
+ corrupt_count: number;
296
+ authority_index_count: number;
297
+ authority_evicted_count: number;
298
+ oldest_timestamp: string | null;
299
+ newest_timestamp: string | null;
300
+ bytes: number;
301
+ limit_bytes: number;
302
+ }): void;
303
+ shellAuditHealth(data: {
304
+ trigger: "compaction" | "close";
305
+ accepted_count: number;
306
+ compacted_count: number;
307
+ retained_count: number;
308
+ dropped_count: number;
309
+ corrupt_count: number;
310
+ oldest_timestamp: string | null;
311
+ newest_timestamp: string | null;
312
+ bytes: number;
313
+ limit_bytes: number;
314
+ }): void;
315
+ trajectoryHealth(data: {
316
+ trigger: "compaction" | "cleanup" | "append_skip";
317
+ retained_count: number;
318
+ dropped_count: number;
319
+ corrupt_count: number;
320
+ skipped_lock_count: number;
321
+ bytes: number;
322
+ limit_bytes: number;
323
+ }): void;
324
+ prSubscriptionHealth(data: {
325
+ trigger: "compact" | "migrate-complete" | "archive" | "foreign-rebind" | "corrupt-quarantine";
326
+ active_count: number;
327
+ terminal_count: number;
328
+ compactions: number;
329
+ corrupt_count: number;
330
+ dropped_audit_count: number;
331
+ recovery_resets: number;
332
+ checkpoint_bytes: number;
333
+ limit_bytes: number;
334
+ }): void;
335
+ learningHealthAlarm(data: {
336
+ alarm: string;
337
+ transition: "raised" | "sustained" | "recovered";
338
+ severity: "warning" | "critical";
339
+ scope_class: "session" | "identity" | "project";
340
+ session_ref?: string;
341
+ model?: string;
342
+ provider?: string;
343
+ window_ms: number;
344
+ coverage_facts: number;
345
+ raise_facts: number;
346
+ age_ms: number;
347
+ limit_source?: string;
348
+ denominator_fallback?: boolean;
349
+ pressure_pct?: number;
350
+ band?: string;
351
+ share_pct?: number;
352
+ field_count?: number;
353
+ non_field_count?: number;
354
+ store?: string;
355
+ dropped?: number;
356
+ corrupt?: number;
357
+ retained?: number;
358
+ accepted?: number;
359
+ gap_type?: "membership_to_terminal" | "terminal_to_application";
360
+ role?: string;
361
+ phase?: number;
362
+ reason?: string;
363
+ }): void;
364
+ prmHardStopDelivered(sessionId: string, pattern: string, level: number, occurrenceCount: number): void;
365
+ retrievalRouted(sessionId: string, data: {
366
+ mode: "graph" | "lexical" | "semantic" | "security" | "test" | "hybrid";
367
+ graph_hit: boolean;
368
+ fallback_reason: string | null;
369
+ token_budget_requested: number;
370
+ token_budget_used: number;
371
+ omitted_context_count: number;
372
+ }): void;
373
+ };
374
+ };
@@ -711,6 +711,12 @@ export declare function findByBatchId(directory: string, batchId: string, opts?:
711
711
  parentSessionId?: string;
712
712
  }): BackgroundDelegationRecord[];
713
713
  export declare function findOpenAsyncLaneBatches(directory: string): BackgroundDelegationRecord[];
714
+ /**
715
+ * Public alias of the internal terminal predicate so shared lifecycle callers
716
+ * (issue #2045: `delegation-lifecycle.ts`) classify post-claim re-reads with
717
+ * the exact same status set as the store's own guards — never a second copy.
718
+ */
719
+ export declare function isTerminalDelegationStatus(status: BackgroundDelegationStatus): boolean;
714
720
  /**
715
721
  * Public stale sweep: acquires the store lock and marks overdue pendings as `stale`.
716
722
  * Best-effort; returns the number swept (0 on lock timeout / error).
@@ -0,0 +1,13 @@
1
+ export interface ResolvedCommand {
2
+ argv: string[];
3
+ shellCommand: string;
4
+ }
5
+ /**
6
+ * Tokenize a generated command into argv without invoking a shell. Supports
7
+ * single and double quotes for grouping, but never interprets shell
8
+ * metacharacters.
9
+ */
10
+ export declare function tokenizeCommand(command: string): string[];
11
+ /** Resolve a plugin-generated command without invoking a package downloader. */
12
+ export declare function resolveLocalCommand(command: string, workingDir: string, isAvailable: (binary: string) => boolean, platform?: NodeJS.Platform): ResolvedCommand | null;
13
+ export declare function resolveLocalNodeTool(tool: string, args: string[], workingDir: string, platform?: NodeJS.Platform, isAvailable?: (binary: string) => boolean): string[] | null;
@@ -1,4 +1,6 @@
1
1
  import { tool } from '@opencode-ai/plugin';
2
+ import { detectProjectLanguages } from '../lang/detector';
3
+ import { LANGUAGE_REGISTRY } from '../lang/profiles';
2
4
  import { bunSpawnSync } from '../utils/bun-compat';
3
5
  export interface BuildCommand {
4
6
  ecosystem: string;
@@ -8,10 +10,13 @@ export interface BuildCommand {
8
10
  }
9
11
  export interface BuildDiscoveryResult {
10
12
  commands: BuildCommand[];
11
- skipped: {
12
- ecosystem: string;
13
- reason: string;
14
- }[];
13
+ skipped: BuildDiscoverySkip[];
14
+ }
15
+ export interface BuildDiscoverySkip {
16
+ ecosystem: string;
17
+ reason: string;
18
+ code?: 'environment_unavailable';
19
+ required_commands?: string[];
15
20
  }
16
21
  export interface BuildDiscoveryOptions {
17
22
  scope?: 'changed' | 'all';
@@ -27,6 +32,8 @@ export declare const _internals: {
27
32
  discoverBuildCommands: typeof discoverBuildCommands;
28
33
  clearToolchainCache: typeof clearToolchainCache;
29
34
  getEcosystems: typeof getEcosystems;
35
+ detectProjectLanguagesImpl: typeof detectProjectLanguages;
36
+ getLanguageProfileImpl: typeof LANGUAGE_REGISTRY.get;
30
37
  spawnSyncImpl: typeof bunSpawnSync;
31
38
  };
32
39
  export declare function discoverBuildCommandsFromProfiles(workingDir: string): Promise<BuildDiscoveryResult>;
@@ -13,27 +13,27 @@ import {
13
13
  releaseCoderDispatchOwnership,
14
14
  settleCoderDispatch,
15
15
  transferCoderSettlementToBackground
16
- } from "./index-dq93ar40.js";
16
+ } from "./index-w9g58t3j.js";
17
17
  import"./index-3f7hqtgg.js";
18
18
  import"./index-awjx0wgk.js";
19
- import"./index-z0bxbb9g.js";
20
- import"./index-qc53dfzh.js";
19
+ import"./index-t81fmfjj.js";
20
+ import"./index-xy0e0r5f.js";
21
21
  import"./index-ddszzne4.js";
22
- import"./index-7h2armwf.js";
22
+ import"./index-7619tk9a.js";
23
23
  import"./index-556ja8an.js";
24
24
  import"./index-dtn0pne5.js";
25
25
  import"./index-z6xqpmqg.js";
26
26
  import"./index-r3mvsbtq.js";
27
27
  import"./index-9e6fdz2h.js";
28
28
  import"./index-4rhhvd1a.js";
29
- import"./index-mmr96pwr.js";
29
+ import"./index-k55td019.js";
30
30
  import"./index-c9zp4xwc.js";
31
31
  import"./index-zjygnfay.js";
32
- import"./index-7ezv5j23.js";
33
- import"./index-b5adxpy6.js";
32
+ import"./index-d8st72j7.js";
33
+ import"./index-nqbqkx4c.js";
34
34
  import"./index-dh5q6z7y.js";
35
35
  import"./index-ed5ykebp.js";
36
- import"./index-dke4wzkn.js";
36
+ import"./index-fx3em0dr.js";
37
37
  import"./index-7g4c7s5r.js";
38
38
  import"./index-bpmtbmy9.js";
39
39
  import"./index-zgwm4ryv.js";
@@ -12,9 +12,9 @@ import {
12
12
  shouldRunOnStartup,
13
13
  writeBackupArtifact,
14
14
  writeDoctorArtifact
15
- } from "./index-f72612zc.js";
15
+ } from "./index-h6wtxzvm.js";
16
16
  import"./index-z6xqpmqg.js";
17
- import"./index-7npz9n4s.js";
17
+ import"./index-zkgrayya.js";
18
18
  import"./index-c9zp4xwc.js";
19
19
  import"./index-zjygnfay.js";
20
20
  import"./index-bk5tah7q.js";
@@ -14,7 +14,7 @@ import {
14
14
  resolveWorktreeBaseDir,
15
15
  shortenWorktreePath,
16
16
  writeLaneProfileToDiskReal
17
- } from "./index-qc53dfzh.js";
17
+ } from "./index-xy0e0r5f.js";
18
18
  import"./index-z6xqpmqg.js";
19
19
  import"./index-r3mvsbtq.js";
20
20
  import"./index-9e6fdz2h.js";
@@ -4,14 +4,14 @@ import {
4
4
  authorizeCuration,
5
5
  buildConfigFingerprintInput,
6
6
  readCohortConfigFingerprint
7
- } from "./index-znf683sm.js";
7
+ } from "./index-f5axf6vq.js";
8
8
  import"./index-rtry5xyf.js";
9
- import"./index-prv20xq6.js";
10
- import"./index-7ezv5j23.js";
11
- import"./index-b5adxpy6.js";
9
+ import"./index-c6zyqgpq.js";
10
+ import"./index-d8st72j7.js";
11
+ import"./index-nqbqkx4c.js";
12
12
  import"./index-dh5q6z7y.js";
13
13
  import"./index-ed5ykebp.js";
14
- import"./index-dke4wzkn.js";
14
+ import"./index-fx3em0dr.js";
15
15
  import"./index-7g4c7s5r.js";
16
16
  import"./index-bpmtbmy9.js";
17
17
  import"./index-zgwm4ryv.js";