opencode-swarm 7.130.2 → 7.132.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 (85) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/{config-doctor-489wej17.js → config-doctor-3rssa871.js} +2 -2
  3. package/dist/cli/{curation-policy-aaz0hap6.js → curation-policy-3914h3g9.js} +2 -2
  4. package/dist/cli/{curator-s2ynewsr.js → curator-1e77dq6x.js} +13 -13
  5. package/dist/cli/{curator-llm-factory-mxjcctn8.js → curator-llm-factory-41c17ybs.js} +13 -13
  6. package/dist/cli/{evidence-summary-service-hf7qq777.js → evidence-summary-service-jfagmvrh.js} +1 -1
  7. package/dist/cli/{guardrail-explain-s85kv81r.js → guardrail-explain-624brj1y.js} +14 -14
  8. package/dist/cli/{guardrail-log-2cejc33f.js → guardrail-log-vfka4tcn.js} +3 -3
  9. package/dist/cli/{hive-promoter-qdgxajht.js → hive-promoter-mw5prfy2.js} +13 -13
  10. package/dist/cli/{index-kzvwb2se.js → index-03zyn94g.js} +110 -85
  11. package/dist/cli/{index-xa29259t.js → index-2dtwjx39.js} +1 -1
  12. package/dist/cli/{index-5xxgeksy.js → index-5x3g1q91.js} +50 -2
  13. package/dist/cli/{index-zft4hp35.js → index-64z5dvam.js} +1 -1
  14. package/dist/cli/{index-ts9wh1ba.js → index-6mcx76j9.js} +60 -8
  15. package/dist/cli/{index-etdgtk3m.js → index-8w3sp149.js} +3502 -1947
  16. package/dist/cli/{index-6fqj8fcp.js → index-99tq1zd5.js} +2 -2
  17. package/dist/cli/{index-jvyn76xe.js → index-e630fw43.js} +3 -3
  18. package/dist/cli/{index-nr5pr54q.js → index-exs5bt14.js} +1 -1
  19. package/dist/cli/{index-hjar4crm.js → index-fegng5pv.js} +1 -1
  20. package/dist/cli/{index-jxha73tm.js → index-nsxchdx0.js} +1 -1
  21. package/dist/cli/{index-3rnkyb60.js → index-ppkp20fp.js} +5 -5
  22. package/dist/cli/{index-p6qgjkbt.js → index-q0ysv66x.js} +9 -1
  23. package/dist/cli/{index-jvwkxz8q.js → index-qg1cj1ev.js} +15 -15
  24. package/dist/cli/{index-s8h2cedv.js → index-sbq3gbjs.js} +1 -1
  25. package/dist/cli/{index-wy3q73j2.js → index-zah4qws8.js} +18 -14
  26. package/dist/cli/{index-agte9w1e.js → index-zrh07ykv.js} +2 -2
  27. package/dist/cli/index.js +13 -13
  28. package/dist/cli/{knowledge-escalator-1ypz0266.js → knowledge-escalator-5hv2dwhz.js} +3 -3
  29. package/dist/cli/{knowledge-events-yvcczmja.js → knowledge-events-434663dp.js} +1 -1
  30. package/dist/cli/{knowledge-store-h8bp9eje.js → knowledge-store-73ykq5j3.js} +3 -1
  31. package/dist/cli/{knowledge-validator-sz9v1fdq.js → knowledge-validator-tnadtt1h.js} +4 -4
  32. package/dist/cli/{scan-cursor-k9zyt374.js → scan-cursor-e33n9pej.js} +2 -2
  33. package/dist/cli/{schema-fkpn2r1s.js → schema-1kwmcayk.js} +5 -1
  34. package/dist/cli/{skill-generator-a6a9jxfd.js → skill-generator-k111qfdw.js} +5 -5
  35. package/dist/commands/consolidate.d.ts +14 -0
  36. package/dist/commands/curate.d.ts +2 -0
  37. package/dist/commands/promote.d.ts +25 -0
  38. package/dist/commands/registry.d.ts +2 -2
  39. package/dist/config/schema.d.ts +101 -0
  40. package/dist/consensus/contracts.d.ts +479 -0
  41. package/dist/consensus/corpus.d.ts +261 -0
  42. package/dist/consensus/miner.d.ts +200 -0
  43. package/dist/consensus/public-api.d.ts +42 -0
  44. package/dist/consensus/store.d.ts +119 -0
  45. package/dist/evidence/immutable-store.d.ts +86 -0
  46. package/dist/evidence/manager.d.ts +31 -1
  47. package/dist/hooks/actionability-predicate.d.ts +36 -0
  48. package/dist/hooks/curator.d.ts +3 -0
  49. package/dist/hooks/hive-policy.d.ts +11 -0
  50. package/dist/hooks/hive-promoter.d.ts +27 -1
  51. package/dist/hooks/knowledge-dedup-sweep.d.ts +145 -0
  52. package/dist/hooks/knowledge-injector.d.ts +4 -0
  53. package/dist/hooks/knowledge-store.d.ts +26 -0
  54. package/dist/hooks/knowledge-types.d.ts +6 -0
  55. package/dist/hooks/knowledge-validator.d.ts +10 -14
  56. package/dist/hooks/micro-reflector.d.ts +106 -2
  57. package/dist/hooks/realtime-learning-nudge.d.ts +19 -0
  58. package/dist/index.js +464 -456
  59. package/dist/knowledge/entry-merge.d.ts +112 -0
  60. package/dist/knowledge/family-migration.d.ts +6 -2
  61. package/dist/learning/admission.d.ts +154 -0
  62. package/dist/learning/candidate-queue.d.ts +178 -0
  63. package/dist/learning/fingerprint.d.ts +56 -0
  64. package/dist/learning/prm-pattern-support.d.ts +124 -0
  65. package/dist/learning/provenance.d.ts +86 -0
  66. package/dist/parallel/index.d.ts +0 -2
  67. package/dist/plan/parallel-verdict.d.ts +94 -0
  68. package/dist/prm/index.d.ts +46 -1
  69. package/dist/services/recommendation-ledger.d.ts +266 -0
  70. package/dist/services/skill-improver.d.ts +4 -0
  71. package/dist/services/status-service.d.ts +27 -0
  72. package/dist/services/trajectory-cluster.d.ts +65 -2
  73. package/dist/tools/consensus-mine.d.ts +172 -0
  74. package/dist/tools/index.d.ts +2 -0
  75. package/dist/tools/knowledge-add.d.ts +34 -0
  76. package/dist/tools/lean-turbo-run-phase.d.ts +7 -0
  77. package/dist/tools/manifest.d.ts +2 -0
  78. package/dist/tools/plan-conflict-check.d.ts +61 -0
  79. package/dist/tools/tool-metadata.d.ts +8 -0
  80. package/dist/turbo/lean/recovery.d.ts +90 -0
  81. package/dist/turbo/lean/runner.d.ts +16 -1
  82. package/dist/worktree/merge.d.ts +5 -0
  83. package/package.json +1 -1
  84. package/dist/parallel/dependency-graph.d.ts +0 -34
  85. package/dist/parallel/meta-indexer.d.ts +0 -32
