opencode-swarm 7.117.0 → 7.118.1

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 (49) hide show
  1. package/dist/cli/{config-doctor-htzxe394.js → config-doctor-p73yfdz0.js} +2 -2
  2. package/dist/cli/{core-zg8s1jwx.js → core-gf1t2jmn.js} +2 -1
  3. package/dist/cli/{curator-fr2m5hna.js → curator-8hfdh6cr.js} +14 -13
  4. package/dist/cli/{curator-drift-7p2ky1th.js → curator-drift-nn71xfz6.js} +2 -1
  5. package/dist/cli/{curator-llm-factory-m0kvna8n.js → curator-llm-factory-exrqs5ax.js} +14 -13
  6. package/dist/cli/{evidence-summary-service-bwt6drb3.js → evidence-summary-service-cy3k37je.js} +3 -2
  7. package/dist/cli/{guardrail-explain-xjgk822d.js → guardrail-explain-0rzn7wy4.js} +15 -14
  8. package/dist/cli/{guardrail-log-dzbqcgz9.js → guardrail-log-qjqgg7pz.js} +3 -3
  9. package/dist/cli/{hive-promoter-zpk5jmx0.js → hive-promoter-56zm6jfy.js} +19 -13
  10. package/dist/cli/{index-m7hc7nn7.js → index-11q1t6g0.js} +1 -1
  11. package/dist/cli/{index-dc3j6jmk.js → index-5hze8ztj.js} +10 -21
  12. package/dist/cli/{index-m43zgtjn.js → index-bcg89pt8.js} +1 -1
  13. package/dist/cli/{index-n3sm9ewn.js → index-ez0763de.js} +2 -2
  14. package/dist/cli/{index-8j5d3ytd.js → index-g2wrs7jy.js} +1 -1
  15. package/dist/cli/{index-af94ke83.js → index-jbdgpqpj.js} +3 -3
  16. package/dist/cli/{index-pgtqxat4.js → index-m90weyvs.js} +16 -15
  17. package/dist/cli/{index-vkdr12ek.js → index-ms18nybj.js} +1941 -1362
  18. package/dist/cli/{index-xdsd2hma.js → index-p45sshb5.js} +7 -5
  19. package/dist/cli/{index-nvc0nsvg.js → index-pdk1n8yh.js} +2 -0
  20. package/dist/cli/{index-c9m5skgs.js → index-pjhfe7c3.js} +2 -2
  21. package/dist/cli/{index-rdwfqhp7.js → index-q4kgsvdd.js} +7 -4
  22. package/dist/cli/{index-aws8yhwq.js → index-qx5z58a7.js} +19 -31
  23. package/dist/cli/{index-hskmya46.js → index-r2ysfdet.js} +7 -29
  24. package/dist/cli/{index-scww5b77.js → index-vwvkpg0d.js} +4 -1
  25. package/dist/cli/{index-4f4zv8pb.js → index-wpgv6hht.js} +25 -1
  26. package/dist/cli/index-ye3fndsw.js +30 -0
  27. package/dist/cli/index.js +14 -13
  28. package/dist/cli/{knowledge-escalator-bmk725p4.js → knowledge-escalator-gt1q632m.js} +4 -4
  29. package/dist/cli/{knowledge-events-px14n75v.js → knowledge-events-0bjdtdsb.js} +2 -2
  30. package/dist/cli/{knowledge-store-z9znvtn2.js → knowledge-store-fm2300xf.js} +7 -2
  31. package/dist/cli/{knowledge-validator-7r5t821g.js → knowledge-validator-91kx3eqv.js} +3 -3
  32. package/dist/cli/{schema-bqn7g3ez.js → schema-7c484wjz.js} +1 -1
  33. package/dist/cli/{skill-generator-4d3w8sqt.js → skill-generator-3f8dqjj2.js} +6 -5
  34. package/dist/commands/promote.d.ts +3 -0
  35. package/dist/commands/registry.d.ts +2 -2
  36. package/dist/config/schema.d.ts +8 -4
  37. package/dist/diff/semantic-classifier.d.ts +1 -1
  38. package/dist/hooks/curator-postmortem.d.ts +1 -1
  39. package/dist/hooks/hive-policy.d.ts +81 -0
  40. package/dist/hooks/hive-promoter.d.ts +147 -28
  41. package/dist/hooks/hive-transaction.d.ts +113 -0
  42. package/dist/hooks/knowledge-curator.d.ts +4 -5
  43. package/dist/hooks/knowledge-events.d.ts +2 -1
  44. package/dist/hooks/knowledge-store.d.ts +6 -5
  45. package/dist/hooks/knowledge-types.d.ts +87 -0
  46. package/dist/index.js +352 -342
  47. package/dist/knowledge/hive-paths.d.ts +51 -0
  48. package/dist/services/knowledge-diagnostics.d.ts +18 -0
  49. package/package.json +1 -1
