opencode-swarm 7.130.2 → 7.131.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 (75) hide show
  1. package/dist/cli/{config-doctor-489wej17.js → config-doctor-rjvb0hhn.js} +2 -2
  2. package/dist/cli/{curation-policy-aaz0hap6.js → curation-policy-f5bybzmh.js} +2 -2
  3. package/dist/cli/{curator-s2ynewsr.js → curator-kjzqa5m7.js} +13 -13
  4. package/dist/cli/{curator-llm-factory-mxjcctn8.js → curator-llm-factory-ezq4fakt.js} +13 -13
  5. package/dist/cli/{evidence-summary-service-hf7qq777.js → evidence-summary-service-jfagmvrh.js} +1 -1
  6. package/dist/cli/{guardrail-explain-s85kv81r.js → guardrail-explain-n93qh8k6.js} +14 -14
  7. package/dist/cli/{guardrail-log-2cejc33f.js → guardrail-log-h4tmcjwx.js} +3 -3
  8. package/dist/cli/{hive-promoter-qdgxajht.js → hive-promoter-mammxe5p.js} +13 -13
  9. package/dist/cli/{index-jvyn76xe.js → index-01nyrk95.js} +3 -3
  10. package/dist/cli/{index-kzvwb2se.js → index-03zyn94g.js} +110 -85
  11. package/dist/cli/{index-xa29259t.js → index-1atz85c8.js} +1 -1
  12. package/dist/cli/{index-6fqj8fcp.js → index-6vg0qczz.js} +2 -2
  13. package/dist/cli/{index-zft4hp35.js → index-cnwh164k.js} +1 -1
  14. package/dist/cli/{index-hjar4crm.js → index-cq22pnd2.js} +1 -1
  15. package/dist/cli/{index-etdgtk3m.js → index-d2tgc580.js} +2688 -1240
  16. package/dist/cli/{index-jxha73tm.js → index-fepakwhp.js} +1 -1
  17. package/dist/cli/{index-5xxgeksy.js → index-hynbzh7s.js} +46 -2
  18. package/dist/cli/{index-p6qgjkbt.js → index-kbfhyxtv.js} +9 -1
  19. package/dist/cli/{index-3rnkyb60.js → index-m6zfm7cm.js} +5 -5
  20. package/dist/cli/{index-s8h2cedv.js → index-pft1qxqh.js} +1 -1
  21. package/dist/cli/{index-ts9wh1ba.js → index-q68sprcy.js} +60 -8
  22. package/dist/cli/{index-agte9w1e.js → index-szkswbqf.js} +2 -2
  23. package/dist/cli/{index-nr5pr54q.js → index-tw33v84d.js} +1 -1
  24. package/dist/cli/{index-jvwkxz8q.js → index-xhd797bz.js} +15 -15
  25. package/dist/cli/{index-wy3q73j2.js → index-xv2ydncg.js} +18 -14
  26. package/dist/cli/index.js +13 -13
  27. package/dist/cli/{knowledge-escalator-1ypz0266.js → knowledge-escalator-cf1kkmfe.js} +3 -3
  28. package/dist/cli/{knowledge-events-yvcczmja.js → knowledge-events-p8t5ev44.js} +1 -1
  29. package/dist/cli/{knowledge-store-h8bp9eje.js → knowledge-store-vpdmcwem.js} +3 -1
  30. package/dist/cli/{knowledge-validator-sz9v1fdq.js → knowledge-validator-w1t0sw5f.js} +4 -4
  31. package/dist/cli/{scan-cursor-k9zyt374.js → scan-cursor-z4c51902.js} +2 -2
  32. package/dist/cli/{schema-fkpn2r1s.js → schema-yqzzfavj.js} +5 -1
  33. package/dist/cli/{skill-generator-a6a9jxfd.js → skill-generator-aaabj00n.js} +5 -5
  34. package/dist/commands/consolidate.d.ts +14 -0
  35. package/dist/commands/curate.d.ts +2 -0
  36. package/dist/commands/promote.d.ts +25 -0
  37. package/dist/commands/registry.d.ts +2 -2
  38. package/dist/config/schema.d.ts +101 -0
  39. package/dist/consensus/contracts.d.ts +479 -0
  40. package/dist/consensus/corpus.d.ts +261 -0
  41. package/dist/consensus/miner.d.ts +200 -0
  42. package/dist/consensus/public-api.d.ts +42 -0
  43. package/dist/consensus/store.d.ts +119 -0
  44. package/dist/evidence/immutable-store.d.ts +86 -0
  45. package/dist/evidence/manager.d.ts +31 -1
  46. package/dist/hooks/actionability-predicate.d.ts +36 -0
  47. package/dist/hooks/curator.d.ts +3 -0
  48. package/dist/hooks/hive-policy.d.ts +11 -0
  49. package/dist/hooks/hive-promoter.d.ts +27 -1
  50. package/dist/hooks/knowledge-dedup-sweep.d.ts +145 -0
  51. package/dist/hooks/knowledge-injector.d.ts +4 -0
  52. package/dist/hooks/knowledge-store.d.ts +26 -0
  53. package/dist/hooks/knowledge-types.d.ts +6 -0
  54. package/dist/hooks/knowledge-validator.d.ts +10 -14
  55. package/dist/hooks/micro-reflector.d.ts +106 -2
  56. package/dist/hooks/realtime-learning-nudge.d.ts +19 -0
  57. package/dist/index.js +426 -418
  58. package/dist/knowledge/entry-merge.d.ts +112 -0
  59. package/dist/knowledge/family-migration.d.ts +6 -2
  60. package/dist/learning/admission.d.ts +154 -0
  61. package/dist/learning/candidate-queue.d.ts +178 -0
  62. package/dist/learning/fingerprint.d.ts +56 -0
  63. package/dist/learning/prm-pattern-support.d.ts +124 -0
  64. package/dist/learning/provenance.d.ts +86 -0
  65. package/dist/prm/index.d.ts +46 -1
  66. package/dist/services/recommendation-ledger.d.ts +266 -0
  67. package/dist/services/skill-improver.d.ts +4 -0
  68. package/dist/services/status-service.d.ts +13 -0
  69. package/dist/services/trajectory-cluster.d.ts +65 -2
  70. package/dist/tools/consensus-mine.d.ts +172 -0
  71. package/dist/tools/index.d.ts +1 -0
  72. package/dist/tools/knowledge-add.d.ts +34 -0
  73. package/dist/tools/manifest.d.ts +1 -0
  74. package/dist/tools/tool-metadata.d.ts +4 -0
  75. package/package.json +1 -1
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Learning provenance records (issue #1821, Lane 0a).
3
+ *
4
+ * Every durable learning write — a curator sweep decision, a micro-reflection,
5
+ * a PRM pattern, a consensus-mined lesson, a skill-improver edit — must be able
6
+ * to say *where it came from*. `LearningProvenanceV1` is the shared envelope:
7
+ * which mechanism produced it, which knowledge/task/evidence/run/model ids fed
8
+ * it, and which session/agent physically wrote it.
9
+ *
10
+ * Every reference class is deduplicated, sorted, and hard-capped at
11
+ * `MAX_REFS_PER_CLASS` so a long-running session cannot grow a provenance record
12
+ * without bound (AGENTS.md invariant 8 — global/persisted state must be
13
+ * bounded).
14
+ *
15
+ * This module is pure: no filesystem, no network, no module-level mutable state.
16
+ * The only ambient input is the wall clock, and only when the caller does not
17
+ * supply `producedAt` — callers that need determinism pass it explicitly.
18
+ */
19
+ import { z } from 'zod';
20
+ /**
21
+ * Hard cap on entries retained per reference class. Sorting happens before the
22
+ * cap so truncation is deterministic rather than insertion-order dependent.
23
+ */
24
+ export declare const MAX_REFS_PER_CLASS = 50;
25
+ /** The learning mechanism that produced a record. */
26
+ export type LearningMechanism = 'curator_sweep' | 'micro_reflection' | 'prm_pattern' | 'consensus_mine' | 'skill_improver';
27
+ export declare const LearningProvenanceV1Schema: z.ZodObject<{
28
+ v: z.ZodLiteral<1>;
29
+ mechanism: z.ZodEnum<{
30
+ skill_improver: "skill_improver";
31
+ consensus_mine: "consensus_mine";
32
+ curator_sweep: "curator_sweep";
33
+ micro_reflection: "micro_reflection";
34
+ prm_pattern: "prm_pattern";
35
+ }>;
36
+ sourceKnowledgeIds: z.ZodArray<z.ZodString>;
37
+ sourceTaskIds: z.ZodArray<z.ZodString>;
38
+ sourceEvidenceRefs: z.ZodArray<z.ZodString>;
39
+ sourceRunIds: z.ZodArray<z.ZodString>;
40
+ sourceModelIds: z.ZodArray<z.ZodString>;
41
+ writeOrigin: z.ZodObject<{
42
+ sessionId: z.ZodOptional<z.ZodString>;
43
+ agentRole: z.ZodOptional<z.ZodString>;
44
+ producedAt: z.ZodISODateTime;
45
+ }, z.core.$strict>;
46
+ }, z.core.$strict>;
47
+ export interface LearningProvenanceV1 {
48
+ v: 1;
49
+ mechanism: LearningMechanism;
50
+ sourceKnowledgeIds: string[];
51
+ sourceTaskIds: string[];
52
+ sourceEvidenceRefs: string[];
53
+ sourceRunIds: string[];
54
+ sourceModelIds: string[];
55
+ /** See `LearningWriteOriginSchema` for why there is no `agentId` here. */
56
+ writeOrigin: {
57
+ sessionId?: string;
58
+ agentRole?: string;
59
+ producedAt: string;
60
+ };
61
+ }
62
+ /** Caller-supplied provenance body. Every reference class is optional. */
63
+ export interface LearningProvenanceInput {
64
+ mechanism: LearningMechanism;
65
+ sourceKnowledgeIds?: string[];
66
+ sourceTaskIds?: string[];
67
+ sourceEvidenceRefs?: string[];
68
+ sourceRunIds?: string[];
69
+ sourceModelIds?: string[];
70
+ }
71
+ /** Caller-supplied write origin. `producedAt` defaults to now when omitted. */
72
+ export interface LearningWriteOriginInput {
73
+ sessionId?: string;
74
+ agentRole?: string;
75
+ producedAt?: string;
76
+ }
77
+ /**
78
+ * Build a validated `LearningProvenanceV1` from a partial body and a write
79
+ * origin. Fills `v`, missing reference classes, and `producedAt`; deduplicates,
80
+ * sorts, and caps every reference class; then parses through
81
+ * `LearningProvenanceV1Schema` so the returned value is guaranteed valid.
82
+ *
83
+ * Throws (via Zod) on an unknown mechanism or an unparseable `producedAt` —
84
+ * a malformed provenance record must never be persisted silently.
85
+ */
86
+ export declare function stampLearningProvenance(partial: LearningProvenanceInput, origin?: LearningWriteOriginInput): LearningProvenanceV1;
@@ -16,6 +16,10 @@ export { formatCourseCorrectionForInjection, generateCourseCorrection, } from '.
16
16
  export { createDefaultEscalationState, EscalationTracker } from './escalation';
17
17
  export { detectContextThrash, detectExpansionDrift, detectPatterns, detectPingPong, detectRepetitionLoop, detectStuckOnTest, } from './pattern-detector';
18
18
  export type { CourseCorrection, EscalationState, PatternDetectionResult, PatternMatch, PatternSeverity, PatternType, PrmConfig, TaxonomyCategory, TrajectoryEntry, } from './types';
19
+ import type { LearningConfig } from '../config/schema.js';
20
+ import { appendInsightCandidates } from '../hooks/micro-reflector.js';
21
+ import { enqueueCandidate } from '../learning/candidate-queue.js';
22
+ import { recordPatternObservation } from '../learning/prm-pattern-support.js';
19
23
  import { getAgentSession } from '../state';
20
24
  import { telemetry } from '../telemetry';
21
25
  import { formatCourseCorrectionForInjection, generateCourseCorrection } from './course-correction';
@@ -44,6 +48,9 @@ export declare const _internals: {
44
48
  recordReplayEntry: typeof recordReplayEntry;
45
49
  startReplayRecording: typeof startReplayRecording;
46
50
  telemetry: typeof telemetry;
51
+ recordPatternObservation: typeof recordPatternObservation;
52
+ enqueueCandidate: typeof enqueueCandidate;
53
+ appendInsightCandidates: typeof appendInsightCandidates;
47
54
  };
48
55
  /**
49
56
  * Context passed to toolAfter handler
@@ -94,4 +101,42 @@ export declare function resetPrmSessionState(session: ResettablePrmSessionState,
94
101
  * // Wire prmHook.toolAfter into your tool.execute.after hook
95
102
  * ```
96
103
  */
97
- export declare function createPrmHook(config: PrmConfig, directory: string): PrmHook;
104
+ /**
105
+ * Knobs the PRM hook needs to hand supported patterns to the real-time
106
+ * admission queue (issue #1821, AC10). Optional so existing callers and tests
107
+ * keep working unchanged; when absent, pattern persistence is simply off.
108
+ */
109
+ export interface PrmPatternPersistenceOptions {
110
+ /**
111
+ * `learning.prm_persistence.enabled`. Governs the WHOLE producer, durable
112
+ * append included.
113
+ */
114
+ enabled: boolean;
115
+ min_support: number;
116
+ cooldown_ms: number;
117
+ /**
118
+ * `learning.realtime_admission.enabled`. Governs ONLY the in-memory enqueue.
119
+ *
120
+ * It must never gate the durable append (issue #1821 F3): AC8 says disabled
121
+ * or crashed real-time work loses nothing, and the phase-boundary backstop
122
+ * can only see candidates that reached `.swarm/insight-candidates.jsonl`.
123
+ * ANDing the two flags at the call site made `realtime_admission.enabled=false`
124
+ * silently discard every PRM candidate. Mirrors `micro-reflector.ts`, which
125
+ * appends unconditionally and gates only its enqueue.
126
+ */
127
+ admission_enabled: boolean;
128
+ /** `learning.realtime_admission.max_queue_size` — bounds the shared queue. */
129
+ max_queue_size: number;
130
+ }
131
+ /**
132
+ * Map a parsed `learning` config onto the PRM producer's knobs (issue #1821 F3).
133
+ *
134
+ * The mapping lives here, beside the interface that documents which flag governs
135
+ * what, and `src/index.ts` is its only production caller — so the AC8 coupling
136
+ * ("durable persistence is gated by `prm_persistence.enabled` ALONE") is
137
+ * expressed once and can be asserted directly by a test instead of being
138
+ * re-derived from an object literal at the plugin's wiring site, where the two
139
+ * flags were previously ANDed together.
140
+ */
141
+ export declare function resolvePrmPatternPersistenceOptions(learning: LearningConfig): PrmPatternPersistenceOptions;
142
+ export declare function createPrmHook(config: PrmConfig, directory: string, patternPersistence?: PrmPatternPersistenceOptions): PrmHook;
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Cross-producer recommendation dedup ledger (issue #1821, AC21).
3
+ *
4
+ * Three mechanisms propose learning recommendations: the curator sweep
5
+ * (`src/hooks/curator.ts`), the skill improver's macro-reflector
6
+ * (`src/services/trajectory-cluster.ts`), and the consensus miner
7
+ * (`src/consensus/miner.ts`). `src/learning/fingerprint.ts` gave them a shared
8
+ * identity function, but a fingerprint alone dedups nothing: somebody has to
9
+ * remember what was already emitted. This module is that memory.
10
+ *
11
+ * ## Why a second key
12
+ *
13
+ * `computeRecommendationFingerprint` deliberately folds `kind` into the digest —
14
+ * its own doc says "dedup across mechanisms is a policy decision made by the
15
+ * caller, not baked into the identity". This module *is* that caller, and the
16
+ * policy it implements is: **two recommendations are the same recommendation
17
+ * when they say the same thing about the same scope, regardless of which
18
+ * mechanism noticed it first.** So every ledger entry carries two ids:
19
+ *
20
+ * - `fingerprint` — the producer-scoped `lrec_…` id, kept verbatim for audit.
21
+ * - `crossKey` — an `lxk_…` id over `{ normalizedStatement, sortedScopeKeys }`.
22
+ *
23
+ * `crossKey` drops both `kind` and `target`. Dropping `kind` is the whole point.
24
+ * Dropping `target` is a redundancy call, not a necessity: each producer already
25
+ * interpolates its target into the statement it emits (the miner's `intent`
26
+ * names its `proposedTarget`; the improver's statement names the tool and
27
+ * failure kind its slug is built from; the curator's `entry_id` travels in
28
+ * `scopeKeys`), so keeping `target` would add no discriminating power while
29
+ * splitting identity on producer-local vocabulary.
30
+ *
31
+ * Disambiguation that genuinely must survive therefore travels in `scopeKeys`,
32
+ * which is order- and duplicate-insensitive (`normalizeScopeKeys`). The curator
33
+ * uses that to keep `archive entry-X` and `rewrite entry-X` distinct even when
34
+ * both carry the same lesson text; recommendations that mint *new* knowledge
35
+ * carry no scope keys, because content alone is their identity.
36
+ *
37
+ * ## What this does and does not achieve today
38
+ *
39
+ * The key is an EXACT hash of normalized text. Two producers dedup against each
40
+ * other only when they emit the same sentence. In the current codebase the
41
+ * improver and the miner build their statements from fixed templates
42
+ * (`Avoid the recurring … failure in …`, `Investigate the smallest … change …`)
43
+ * while the curator's statement is a free-form LLM lesson, so a cross-producer
44
+ * collision is possible but uncommon. The concrete, everyday win is therefore:
45
+ * (a) WITHIN-producer dedup, which the curator and the improver had none of, and
46
+ * (b) one shared, provenance-stamped record of every emitted recommendation that
47
+ * any producer can consult. Making cross-producer suppression routine would need
48
+ * near-duplicate matching (see `findNearDuplicate` / the knowledge dedup sweep)
49
+ * rather than an exact fingerprint, which is a different mechanism than the one
50
+ * `src/learning/fingerprint.ts` defines.
51
+ *
52
+ * ## Check, emit, then record — never "reserve"
53
+ *
54
+ * The two phases are deliberately separate:
55
+ *
56
+ * - `checkRecommendations` is read-only. It answers "has this been emitted
57
+ * before?" and writes nothing.
58
+ * - `recordEmittedRecommendations` is the locked write, and callers run it only
59
+ * for recommendations that **actually took effect**.
60
+ *
61
+ * An earlier design claimed the key up-front, before the caller emitted. That is
62
+ * wrong here: the curator legitimately *defers* recommendations (cohort-safe
63
+ * authorization not yet granted, target entry temporarily inactive, CAS revision
64
+ * drift, fair-scan generation already curated, actionability quarantine pending
65
+ * the hardening loop). Every one of those paths expects a later sweep to retry,
66
+ * and a key claimed before the emit would suppress that retry permanently.
67
+ * Recording after the fact costs a small race — two producers can both pass
68
+ * `check` and both emit — whose worst case is one duplicate, exactly the
69
+ * pre-#1821 behaviour. A burned key, by contrast, loses a lesson forever. The
70
+ * record step re-checks under the lock, so the ledger itself never grows a
71
+ * duplicate.
72
+ *
73
+ * ## Boundedness and containment
74
+ *
75
+ * The ledger is a JSONL file at `.swarm/learning/recommendation-ledger.jsonl`,
76
+ * hard-capped at `MAX_RECOMMENDATION_LEDGER_ENTRIES` entries with oldest-first
77
+ * FIFO eviction, and every entry is capped at `MAX_ENTRY_BYTES` (provenance is
78
+ * dropped rather than allowed to blow the bound), so the file has a hard ceiling
79
+ * of roughly 2 MB (AGENTS.md invariant 8). It never touches `process.cwd()`
80
+ * (invariant 4). Its own subdirectory is deliberate: `transactFile` locks the
81
+ * *containing directory*, so living under `learning/` keeps this lock disjoint
82
+ * from the `.swarm/` root lock that `transactKnowledge` takes.
83
+ *
84
+ * The root is `resolveKnowledgeStoreDir`, NOT a hardcoded `<directory>/.swarm`.
85
+ * That is the same link-aware resolution `resolveSwarmKnowledgePath` uses, so in
86
+ * a linked cohort the ledger lives beside the knowledge store it guards instead
87
+ * of being stranded per-worktree — a lane worktree would otherwise discard its
88
+ * ledger while the lessons it recorded persisted in the shared store.
89
+ *
90
+ * Eviction means a recommendation older than the last 500 emissions can surface
91
+ * again. That is the intended trade: bounded state beats perfect recall. Note
92
+ * this cap is independent of `knowledge.swarm_max_entries` (default 100): a
93
+ * lesson the knowledge store has already FIFO-evicted can still be suppressed
94
+ * here until its own entry ages out.
95
+ *
96
+ * ## Fail-open
97
+ *
98
+ * Every failure mode — unreadable ledger, lock timeout, corrupt lines, a
99
+ * provenance record the schema rejects — resolves to "emit everything", i.e.
100
+ * exactly the behaviour that existed before this module. A broken dedup ledger
101
+ * must never silence the learning loops.
102
+ */
103
+ import { transactFile } from '../hooks/knowledge-store.js';
104
+ import { type RecommendationKind } from '../learning/fingerprint.js';
105
+ import { type LearningProvenanceInput, type LearningProvenanceV1, type LearningWriteOriginInput } from '../learning/provenance.js';
106
+ /**
107
+ * Hard cap on retained ledger entries (AGENTS.md invariant 8). Sized to match
108
+ * `INSIGHT_CANDIDATES_MAX_ENTRIES` in `src/hooks/micro-reflector.ts`, the
109
+ * closest sibling queue: the same order of magnitude of learning artifacts flows
110
+ * through both, and one shared number is easier to reason about than two.
111
+ */
112
+ export declare const MAX_RECOMMENDATION_LEDGER_ENTRIES = 500;
113
+ /**
114
+ * Hard cap on one serialized entry. Provenance reference lists are individually
115
+ * bounded by `stampLearningProvenance`, but 5 classes x 50 refs x 512 chars is
116
+ * still ~128 KB, which would put the file's ceiling in the tens of megabytes.
117
+ * An entry over this budget keeps its identity fields and drops provenance —
118
+ * dedup is the contract, provenance is the bonus.
119
+ */
120
+ export declare const MAX_ENTRY_BYTES = 4096;
121
+ /** A recommendation a producer is about to emit, or has just emitted. */
122
+ export interface RecommendationCandidate {
123
+ /** Producing mechanism. Recorded on the entry; excluded from `crossKey`. */
124
+ kind: RecommendationKind;
125
+ /** Producer-local target (knowledge id, proposal slug, subsystem name). */
126
+ target: string;
127
+ /** The recommendation body. Normalized before hashing. */
128
+ statement: string;
129
+ /** Scope disambiguators. Order- and duplicate-insensitive. */
130
+ scopeKeys?: string[];
131
+ /** Learning provenance stamped onto the ledger entry when recorded. */
132
+ provenance?: LearningProvenanceInput;
133
+ /** Write origin for the provenance stamp. */
134
+ origin?: LearningWriteOriginInput;
135
+ }
136
+ /** The two ids a candidate resolves to. */
137
+ export interface RecommendationIdentity {
138
+ /** Producer-scoped `lrec_…` id from `src/learning/fingerprint.ts`. */
139
+ fingerprint: string;
140
+ /** Kind- and target-independent `lxk_…` id used for dedup. */
141
+ crossKey: string;
142
+ }
143
+ /** One durable record of an emitted recommendation. */
144
+ export interface RecommendationLedgerEntry extends RecommendationIdentity {
145
+ v: 1;
146
+ kind: RecommendationKind;
147
+ target: string;
148
+ /** ISO-8601 timestamp of the record call that appended this entry. */
149
+ emittedAt: string;
150
+ provenance?: LearningProvenanceV1;
151
+ }
152
+ /**
153
+ * Why a candidate was not accepted for emission.
154
+ *
155
+ * Module-private: consumers reach this set through
156
+ * `RecommendationDecision['suppressedBy']`, which is exported and narrows to the
157
+ * same two literals. Exporting the alias as well gave it a name no importer ever
158
+ * used.
159
+ */
160
+ type RecommendationSuppressionSource = 'ledger' | 'batch';
161
+ /** Per-candidate outcome, in input order. */
162
+ export interface RecommendationDecision extends RecommendationIdentity {
163
+ /** Index into the candidate array this decision belongs to. */
164
+ index: number;
165
+ /** True when the caller should emit this recommendation. */
166
+ emit: boolean;
167
+ /**
168
+ * `'ledger'` — a previous emission (possibly by another producer) already
169
+ * claimed this `crossKey`. `'batch'` — an earlier candidate in this same call
170
+ * claimed it. Absent when `emit` is true.
171
+ */
172
+ suppressedBy?: RecommendationSuppressionSource;
173
+ }
174
+ export interface CheckRecommendationsResult {
175
+ decisions: RecommendationDecision[];
176
+ /** Count of `emit === true` decisions. */
177
+ accepted: number;
178
+ /** Count of `emit === false` decisions. */
179
+ suppressed: number;
180
+ /**
181
+ * True when the ledger could not be read and every candidate was accepted by
182
+ * fail-open default rather than by an actual dedup check.
183
+ */
184
+ degraded: boolean;
185
+ }
186
+ export interface RecordRecommendationsResult {
187
+ /** Entries actually appended. */
188
+ recorded: number;
189
+ /** Candidates skipped because the ledger already carried their cross key. */
190
+ suppressed: number;
191
+ /** Entries dropped by the FIFO cap during this record. */
192
+ evicted: number;
193
+ /** True when the ledger could not be written. */
194
+ degraded: boolean;
195
+ }
196
+ /**
197
+ * Absolute path of the ledger, link-aware.
198
+ *
199
+ * Byte-identical to `<directory>/.swarm/learning/recommendation-ledger.jsonl`
200
+ * for an unlinked worktree; redirects to the shared cohort store when
201
+ * `.swarm/link.json` is active, exactly as `resolveSwarmKnowledgePath` does for
202
+ * the knowledge file this ledger shadows.
203
+ */
204
+ export declare function resolveRecommendationLedgerPath(directory: string): string;
205
+ /** Shape check for a value that claims to be a cross-producer key. */
206
+ export declare function isRecommendationCrossKey(value: string): boolean;
207
+ /**
208
+ * Compute the kind- and target-independent dedup key.
209
+ *
210
+ * Built from the same normalizers the `lrec_` fingerprint uses, so whitespace
211
+ * runs, surrounding space, letter case, trailing sentence punctuation, scope-key
212
+ * order, and scope-key duplicates all collapse to one identity.
213
+ */
214
+ export declare function computeCrossProducerKey(input: {
215
+ statement: string;
216
+ scopeKeys?: string[];
217
+ }): string;
218
+ /** Compute both ids for a candidate. Pure. */
219
+ export declare function computeRecommendationIdentity(candidate: RecommendationCandidate): RecommendationIdentity;
220
+ /**
221
+ * Read every retained ledger entry, propagating a real read failure.
222
+ *
223
+ * A missing ledger is the first-run case and yields `[]`; anything else throws
224
+ * so `checkRecommendations` can report `degraded` truthfully instead of treating
225
+ * an unreadable ledger as an empty one and silently claiming it deduped.
226
+ */
227
+ declare function readLedgerStrict(directory: string): Promise<RecommendationLedgerEntry[]>;
228
+ /**
229
+ * Read every retained ledger entry. Fail-open: returns `[]` when the ledger is
230
+ * absent or unreadable. This is the inspection entry point — the dedup decision
231
+ * itself goes through `readLedgerStrict` so a failure is visible rather than
232
+ * disguised as an empty ledger.
233
+ */
234
+ export declare function readRecommendationLedger(directory: string): Promise<RecommendationLedgerEntry[]>;
235
+ /**
236
+ * Answer "has this been emitted before?" without writing anything.
237
+ *
238
+ * Suppression is decided against the ledger's existing cross keys plus the keys
239
+ * claimed by earlier candidates in this same batch. Fail-open: an unreadable
240
+ * ledger yields `degraded: true` and accepts everything.
241
+ */
242
+ export declare function checkRecommendations(directory: string, candidates: readonly RecommendationCandidate[]): Promise<CheckRecommendationsResult>;
243
+ /**
244
+ * Append the recommendations a producer actually emitted.
245
+ *
246
+ * Runs one locked read-modify-write and re-checks every cross key under that
247
+ * lock, so a candidate another producer recorded between `checkRecommendations`
248
+ * and here is counted as `suppressed` rather than duplicated into the ledger.
249
+ * Candidates whose statement normalizes to nothing are never recorded.
250
+ */
251
+ export declare function recordEmittedRecommendations(directory: string, candidates: readonly RecommendationCandidate[], options?: {
252
+ producedAt?: string;
253
+ }): Promise<RecordRecommendationsResult>;
254
+ /**
255
+ * DI seam (AGENTS.md invariant 7). `now` lets tests pin `emittedAt`;
256
+ * `transactFile`, `readRecommendationLedger`, and
257
+ * `resolveRecommendationLedgerPath` let them exercise the fail-open paths
258
+ * without `mock.module`. Restore each entry in `afterEach`.
259
+ */
260
+ export declare const _internals: {
261
+ now: () => Date;
262
+ transactFile: typeof transactFile;
263
+ readLedgerStrict: typeof readLedgerStrict;
264
+ resolveRecommendationLedgerPath: typeof resolveRecommendationLedgerPath;
265
+ };
266
+ export {};
@@ -83,11 +83,15 @@ export interface SkillImproveResult {
83
83
  macroMotifs?: {
84
84
  motifs: number;
85
85
  proposalsWritten: number;
86
+ /** #1821 AC21: motifs suppressed by the cross-producer dedup ledger. */
87
+ duplicatesSuppressed?: number;
86
88
  };
87
89
  /** #1234 Part 4: success workflow-motif proposals written this run. */
88
90
  successMotifs?: {
89
91
  motifs: number;
90
92
  proposalsWritten: number;
93
+ /** #1821 AC21: motifs suppressed by the cross-producer dedup ledger. */
94
+ duplicatesSuppressed?: number;
91
95
  };
92
96
  /** Issue #1477: self-healing reconciliation of stale active generated skills
93
97
  * (regenerate-or-retire). In proposal-only runs this is a dry run (the
@@ -78,6 +78,19 @@ export interface StatusData {
78
78
  unactionableQueueDepth?: number;
79
79
  /** #1234 Part 3: pending insight candidates awaiting phase boundary consumption */
80
80
  insightCandidatesPending?: number;
81
+ /**
82
+ * #1821 AC22: consensus report FILES stored under
83
+ * `.swarm/evolution/consensus/`.
84
+ *
85
+ * The reader half of the consensus store. Without it the miner's reports were
86
+ * a write-only directory — nothing in the product enumerated them, so a user
87
+ * had no way to learn they existed. A file count, deliberately: it is one
88
+ * `readdir` with no JSON parse and no integrity recomputation, so it costs the
89
+ * same whether the store holds one report or fifty. It therefore includes any
90
+ * corrupt report, because what it counts is files whose name is a well-formed
91
+ * report id.
92
+ */
93
+ consensusReports?: number;
81
94
  /**
82
95
  * Cohort/link status (issue #1846). Makes the linked knowledge store and its
83
96
  * health obvious in `/swarm status`. `undefined` when link state is absent.
@@ -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 {};