@@ -16,6 +16,7 @@
16
16
  * Read-only over the knowledge store; writes only proposal markdown (never
17
17
  * active skills). Fail-open.
18
18
  */
19
+ import { checkRecommendations, recordEmittedRecommendations } from './recommendation-ledger.js';
19
20
  /** Trajectories scanned per macro pass (the plan's N=200 window). */
20
21
  export declare const MACRO_TRAJECTORY_WINDOW = 200;
21
22
  /** A motif must recur across at least this many distinct tasks to propose. */
@@ -36,11 +37,31 @@ export declare function gatherFailureMotifs(directory: string, opts?: {
36
37
  window?: number;
37
38
  minTasks?: number;
38
39
  }): Promise<FailureMotif[]>;
40
+ /**
41
+ * The recommendation a failure motif asserts, in cross-producer form
42
+ * (issue #1821 AC21).
43
+ *
44
+ * The agent role is folded into the *statement* rather than carried as a scope
45
+ * key so this statement can collide with an equivalent lesson proposed by the
46
+ * curator or the consensus miner — the whole point of a shared identity. The
47
+ * motif signature's constituents (tool, failure kind) are what actually vary
48
+ * between motifs, so two distinct motifs never produce the same statement.
49
+ */
50
+ declare function motifStatement(motif: FailureMotif): string;
39
51
  /** Render a draft SKILL.md proposal body for a motif (with full provenance). */
40
- export declare function buildMotifProposal(motif: FailureMotif): string;
52
+ export declare function buildMotifProposal(motif: FailureMotif, options?: {
53
+ fingerprint?: string;
54
+ producedAt?: string;
55
+ }): string;
41
56
  export interface MotifProposalResult {
42
57
  motifs: number;
43
58
  proposalsWritten: string[];
59
+ /**
60
+ * #1821 AC21: motifs whose recommendation was already emitted — by an earlier
61
+ * improver run, the curator sweep, or the consensus miner — and were therefore
62
+ * not re-proposed.
63
+ */
64
+ duplicatesSuppressed: number;
44
65
  }
45
66
  /**
46
67
  * Run the macro motif pass and write one proposal per recurring motif. Returns
@@ -50,6 +71,8 @@ export declare function writeMotifProposals(directory: string, opts?: {
50
71
  window?: number;
51
72
  minTasks?: number;
52
73
  maxProposals?: number;
74
+ /** Recorded in the ledger entry's provenance write origin. */
75
+ sessionId?: string;
53
76
  }): Promise<MotifProposalResult>;
54
77
  /** Minimum number of steps in a trajectory for it to qualify as a workflow. */
55
78
  export declare const SUCCESS_SEQUENCE_MIN_STEPS = 3;