@@ -0,0 +1,81 @@
1
+ /**
2
+ * One promotion policy evaluator used by automatic promotion AND the manual
3
+ * `/swarm promote` command (issue #1847 §4).
4
+ *
5
+ * Previously, automatic promotion went through `isHiveEligible` (3 routes:
6
+ * hive_eligible+3 phases / hive-fast-track tag / age) while manual promotion
7
+ * (`promoteToHive` / `promoteFromSwarm`) bypassed policy entirely — an exact
8
+ * entry id was effectively authorization to skip the gate, with no durable
9
+ * override record. This module is the single policy function both paths share.
10
+ *
11
+ * Manual promotion that fails the policy MUST either be blocked, or proceed
12
+ * only with an explicit `--force` override that records a durable, audited
13
+ * override (actor, reason, source revision, failed gates). An exact entry id
14
+ * alone is NEVER authorization to bypass policy.
15
+ *
16
+ * Conservative application evidence (#1847 §2, AC5/AC6): the
17
+ * `validated_terminal_applications` gate counts ONLY validated terminal receipts
18
+ * tied to a real retrieval trace + result membership. Legacy records carry no
19
+ * evidence and receive NO synthetic credit. Until #1849 produces real receipts,
20
+ * the configured thresholds default to 0, so the gate is satisfied by absence
21
+ * (it neither credits nor blocks) and current behavior is preserved. Operators
22
+ * raise the thresholds to activate application-evidence gating.
23
+ *
24
+ * This module performs NO I/O and holds NO module-level mutable state
25
+ * (invariant 8). It is NOT imported on the plugin-init path (invariant 1).
26
+ */
27
+ import type { KnowledgeConfig, PromotionEvidenceRecord, SwarmKnowledgeEntry } from './knowledge-types.js';
28
+ /** A single named policy gate and whether it passed. */
29
+ export interface PromotionPolicyGate {
30
+ name: string;
31
+ passed: boolean;
32
+ detail: string;
33
+ }
34
+ export interface PromotionPolicyInput {
35
+ entry: SwarmKnowledgeEntry;
36
+ config: KnowledgeConfig;
37
+ /**
38
+ * Validated terminal-application evidence for this entry's lesson
39
+ * (near-duplicate-clustered). Empty for legacy/no-evidence records. The
40
+ * promoter loads this OUTSIDE the hive transaction and passes it in.
41
+ */
42
+ evidence: PromotionEvidenceRecord[];
43
+ }
44
+ export interface PromotionPolicyDecision {
45
+ eligible: boolean;
46
+ /** Every gate and whether it passed — surfaced in diagnostics + override audit. */
47
+ gates: PromotionPolicyGate[];
48
+ /** Human-readable summary of the deciding gate. */
49
+ reason: string;
50
+ }
51
+ /**
52
+ * Evaluate the one promotion policy. Used by auto promotion and manual promote.
53
+ * Returns the full gates list so diagnostics can explain each failed condition
54
+ * (AC: "Promotion diagnostics explain eligibility failures and lineage").
55
+ */
56
+ export declare function evaluatePromotionPolicy(input: PromotionPolicyInput): PromotionPolicyDecision;
57
+ /**
58
+ * The three historical eligibility routes, preserved verbatim from the
59
+ * pre-#1847 `isHiveEligible`. Exposed so `isHiveEligible` can delegate to it
60
+ * (M1 fix — keeps the public export + its test consumers working) and so the
61
+ * policy gate above can report which route satisfied it.
62
+ */
63
+ export declare function describeEligibilityRoute(entry: SwarmKnowledgeEntry, autoPromoteDays: number): {
64
+ passed: boolean;
65
+ detail: string;
66
+ };
67
+ /** Names of override-audit fields the promoter fills when a manual --force fires. */
68
+ export interface OverrideAuditDetail {
69
+ actor: 'manual-override';
70
+ reason: string;
71
+ failedGates: PromotionPolicyGate[];
72
+ entryId: string;
73
+ lesson: string;
74
+ }
75
+ /**
76
+ * Build the failed-gates summary for a manual override, given a decision that
77
+ * was NOT eligible. Used by the promoter to populate `lineage.override_failed_gates`.
78
+ */
79
+ export declare function failedGateNames(decision: PromotionPolicyDecision): string[];
80
+ /** Count distinct canonical cohorts among promotion evidence (for diagnostics). */
81
+ export declare function countDistinctEvidenceCohorts(evidence: PromotionEvidenceRecord[]): number;
@@ -1,59 +1,178 @@
1
- /** Hive promoter hook for opencode-swarm v6.17 two-tier knowledge system. */
1
+ /**
2
+ * Hive promoter for opencode-swarm v6.17 two-tier knowledge system.
3
+ *
4
+ * #1847 (transactional hive promotion): every hive write is routed through ONE
5
+ * global cross-process transaction (`transactHiveStore` in `./hive-transaction`).
6
+ * The transaction holds the hive directory lock across read → normalize →
7
+ * eligibility → canonical-cohort counting → dedup/merge → append/update →
8
+ * source confirmation → cap → staged audit → atomic persist. No caller reads
9
+ * the hive, makes a promotion decision, and later calls a separate unlocked
10
+ * write API.
11
+ *
12
+ * Canonical project identity (#1846): cross-project confirmations key on the
13
+ * canonical `cohort_id` from `resolveCohortId`, not the worktree `project_name`.
14
+ * Sibling worktrees and remote aliases of one repository count as one project.
15
+ *
16
+ * Lineage (#1847 §3): promoted entries retain source entry id, source cohort,
17
+ * promotion event id, and actor. Manual promotion that fails policy proceeds
18
+ * only with an explicit `--force` override that records a durable audit entry;
19
+ * an exact entry id alone is never authorization to bypass policy.
20
+ *
21
+ * This module is NOT imported on the plugin-init path (invariant 1): promotion
22
+ * runs only on the lazy `/swarm promote`, close, curate, and postmortem paths.
23
+ */
24
+ import { resolveCohortId } from '../knowledge/cohort-identity.js';
2
25
  import { appendCuratorRecommendation, readCuratorSummary } from './curator.js';
