opencode-swarm 7.116.1 → 7.118.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.
- package/dist/cli/{config-doctor-htzxe394.js → config-doctor-p73yfdz0.js} +2 -2
- package/dist/cli/{curator-yxmtp8py.js → curator-920swpnk.js} +10 -10
- package/dist/cli/{curator-llm-factory-9qn52fnv.js → curator-llm-factory-kqt1f4sw.js} +10 -10
- package/dist/cli/{guardrail-explain-xvyz2eag.js → guardrail-explain-bycz51b0.js} +11 -11
- package/dist/cli/{guardrail-log-dzbqcgz9.js → guardrail-log-qjqgg7pz.js} +3 -3
- package/dist/cli/{hive-promoter-ykqc60w1.js → hive-promoter-w9pgfs6w.js} +15 -10
- package/dist/cli/{index-m7hc7nn7.js → index-11q1t6g0.js} +1 -1
- package/dist/cli/{index-7kcpkm4y.js → index-34tsqvqb.js} +2527 -1354
- package/dist/cli/{index-efdyjgeh.js → index-470tykwx.js} +2 -2
- package/dist/cli/{index-g4p13g8t.js → index-5hze8ztj.js} +12 -22
- package/dist/cli/{index-m43zgtjn.js → index-bcg89pt8.js} +1 -1
- package/dist/cli/{index-8j5d3ytd.js → index-g2wrs7jy.js} +1 -1
- package/dist/cli/{index-wegc6xwr.js → index-jbdgpqpj.js} +3 -3
- package/dist/cli/{index-p9jf8gt8.js → index-k83hebh1.js} +12 -12
- package/dist/cli/{index-nvc0nsvg.js → index-pdk1n8yh.js} +2 -0
- package/dist/cli/{index-t5f01hnz.js → index-pjhfe7c3.js} +2 -2
- package/dist/cli/{index-5h7d37nf.js → index-qx5z58a7.js} +19 -31
- package/dist/cli/{index-wxkf3089.js → index-sb9x6xjj.js} +3 -3
- package/dist/cli/{index-34g1mv3c.js → index-wpgv6hht.js} +52 -6
- package/dist/cli/index.js +10 -10
- package/dist/cli/{knowledge-escalator-tthw6c9q.js → knowledge-escalator-gt1q632m.js} +4 -4
- package/dist/cli/{knowledge-events-hrbmwxxj.js → knowledge-events-0bjdtdsb.js} +2 -2
- package/dist/cli/{knowledge-store-5cns5sns.js → knowledge-store-fm2300xf.js} +7 -2
- package/dist/cli/{knowledge-validator-xre0s774.js → knowledge-validator-91kx3eqv.js} +3 -3
- package/dist/cli/{schema-bqn7g3ez.js → schema-7c484wjz.js} +1 -1
- package/dist/cli/{skill-generator-22nk6c6a.js → skill-generator-t47xyfps.js} +5 -5
- package/dist/commands/link.d.ts +7 -4
- package/dist/commands/promote.d.ts +3 -0
- package/dist/commands/registry.d.ts +2 -2
- package/dist/commands/unlink.d.ts +12 -5
- package/dist/config/schema.d.ts +8 -4
- package/dist/hooks/curator-postmortem.d.ts +1 -1
- package/dist/hooks/hive-policy.d.ts +81 -0
- package/dist/hooks/hive-promoter.d.ts +147 -28
- package/dist/hooks/hive-transaction.d.ts +113 -0
- package/dist/hooks/knowledge-events.d.ts +4 -1
- package/dist/hooks/knowledge-link.d.ts +31 -4
- package/dist/hooks/knowledge-store.d.ts +6 -5
- package/dist/hooks/knowledge-types.d.ts +87 -0
- package/dist/index.js +343 -325
- package/dist/knowledge/cohort-identity.d.ts +103 -0
- package/dist/knowledge/config-fingerprint.d.ts +57 -0
- package/dist/knowledge/family-manifest.d.ts +50 -0
- package/dist/knowledge/family-migration.d.ts +109 -0
- package/dist/knowledge/hive-paths.d.ts +51 -0
- package/dist/knowledge/identity.d.ts +12 -2
- package/dist/knowledge/index.d.ts +1 -0
- package/dist/services/knowledge-diagnostics.d.ts +45 -0
- package/dist/services/status-service.d.ts +13 -0
- package/package.json +1 -1
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
* Handles the `/swarm unlink` command.
|
|
3
3
|
*
|
|
4
4
|
* Stops sharing this worktree's swarm knowledge with its link store and returns
|
|
5
|
-
* it to a local `.swarm/knowledge.jsonl`. By default the shared
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* it to a local `.swarm/knowledge.jsonl`. By default the shared knowledge
|
|
6
|
+
* *family* (store, events, rejected, retractions, counters, quarantine,
|
|
7
|
+
* unactionable, application-legacy) is copied back into the local store per the
|
|
8
|
+
* family manifest so the worktree keeps the pooled knowledge it had access to;
|
|
9
|
+
* pass `--no-copy` to skip the copy-back.
|
|
10
|
+
*
|
|
11
|
+
* The copy-back is transactional (issue #1846): it reads the shared family
|
|
12
|
+
* under the shared-store lock and writes the local family atomically, so a
|
|
13
|
+
* concurrent append in a peer worktree cannot be lost. The shared cohort is
|
|
14
|
+
* never deleted or truncated.
|
|
8
15
|
*
|
|
9
16
|
* Usage:
|
|
10
|
-
* - /swarm unlink — unlink and copy shared
|
|
11
|
-
* - /swarm unlink --no-copy — unlink without copying shared
|
|
17
|
+
* - /swarm unlink — unlink and copy the shared family back to local.
|
|
18
|
+
* - /swarm unlink --no-copy — unlink without copying the shared family back.
|
|
12
19
|
*/
|
|
13
20
|
export declare function handleUnlinkCommand(directory: string, args: string[]): Promise<string>;
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -420,9 +420,9 @@ export declare const LintConfigSchema: z.ZodObject<{
|
|
|
420
420
|
fix: "fix";
|
|
421
421
|
}>>;
|
|
422
422
|
linter: z.ZodDefault<z.ZodEnum<{
|
|
423
|
+
auto: "auto";
|
|
423
424
|
biome: "biome";
|
|
424
425
|
eslint: "eslint";
|
|
425
|
-
auto: "auto";
|
|
426
426
|
}>>;
|
|
427
427
|
patterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
428
428
|
exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -630,6 +630,8 @@ export declare const KnowledgeConfigSchema: z.ZodObject<{
|
|
|
630
630
|
contradiction_quarantine_window_days: z.ZodDefault<z.ZodNumber>;
|
|
631
631
|
promoted_demotion_min_negative_phases: z.ZodDefault<z.ZodNumber>;
|
|
632
632
|
promoted_demotion_signal_threshold: z.ZodDefault<z.ZodNumber>;
|
|
633
|
+
promotion_min_terminal_applications: z.ZodDefault<z.ZodNumber>;
|
|
634
|
+
promotion_min_distinct_cohorts: z.ZodDefault<z.ZodNumber>;
|
|
633
635
|
realtime_learning_nudge: z.ZodDefault<z.ZodObject<{
|
|
634
636
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
635
637
|
first_after_tool_calls: z.ZodDefault<z.ZodNumber>;
|
|
@@ -1068,8 +1070,8 @@ export declare const ParallelizationConfigSchema: z.ZodObject<{
|
|
|
1068
1070
|
export type ParallelizationConfig = z.infer<typeof ParallelizationConfigSchema>;
|
|
1069
1071
|
export declare const WorktreeIsolationConfigSchema: z.ZodObject<{
|
|
1070
1072
|
policy: z.ZodDefault<z.ZodEnum<{
|
|
1071
|
-
disabled: "disabled";
|
|
1072
1073
|
auto: "auto";
|
|
1074
|
+
disabled: "disabled";
|
|
1073
1075
|
required: "required";
|
|
1074
1076
|
}>>;
|
|
1075
1077
|
merge_strategy: z.ZodDefault<z.ZodEnum<{
|
|
@@ -1852,9 +1854,9 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
1852
1854
|
fix: "fix";
|
|
1853
1855
|
}>>;
|
|
1854
1856
|
linter: z.ZodDefault<z.ZodEnum<{
|
|
1857
|
+
auto: "auto";
|
|
1855
1858
|
biome: "biome";
|
|
1856
1859
|
eslint: "eslint";
|
|
1857
|
-
auto: "auto";
|
|
1858
1860
|
}>>;
|
|
1859
1861
|
patterns: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
1860
1862
|
exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -1940,6 +1942,8 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
1940
1942
|
contradiction_quarantine_window_days: z.ZodDefault<z.ZodNumber>;
|
|
1941
1943
|
promoted_demotion_min_negative_phases: z.ZodDefault<z.ZodNumber>;
|
|
1942
1944
|
promoted_demotion_signal_threshold: z.ZodDefault<z.ZodNumber>;
|
|
1945
|
+
promotion_min_terminal_applications: z.ZodDefault<z.ZodNumber>;
|
|
1946
|
+
promotion_min_distinct_cohorts: z.ZodDefault<z.ZodNumber>;
|
|
1943
1947
|
realtime_learning_nudge: z.ZodDefault<z.ZodObject<{
|
|
1944
1948
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
1945
1949
|
first_after_tool_calls: z.ZodDefault<z.ZodNumber>;
|
|
@@ -2255,8 +2259,8 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
2255
2259
|
}, z.core.$strip>>;
|
|
2256
2260
|
worktree: z.ZodOptional<z.ZodObject<{
|
|
2257
2261
|
policy: z.ZodDefault<z.ZodEnum<{
|
|
2258
|
-
disabled: "disabled";
|
|
2259
2262
|
auto: "auto";
|
|
2263
|
+
disabled: "disabled";
|
|
2260
2264
|
required: "required";
|
|
2261
2265
|
}>>;
|
|
2262
2266
|
merge_strategy: z.ZodDefault<z.ZodEnum<{
|
|
@@ -136,7 +136,7 @@ export declare const _internals: {
|
|
|
136
136
|
applied: number;
|
|
137
137
|
skipped: number;
|
|
138
138
|
}>;
|
|
139
|
-
checkHivePromotions: (entries: SwarmKnowledgeEntry[], knowledgeConfig: KnowledgeConfig) => Promise<import("./hive-promoter.js").HivePromotionSummary>;
|
|
139
|
+
checkHivePromotions: (entries: SwarmKnowledgeEntry[], knowledgeConfig: KnowledgeConfig, directory: string) => Promise<import("./hive-promoter.js").HivePromotionSummary>;
|
|
140
140
|
applyProposalTriage: (directory: string, triage: ParsedPostMortemActions["queueTriage"]) => Promise<{
|
|
141
141
|
approved: string[];
|
|
142
142
|
rejected: string[];
|
|
@@ -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
|
-
/**
|
|
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
|
|
4
|
-
|
|
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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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
|
|
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
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
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
|
+
};
|
|
@@ -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
|
|
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. */
|
|
@@ -267,6 +268,7 @@ export interface CounterRollup {
|
|
|
267
268
|
}
|
|
268
269
|
/** Cap on retained per-entry violation timestamps. */
|
|
269
270
|
export declare const MAX_VIOLATION_TIMESTAMPS = 10;
|
|
271
|
+
declare function mergeRollupInto(target: CounterRollup, source: CounterRollup): void;
|
|
270
272
|
declare function readCounterBaseline(directory: string): Promise<Map<string, CounterRollup>>;
|
|
271
273
|
/**
|
|
272
274
|
* Recompute per-entry counters deterministically from the immutable event log,
|
|
@@ -362,5 +364,6 @@ export declare const _internals: {
|
|
|
362
364
|
appendHiveKnowledgeEvent: typeof appendHiveKnowledgeEvent;
|
|
363
365
|
recordHiveKnowledgeEvent: typeof recordHiveKnowledgeEvent;
|
|
364
366
|
readHiveKnowledgeEvents: typeof readHiveKnowledgeEvents;
|
|
367
|
+
mergeRollupInto: typeof mergeRollupInto;
|
|
365
368
|
};
|
|
366
369
|
export {};
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
*/
|
|
28
28
|
/** On-disk pointer at `<directory>/.swarm/link.json`. */
|
|
29
29
|
export interface LinkPointer {
|
|
30
|
-
version
|
|
30
|
+
/** Pointer schema version. v1 pointers stay valid; v2 carries cohort metadata. */
|
|
31
|
+
version: 1 | 2;
|
|
31
32
|
/** Path-safe identifier of the shared store (projectHash or sanitized name). */
|
|
32
33
|
linkId: string;
|
|
33
34
|
/** Human-friendly name when the link was created from an explicit name. */
|
|
@@ -36,6 +37,16 @@ export interface LinkPointer {
|
|
|
36
37
|
createdAt: string;
|
|
37
38
|
/** How the link was established. */
|
|
38
39
|
source: 'manual' | 'auto';
|
|
40
|
+
/** Canonical cohort id from `resolveCohortId` (issue #1846). */
|
|
41
|
+
cohortId?: string;
|
|
42
|
+
/** How the cohort id was derived. */
|
|
43
|
+
identitySource?: 'remote' | 'git-common-dir' | 'path';
|
|
44
|
+
/** True when the cohort id is machine-local (git-common-dir/path fallback). */
|
|
45
|
+
degraded?: boolean;
|
|
46
|
+
/** Deterministic hash of cohort-relevant config (issue #1846 §4). */
|
|
47
|
+
configFingerprint?: string;
|
|
48
|
+
/** Monotonic generation counter, bumped on each link/unlink. Aids cache invalidation. */
|
|
49
|
+
generation?: number;
|
|
39
50
|
}
|
|
40
51
|
export interface LinkedLocalKnowledgeStatus {
|
|
41
52
|
linked: boolean;
|
|
@@ -55,7 +66,13 @@ export declare function resolveLinkDir(linkId: string): string;
|
|
|
55
66
|
* project hash). Lowercased for case-insensitive stability across worktrees.
|
|
56
67
|
*/
|
|
57
68
|
export declare function sanitizeLinkId(name: string): string | null;
|
|
58
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Read and validate the link pointer for a worktree. Null if absent/invalid.
|
|
71
|
+
*
|
|
72
|
+
* Schema-version aware (issue #1846): reads `version` from disk rather than
|
|
73
|
+
* hard-stamping `1`, validates it is a known version, and preserves v2 cohort
|
|
74
|
+
* metadata fields when present. Unknown fields are ignored (forward-compat).
|
|
75
|
+
*/
|
|
59
76
|
export declare function readLinkPointer(directory: string): LinkPointer | null;
|
|
60
77
|
export declare function getLinkedLocalKnowledgeStatus(directory: string): LinkedLocalKnowledgeStatus;
|
|
61
78
|
/** Write the link pointer atomically and invalidate the resolution cache. */
|
|
@@ -67,8 +84,18 @@ export declare function removeLinkPointer(directory: string): Promise<void>;
|
|
|
67
84
|
*
|
|
68
85
|
* Returns the shared link directory when an active pointer is present, otherwise
|
|
69
86
|
* the local `<directory>/.swarm`. Fail-open: any read/parse error degrades to the
|
|
70
|
-
* local directory, so a corrupt pointer never strands knowledge. Synchronous
|
|
71
|
-
* cached
|
|
87
|
+
* local directory, so a corrupt pointer never strands knowledge. Synchronous
|
|
88
|
+
* and cached; on a cache hit a cheap `stat` of the pointer file revalidates
|
|
89
|
+
* cross-process changes so the hot retrieval path stays fast while still
|
|
90
|
+
* observing link/unlink without waiting for the TTL to expire.
|
|
91
|
+
*
|
|
92
|
+
* Cross-process revalidation (issue #1846 §2): on a cache hit we additionally
|
|
93
|
+
* `stat` the pointer file and compare `mtimeMs:ctimeMs:size` to the cached
|
|
94
|
+
* fingerprint. A change (another process wrote/removed the pointer) invalidates
|
|
95
|
+
* the entry, so a stale process observes link/unlink without waiting for the
|
|
96
|
+
* TTL to expire. The TTL remains a backstop. The per-call `stat` is negligible
|
|
97
|
+
* (the pointer is tiny and almost never changes) and is the cost of correct
|
|
98
|
+
* cross-process link/unlink observation.
|
|
72
99
|
*
|
|
73
100
|
* NOTE: when unlinked, the return value is byte-identical to the legacy
|
|
74
101
|
* `path.join(directory, '.swarm')`, so existing callers/tests are unaffected.
|
|
@@ -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
|
|
8
|
-
export declare
|
|
9
|
-
export declare
|
|
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 {};
|