@@ -66,16 +89,56 @@ export interface SuccessMotif {
66
89
  export interface SuccessMotifProposalResult {
67
90
  motifs: number;
68
91
  proposalsWritten: string[];
92
+ /** #1821 AC21: see `MotifProposalResult.duplicatesSuppressed`. */
93
+ duplicatesSuppressed: number;
69
94
  }
70
95
  export declare function gatherSuccessMotifs(directory: string, opts?: {
71
96
  window?: number;
72
97
  minTasks?: number;
73
98
  minSteps?: number;
74
99
  }): Promise<SuccessMotif[]>;
75
- export declare function buildWorkflowProposal(motif: SuccessMotif): string;
100
+ /**
101
+ * The recommendation a success motif asserts, in cross-producer form
102
+ * (issue #1821 AC21). Same agent-in-statement rationale as `motifStatement`.
103
+ *
104
+ * The rendered sequence MUST mirror `sequenceSignature` (`tool:action` per
105
+ * step), not just the tool names: two motifs that share a tool chain but differ
106
+ * in actions have different signatures, different slugs, and different proposal
107
+ * files, so a tool-only statement would collapse two genuinely different
108
+ * workflows onto one cross key and suppress the second.
109
+ */
110
+ declare function workflowStatement(motif: SuccessMotif): string;
111
+ export declare function buildWorkflowProposal(motif: SuccessMotif, options?: {
112
+ fingerprint?: string;
113
+ producedAt?: string;
114
+ }): string;
76
115
  export declare function writeSuccessMotifProposals(directory: string, opts?: {
77
116
  window?: number;
78
117
  minTasks?: number;
79
118
  minSteps?: number;
80
119
  maxProposals?: number;
120
+ /** Recorded in the ledger entry's provenance write origin. */
121
+ sessionId?: string;
81
122
  }): Promise<SuccessMotifProposalResult>;
123
+ /**
124
+ * DI seam (AGENTS.md invariant 7). `now` pins the shared `producedAt` that the
125
+ * ledger entry and the proposal frontmatter both carry; the two ledger functions
126
+ * let a test exercise the suppressed / degraded branches without touching the
127
+ * real ledger. Restore each entry in `afterEach`.
128
+ */
129
+ export declare const _internals: {
130
+ now: () => Date;
131
+ checkRecommendations: typeof checkRecommendations;
132
+ recordEmittedRecommendations: typeof recordEmittedRecommendations;
133
+ };
134
+ /**
135
+ * Pure-function seam for tests (writing-tests SKILL.md, Tier 0). The two
136
+ * statement builders define the cross-producer identity of a motif, so a test
137
+ * asserting cross-producer suppression must derive the competing statement from
138
+ * them rather than hardcode a copy that can silently drift.
139
+ */
140
+ export declare const _test_exports: {
141
+ motifStatement: typeof motifStatement;
142
+ workflowStatement: typeof workflowStatement;
143
+ };
144
+ export {};
@@ -0,0 +1,172 @@
1
+ /**
2
+ * `consensus_mine` — run the consensus miner and persist its report.
3
+ *
4
+ * The tool is a thin, fully-wired shell around `src/consensus/`: it resolves the
5
+ * effective consensus config, assembles a `ConsensusMineRequest` from the
6
+ * caller's overrides, optionally supplies an LLM dispatcher, delegates to
7
+ * `mineAndStoreConsensusV1`, applies report retention, and renders a bounded
8
+ * summary.
9
+ *
10
+ * It is NOT write-minimal, and the description the model reads says so. The
11
+ * complete list of effects, none of which touches a project file:
12
+ *
13
+ * 1. **Its own report** under `.swarm/evolution/consensus/<reportId>.json`.
14
+ * 2. **Deletions of its own prior reports.** `pruneConsensusReports` runs
15
+ * unconditionally after every mine and `consensus.report_retention` defaults
16
+ * to 50, so the steady state of a long-lived project is that each run
17
+ * `unlink`s the oldest report. Only `report_retention: 0` disables it.
18
+ * 3. **The shared dedup ledger** (issue #1821 AC21). It gains one entry per
19
+ * emitted proposal EXCEPT where it already carries that recommendation's
20
+ * cross key — those are counted as `duplicate_recommendation_count` instead.
21
+ * The append is a whole-file rewrite with FIFO eviction at
22
+ * `MAX_RECOMMENDATION_LEDGER_ENTRIES`, and eviction is by position, not by
23
+ * producer: a miner append can evict the CURATOR's or the IMPROVER's oldest
24
+ * entries. The ledger normally lives at
25
+ * `.swarm/learning/recommendation-ledger.jsonl`, but its root is
26
+ * `resolveKnowledgeStoreDir`, so under a knowledge-link pointer it lands in
27
+ * the shared cohort root rather than this project's `.swarm/`.
28
+ * 4. **Lock sentinels.** The report goes through `withEvidenceLock`, whose
29
+ * sentinel is an empty `<sha256>.lock` file under this project's
30
+ * `.swarm/locks/`; `proper-lockfile` removes its own lock directory but
31
+ * NOTHING removes that sentinel, so each distinct report id leaves one behind
32
+ * permanently. The ledger goes through `transactFile`, which locks the
33
+ * ledger's own containing directory and therefore produces
34
+ * `<resolveKnowledgeStoreDir(directory)>/learning.lock` — outside this
35
+ * project's `.swarm/` entirely whenever a knowledge link is active.
36
+ * 5. **A pending memory proposal per emitted proposal**, but only when
37
+ * `memory.enabled` is `true` (it defaults to `false`,
38
+ * `src/config/schema.ts`). This is issue #1821 AC22 — "use existing
39
+ * proposal/MemoryRecord paths, not another inbox" — so the mirror goes
40
+ * through the same `MemoryGateway.propose` path `swarm_memory_propose` uses,
41
+ * which creates a *pending proposal* requiring curator review and never a
42
+ * durable memory record.
43
+ * 6. **Up to `MAX_LLM_SUMMARIES` (20) `session.create` + `session.prompt`
44
+ * calls** whenever `consensus.llm_summarization_enabled` (default `true`)
45
+ * finds a wired OpenCode client.
46
+ *
47
+ * Effects 2-6 were absent from the tool description for four review rounds
48
+ * while it claimed "its only writes are its own immutable report and one entry
49
+ * in the shared recommendation dedup ledger". That string is read by a model as
50
+ * fact and was the premise of the Full-Auto auto-allow in
51
+ * `src/full-auto/policy.ts`; both now state the list above.
52
+ *
53
+ * What the tool does NOT do is mutate any of the evidence it reads. That is a
54
+ * chosen property, not a free one:
55
+ * `loadEvidence` upgrades a legacy flat retrospective in place by default, so
56
+ * `src/consensus/corpus.ts` binds it with `{ migrate: false }`. Without that, a
57
+ * mining run would rewrite evidence bundles — and remap their legacy
58
+ * `task_complexity` values — while advertising itself as read-only.
59
+ *
60
+ * It activates no skill, writes no knowledge entry, admits no durable memory
61
+ * record, and touches no project file — which is why it is classified as a
62
+ * **pathless write-like** tool in `src/full-auto/policy.ts` alongside
63
+ * `write_retro` and `knowledge_add`, and why it is absent from
64
+ * `WRITE_TOOL_NAMES` (`src/config/constants.ts`), whose members are the tools
65
+ * that write PROJECT FILE CONTENTS and are therefore subject to the scope guard.
66
+ * The knowledge-link redirection in effects 3 and 5 does not change that
67
+ * classification: `knowledge_add` and `knowledge_remove` are in the same
68
+ * auto-allow set and have the same redirection, and it comes from operator
69
+ * configuration rather than from anything the caller passed.
70
+ *
71
+ * `ctx.directory` is injected by `createSwarmTool`; there is no `process.cwd()`
72
+ * anywhere in this file (AGENTS.md invariant 4).
73
+ */
74
+ import type { MemoryConfig } from '../config/schema';
75
+ import type { ConsensusAttributeV1, ConsensusReportV1 } from '../consensus/contracts';
76
+ import { createMemoryGateway } from '../memory';
77
+ import { type RecommendationCandidate } from '../services/recommendation-ledger';
78
+ import { createSwarmTool } from './create-tool';
79
+ /**
80
+ * Reconcile the summary counters with what the inline echo actually shows.
81
+ *
82
+ * These are two different orderings and they do not line up. Restatements are
83
+ * spent on the top `MAX_LLM_SUMMARIES` attributes by CONFIDENCE
84
+ * (`src/consensus/miner.ts`), while the echo below is the first
85
+ * `MAX_INLINE_ATTRIBUTES` in canonical SIGNAL order. With confidence inverted
86
+ * relative to signal order, a report can print `summarized_count: 20` while zero
87
+ * `llm_summary` values are visible — which is exactly what "the count always
88
+ * agrees with the values echoed alongside it" used to claim was impossible.
89
+ * `hidden` is what makes the discrepancy legible instead of mysterious.
90
+ */
91
+ declare function countSummaries(attributes: readonly ConsensusAttributeV1[], inlineLimit: number): {
92
+ total: number;
93
+ hidden: number;
94
+ };
95
+ /**
96
+ * Describe the miner's emissions to the cross-producer dedup ledger
97
+ * (issue #1821 AC21).
98
+ *
99
+ * The statement is the proposal's `intent` — the sentence the miner actually
100
+ * emits — not the internal attribute statement its own `lrec_` fingerprint is
101
+ * built from: only the intent survives onto the persisted report, and it is the
102
+ * text a human reads as "the recommendation". `scopeKeys` stays empty for the
103
+ * same reason the curator's new-knowledge recommendations carry none: content
104
+ * alone is the identity of a freshly-minted lesson, and that is exactly where
105
+ * another producer can legitimately be proposing the same thing.
106
+ */
107
+ declare function buildMinerRecommendationCandidates(report: Pick<ConsensusReportV1, 'proposals' | 'generatedAt'>, sessionId?: string): RecommendationCandidate[];
108
+ /**
109
+ * What the AC22 memory mirror actually did, reported rather than assumed.
110
+ *
111
+ * `attempted` is 0 whenever `enabled` is false, so a reader can tell "memory is
112
+ * off" from "memory is on and nothing was proposed". `failed` and `error` exist
113
+ * because the mirror is deliberately fail-open — the report is already durably
114
+ * written by the time it runs, and losing the whole mine to a memory-store
115
+ * problem would be worse than losing the mirror — but a fail-open path that
116
+ * reports nothing is exactly the defect `recommendation_ledger.degraded` was
117
+ * added to close.
118
+ */
119
+ type MemoryMirrorResult = {
120
+ enabled: boolean;
121
+ attempted: number;
122
+ proposed: number;
123
+ rejected: number;
124
+ failed: number;
125
+ error?: string;
126
+ };
127
+ /**
128
+ * Issue #1821 AC22: mirror each emitted proposal into the EXISTING memory
129
+ * proposal path rather than leaving the consensus report as a write-only inbox.
130
+ *
131
+ * `MemoryGateway.propose` is the same entry point `swarm_memory_propose` uses
132
+ * (`src/tools/swarm-memory-propose.ts`), including its `dispose()` in a
133
+ * `finally`. It creates a PENDING proposal — curator review is still required,
134
+ * nothing durable is admitted — which is why the tool's proposals-only guarantee
135
+ * survives the mirror.
136
+ *
137
+ * Fail-open by design and per-proposal: one rejected or throwing proposal must
138
+ * not discard the ones that succeeded, and none of it can unwrite the report.
139
+ * Every outcome is counted and returned, so the tool's response says what
140
+ * happened instead of implying success.
141
+ */
142
+ declare function mirrorProposalsToMemory(options: {
143
+ directory: string;
144
+ memoryConfig: MemoryConfig | undefined;
145
+ report: Pick<ConsensusReportV1, 'proposals' | 'reportId'>;
146
+ sessionId?: string;
147
+ }): Promise<MemoryMirrorResult>;
148
+ /**
149
+ * DI seam (AGENTS.md invariant 7). Lets a test drive the AC22 mirror's
150
+ * failure and rejection branches without a real provider and without
151
+ * `mock.module`. Restore each entry in `afterEach`.
152
+ */
153
+ export declare const _internals: {
154
+ createMemoryGateway: typeof createMemoryGateway;
155
+ };
156
+ /**
157
+ * Pure-function seam for tests (writing-tests SKILL.md, Tier 0). Exported so a
158
+ * cross-producer dedup test can build the miner's ledger candidates through the
159
+ * same code the tool runs instead of restating the mapping, and so the
160
+ * summary-counter reconciliation can be asserted at the ordering that produces
161
+ * it — 20 summarized attributes with none visible — rather than only at the
162
+ * degenerate all-zero case a dispatcher-less run reaches.
163
+ */
164
+ export declare const _test_exports: {
165
+ buildMinerRecommendationCandidates: typeof buildMinerRecommendationCandidates;
166
+ countSummaries: typeof countSummaries;
167
+ mirrorProposalsToMemory: typeof mirrorProposalsToMemory;
168
+ MAX_INLINE_ATTRIBUTES: number;
169
+ MIRRORED_PROPOSAL_KIND: "todo";
170
+ };
171
+ export declare const consensus_mine: ReturnType<typeof createSwarmTool>;
172
+ export {};
@@ -10,6 +10,7 @@ export { checkpoint } from './checkpoint';
10
10
  export { co_change_analyzer } from './co-change-analyzer';
11
11
  export { completion_verify } from './completion-verify';
12
12
  export { complexity_hotspots } from './complexity-hotspots';
13
+ export { consensus_mine } from './consensus-mine';
13
14
  export { context_status } from './context-status';
14
15
  export { submit_council_verdicts } from './convene-council';
15
16
  export { convene_general_council } from './convene-general-council';
@@ -48,6 +49,7 @@ export { parse_lane_candidates } from './parse-lane-candidates';
48
49
  export { phase_complete } from './phase-complete';
49
50
  export { pkg_audit } from './pkg-audit';
50
51
  export { type PlaceholderFinding, type PlaceholderScanInput, type PlaceholderScanResult, placeholder_scan, placeholderScan, } from './placeholder-scan';
52
+ export { plan_conflict_check } from './plan-conflict-check';
51
53
  export { pr_workflow_status } from './pr-workflow-status';
52
54
  export { type PreCheckBatchInput, type PreCheckBatchResult, pre_check_batch, runPreCheckBatch, type ToolResult, } from './pre-check-batch';
53
55
  export { executePreparePrFeedbackScope, prepare_pr_feedback_scope, } from './prepare-pr-feedback-scope';
@@ -1,2 +1,36 @@
1
1
  import { createSwarmTool } from './create-tool.js';
2
+ /**
3
+ * Merge caller-supplied tags with the tags inferred from the lesson.
4
+ *
5
+ * Caller tags come FIRST so that when the combined list exceeds the cap,
6
+ * truncation drops inferred tags before it drops user intent. `dedupeCapped`
7
+ * is case-insensitive (first casing wins), so an inferred tag the caller
8
+ * already supplied collapses into the caller's casing instead of duplicating.
9
+ *
10
+ * Hoisted to module scope and exposed via `_test_exports` so this call site is
11
+ * observable on its own: the store write boundary normalizes the same fields,
12
+ * which would otherwise mask a regression here (issue #1821 Lane 0b).
13
+ */
14
+ declare function mergeLessonTags(tagsInput: unknown, lesson: string): string[];
15
+ /**
16
+ * Normalize one optional v3 actionability array from untrusted tool input.
17
+ *
18
+ * The `undefined` return is load-bearing: `validateActionableFields` and
19
+ * `validateActionability` distinguish an ABSENT field from an empty one, and
20
+ * JSON.stringify drops an `undefined` value so the persisted record keeps that
21
+ * distinction. Only the inner filter+cap is `dedupeCapped` (#1821 Lane 0b).
22
+ */
23
+ declare function strArray(v: unknown): string[] | undefined;
2
24
  export declare const knowledge_add: ReturnType<typeof createSwarmTool>;
25
+ /**
26
+ * Tier-0 test seam (issue #1821 Lane 0b). These are pure functions with no I/O.
27
+ * They are exported so the call-site normalization can be asserted directly:
28
+ * the store write boundary normalizes the same fields with the same semantics,
29
+ * so an assertion made against a PERSISTED entry cannot distinguish "call site
30
+ * fixed" from "call site reverted".
31
+ */
32
+ export declare const _test_exports: {
33
+ mergeLessonTags: typeof mergeLessonTags;
34
+ strArray: typeof strArray;
35
+ };
36
+ export {};
@@ -6,6 +6,7 @@ import type { ToolDefinition } from '@opencode-ai/plugin/tool';
6
6
  import { loadPluginConfigWithMeta as loadPluginConfigWithMeta_import } from '../config';
7
7
  import type { LaneResult, MergeBackFailureInfo } from '../turbo/lean/runner';
8
8
  import { LeanTurboRunner as LeanTurboRunner_import } from '../turbo/lean/runner';
9
+ import type { LeanTurboDegradedTask } from '../turbo/lean/state';
9
10
  /**
10
11
  * Arguments for the lean_turbo_run_phase tool
11
12
  */
@@ -21,6 +22,12 @@ export interface LeanTurboRunPhaseResult {
21
22
  success: boolean;
22
23
  lanes?: LaneResult[];
23
24
  degradedTasks?: string[];
25
+ /**
26
+ * #1657: full degraded-task details (reason/files/requiredMode), additive
27
+ * alongside `degradedTasks`. Lets the architect see per-task degradation
28
+ * reasons in the tool result without reading /swarm status separately.
29
+ */
30
+ degradedDetails?: LeanTurboDegradedTask[];
24
31
  serializedTasks?: string[];
25
32
  mergeBackFailures?: MergeBackFailureInfo[];
26
33
  reason?: string;
@@ -53,6 +53,7 @@ export declare const TOOL_MANIFEST: {
53
53
  checkpoint: () => ToolDefinition;
54
54
  pkg_audit: () => ToolDefinition;
55
55
  parse_lane_candidates: () => ToolDefinition;
56
+ plan_conflict_check: () => ToolDefinition;
56
57
  prepare_pr_feedback_scope: () => ToolDefinition;
57
58
  write_pr_review_artifact: () => ToolDefinition;
58
59
  write_pr_review_trigger_eval: () => ToolDefinition;
@@ -79,6 +80,7 @@ export declare const TOOL_MANIFEST: {
79
80
  doc_scan: () => ToolDefinition;
80
81
  doc_extract: () => ToolDefinition;
81
82
  curator_analyze: () => ToolDefinition;
83
+ consensus_mine: () => ToolDefinition;
82
84
  knowledge_add: () => ToolDefinition;
83
85
  knowledge_recall: () => ToolDefinition;
84
86
  knowledge_remove: () => ToolDefinition;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * `plan_conflict_check` tool (#1656 — v8 parallel-first execution prerequisite).
3
+ *
4
+ * Read-only advisory: compute a pairwise file-conflict matrix for N proposed
5
+ * parallel task groups using declared scopes (and optional git co-change),
6
+ * returning a verdict and suggested serialization order.
7
+ *
8
+ * READ-ONLY CONTRACT (issue #1656 acceptance): this tool writes NOTHING — not
9
+ * to `.swarm/`, not to the source tree. It calls no other tools. The only I/O
10
+ * is reading `.swarm/plan.json` (via `loadPlanJsonOnly`) and
11
+ * `.swarm/scopes/scope-<taskId>.json` (via `readTaskScopes` inside
12
+ * `computeParallelVerdict`), plus an optional `git log` (only when the caller
13
+ * opts in via `use_cochange: true`).
14
+ *
15
+ * Why this tool exists alongside the gate: the gate (`delegation-gate.ts`)
16
+ * independently recomputes the verdict INLINE at coder-dispatch time via the
17
+ * same shared `computeParallelVerdict` helper. The tool gives the architect a
18
+ * way to inspect the conflict matrix BEFORE attempting parallel dispatch, so it
19
+ * can choose disjoint task groups and understand why the gate will (or won't)
20
+ * permit parallelism. Single source of truth (the helper), two call sites.
21
+ */
22
+ import type { tool } from '@opencode-ai/plugin';
23
+ import { z } from 'zod';
24
+ export declare const plan_conflict_check_args: {
25
+ task_ids: z.ZodArray<z.ZodString>;
26
+ use_cochange: z.ZodOptional<z.ZodBoolean>;
27
+ phase_id: z.ZodOptional<z.ZodString>;
28
+ };
29
+ export interface PlanConflictCheckResult {
30
+ /** Summary verdict. `all_disjoint` ⇒ safe to parallelize. */
31
+ verdict: 'all_disjoint' | 'conflicts_present' | 'unknown_scopes';
32
+ /** Pairwise results. */
33
+ pairs: Array<{
34
+ a: string;
35
+ b: string;
36
+ verdict: 'conflict' | 'disjoint' | 'unknown';
37
+ evidence: string[];
38
+ }>;
39
+ /** Suggested serialization order (topological sort over the conflict graph). */
40
+ suggested_serial_order: string[];
41
+ /** Tasks whose declared scope could not be resolved. */
42
+ unknown_scope_tasks: string[];
43
+ /** Whether co-change signal was used. */
44
+ used_cochange: boolean;
45
+ /** Diagnostic: did plan.json load successfully? */
46
+ plan_loaded: boolean;
47
+ /** Task ids requested by the caller but absent from the loaded plan. */
48
+ unknown_to_plan?: string[];
49
+ }
50
+ /**
51
+ * Pure executor (no createSwarmTool wrapper) so it is directly unit-testable.
52
+ *
53
+ * Reads `.swarm/plan.json` (to confirm the task ids exist) and
54
+ * `.swarm/scopes/scope-*.json` (via `computeParallelVerdict`). Writes nothing.
55
+ */
56
+ export declare function executePlanConflictCheck(args: {
57
+ task_ids: string[];
58
+ use_cochange?: boolean;
59
+ phase_id?: string;
60
+ }, directory: string): Promise<PlanConflictCheckResult>;
61
+ export declare const plan_conflict_check: ReturnType<typeof tool>;
@@ -196,6 +196,10 @@ export declare const TOOL_METADATA: {
196
196
  description: string;
197
197
  agents: "architect"[];
198
198
  };
199
+ plan_conflict_check: {
200
+ description: string;
201
+ agents: "architect"[];
202
+ };
199
203
  write_pr_review_trigger_eval: {
200
204
  description: string;
201
205
  agents: "architect"[];
@@ -320,6 +324,10 @@ export declare const TOOL_METADATA: {
320
324
  description: string;
321
325
  agents: "architect"[];
322
326
  };
327
+ consensus_mine: {
328
+ description: string;
329
+ agents: ("architect" | "curator_phase" | "curator_postmortem")[];
330
+ };
323
331
  knowledge_add: {
324
332
  description: string;
325
333
  agents: ("coder" | "architect")[];
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Durable merge-back conflict recovery records (#1657).
3
+ *
4
+ * When a lean-turbo lane's merge-back fails (conflict / partial / error), the
5
+ * runner preserves the lane's worktree + branch for manual recovery but —
6
+ * before this module — that information lived only in:
7
+ * - the in-memory `LeanTurboLane` state (lost when the session ends), and
8
+ * - a scrolled-away log line.
9
+ *
10
+ * Problem #1: `cleanupOrphanedBranches` (`src/worktree/merge.ts`) force-deletes
11
+ * any lane branch whose session isn't active, including a branch just preserved
12
+ * for recovery. The only copy of unmerged lane work could be deleted by routine
13
+ * init-time cleanup before a human ever sees the recovery message.
14
+ *
15
+ * Problem #2: `/swarm status` had no concept of preserved worktrees, so the
16
+ * architect was blind to pending recovery work unless it re-read the tool
17
+ * result inline.
18
+ *
19
+ * This module writes a durable record under `.swarm/recovery/` on every
20
+ * merge-back failure, lets `cleanupOrphanedBranches` EXEMPT recovery branches
21
+ * (fail-safe on read error), and lets `/swarm status` surface them. Records are
22
+ * auto-cleared when the lane later merges back successfully (no accumulation).
23
+ *
24
+ * Records live ONLY under `.swarm/recovery/` (AGENTS.md invariant 4 — `.swarm/`
25
+ * containment). They are intentionally NOT part of the plan ledger or
26
+ * turbo-state: recovery is a side-channel safety net, not authoritative plan
27
+ * or run state.
28
+ */
29
+ /**
30
+ * A durable merge-back recovery record. Mirrors `MergeBackFailureInfo` plus
31
+ * the session/bookkeeping fields needed for orphan-cleanup exemption and
32
+ * status surfacing.
33
+ */
34
+ export interface RecoveryRecord {
35
+ /** Lane identifier (e.g. "lane-1"). */
36
+ laneId: string;
37
+ /** Session that created the record (for scoping/status). */
38
+ sessionId: string;
39
+ /** Lane branch preserved for recovery (e.g. "swarm-lane/<sid>/<laneId>"). */
40
+ branchName?: string;
41
+ /** Lane worktree filesystem path preserved for recovery. */
42
+ worktreePath: string;
43
+ /** Merge-back failure class. */
44
+ status: 'failed' | 'partial' | 'conflict';
45
+ /** Human-readable reason for the merge-back failure. */
46
+ reason: string;
47
+ /** Conflict files (when status === 'conflict'). */
48
+ conflictFiles?: string[];
49
+ /** Epoch ms when the record was written. */
50
+ recordedAt: number;
51
+ /** Portable human guidance for inspecting the preserved worktree. */
52
+ replayHint: string;
53
+ }
54
+ /**
55
+ * Write a recovery record atomically (temp file + rename, mirroring the
56
+ * `turbo-state.json` pattern in `src/turbo/lean/state.ts`). Best-effort:
57
+ * swallows errors so a recovery-write failure can never break the merge-back
58
+ * path that called it.
59
+ */
60
+ export declare function writeRecoveryRecord(directory: string, record: Omit<RecoveryRecord, 'recordedAt'> & {
61
+ recordedAt?: number;
62
+ }): void;
63
+ /**
64
+ * List all recovery records under `.swarm/recovery/`. Tolerates malformed
65
+ * files (skips + continues). Returns `[]` if the directory is absent or
66
+ * unreadable.
67
+ */
68
+ export declare function listRecoveryRecords(directory: string): RecoveryRecord[];
69
+ /**
70
+ * Fast check: does any recovery record reference the given branch name?
71
+ * Used by `cleanupOrphanedBranches` to exempt recovery branches. Returns
72
+ * `false` on any read error (the caller decides fail-open vs fail-safe —
73
+ * see `cleanupOrphanedBranches`).
74
+ */
75
+ export declare function hasRecoveryRecordForBranch(directory: string, branchName: string): boolean;
76
+ /**
77
+ * Remove the recovery record for a specific lane + session. Called on
78
+ * SUCCESSFUL merge-back so records don't accumulate (a record exists only
79
+ * while a lane is in a failed/preserved state). Best-effort.
80
+ */
81
+ export declare function clearRecoveryRecord(directory: string, laneId: string, sessionId: string): void;
82
+ /**
83
+ * Did the recovery-directory read itself throw (vs simply being empty)?
84
+ * `cleanupOrphanedBranches` uses this to decide fail-safe behavior: on a
85
+ * genuine read error it skips ALL lane-branch deletions for that pass
86
+ * (recovery safety trumps orphan cleanliness). Returns `true` if the
87
+ * directory exists but could not be read, OR if any record file exists but
88
+ * is unreadable.
89
+ */
90
+ export declare function recoveryReadErrored(directory: string): boolean;
@@ -25,7 +25,7 @@ import { type ModelOverride } from '../../utils/model-dispatch-fallback';
25
25
  import { writeLaneEvidence, writePhaseEvidence } from './evidence';
26
26
  import { attemptMergeBackFromDirty, getMergeStrategy, mergeLaneBranch, postMergeCleanup, startupOrphanRecovery } from './merge-back';
27
27
  import { planLeanTurboLanes } from './planner';
28
- import type { LeanTurboLane } from './state';
28
+ import type { LeanTurboDegradedTask, LeanTurboLane } from './state';
29
29
  import { loadLeanTurboRunState, saveLeanTurboRunState } from './state';
30
30
  import { assertCleanWorkingTree, provisionWorktree, removeLaneProfileFromDiskReal, removeWorktree } from './worktree';
31
31
  /**
@@ -152,6 +152,12 @@ export interface LeanTurboPhaseResult {
152
152
  lanes: LaneResult[];
153
153
  /** Task IDs that were degraded (risk conditions) */
154
154
  degradedTasks: string[];
155
+ /**
156
+ * #1657: full degraded-task details (reason/files/requiredMode) so the
157
+ * architect sees per-task degradation reasons in the tool result, not
158
+ * just task IDs. Additive alongside `degradedTasks` (kept for back-compat).
159
+ */
160
+ degradedDetails?: LeanTurboDegradedTask[];
155
161
  /**
156
162
  * Task IDs excluded from parallel lanes due to lock conflicts.
157
163
  * Caller must complete these via standard serial flow before phase can advance.
@@ -323,6 +329,15 @@ export declare class LeanTurboRunner {
323
329
  * the serialized tasks set for standard serial fallback.
324
330
  */
325
331
  private _processLane;
332
+ /**
333
+ * Persist a durable recovery record for a merge-back / dispatch failure (#1657).
334
+ *
335
+ * Wraps `writeRecoveryRecord` (best-effort, non-fatal) with this session's
336
+ * id and portable human recovery guidance. Called from every failure branch of
337
+ * `_sequentialWorktreeCleanup`. Records are auto-cleared on successful
338
+ * merge-back so they exist only while a lane is preserved.
339
+ */
340
+ private _persistRecovery;
326
341
  /**
327
342
  * Sequential worktree cleanup for completed and failed worktree lanes.
328
343
  *
@@ -86,6 +86,11 @@ export interface OrphanCleanupResult {
86
86
  branch: string;
87
87
  error: string;
88
88
  }>;
89
+ /** #1657: lane branches skipped because a recovery record references them. */
90
+ skippedRecoveryBranches: string[];
91
+ /** #1657: set when the recovery-directory read errored and ALL lane-branch
92
+ * deletions were skipped this pass (fail-safe). */
93
+ recoveryReadError?: boolean;
89
94
  }
90
95
  export interface StartupRecoveryResult {
91
96
  prunedWorktrees: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.130.2",
3
+ "version": "7.132.0",
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",
@@ -1,34 +0,0 @@
1
- export interface TaskNode {
2
- id: string;
3
- phase: number;
4
- description: string;
5
- depends: string[];
6
- dependents: string[];
7
- status: 'pending' | 'in_progress' | 'complete' | 'blocked';
8
- }
9
- export interface DependencyGraph {
10
- tasks: Map<string, TaskNode>;
11
- phases: Map<number, string[]>;
12
- roots: string[];
13
- leaves: string[];
14
- }
15
- /**
16
- * Parse plan.json and build dependency graph
17
- */
18
- export declare function parseDependencyGraph(planPath: string): DependencyGraph;
19
- /**
20
- * Get tasks that can run in parallel (no unresolved dependencies)
21
- */
22
- export declare function getRunnableTasks(graph: DependencyGraph): string[];
23
- /**
24
- * Check if a task is blocked (has incomplete dependencies)
25
- */
26
- export declare function isTaskBlocked(graph: DependencyGraph, taskId: string): boolean;
27
- /**
28
- * Get execution order (topological sort)
29
- */
30
- export declare function getExecutionOrder(graph: DependencyGraph): string[];
31
- /**
32
- * Find all paths from root to a task
33
- */
34
- export declare function getDependencyChain(graph: DependencyGraph, taskId: string): string[];