3
- import type { KnowledgeConfig, SwarmKnowledgeEntry } from './knowledge-types.js';
4
- /** Hive promotion summary for curator state */
26
+ import { transactHiveStore } from './hive-transaction.js';
27
+ import type { KnowledgeConfig, ProjectConfirmationRecord, PromotionEvidenceRecord, SwarmKnowledgeEntry } from './knowledge-types.js';
28
+ import { validateLesson } from './knowledge-validator.js';
29
+ /** Hive promotion summary for curator state. */
5
30
  export interface HivePromotionSummary {
6
31
  timestamp: string;
7
32
  new_promotions: number;
8
33
  encounters_incremented: number;
9
34
  advancements: number;
10
35
  total_hive_entries: number;
36
+ /** #1847: per-entry policy diagnostics for operator visibility. */
37
+ diagnostics?: string[];
11
38
  }
12
39
  /**
13
- * Check whether a swarm knowledge entry is eligible for hive promotion.
14
- * Three routes to eligibility:
15
- * Route 1: hive_eligible flag + 3+ distinct phases
16
- * Route 2: 'hive-fast-track' tag
17
- * Route 3: age exceeds auto_promote_days threshold
18
- *
19
- * @param entry - The swarm knowledge entry to check
20
- * @param autoPromoteDays - Number of days before age-based promotion kicks in
21
- * @returns true if the entry is eligible for hive promotion
40
+ * Check whether a swarm entry is eligible for hive promotion via the historical
41
+ * 3 routes. Kept as a thin wrapper delegating to the canonical policy route
42
+ * description (M1 fix, #1847) so existing test consumers
43
+ * (`hive-promoter-inactive.test.ts`, `close.test.ts` mock) keep working.
44
+ *
45
+ * This is ONE of the gates inside {@link evaluatePromotionPolicy}; it is not
46
+ * the whole policy. New callers should use `evaluatePromotionPolicy`.
22
47
  */
23
48
  export declare function isHiveEligible(entry: SwarmKnowledgeEntry, autoPromoteDays: number): boolean;
24
49
  /**
25
- * Main promotion logic: checks swarm entries and promotes eligible ones to hive.
26
- * Also updates existing hive entries with new project confirmations.
27
- * Returns a summary of the promotion activity for curator state.
50
+ * Count distinct projects/cohort identities in a hive entry's confirmed_by.
28
51
  *
29
- * @note The 'hive-fast-track' tag is treated as privileged it bypasses the
30
- * 3-phase confirmation requirement. It should only be set by authorized tooling
31
- * (inferTags() never produces it automatically).
52
+ * #1847: dedup by canonical `cohort_id` (sibling worktrees + remote aliases of
53
+ * one repo count as one project). Legacy records written before #1847 lack
54
+ * `cohort_id`; for those, fall back to `project_name` — but two records sharing
55
+ * a `cohort_id` are NEVER counted as distinct. This means AC3 ("sibling
56
+ * worktrees count as one project") is enforced for confirmations written from
57
+ * this PR forward; legacy confirmations remain `project_name`-keyed and are NOT
58
+ * retroactively re-counted (consistent with the no-broad-rewrite non-goal).
32
59
  */
33
- export declare function checkHivePromotions(swarmEntries: SwarmKnowledgeEntry[], config: KnowledgeConfig): Promise<HivePromotionSummary>;
60
+ export declare function countDistinctProjects(confirmedBy: ProjectConfirmationRecord[]): number;
61
+ /**
62
+ * Main promotion logic: checks swarm entries and promotes eligible ones to hive
63
+ * inside ONE cross-process transaction. Also updates existing hive entries with
64
+ * new canonical-cohort confirmations. Returns a summary for curator state.
65
+ *
66
+ * `directory` is required (#1847) to resolve the canonical cohort identity
67
+ * (#1846) for cross-project distinctness.
68
+ *
69
+ * @note The 'hive-fast-track' tag is privileged — it bypasses the 3-phase
70
+ * confirmation requirement inside the eligibility_route gate. It should only
71
+ * be set by authorized tooling (inferTags() never produces it automatically).
72
+ */
73
+ export declare function checkHivePromotions(swarmEntries: SwarmKnowledgeEntry[], config: KnowledgeConfig, directory: string): Promise<HivePromotionSummary>;
34
74
  export declare const _internals: {
35
75
  readSwarmEntries: (directory: string) => Promise<SwarmKnowledgeEntry[]>;
36
76
  checkHivePromotions: typeof checkHivePromotions;
37
77
  readCuratorSummary: typeof readCuratorSummary;
38
78
  appendCuratorRecommendation: typeof appendCuratorRecommendation;
79
+ resolveCohortId: typeof resolveCohortId;
80
+ transactHiveStore: typeof transactHiveStore;
81
+ validateLesson: typeof validateLesson;
82
+ /** Loads validated terminal-application evidence per swarm entry id. Empty
83
+ * until #1849 produces real receipts (conservative: no synthetic credit). */
84
+ loadPromotionEvidence: (_swarmEntries: SwarmKnowledgeEntry[]) => Promise<Record<string, PromotionEvidenceRecord[]>>;
85
+ /** Loads the default KnowledgeConfig (schema defaults) for manual promotion
86
+ * paths when the command did not load one. */
87
+ loadDefaultKnowledgeConfig: () => {
88
+ enabled: boolean;
89
+ swarm_max_entries: number;
90
+ hive_max_entries: number;
91
+ auto_promote_days: number;
92
+ max_inject_count: number;
93
+ delegate_max_inject_count: number;
94
+ inject_char_budget: number;
95
+ max_lesson_display_chars: number;
96
+ dedup_threshold: number;
97
+ scope_filter: string[];
98
+ hive_enabled: boolean;
99
+ rejected_max_entries: number;
100
+ validation_enabled: boolean;
101
+ evergreen_confidence: number;
102
+ evergreen_utility: number;
103
+ low_utility_threshold: number;
104
+ min_retrievals_for_utility: number;
105
+ schema_version: number;
106
+ directive_min_confidence: number;
107
+ same_project_weight: number;
108
+ cross_project_weight: number;
109
+ min_encounter_score: number;
110
+ initial_encounter_score: number;
111
+ encounter_increment: number;
112
+ max_encounter_score: number;
113
+ default_max_phases: number;
114
+ todo_max_phases: number;
115
+ sweep_enabled: boolean;
116
+ confidence_floor_action: "none" | "demote" | "quarantine";
117
+ confidence_floor_min_outcomes: number;
118
+ confidence_floor_signal_threshold: number;
119
+ contradiction_threshold_action: "quarantine" | "tag_only";
120
+ contradiction_quarantine_threshold: number;
121
+ contradiction_quarantine_window_days: number;
122
+ promoted_demotion_min_negative_phases: number;
123
+ promoted_demotion_signal_threshold: number;
124
+ promotion_min_terminal_applications: number;
125
+ promotion_min_distinct_cohorts: number;
126
+ realtime_learning_nudge: {
127
+ enabled: boolean;
128
+ first_after_tool_calls: number;
129
+ repeat_after_tool_calls: number;
130
+ };
131
+ enrichment: {
132
+ max_calls_per_day: number;
133
+ quota_window: "utc" | "local";
134
+ batch_size?: number | undefined;
135
+ };
136
+ context_budget_threshold?: number | undefined;
137
+ retrieval?: {
138
+ mmr_lambda: number;
139
+ cold_start_bonus: number;
140
+ cold_start_max_age_phases: number;
141
+ synonym_min_cooccurrence: number;
142
+ synonym_map_max_pairs: number;
143
+ } | undefined;
144
+ };
39
145
  };
40
146
  /**
41
147
  * Create a hook that promotes swarm entries to the hive.
42
148
  * The hook fires unconditionally - the caller decides when to invoke it.
43
149
  */
44
150
  export declare function createHivePromoterHook(directory: string, config: KnowledgeConfig): (input: unknown, output: unknown) => Promise<void>;
151
+ /** Options for manual promotion (override semantics, #1847 §4). */
152
+ export interface ManualPromotionOptions {
153
+ force?: boolean;
154
+ reason?: string;
155
+ }
45
156
  /**
46
157
  * Promote a lesson directly to the hive (manual promotion).
47
- * @param directory - Project directory
48
- * @param lesson - The lesson text to promote
49
- * @param category - Optional category (defaults to 'process')
50
- * @returns Confirmation message
158
+ *
159
+ * Runs the one policy evaluator. On a policy FAIL:
160
+ * - without `force` returns a diagnostic string listing the failed gates
161
+ * (does NOT promote);
162
+ * - with `force` + `reason` → promotes and writes a durable override audit
163
+ * record (actor='manual-override', failed gates recorded) inside the
164
+ * transaction.
165
+ * On a policy PASS → promotes with actor='manual'.
166
+ *
167
+ * An exact entry id / direct text alone is NEVER authorization to bypass policy.
51
168
  */
52
- export declare function promoteToHive(directory: string, lesson: string, category?: string): Promise<string>;
169
+ export declare function promoteToHive(directory: string, lesson: string, category?: string, options?: ManualPromotionOptions, config?: KnowledgeConfig): Promise<string>;
53
170
  /**
54
171
  * Promote a lesson from swarm knowledge to hive.
55
- * @param directory - Project directory
56
- * @param lessonId - The ID of the lesson to promote from swarm
57
- * @returns Confirmation message
172
+ *
173
+ * Snapshots the swarm read OUTSIDE the hive transaction (the swarm store is
174
+ * read-only here — no two-phase locking needed). Then runs the one policy
175
+ * evaluator + override semantics inside the hive transaction.
58
176
  */
59
- export declare function promoteFromSwarm(directory: string, lessonId: string): Promise<string>;
177
+ export declare function promoteFromSwarm(directory: string, lessonId: string, options?: ManualPromotionOptions, config?: KnowledgeConfig): Promise<string>;
178
+ export { resolveHiveKnowledgePath } from './knowledge-store.js';
@@ -0,0 +1,113 @@
1
+ /**
2
+ * One global, cross-process transaction primitive for hive storage (issue #1847 §1).
3
+ *
4
+ * Problem: hive promotion previously performed read → N× append → batch rewrite
5
+ * → cap enforcement as FOUR separate directory-lock acquisitions, with unlocked
6
+ * read/validate/dedup windows between them. Because the hive store
7
+ * (`shared-learnings.jsonl`) is a shared, cross-project, cross-process file,
8
+ * two opencode-swarm sessions could each read the same snapshot and one's
9
+ * rewrite silently dropped the other's entries (TOCTOU lost update — #1604).
10
+ *
11
+ * This module routes every hive writer through ONE critical section that spans,
12
+ * under a single directory lock:
13
+ * 1. read + mixed-schema normalize;
14
+ * 2. the caller's mutate (eligibility, canonical project counting, dedup,
15
+ * merge decision, append/update, source confirmation);
16
+ * 3. validate-before-commit;
17
+ * 4. priority-aware cap enforcement (in the same closure, not a separate call);
18
+ * 5. staged audit/reject appends (raw, under the held lock — never via
19
+ * `appendHiveKnowledgeEvent`, which would re-enter the directory lock and
20
+ * deadlock — see the curator precedent at `curator.ts:1966-1969`);
21
+ * 6. atomic persistence (temp + rename).
22
+ *
23
+ * Lock contract (AGENTS.md invariants 3, 8): `stale: 5000` to MATCH every other
24
+ * hive writer (`appendKnowledge`, `rewriteKnowledge`, `transactKnowledge`,
25
+ * `knowledge-application.ts`, `knowledge-escalator.ts`). proper-lockfile's stale
26
+ * mechanism is preemptive — a process that cannot acquire the lock and sees its
27
+ * mtime older than its OWN stale threshold force-breaks it. Using a longer stale
28
+ * here would let a concurrent 5s writer break this transaction mid-flight and
29
+ * tear the file, which is exactly the bug this PR fixes. The held closure is
30
+ * kept fast (in-memory mutate + one atomic write + raw appends) so 5s is never
31
+ * exceeded; all heavy work (git cohort resolution, evidence rollup, near-
32
+ * duplicate precompute) runs OUTSIDE the lock and is passed in via HiveTxContext.
33
+ *
34
+ * This module holds NO module-level mutable state (invariant 8) and is NOT
35
+ * imported on the plugin-init path (invariant 1).
36
+ */
37
+ import * as path from 'node:path';
38
+ import lockfile from 'proper-lockfile';
39
+ import { atomicWriteFile } from '../evidence/task-file.js';
40
+ import { resolveHiveDataDir, resolveHiveEventsPath, resolveHiveKnowledgePath, resolveHiveRejectedPath } from '../knowledge/hive-paths.js';
41
+ import { readKnowledge, selectKnowledgeCapSurvivors } from './knowledge-store.js';
42
+ import type { HiveKnowledgeEntry, RejectedLesson } from './knowledge-types.js';
43
+ /**
44
+ * Context handed to a hive mutation closure. All expensive pre-work (cohort
45
+ * resolution, evidence loading, near-duplicate indexing) is performed by the
46
+ * caller OUTSIDE the lock and injected here so the held closure stays fast.
47
+ */
48
+ export interface HiveTxContext {
49
+ /** Current hive entries (read + normalized under the lock). */
50
+ entries: HiveKnowledgeEntry[];
51
+ }
52
+ /** A pre-serialized knowledge-event line to append to the hive audit log. */
53
+ export interface HiveAuditEntry {
54
+ line: string;
55
+ }
56
+ /** The result a mutation closure returns. */
57
+ export type HiveMutationOutcome<T> = {
58
+ kind: 'commit';
59
+ entries: HiveKnowledgeEntry[];
60
+ /** Hive cap to enforce inside the same closure (omit for no cap). */
61
+ maxEntries?: number;
62
+ /** Rejected lessons to append to the hive rejected log under the lock. */
63
+ rejects?: RejectedLesson[];
64
+ /** Audit lines to append to the hive events log under the lock. */
65
+ audit?: HiveAuditEntry[];
66
+ /** Caller return value surfaced back through HiveTransactionResult. */
67
+ return: T;
68
+ } | {
69
+ kind: 'noop';
70
+ return: T;
71
+ };
72
+ export interface HiveTransactionResult<T> {
73
+ /** True iff the hive file was rewritten within the transaction. */
74
+ committed: boolean;
75
+ /**
76
+ * The caller's return value. Present when `committed` is true OR the mutate
77
+ * closure returned a `noop` outcome. Undefined on lock-acquire / mkdir /
78
+ * validation-before-commit failure (F-004/PRR-1): callers MUST check
79
+ * `committed` (or `return !== undefined`) before dereferencing it.
80
+ */
81
+ return: T | undefined;
82
+ /** Human-readable diagnostics (lock timeout, validation failure, etc.). */
83
+ diagnostics: string[];
84
+ }
85
+ /**
86
+ * Run `mutate` against the hive store inside one cross-process transaction.
87
+ *
88
+ * On lock-acquire failure or validation failure the prior hive file is left
89
+ * intact (the atomic write is not performed) and `committed` is false; the
90
+ * function never hangs. `mutate` receives the current (normalized) entries and
91
+ * returns either a `commit` (new entries + optional cap/rejects/audit) or a
92
+ * `noop`.
93
+ */
94
+ export declare function transactHiveStore<T>(mutate: (ctx: HiveTxContext) => Promise<HiveMutationOutcome<T>> | HiveMutationOutcome<T>): Promise<HiveTransactionResult<T>>;
95
+ /** Path to the hive events log (re-exported for callers building audit lines). */
96
+ export { resolveHiveEventsPath };
97
+ export declare const HIVE_TXN_LOCK_STALE_MS = 5000;
98
+ /**
99
+ * Test-only DI seam (AGENTS.md invariant 7). Tests inject a fake lockfile /
100
+ * readers / writers rather than `mock.module`-ing the consumers, which leaks
101
+ * across test files in Bun's shared runner.
102
+ */
103
+ export declare const _internals: {
104
+ lockfile: typeof lockfile;
105
+ readKnowledge: typeof readKnowledge;
106
+ atomicWriteFile: typeof atomicWriteFile;
107
+ selectKnowledgeCapSurvivors: typeof selectKnowledgeCapSurvivors;
108
+ resolveHiveDataDir: typeof resolveHiveDataDir;
109
+ resolveHiveKnowledgePath: typeof resolveHiveKnowledgePath;
110
+ resolveHiveRejectedPath: typeof resolveHiveRejectedPath;
111
+ resolveHiveEventsPath: typeof resolveHiveEventsPath;
112
+ path: typeof path;
113
+ };
@@ -1,4 +1,3 @@
1
- /** Knowledge curator hook for opencode-swarm v6.17 two-tier knowledge system. */
2
1
  import { realpath } from 'node:fs/promises';
3
2
  import type { CuratorLLMDelegate } from './curator.js';
4
3
  import type { ActionableDirectiveFields, KnowledgeConfig, SwarmKnowledgeEntry } from './knowledge-types.js';
@@ -56,7 +55,7 @@ export interface EnrichmentQuotaOptions {
56
55
  * One retry on schema failure (with a RETRY message naming the missing
57
56
  * fields). Quota-gated per call via the dedicated knowledge-enrichment quota.
58
57
  * Returns null when
59
- * enrichment is unavailable (quota exhausted) or fails twice the caller
58
+ * enrichment is unavailable (quota exhausted) or fails twice — the caller
60
59
  * quarantines the entry. Never throws.
61
60
  */
62
61
  export declare function enrichLessonToV3(params: {
@@ -107,15 +106,15 @@ export declare function runAutoPromotion(directory: string, config: KnowledgeCon
107
106
  /**
108
107
  * G7 (#1716): Auto-demote swarm entries that have sustained a net-negative
109
108
  * outcome signal over consecutive phase EVALUATIONS (i.e. consecutive
110
- * `runAutoDemotion` invocations with distinct phase numbers a skipped phase
111
- * in between still counts, matching the issue's "3 consecutive" intent as
109
+ * `runAutoDemotion` invocations with distinct phase numbers — a skipped phase
110
+ * in between still counts, matching the issue's "≥3 consecutive" intent as
112
111
  * implemented against evaluation cadence, not wall-clock phase contiguity).
113
112
  *
114
113
  * Companion to {@link runAutoPromotion}. For each `promoted` entry:
115
114
  * 1. Dedupe by phase: if `entry.last_demotion_phase === phaseNumber`, this
116
115
  * entry has already been processed for this phase (handles the case where
117
116
  * `curateAndStoreSwarm` is invoked multiple times in the same logical
118
- * phase e.g. phase-complete + close). Skip the counter update.
117
+ * phase — e.g. phase-complete + close). Skip the counter update.
119
118
  * 2. Otherwise compute the outcome signal. If at/below
120
119
  * `config.promoted_demotion_signal_threshold`, increment
121
120
  * `recent_negative_phase_count`; else reset it to 0.
@@ -19,6 +19,7 @@
19
19
  * `directory` argument injected by `createSwarmTool` / hook constructors — never
20
20
  * from the process working directory.
21
21
  */
22
+ import { resolveHiveEventsPath as resolveHiveEventsPathImpl } from '../knowledge/hive-paths.js';
22
23
  import type { ConfidenceFloorOptions } from './knowledge-store.js';
23
24
  import type { KnowledgeApplicationRecord, RetrievalOutcome } from './knowledge-types.js';
24
25
  /** Current event-log record schema version. Bump when the on-disk shape changes. */
@@ -182,7 +183,7 @@ export declare const RECEIPT_EVENT_TYPES: ReadonlySet<string>;
182
183
  export declare function resolveKnowledgeEventsPath(directory: string): string;
183
184
  /** Returns the knowledge-counter-baseline.json path (link-aware). */
184
185
  export declare function resolveKnowledgeCounterBaselinePath(directory: string): string;
185
- export declare function resolveHiveEventsPath(): string;
186
+ export declare const resolveHiveEventsPath: typeof resolveHiveEventsPathImpl;
186
187
  /** Returns the knowledge-application.jsonl path for legacy v2 audit records (link-aware). */
187
188
  export declare function resolveLegacyApplicationLogPath(directory: string): string;
188
189
  /** Generate a fresh trace id. One per retrieval; receipts reference it. */
@@ -1,12 +1,14 @@
1
1
  /** Core storage layer for the opencode-swarm v6.17 two-tier knowledge system. */
2
+ import { resolveHiveDataDir, resolveHiveEventsPath as resolveHiveEventsPathImpl, resolveHiveKnowledgePath as resolveHiveKnowledgePathImpl, resolveHiveRejectedPath as resolveHiveRejectedPathImpl } from '../knowledge/hive-paths.js';
2
3
  import type { KnowledgeEntryBase, RejectedLesson, RetrievalOutcome } from './knowledge-types.js';
3
4
  export declare function getPlatformConfigDir(): string;
4
5
  export declare function resolveSwarmKnowledgePath(directory: string): string;
5
6
  export declare function resolveSwarmRejectedPath(directory: string): string;
6
7
  export declare function resolveSwarmRetractionsPath(directory: string): string;
7
- export declare function resolveHiveKnowledgePath(): string;
8
- export declare function resolveHiveRejectedPath(): string;
9
- export declare function resolveHiveEventsPath(): string;
8
+ export declare const resolveHiveKnowledgePath: typeof resolveHiveKnowledgePathImpl;
9
+ export declare const resolveHiveRejectedPath: typeof resolveHiveRejectedPathImpl;
10
+ export declare const resolveHiveEventsPath: typeof resolveHiveEventsPathImpl;
11
+ export { resolveHiveDataDir };
10
12
  declare function parseKnowledgeContent<T>(content: string, max: number): T[];
11
13
  export declare function readKnowledge<T>(filePath: string, maxEntries?: number): Promise<T[]>;
12
14
  export declare function normalizeEntry<T>(raw: T): T;
@@ -29,7 +31,7 @@ export declare function transactKnowledge<T>(filePath: string, mutate: (entries:
29
31
  export declare function getArchivedKnowledgeIds(directory: string): Promise<Set<string>>;
30
32
  export declare function appendKnowledgeWithCapEnforcement<T>(filePath: string, entry: T, maxEntries: number): Promise<boolean>;
31
33
  export declare function enforceKnowledgeCap<T>(filePath: string, maxEntries: number): Promise<void>;
32
- declare function selectKnowledgeCapSurvivors<T>(entries: T[], maxEntries: number): T[];
34
+ export declare function selectKnowledgeCapSurvivors<T>(entries: T[], maxEntries: number): T[];
33
35
  export interface SweepResult {
34
36
  scanned: number;
35
37
  aged: number;
@@ -136,4 +138,3 @@ export declare const _internals: {
136
138
  bumpKnowledgeConfidenceBatch: typeof bumpKnowledgeConfidenceBatch;
137
139
  getArchivedKnowledgeIds: typeof getArchivedKnowledgeIds;
138
140
  };
139
- export {};
@@ -7,6 +7,15 @@ export interface PhaseConfirmationRecord {
7
7
  }
8
8
  export interface ProjectConfirmationRecord {
9
9
  project_name: string;
10
+ /**
11
+ * Canonical cohort id (issue #1847) — the identity used for cross-project
12
+ * distinctness, from `resolveCohortId` (#1846). Sibling worktrees and remote
13
+ * aliases of one repository share one `cohort_id`, so they count as a single
14
+ * project. Absent on legacy records written before #1847; such records are
15
+ * counted by `project_name` as a degraded fallback and are NEVER synthetically
16
+ * credited with a cohort id.
17
+ */
18
+ cohort_id?: string;
10
19
  confirmed_at: string;
11
20
  phase_number?: number;
12
21
  }
@@ -205,6 +214,69 @@ export interface SwarmKnowledgeEntry extends KnowledgeEntryBase {
205
214
  confirmed_by: PhaseConfirmationRecord[];
206
215
  project_name: string;
207
216
  }
217
+ /**
218
+ * A single validated terminal application of a knowledge entry, usable as
219
+ * promotion evidence (issue #1847). Only receipts tied to a real retrieval
220
+ * trace AND a terminal outcome (`applied` / `violated` / `contradicted`) that
221
+ * is a member of that trace's result set qualify. Shown / retrieved / injected
222
+ * / acknowledged-only states do NOT qualify — they are display/attention
223
+ * signals, not application evidence.
224
+ *
225
+ * Production of real host traces is owned by #1849; this PR (#1847) owns the
226
+ * schema and the conservative promotion-side consumer. Legacy records carry no
227
+ * `PromotionEvidenceRecord`s and receive NO synthetic credit.
228
+ */
229
+ export interface PromotionEvidenceRecord {
230
+ /** Canonical repository/cohort identity (from `resolveCohortId`, #1846). */
231
+ cohort_id: string;
232
+ /** Source cohort / link id when known (from the v2 LinkPointer). */
233
+ source_link_id?: string;
234
+ /** The hive/swarm entry id this evidence contributes to. */
235
+ entry_id: string;
236
+ /** Retrieval trace id that surfaced the entry (ties to a RetrievedEvent). */
237
+ retrieval_trace_id: string;
238
+ /** Terminal receipt outcome. */
239
+ receipt_outcome: 'applied' | 'violated' | 'contradicted';
240
+ /** Id of the ReceiptEvent this evidence was derived from. */
241
+ receipt_event_id: string;
242
+ phase?: string;
243
+ timestamp: string;
244
+ }
245
+ /** How a hive entry was promoted (issue #1847 §3 lineage). */
246
+ export type PromotionActor = 'auto' | 'manual' | 'manual-override';
247
+ /**
248
+ * Lineage + validated-evidence block attached to a promoted hive entry (issue
249
+ * #1847 §3). A promoted hive record must retain enough provenance to audit
250
+ * redaction/ownership and to trace back to its source without storing
251
+ * unnecessary sensitive content.
252
+ *
253
+ * All fields optional except `actor` so legacy on-disk records (which predate
254
+ * this block) load unchanged. Legacy records are NOT retroactively given a
255
+ * synthetic lineage block (no broad rewrite); consumers treat an absent
256
+ * `lineage` as "origin unknown, pre-#1847".
257
+ */
258
+ export interface PromotionLineage {
259
+ /** UUID of the source swarm entry this hive record was promoted from. */
260
+ source_entry_id?: string;
261
+ /** Canonical cohort id of the source repository (#1846). */
262
+ source_cohort_id?: string;
263
+ /** Source entry content hash/revision (for drift detection). */
264
+ source_revision?: string;
265
+ /** Prior phase/confidence snapshot captured at promotion time. */
266
+ prior_confidence?: number;
267
+ prior_phases_alive?: number;
268
+ /** Ids of {@link PromotionEvidenceRecord}s that contributed to this promotion. */
269
+ contributing_evidence_ids?: string[];
270
+ /** For merged near-duplicates: losing entry ids preserved for audit. */
271
+ merged_from?: string[];
272
+ /** The promotion transaction/event id (ties to the hive audit-event log). */
273
+ promotion_event_id?: string;
274
+ /** Who/what initiated the promotion. */
275
+ actor: PromotionActor;
276
+ reason?: string;
277
+ /** When an override was used: the policy gates that failed. */
278
+ override_failed_gates?: string[];
279
+ }
208
280
  export interface HiveKnowledgeEntry extends KnowledgeEntryBase {
209
281
  tier: 'hive';
210
282
  confirmed_by: ProjectConfirmationRecord[];
@@ -213,6 +285,9 @@ export interface HiveKnowledgeEntry extends KnowledgeEntryBase {
213
285
  encounter_score: number;
214
286
  /** @deprecated Legacy field for backward compatibility. Use encounter_score for weighting. */
215
287
  encounter_count?: number;
288
+ /** #1847: promotion lineage + validated-evidence references. Optional so
289
+ * legacy on-disk records load unchanged; normalized in-memory on read. */
290
+ lineage?: PromotionLineage;
216
291
  }
217
292
  export interface RejectedLesson {
218
293
  id: string;
@@ -298,6 +373,18 @@ export interface KnowledgeConfig {
298
373
  * demotion counter increments for the current phase. Default: -0.3 (matches
299
374
  * `OUTCOME_BLOCK_THRESHOLD`). */
300
375
  promoted_demotion_signal_threshold: number;
376
+ /** #1847: minimum number of validated terminal-application receipts
377
+ * (PromotionEvidenceRecord) required for the `validated_terminal_applications`
378
+ * promotion gate. Default 0 — conservative: until #1849 produces real
379
+ * receipts, absence of evidence neither credits nor blocks. Raising this
380
+ * activates application-evidence gating. Legacy records get NO synthetic
381
+ * credit; they simply do not add to the count. */
382
+ promotion_min_terminal_applications: number;
383
+ /** #1847: minimum number of DISTINCT canonical cohort ids that must appear
384
+ * among the validated terminal-application receipts for the
385
+ * `validated_terminal_applications` gate. Default 0 (conservative; see
386
+ * `promotion_min_terminal_applications`). */
387
+ promotion_min_distinct_cohorts: number;
301
388
  /** Change 5: retrieval-upgrade tuning (MMR / cold-start / synonyms). */
302
389
  retrieval?: {
303
390
  mmr_lambda?: number;