opencode-swarm 7.177.0 → 7.178.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/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
- package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
- package/dist/agents/architect.d.ts +9 -1
- package/dist/cli/{capability-probe-pdwwz8gs.js → capability-probe-8k8ebeyp.js} +2 -2
- package/dist/cli/{coder-settlement-pm07553t.js → coder-settlement-d2bfb25j.js} +13 -13
- package/dist/cli/{config-doctor-3mwx2rwq.js → config-doctor-905x2s1c.js} +4 -4
- package/dist/cli/{core-ty985ae1.js → core-k7wfx1wx.js} +4 -4
- package/dist/cli/{curation-policy-d8s9w1z8.js → curation-policy-n4hd2wsa.js} +5 -5
- package/dist/cli/{curator-drift-4m984tvy.js → curator-drift-smqmej5v.js} +4 -4
- package/dist/cli/curator-llm-factory-qhhxev17.js +72 -0
- package/dist/cli/{curator-pch7ww87.js → curator-wyadq69k.js} +40 -40
- package/dist/cli/{evidence-summary-service-cxed89n3.js → evidence-summary-service-r3wcprfk.js} +19 -19
- package/dist/cli/{gate-evidence-ymye7yew.js → gate-evidence-tq9rb2aj.js} +8 -8
- package/dist/cli/guardrail-explain-kvgejh24.js +73 -0
- package/dist/cli/{guardrail-log-pgggycf9.js → guardrail-log-m6twga6x.js} +9 -9
- package/dist/cli/{guardrail-reset-aghymhr5.js → guardrail-reset-86esv8cr.js} +40 -40
- package/dist/cli/hive-promoter-s87hkb40.js +87 -0
- package/dist/cli/{index-zzr7f594.js → index-18hjycr1.js} +1 -1
- package/dist/cli/{index-w7aedfce.js → index-29begjkp.js} +8 -8
- package/dist/cli/{index-7nsvszwn.js → index-2b1whj28.js} +2 -2
- package/dist/cli/{index-7bvzssz0.js → index-2bmhe2jn.js} +4 -4
- package/dist/cli/{index-hxdswvtv.js → index-2mdk6nt9.js} +7 -7
- package/dist/cli/{index-rz4gpt3n.js → index-2vhkwy6v.js} +3 -3
- package/dist/cli/{index-fpbcxykc.js → index-6b295cas.js} +87 -1
- package/dist/cli/{index-yzjaj53h.js → index-9rwhas09.js} +1 -1
- package/dist/cli/{index-8dy3qf4q.js → index-at0ygwwp.js} +5 -5
- package/dist/cli/{index-q1jterxw.js → index-azy3tw6c.js} +2 -2
- package/dist/cli/{index-eefz8ghz.js → index-bfz1jvcn.js} +1 -1
- package/dist/cli/{index-gwnbprzk.js → index-d5dexrv1.js} +4 -4
- package/dist/cli/{index-wemtrnch.js → index-ffqy30e5.js} +2 -2
- package/dist/cli/{index-5w51tawb.js → index-gg44hytk.js} +9 -9
- package/dist/cli/{index-ebmep9me.js → index-heg4em8j.js} +591 -261
- package/dist/cli/{index-z8j7jqxt.js → index-j1v54ahd.js} +4 -3
- package/dist/cli/{index-tj0sdxg9.js → index-j3rkh9mp.js} +2 -2
- package/dist/cli/{index-zbq9091c.js → index-jzxeymr0.js} +13 -13
- package/dist/cli/{index-zwam9stk.js → index-ke3m026z.js} +6 -6
- package/dist/cli/{index-r18n5f17.js → index-kfk403mc.js} +5 -5
- package/dist/cli/{index-5frh43fj.js → index-m2n1rhgv.js} +12 -0
- package/dist/cli/{index-k6bjkqw8.js → index-mpcp6rmj.js} +1 -1
- package/dist/cli/{index-fnmt1ema.js → index-p27az2hc.js} +4 -4
- package/dist/cli/{index-5d2avnj9.js → index-pm7c6f66.js} +2 -2
- package/dist/cli/{index-vfp9hjgv.js → index-q9vb5e7n.js} +1 -1
- package/dist/cli/{index-0z5s71kn.js → index-qk29b7s4.js} +42 -42
- package/dist/cli/{index-by7xpk8f.js → index-qs8eth8h.js} +37 -6
- package/dist/cli/{index-ef2fa774.js → index-rp0jk48x.js} +7 -7
- package/dist/cli/{index-9ftc8saz.js → index-sqngtwyq.js} +1 -1
- package/dist/cli/{index-tzxk6ct7.js → index-swxaa0zs.js} +40 -40
- package/dist/cli/{index-xnednxvm.js → index-tnjmn0dn.js} +1 -1
- package/dist/cli/{index-rgapg88w.js → index-vfc7d22n.js} +7 -3
- package/dist/cli/{index-1bq6hkw5.js → index-w4yjy3vp.js} +1 -1
- package/dist/cli/{index-73arkdgf.js → index-wbkxbgk8.js} +9 -5
- package/dist/cli/{index-2jtmevrr.js → index-wfbh1bx8.js} +3 -3
- package/dist/cli/{index-4chr4gbq.js → index-wkde35ej.js} +9 -9
- package/dist/cli/{index-76qm2c2y.js → index-wvghb3x6.js} +4 -4
- package/dist/cli/{index-v2he8xfj.js → index-y6a2bkp1.js} +2 -2
- package/dist/cli/{index-eya03h5m.js → index-y74n1sy8.js} +7 -7
- package/dist/cli/{index-tbswvz8s.js → index-yeh78c1g.js} +2 -2
- package/dist/cli/{index-en0p0hqp.js → index-yj1kr0ft.js} +1 -1
- package/dist/cli/{index-nzz58tbs.js → index-zex7644g.js} +2 -2
- package/dist/cli/{index-exhm2ztr.js → index-zmb8fjp5.js} +1 -1
- package/dist/cli/{index-mcq6xs4k.js → index-zsm8h8y1.js} +4 -4
- package/dist/cli/{index-yhaxn5r7.js → index-zsp9de3k.js} +2 -2
- package/dist/cli/index.js +41 -41
- package/dist/cli/{knowledge-escalator-praedffr.js → knowledge-escalator-rgz471df.js} +13 -13
- package/dist/cli/{knowledge-events-sn5gs5q9.js → knowledge-events-4mq0ypx9.js} +11 -11
- package/dist/cli/{knowledge-link-9z3fha35.js → knowledge-link-npk50523.js} +4 -4
- package/dist/cli/{knowledge-store-838sgd97.js → knowledge-store-gft25s5t.js} +5 -5
- package/dist/cli/{knowledge-validator-bsxgarqv.js → knowledge-validator-ejqacbx4.js} +7 -7
- package/dist/cli/{mcp-skh29erk.js → mcp-emaeeq4s.js} +48 -7
- package/dist/cli/mcp.d.ts +9 -0
- package/dist/cli/{model-preflight-gk1jysa8.js → model-preflight-kh9dwts2.js} +1 -1
- package/dist/cli/{pending-delegations-6zr5es4b.js → pending-delegations-4bhkcykk.js} +11 -11
- package/dist/cli/{pr-subscriptions-x2rr3gva.js → pr-subscriptions-h1n6dbds.js} +11 -11
- package/dist/cli/{pr-workflow-gate-jx30c9vz.js → pr-workflow-gate-wn6v5za8.js} +40 -40
- package/dist/cli/{runner-yhfmejzb.js → runner-ffy5ahhz.js} +5 -5
- package/dist/cli/{scan-cursor-6h85e2g0.js → scan-cursor-e63t0g5b.js} +6 -6
- package/dist/cli/{schema-8h1b602y.js → schema-4k4hr33y.js} +4 -2
- package/dist/cli/{scope-persistence-rb99xjr0.js → scope-persistence-qcw3rtxq.js} +16 -16
- package/dist/cli/{server-kdjc5aw8.js → server-58s1wwap.js} +2055 -736
- package/dist/cli/{skill-generator-nz7wjabh.js → skill-generator-fhj13jmq.js} +15 -15
- package/dist/cli/{snapshot-coordination-init-hexx2msp.js → snapshot-coordination-init-5fawrj35.js} +40 -40
- package/dist/cli/{telemetry-w774xb8g.js → telemetry-ca8dmn26.js} +1 -1
- package/dist/cli/{workspace-snapshot-9gqna7gq.js → workspace-snapshot-v5qyjxg0.js} +3 -3
- package/dist/cli/{worktree-collision-ownership-ecwj3ehp.js → worktree-collision-ownership-hjk0jx19.js} +12 -12
- package/dist/cli/{worktree-isolation-0x9fx30v.js → worktree-isolation-aj20bs6q.js} +40 -40
- package/dist/commands/dashboard.d.ts +15 -0
- package/dist/commands/registry.d.ts +7 -0
- package/dist/config/schema.d.ts +16 -0
- package/dist/dashboard/assets.d.ts +12 -0
- package/dist/dashboard/index.d.ts +74 -0
- package/dist/dashboard/server.d.ts +94 -0
- package/dist/dashboard/views.d.ts +131 -0
- package/dist/events/core-events.d.ts +1 -1
- package/dist/hooks/delegation-gate.d.ts +26 -0
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/tool-summarizer.d.ts +7 -2
- package/dist/index.js +437 -276
- package/dist/mcp/adapters/knowledge-add.d.ts +19 -0
- package/dist/mcp/adapters/receipt-status.d.ts +3 -0
- package/dist/mcp/adapters/scope-validation.d.ts +3 -0
- package/dist/mcp/registry.d.ts +26 -14
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/write-receipts.d.ts +168 -0
- package/dist/summaries/index.d.ts +1 -1
- package/dist/summaries/manager.d.ts +27 -1
- package/dist/tools/approve-retry-sounding-board.d.ts +5 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/knowledge-receipt-status.d.ts +8 -0
- package/dist/tools/manifest.d.ts +3 -0
- package/dist/tools/scope-validate.d.ts +55 -0
- package/dist/tools/tool-metadata.d.ts +12 -0
- package/dist/types/events.d.ts +5 -1
- package/dist/utils/path-security.d.ts +21 -0
- package/opencode-swarm.schema.json +14 -0
- package/package.json +1 -1
- package/dist/cli/curator-llm-factory-zbs6hm60.js +0 -72
- package/dist/cli/guardrail-explain-1zy8c3vn.js +0 -73
- package/dist/cli/hive-promoter-3dp8qbqc.js +0 -87
|
@@ -10,106 +10,142 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
MAX_PARALLEL_VERDICT_TASKS,
|
|
12
12
|
buildSynonymIndex,
|
|
13
|
+
classifyCommand,
|
|
13
14
|
computeParallelVerdict,
|
|
14
15
|
computeQualityBudget,
|
|
15
16
|
computeSastScan,
|
|
16
17
|
createMemoryGateway,
|
|
17
18
|
createSwarmTool,
|
|
19
|
+
detectPosixWrites,
|
|
20
|
+
detectWindowsWrites,
|
|
18
21
|
expandTokens,
|
|
19
22
|
getAgentSession,
|
|
20
23
|
getCoChangePairs,
|
|
21
24
|
readSynonymMap,
|
|
22
25
|
redactSecrets,
|
|
26
|
+
resolveWriteTargets,
|
|
23
27
|
symbols
|
|
24
|
-
} from "./index-
|
|
28
|
+
} from "./index-heg4em8j.js";
|
|
25
29
|
import {
|
|
26
30
|
getProfileForFile
|
|
27
31
|
} from "./index-hnvmj9wx.js";
|
|
28
32
|
import {
|
|
29
33
|
pLimit
|
|
30
|
-
} from "./index-
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
} from "./index-q9vb5e7n.js";
|
|
35
|
+
import {
|
|
36
|
+
checkFileAuthority,
|
|
37
|
+
checkWriteTargetForSymlink
|
|
38
|
+
} from "./index-qs8eth8h.js";
|
|
39
|
+
import"./index-bfz1jvcn.js";
|
|
40
|
+
import"./index-vfc7d22n.js";
|
|
34
41
|
import {
|
|
35
42
|
_internals,
|
|
43
|
+
loadPlan,
|
|
36
44
|
loadPlanJsonOnly,
|
|
37
45
|
saveEvidence
|
|
38
|
-
} from "./index-
|
|
39
|
-
import"./index-
|
|
46
|
+
} from "./index-jzxeymr0.js";
|
|
47
|
+
import"./index-mpcp6rmj.js";
|
|
40
48
|
import"./index-txdagbh7.js";
|
|
41
49
|
import"./index-n1tcj1q7.js";
|
|
42
50
|
import {
|
|
43
51
|
containsControlChars,
|
|
52
|
+
createTargetWithinRootValidator,
|
|
44
53
|
isCanonicalPathWithinRoot,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
import"./index-
|
|
54
|
+
validateSymlinkBoundary,
|
|
55
|
+
validateTargetWithinRoot,
|
|
56
|
+
validateWorkspaceRoot
|
|
57
|
+
} from "./index-6b295cas.js";
|
|
58
|
+
import"./index-zsm8h8y1.js";
|
|
59
|
+
import"./index-at0ygwwp.js";
|
|
60
|
+
import {
|
|
61
|
+
resolveCohortId
|
|
62
|
+
} from "./index-zex7644g.js";
|
|
50
63
|
import"./index-fyfbpz4f.js";
|
|
51
|
-
import"./index-
|
|
64
|
+
import"./index-y74n1sy8.js";
|
|
52
65
|
import"./index-549wxbe1.js";
|
|
53
66
|
import"./index-zsrtt42s.js";
|
|
54
|
-
import"./index-
|
|
55
|
-
import"./index-
|
|
56
|
-
import"./index-
|
|
67
|
+
import"./index-wkde35ej.js";
|
|
68
|
+
import"./index-pm7c6f66.js";
|
|
69
|
+
import"./index-2b1whj28.js";
|
|
57
70
|
import {
|
|
58
71
|
getLanguageForExtension,
|
|
59
72
|
getParserForFile
|
|
60
73
|
} from "./index-pt7h91zc.js";
|
|
61
74
|
import"./index-q7sjgr1y.js";
|
|
62
|
-
import"./index-
|
|
63
|
-
import"./index-
|
|
64
|
-
import"./index-
|
|
75
|
+
import"./index-kfk403mc.js";
|
|
76
|
+
import"./index-p27az2hc.js";
|
|
77
|
+
import"./index-rp0jk48x.js";
|
|
65
78
|
import {
|
|
66
79
|
effectiveRetrievalOutcomes,
|
|
67
80
|
newTraceId,
|
|
68
81
|
readAuthoritativeKnowledgeCounterRollups,
|
|
69
82
|
recordKnowledgeEvent
|
|
70
|
-
} from "./index-
|
|
71
|
-
import"./index-
|
|
83
|
+
} from "./index-29begjkp.js";
|
|
84
|
+
import"./index-zmb8fjp5.js";
|
|
72
85
|
import"./index-2s7dvzff.js";
|
|
73
|
-
import
|
|
74
|
-
|
|
86
|
+
import {
|
|
87
|
+
appendUnactionable,
|
|
88
|
+
validateActionability,
|
|
89
|
+
validateActionableFields,
|
|
90
|
+
validateLesson
|
|
91
|
+
} from "./index-d5dexrv1.js";
|
|
92
|
+
import {
|
|
93
|
+
resolveWorktreeId
|
|
94
|
+
} from "./index-ffqy30e5.js";
|
|
75
95
|
import"./index-rtry5xyf.js";
|
|
76
|
-
import"./index-
|
|
96
|
+
import"./index-2vhkwy6v.js";
|
|
77
97
|
import"./index-frcemc0b.js";
|
|
78
98
|
import"./index-wrt875kh.js";
|
|
79
|
-
import
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
import"./index-
|
|
83
|
-
import"./index-
|
|
84
|
-
import"./index-
|
|
99
|
+
import {
|
|
100
|
+
normalizeScopeFiles
|
|
101
|
+
} from "./index-gg44hytk.js";
|
|
102
|
+
import"./index-ke3m026z.js";
|
|
103
|
+
import"./index-j1v54ahd.js";
|
|
104
|
+
import"./index-y6a2bkp1.js";
|
|
105
|
+
import"./index-w4yjy3vp.js";
|
|
106
|
+
import"./index-tnjmn0dn.js";
|
|
85
107
|
import"./index-rdm3px0g.js";
|
|
86
108
|
import"./index-ry16d01s.js";
|
|
87
|
-
import"./index-
|
|
109
|
+
import"./index-yeh78c1g.js";
|
|
88
110
|
import {
|
|
111
|
+
computeContentHash,
|
|
89
112
|
computeOutcomeSignal,
|
|
113
|
+
dedupeCapped,
|
|
114
|
+
findActiveSwarmNearDuplicate,
|
|
115
|
+
findNearDuplicate,
|
|
116
|
+
inferTags,
|
|
90
117
|
jaccardBigram,
|
|
91
118
|
normalize,
|
|
92
119
|
readKnowledge,
|
|
93
120
|
readRetractionRecords,
|
|
121
|
+
reinforceSwarmKnowledgeEntry,
|
|
94
122
|
resolveHiveKnowledgePath,
|
|
95
123
|
resolveSwarmKnowledgePath,
|
|
124
|
+
transactKnowledge,
|
|
96
125
|
wordBigrams
|
|
97
|
-
} from "./index-
|
|
126
|
+
} from "./index-2mdk6nt9.js";
|
|
98
127
|
import"./index-vzm6zre7.js";
|
|
99
128
|
import"./index-ae75rja9.js";
|
|
100
|
-
import"./index-
|
|
101
|
-
import
|
|
129
|
+
import"./index-yj1kr0ft.js";
|
|
130
|
+
import {
|
|
131
|
+
tryAcquireLock
|
|
132
|
+
} from "./index-j3rkh9mp.js";
|
|
102
133
|
import"./index-7g4c7s5r.js";
|
|
103
|
-
import"./index-
|
|
104
|
-
import
|
|
105
|
-
|
|
134
|
+
import"./index-wfbh1bx8.js";
|
|
135
|
+
import {
|
|
136
|
+
assertSwarmContainedTarget,
|
|
137
|
+
atomicWriteSwarmFile
|
|
138
|
+
} from "./index-zsp9de3k.js";
|
|
139
|
+
import {
|
|
140
|
+
canonicalRootKeyFresh
|
|
141
|
+
} from "./index-1xnse449.js";
|
|
106
142
|
import {
|
|
107
143
|
escapeRegex
|
|
108
144
|
} from "./index-c1ggtmpm.js";
|
|
109
145
|
import {
|
|
110
146
|
loadGateOverrides,
|
|
111
147
|
loadPluginConfigWithMeta
|
|
112
|
-
} from "./index-
|
|
148
|
+
} from "./index-18hjycr1.js";
|
|
113
149
|
import {
|
|
114
150
|
init_git_executable,
|
|
115
151
|
resolveGitExecutableAsync
|
|
@@ -119,12 +155,13 @@ import {
|
|
|
119
155
|
KnowledgeConfigSchema,
|
|
120
156
|
init_schema,
|
|
121
157
|
stripKnownSwarmPrefix
|
|
122
|
-
} from "./index-
|
|
158
|
+
} from "./index-wbkxbgk8.js";
|
|
123
159
|
import {
|
|
124
160
|
TOOL_METADATA,
|
|
125
161
|
init_tool_metadata
|
|
126
|
-
} from "./index-
|
|
162
|
+
} from "./index-m2n1rhgv.js";
|
|
127
163
|
import {
|
|
164
|
+
KNOWLEDGE_SCHEMA_VERSION,
|
|
128
165
|
init_knowledge_types,
|
|
129
166
|
isActiveStatus
|
|
130
167
|
} from "./index-bk5tah7q.js";
|
|
@@ -167,8 +204,13 @@ import {
|
|
|
167
204
|
unknown
|
|
168
205
|
} from "./index-gknz3bv1.js";
|
|
169
206
|
import"./index-zpsr8rpc.js";
|
|
170
|
-
import"./index-
|
|
171
|
-
import
|
|
207
|
+
import"./index-wvghb3x6.js";
|
|
208
|
+
import {
|
|
209
|
+
getPathFlavor,
|
|
210
|
+
init_path_identity,
|
|
211
|
+
normalizePathIdentity,
|
|
212
|
+
unsafePathTextReason
|
|
213
|
+
} from "./index-9rwhas09.js";
|
|
172
214
|
import {
|
|
173
215
|
init_logger,
|
|
174
216
|
warn
|
|
@@ -7066,7 +7108,7 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
7066
7108
|
});
|
|
7067
7109
|
|
|
7068
7110
|
// src/mcp/server.ts
|
|
7069
|
-
import
|
|
7111
|
+
import path9 from "path";
|
|
7070
7112
|
|
|
7071
7113
|
// node_modules/zod/v3/helpers/util.js
|
|
7072
7114
|
var util;
|
|
@@ -15532,615 +15574,1070 @@ function applyResponsePipeline(payload) {
|
|
|
15532
15574
|
// src/mcp/registry.ts
|
|
15533
15575
|
init_tool_metadata();
|
|
15534
15576
|
|
|
15535
|
-
// src/
|
|
15577
|
+
// src/tools/knowledge-add.ts
|
|
15536
15578
|
init_zod();
|
|
15537
|
-
import {
|
|
15538
|
-
import path5 from "path";
|
|
15539
|
-
init_schema();
|
|
15540
|
-
|
|
15541
|
-
// src/hooks/search-knowledge.ts
|
|
15542
|
-
init_schema();
|
|
15543
|
-
import { existsSync, statSync } from "fs";
|
|
15544
|
-
import path from "path";
|
|
15545
|
-
init_logger();
|
|
15546
|
-
|
|
15547
|
-
// src/hooks/knowledge-reader.ts
|
|
15548
|
-
init_logger();
|
|
15579
|
+
import { randomUUID } from "crypto";
|
|
15549
15580
|
init_knowledge_types();
|
|
15550
|
-
var
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15581
|
+
var VALID_CATEGORIES = [
|
|
15582
|
+
"process",
|
|
15583
|
+
"architecture",
|
|
15584
|
+
"tooling",
|
|
15585
|
+
"security",
|
|
15586
|
+
"testing",
|
|
15587
|
+
"debugging",
|
|
15588
|
+
"performance",
|
|
15589
|
+
"integration",
|
|
15590
|
+
"todo",
|
|
15591
|
+
"other"
|
|
15592
|
+
];
|
|
15593
|
+
var KNOWLEDGE_ADD_FIELD_CAP = 20;
|
|
15594
|
+
function mergeLessonTags(tagsInput, lesson) {
|
|
15595
|
+
return dedupeCapped([...Array.isArray(tagsInput) ? tagsInput : [], ...inferTags(lesson)], { cap: KNOWLEDGE_ADD_FIELD_CAP });
|
|
15596
|
+
}
|
|
15597
|
+
function strArray(v) {
|
|
15598
|
+
return Array.isArray(v) ? dedupeCapped(v, { cap: KNOWLEDGE_ADD_FIELD_CAP }) : undefined;
|
|
15599
|
+
}
|
|
15600
|
+
var knowledge_add = createSwarmTool({
|
|
15601
|
+
allowWorkingDirectoryOverride: true,
|
|
15602
|
+
description: "Store a new lesson in the knowledge base for future reference. The lesson will be available for retrieval via knowledge_recall.",
|
|
15603
|
+
args: {
|
|
15604
|
+
lesson: exports_external.string().min(15).max(280).describe("The lesson to store (15-280 characters)"),
|
|
15605
|
+
category: exports_external.enum(VALID_CATEGORIES).describe("Knowledge category for the lesson"),
|
|
15606
|
+
tags: exports_external.array(exports_external.string()).optional().describe("Optional tags for better searchability"),
|
|
15607
|
+
scope: exports_external.string().optional().describe("Scope of the lesson (global or stack:<name>)"),
|
|
15608
|
+
applies_to_agents: exports_external.array(exports_external.string()).optional().describe('Agent roles this lesson applies to (e.g. ["coder"]). REQUIRED (or applies_to_tools) for the lesson to become active.'),
|
|
15609
|
+
applies_to_tools: exports_external.array(exports_external.string()).optional().describe('Tool names this lesson applies to (e.g. ["edit","bash"]). REQUIRED (or applies_to_agents) for the lesson to become active.'),
|
|
15610
|
+
required_actions: exports_external.array(exports_external.string()).optional().describe("Concrete actions to always take. At least one predicate field (required_actions / forbidden_actions / verification_checks) is REQUIRED for the lesson to become active."),
|
|
15611
|
+
forbidden_actions: exports_external.array(exports_external.string()).optional().describe("Concrete actions to never take."),
|
|
15612
|
+
verification_checks: exports_external.array(exports_external.string()).optional().describe("Checks a reviewer can run to verify compliance.")
|
|
15613
|
+
},
|
|
15614
|
+
execute: async (args, directory) => {
|
|
15615
|
+
let lessonInput;
|
|
15616
|
+
let categoryInput;
|
|
15617
|
+
let tagsInput;
|
|
15618
|
+
let scopeInput;
|
|
15619
|
+
try {
|
|
15620
|
+
if (args && typeof args === "object") {
|
|
15621
|
+
const obj2 = args;
|
|
15622
|
+
lessonInput = obj2.lesson;
|
|
15623
|
+
categoryInput = obj2.category;
|
|
15624
|
+
tagsInput = obj2.tags;
|
|
15625
|
+
scopeInput = obj2.scope;
|
|
15626
|
+
}
|
|
15627
|
+
} catch {}
|
|
15628
|
+
if (typeof lessonInput !== "string") {
|
|
15629
|
+
return JSON.stringify({
|
|
15630
|
+
success: false,
|
|
15631
|
+
error: "lesson must be a string"
|
|
15632
|
+
});
|
|
15587
15633
|
}
|
|
15588
|
-
|
|
15589
|
-
|
|
15590
|
-
|
|
15591
|
-
|
|
15634
|
+
const lesson = lessonInput;
|
|
15635
|
+
if (lesson.length < 15 || lesson.length > 280) {
|
|
15636
|
+
return JSON.stringify({
|
|
15637
|
+
success: false,
|
|
15638
|
+
error: "lesson must be between 15 and 280 characters"
|
|
15639
|
+
});
|
|
15640
|
+
}
|
|
15641
|
+
if (typeof categoryInput !== "string") {
|
|
15642
|
+
return JSON.stringify({
|
|
15643
|
+
success: false,
|
|
15644
|
+
error: "category must be a string"
|
|
15645
|
+
});
|
|
15646
|
+
}
|
|
15647
|
+
const category = categoryInput;
|
|
15648
|
+
if (!VALID_CATEGORIES.includes(category)) {
|
|
15649
|
+
return JSON.stringify({
|
|
15650
|
+
success: false,
|
|
15651
|
+
error: `category must be one of: ${VALID_CATEGORIES.join(", ")}`
|
|
15652
|
+
});
|
|
15653
|
+
}
|
|
15654
|
+
const tags = mergeLessonTags(tagsInput, lesson);
|
|
15655
|
+
const scope = typeof scopeInput === "string" && scopeInput.length > 0 ? scopeInput : "global";
|
|
15656
|
+
const obj = args && typeof args === "object" ? args : {};
|
|
15657
|
+
const actionable = {
|
|
15658
|
+
applies_to_agents: strArray(obj.applies_to_agents),
|
|
15659
|
+
applies_to_tools: strArray(obj.applies_to_tools),
|
|
15660
|
+
required_actions: strArray(obj.required_actions),
|
|
15661
|
+
forbidden_actions: strArray(obj.forbidden_actions),
|
|
15662
|
+
verification_checks: strArray(obj.verification_checks)
|
|
15663
|
+
};
|
|
15664
|
+
const shape = validateActionableFields(actionable);
|
|
15665
|
+
if (!shape.valid) {
|
|
15666
|
+
return JSON.stringify({
|
|
15667
|
+
success: false,
|
|
15668
|
+
error: `invalid actionability fields: ${shape.errors.join("; ")}`
|
|
15669
|
+
});
|
|
15670
|
+
}
|
|
15671
|
+
let project_name = "";
|
|
15672
|
+
let phase_number = 1;
|
|
15673
|
+
try {
|
|
15674
|
+
const plan = await loadPlan(directory);
|
|
15675
|
+
project_name = plan?.title ?? "";
|
|
15676
|
+
if (typeof plan?.current_phase === "number") {
|
|
15677
|
+
phase_number = plan.current_phase;
|
|
15678
|
+
}
|
|
15679
|
+
} catch {}
|
|
15680
|
+
let producer = null;
|
|
15681
|
+
try {
|
|
15682
|
+
const [worktreeId, cohort] = await Promise.all([
|
|
15683
|
+
resolveWorktreeId(directory),
|
|
15684
|
+
resolveCohortId(directory)
|
|
15685
|
+
]);
|
|
15686
|
+
producer = {
|
|
15687
|
+
cohort_id: cohort.cohortId,
|
|
15688
|
+
worktree_id: worktreeId
|
|
15689
|
+
};
|
|
15690
|
+
} catch {}
|
|
15691
|
+
const nowIso = new Date().toISOString();
|
|
15692
|
+
const entry = {
|
|
15693
|
+
id: randomUUID(),
|
|
15694
|
+
tier: "swarm",
|
|
15695
|
+
lesson,
|
|
15696
|
+
category,
|
|
15697
|
+
tags,
|
|
15698
|
+
scope,
|
|
15699
|
+
confidence: 0.5,
|
|
15700
|
+
status: "candidate",
|
|
15701
|
+
confirmed_by: [],
|
|
15702
|
+
project_name,
|
|
15703
|
+
retrieval_outcomes: {
|
|
15704
|
+
applied_count: 0,
|
|
15705
|
+
succeeded_after_count: 0,
|
|
15706
|
+
failed_after_count: 0
|
|
15707
|
+
},
|
|
15708
|
+
schema_version: KNOWLEDGE_SCHEMA_VERSION,
|
|
15709
|
+
producer,
|
|
15710
|
+
revision: 1,
|
|
15711
|
+
content_hash: computeContentHash(lesson),
|
|
15712
|
+
created_at: nowIso,
|
|
15713
|
+
updated_at: nowIso,
|
|
15714
|
+
auto_generated: false,
|
|
15715
|
+
hive_eligible: false,
|
|
15716
|
+
...actionable
|
|
15717
|
+
};
|
|
15718
|
+
let config;
|
|
15719
|
+
let dedupThreshold = 0.6;
|
|
15720
|
+
try {
|
|
15721
|
+
const loaded = loadPluginConfigWithMeta(directory);
|
|
15722
|
+
config = loaded.config;
|
|
15723
|
+
dedupThreshold = config.knowledge?.dedup_threshold ?? 0.6;
|
|
15724
|
+
if (config.knowledge?.validation_enabled !== false) {
|
|
15725
|
+
const validation = validateLesson(lesson, [], {
|
|
15726
|
+
category,
|
|
15727
|
+
scope,
|
|
15728
|
+
confidence: 0.5
|
|
15729
|
+
});
|
|
15730
|
+
if (!validation.valid) {
|
|
15731
|
+
return JSON.stringify({
|
|
15732
|
+
success: false,
|
|
15733
|
+
error: `Validation failed: ${validation.reason}`
|
|
15734
|
+
});
|
|
15735
|
+
}
|
|
15736
|
+
}
|
|
15737
|
+
} catch {}
|
|
15738
|
+
const actionability = validateActionability(entry);
|
|
15739
|
+
if (!actionability.actionable) {
|
|
15740
|
+
try {
|
|
15741
|
+
await appendUnactionable(directory, entry, actionability.reason ?? "unactionable");
|
|
15742
|
+
} catch {}
|
|
15743
|
+
return JSON.stringify({
|
|
15744
|
+
success: false,
|
|
15745
|
+
quarantined: true,
|
|
15746
|
+
id: entry.id,
|
|
15747
|
+
reason: actionability.reason,
|
|
15748
|
+
hint: "Provide at least one of required_actions/forbidden_actions/verification_checks AND at least one of applies_to_agents/applies_to_tools, then retry."
|
|
15749
|
+
});
|
|
15750
|
+
}
|
|
15751
|
+
try {
|
|
15752
|
+
const maxEntries = config?.knowledge?.swarm_max_entries ?? 100;
|
|
15753
|
+
let duplicateResponse;
|
|
15754
|
+
await transactKnowledge(resolveSwarmKnowledgePath(directory), (existingEntries) => {
|
|
15755
|
+
const activeDuplicate = findActiveSwarmNearDuplicate(lesson, existingEntries, dedupThreshold);
|
|
15756
|
+
if (activeDuplicate) {
|
|
15757
|
+
const result = reinforceSwarmKnowledgeEntry(activeDuplicate, {
|
|
15758
|
+
phase_number,
|
|
15759
|
+
confirmed_at: new Date().toISOString(),
|
|
15760
|
+
project_name
|
|
15761
|
+
});
|
|
15762
|
+
duplicateResponse = {
|
|
15763
|
+
id: activeDuplicate.id,
|
|
15764
|
+
reinforced: result.reinforced,
|
|
15765
|
+
idempotent: result.reason === "already_confirmed_phase",
|
|
15766
|
+
inactive: false
|
|
15767
|
+
};
|
|
15768
|
+
return result.reinforced ? existingEntries : null;
|
|
15769
|
+
}
|
|
15770
|
+
const inactiveDuplicate = findNearDuplicate(lesson, existingEntries, dedupThreshold);
|
|
15771
|
+
if (inactiveDuplicate) {
|
|
15772
|
+
duplicateResponse = {
|
|
15773
|
+
id: inactiveDuplicate.id,
|
|
15774
|
+
reinforced: false,
|
|
15775
|
+
idempotent: false,
|
|
15776
|
+
inactive: true
|
|
15777
|
+
};
|
|
15778
|
+
return null;
|
|
15779
|
+
}
|
|
15780
|
+
const updated = [...existingEntries, entry];
|
|
15781
|
+
if (updated.length > maxEntries) {
|
|
15782
|
+
return updated.slice(updated.length - maxEntries);
|
|
15783
|
+
}
|
|
15784
|
+
return updated;
|
|
15785
|
+
});
|
|
15786
|
+
if (duplicateResponse) {
|
|
15787
|
+
if (duplicateResponse.inactive) {
|
|
15788
|
+
return JSON.stringify({
|
|
15789
|
+
success: false,
|
|
15790
|
+
id: duplicateResponse.id,
|
|
15791
|
+
message: "near-duplicate of inactive existing entry"
|
|
15792
|
+
});
|
|
15793
|
+
}
|
|
15794
|
+
return JSON.stringify({
|
|
15795
|
+
success: true,
|
|
15796
|
+
id: duplicateResponse.id,
|
|
15797
|
+
reinforced: duplicateResponse.reinforced,
|
|
15798
|
+
idempotent: duplicateResponse.idempotent,
|
|
15799
|
+
message: duplicateResponse.reinforced ? "near-duplicate reinforced existing entry" : "near-duplicate already confirmed for this phase"
|
|
15800
|
+
});
|
|
15801
|
+
}
|
|
15802
|
+
} catch (err) {
|
|
15803
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
15804
|
+
return JSON.stringify({
|
|
15805
|
+
success: false,
|
|
15806
|
+
error: message
|
|
15807
|
+
});
|
|
15592
15808
|
}
|
|
15809
|
+
return JSON.stringify({
|
|
15810
|
+
success: true,
|
|
15811
|
+
id: entry.id,
|
|
15812
|
+
category
|
|
15813
|
+
});
|
|
15814
|
+
}
|
|
15815
|
+
});
|
|
15816
|
+
|
|
15817
|
+
// src/mcp/write-receipts.ts
|
|
15818
|
+
init_zod();
|
|
15819
|
+
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
15820
|
+
import { lstat, open } from "fs/promises";
|
|
15821
|
+
import * as path from "path";
|
|
15822
|
+
var MCP_WRITE_RECEIPTS_RELATIVE_PATH = path.join(".swarm", "mcp-write-receipts.jsonl");
|
|
15823
|
+
var MCP_WRITE_RECEIPTS_ARCHIVE_RELATIVE_PATH = path.join(".swarm", "mcp-write-receipts-archive.jsonl");
|
|
15824
|
+
var PREPARED_LEASE_MS = 30000;
|
|
15825
|
+
var MAX_RECEIPT_RECORDS = 500;
|
|
15826
|
+
var MAX_RECEIPT_ARCHIVE_RECORDS = 1e4;
|
|
15827
|
+
var MAX_RECEIPT_LINE_BYTES = 16384;
|
|
15828
|
+
var MAX_RECEIPT_JOURNAL_BYTES = 512 * 1024;
|
|
15829
|
+
var MAX_RECEIPT_ARCHIVE_BYTES = 16 * 1024 * 1024;
|
|
15830
|
+
var MAX_RECEIPT_RESPONSE_BYTES = 8 * 1024;
|
|
15831
|
+
var KNOWLEDGE_ADD_POLICY_VERSION = "knowledge_add:v1";
|
|
15832
|
+
var MAX_IDEMPOTENCY_KEY_LENGTH = 128;
|
|
15833
|
+
var MAX_ARGUMENT_BYTES = 128 * 1024;
|
|
15834
|
+
var MAX_CANONICAL_NODES = 2048;
|
|
15835
|
+
var MAX_CANONICAL_ARRAY_ITEMS = 64;
|
|
15836
|
+
var MAX_CANONICAL_OBJECT_KEYS = 64;
|
|
15837
|
+
var MAX_KNOWLEDGE_ARRAY_ITEMS = 20;
|
|
15838
|
+
var MAX_KNOWLEDGE_TEXT_LENGTH = 280;
|
|
15839
|
+
var MAX_KNOWLEDGE_OPTIONAL_TEXT_LENGTH = 256;
|
|
15840
|
+
var KNOWLEDGE_CATEGORIES = [
|
|
15841
|
+
"process",
|
|
15842
|
+
"architecture",
|
|
15843
|
+
"tooling",
|
|
15844
|
+
"security",
|
|
15845
|
+
"testing",
|
|
15846
|
+
"debugging",
|
|
15847
|
+
"performance",
|
|
15848
|
+
"integration",
|
|
15849
|
+
"todo",
|
|
15850
|
+
"other"
|
|
15851
|
+
];
|
|
15852
|
+
var writeReceiptStatusInput = exports_external.object({
|
|
15853
|
+
idempotency_key: exports_external.string().min(1).max(MAX_IDEMPOTENCY_KEY_LENGTH).refine((value) => ![...value].some((character) => {
|
|
15854
|
+
const code = character.codePointAt(0) ?? 0;
|
|
15855
|
+
return code <= 31 || code === 127;
|
|
15856
|
+
}), { message: "idempotency_key must not contain control characters" })
|
|
15857
|
+
});
|
|
15858
|
+
var WRITE_RECEIPT_STATES = [
|
|
15859
|
+
"PREPARED",
|
|
15860
|
+
"COMMITTED",
|
|
15861
|
+
"FAILED_NO_EFFECT",
|
|
15862
|
+
"IN_DOUBT"
|
|
15863
|
+
];
|
|
15864
|
+
|
|
15865
|
+
class WriteReceiptError extends Error {
|
|
15866
|
+
code;
|
|
15867
|
+
constructor(code, message) {
|
|
15868
|
+
super(message);
|
|
15869
|
+
this.name = "WriteReceiptError";
|
|
15870
|
+
this.code = code;
|
|
15593
15871
|
}
|
|
15594
|
-
return ["process", "tooling"];
|
|
15595
15872
|
}
|
|
15596
|
-
|
|
15597
|
-
|
|
15598
|
-
|
|
15599
|
-
|
|
15600
|
-
|
|
15601
|
-
|
|
15602
|
-
|
|
15873
|
+
var receiptRecordSchema = exports_external.object({
|
|
15874
|
+
version: exports_external.literal(1),
|
|
15875
|
+
receipt_id: exports_external.string().regex(/^[0-9a-f-]{36}$/i),
|
|
15876
|
+
attempt_id: exports_external.string().regex(/^[0-9a-f-]{36}$/i),
|
|
15877
|
+
tool: exports_external.string().min(1).max(80),
|
|
15878
|
+
root_hash: exports_external.string().regex(/^[0-9a-f]{64}$/),
|
|
15879
|
+
idempotency_hash: exports_external.string().regex(/^[0-9a-f]{64}$/),
|
|
15880
|
+
arguments_digest: exports_external.string().regex(/^[0-9a-f]{64}$/),
|
|
15881
|
+
policy_digest: exports_external.string().regex(/^[0-9a-f]{64}$/),
|
|
15882
|
+
state: exports_external.enum(WRITE_RECEIPT_STATES),
|
|
15883
|
+
prepared_at: exports_external.number().finite().nonnegative(),
|
|
15884
|
+
updated_at: exports_external.number().finite().nonnegative(),
|
|
15885
|
+
lease_expires_at: exports_external.number().finite().nonnegative(),
|
|
15886
|
+
response: exports_external.string().max(MAX_RECEIPT_RESPONSE_BYTES).optional(),
|
|
15887
|
+
error: exports_external.string().max(MAX_RECEIPT_RESPONSE_BYTES).optional()
|
|
15888
|
+
}).strict();
|
|
15889
|
+
function journalPath(root) {
|
|
15890
|
+
return path.join(root, MCP_WRITE_RECEIPTS_RELATIVE_PATH);
|
|
15891
|
+
}
|
|
15892
|
+
function assertReceiptPaths(root) {
|
|
15893
|
+
const journal = journalPath(root);
|
|
15894
|
+
const lockDirectory = path.join(root, ".swarm", "locks");
|
|
15895
|
+
try {
|
|
15896
|
+
assertSwarmContainedTarget(journal);
|
|
15897
|
+
assertSwarmContainedTarget(path.join(lockDirectory, "mcp-write-receipts.lock"));
|
|
15898
|
+
assertSwarmContainedTarget(path.join(root, MCP_WRITE_RECEIPTS_ARCHIVE_RELATIVE_PATH));
|
|
15899
|
+
validateSymlinkBoundary(journal, root);
|
|
15900
|
+
validateSymlinkBoundary(lockDirectory, root);
|
|
15901
|
+
} catch {
|
|
15902
|
+
throw new WriteReceiptError("JOURNAL_UNAVAILABLE", "Write receipt journal path is outside the project boundary");
|
|
15603
15903
|
}
|
|
15604
|
-
|
|
15605
|
-
|
|
15606
|
-
|
|
15607
|
-
|
|
15608
|
-
|
|
15609
|
-
|
|
15610
|
-
|
|
15611
|
-
|
|
15612
|
-
|
|
15613
|
-
|
|
15904
|
+
}
|
|
15905
|
+
function getWriteReceiptArchivePath(root) {
|
|
15906
|
+
return path.join(root, MCP_WRITE_RECEIPTS_ARCHIVE_RELATIVE_PATH);
|
|
15907
|
+
}
|
|
15908
|
+
function hash(value) {
|
|
15909
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
15910
|
+
}
|
|
15911
|
+
function canonicalRoot(root) {
|
|
15912
|
+
return canonicalRootKeyFresh(root);
|
|
15913
|
+
}
|
|
15914
|
+
function hasControlCharacters(value) {
|
|
15915
|
+
for (const character of value) {
|
|
15916
|
+
const code = character.codePointAt(0);
|
|
15917
|
+
if (code !== undefined && (code <= 31 || code === 127))
|
|
15918
|
+
return true;
|
|
15614
15919
|
}
|
|
15615
|
-
|
|
15616
|
-
|
|
15617
|
-
|
|
15618
|
-
|
|
15920
|
+
return false;
|
|
15921
|
+
}
|
|
15922
|
+
function assertSafeRequest(request) {
|
|
15923
|
+
if (!request.root || !path.isAbsolute(request.root)) {
|
|
15924
|
+
throw new WriteReceiptError("INVALID_REQUEST", "Write receipt requires an absolute project root");
|
|
15925
|
+
}
|
|
15926
|
+
if (!request.tool || request.tool.length > 80 || hasControlCharacters(request.tool)) {
|
|
15927
|
+
throw new WriteReceiptError("INVALID_REQUEST", "Write receipt tool identity is invalid");
|
|
15928
|
+
}
|
|
15929
|
+
if (!request.idempotencyKey || request.idempotencyKey.length > MAX_IDEMPOTENCY_KEY_LENGTH || hasControlCharacters(request.idempotencyKey)) {
|
|
15930
|
+
throw new WriteReceiptError("INVALID_REQUEST", "Idempotency key is empty, too long, or contains control characters");
|
|
15931
|
+
}
|
|
15932
|
+
}
|
|
15933
|
+
function stableValue(value, depth = 0, budget) {
|
|
15934
|
+
const state = budget ?? { nodes: 0, bytes: 0 };
|
|
15935
|
+
state.nodes += 1;
|
|
15936
|
+
if (state.nodes > MAX_CANONICAL_NODES) {
|
|
15937
|
+
throw new WriteReceiptError("INVALID_REQUEST", "Write arguments exceed the bounded receipt digest input");
|
|
15938
|
+
}
|
|
15939
|
+
if (depth > 16)
|
|
15940
|
+
return "[depth-limited]";
|
|
15941
|
+
if (value === null || typeof value === "boolean") {
|
|
15942
|
+
return value;
|
|
15943
|
+
}
|
|
15944
|
+
if (typeof value === "string") {
|
|
15945
|
+
state.bytes += Buffer.byteLength(value, "utf8");
|
|
15946
|
+
if (state.bytes > MAX_ARGUMENT_BYTES) {
|
|
15947
|
+
throw new WriteReceiptError("INVALID_REQUEST", "Write arguments exceed the bounded receipt digest input");
|
|
15619
15948
|
}
|
|
15620
|
-
|
|
15621
|
-
const isHiveNearDup = hiveEntries.some((hiveEntry) => jaccardBigram(swarmBigrams, wordBigrams(normalize(hiveEntry.lesson))) >= JACCARD_THRESHOLD);
|
|
15622
|
-
if (isHiveNearDup)
|
|
15623
|
-
continue;
|
|
15624
|
-
const isSwarmNearDup = merged.some((m) => m.tier === "swarm" && jaccardBigram(swarmBigrams, wordBigrams(normalize(m.lesson))) >= JACCARD_THRESHOLD);
|
|
15625
|
-
if (isSwarmNearDup)
|
|
15626
|
-
continue;
|
|
15627
|
-
seenLessons.add(normalized);
|
|
15628
|
-
merged.push({
|
|
15629
|
-
...entry,
|
|
15630
|
-
relevanceScore: { category: 0, confidence: 0, keywords: 0 },
|
|
15631
|
-
finalScore: 0
|
|
15632
|
-
});
|
|
15949
|
+
return value;
|
|
15633
15950
|
}
|
|
15634
|
-
|
|
15635
|
-
|
|
15636
|
-
|
|
15637
|
-
|
|
15638
|
-
|
|
15639
|
-
|
|
15640
|
-
|
|
15641
|
-
|
|
15642
|
-
|
|
15643
|
-
|
|
15644
|
-
} else if (entry.category === "process") {
|
|
15645
|
-
categoryScore = 0.5;
|
|
15646
|
-
}
|
|
15647
|
-
} else {
|
|
15648
|
-
categoryScore = 0.5;
|
|
15951
|
+
if (typeof value === "number") {
|
|
15952
|
+
return Number.isFinite(value) ? value : "[non-finite]";
|
|
15953
|
+
}
|
|
15954
|
+
if (typeof value === "bigint")
|
|
15955
|
+
return "[bigint]";
|
|
15956
|
+
if (typeof value === "undefined")
|
|
15957
|
+
return "[undefined]";
|
|
15958
|
+
if (Array.isArray(value)) {
|
|
15959
|
+
if (value.length > MAX_CANONICAL_ARRAY_ITEMS) {
|
|
15960
|
+
throw new WriteReceiptError("INVALID_REQUEST", "Write arguments exceed the bounded receipt digest input");
|
|
15649
15961
|
}
|
|
15650
|
-
|
|
15651
|
-
|
|
15652
|
-
|
|
15653
|
-
|
|
15654
|
-
|
|
15655
|
-
|
|
15656
|
-
|
|
15962
|
+
return value.map((item) => stableValue(item, depth + 1, state));
|
|
15963
|
+
}
|
|
15964
|
+
if (typeof value === "object") {
|
|
15965
|
+
const record3 = value;
|
|
15966
|
+
const keys = Object.keys(record3).sort();
|
|
15967
|
+
if (keys.length > MAX_CANONICAL_OBJECT_KEYS) {
|
|
15968
|
+
throw new WriteReceiptError("INVALID_REQUEST", "Write arguments exceed the bounded receipt digest input");
|
|
15657
15969
|
}
|
|
15658
|
-
|
|
15659
|
-
|
|
15660
|
-
|
|
15661
|
-
|
|
15662
|
-
|
|
15663
|
-
|
|
15970
|
+
const result = Object.create(null);
|
|
15971
|
+
for (const key of keys) {
|
|
15972
|
+
state.bytes += Buffer.byteLength(key, "utf8");
|
|
15973
|
+
if (state.bytes > MAX_ARGUMENT_BYTES) {
|
|
15974
|
+
throw new WriteReceiptError("INVALID_REQUEST", "Write arguments exceed the bounded receipt digest input");
|
|
15975
|
+
}
|
|
15976
|
+
const descriptor = Object.getOwnPropertyDescriptor(record3, key);
|
|
15977
|
+
result[key] = stableValue(descriptor && "value" in descriptor ? descriptor.value : "[accessor]", depth + 1, state);
|
|
15664
15978
|
}
|
|
15665
|
-
|
|
15666
|
-
|
|
15667
|
-
|
|
15668
|
-
category: categoryScore,
|
|
15669
|
-
confidence: confidenceScore,
|
|
15670
|
-
keywords: keywordsScore
|
|
15671
|
-
};
|
|
15672
|
-
return {
|
|
15673
|
-
...entry,
|
|
15674
|
-
relevanceScore,
|
|
15675
|
-
finalScore: Math.min(Math.max(finalScore, 0), 1)
|
|
15676
|
-
};
|
|
15677
|
-
});
|
|
15678
|
-
ranked.sort((a, b) => {
|
|
15679
|
-
const scoreDiff = b.finalScore - a.finalScore;
|
|
15680
|
-
if (Math.abs(scoreDiff) > 0.001)
|
|
15681
|
-
return scoreDiff;
|
|
15682
|
-
const dateA = new Date(a.created_at).getTime();
|
|
15683
|
-
const dateB = new Date(b.created_at).getTime();
|
|
15684
|
-
return dateB - dateA;
|
|
15685
|
-
});
|
|
15686
|
-
const maxInject = config.max_inject_count ?? 5;
|
|
15687
|
-
const topN = ranked.slice(0, maxInject);
|
|
15688
|
-
return topN;
|
|
15979
|
+
return result;
|
|
15980
|
+
}
|
|
15981
|
+
return `[${typeof value}]`;
|
|
15689
15982
|
}
|
|
15690
|
-
function
|
|
15691
|
-
|
|
15983
|
+
function canonicalArguments(value) {
|
|
15984
|
+
const normalized = stableValue(value, 0, { nodes: 0, bytes: 0 });
|
|
15985
|
+
const encoded = JSON.stringify(normalized);
|
|
15986
|
+
if (encoded === undefined || Buffer.byteLength(encoded, "utf8") > MAX_ARGUMENT_BYTES) {
|
|
15987
|
+
throw new WriteReceiptError("INVALID_REQUEST", "Write arguments exceed the bounded receipt digest input");
|
|
15988
|
+
}
|
|
15989
|
+
return encoded;
|
|
15692
15990
|
}
|
|
15693
|
-
function
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15991
|
+
function computeWriteReceiptIdentity(request) {
|
|
15992
|
+
assertSafeRequest(request);
|
|
15993
|
+
return {
|
|
15994
|
+
root_hash: hash(canonicalRoot(request.root)),
|
|
15995
|
+
idempotency_hash: hash(request.idempotencyKey),
|
|
15996
|
+
arguments_digest: hash(canonicalArguments(request.arguments)),
|
|
15997
|
+
policy_digest: hash(request.policy ?? KNOWLEDGE_ADD_POLICY_VERSION)
|
|
15998
|
+
};
|
|
15698
15999
|
}
|
|
15699
|
-
function
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
if (
|
|
15706
|
-
|
|
15707
|
-
|
|
15708
|
-
|
|
15709
|
-
|
|
15710
|
-
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
|
|
15714
|
-
|
|
15715
|
-
|
|
15716
|
-
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
const haystack = tokenizeContext(ctx);
|
|
15729
|
-
const triggerHit = entry.triggers && entry.triggers.length > 0 ? anyMatch(haystack, entry.triggers) : false;
|
|
15730
|
-
const actionHit = entry.applies_to_tools && entry.applies_to_tools.length > 0 ? entry.applies_to_tools.map(lc).some((t) => t === lc(ctx.currentTool) || t === lc(ctx.currentAction)) : false;
|
|
15731
|
-
const agentHit = entry.applies_to_agents && entry.applies_to_agents.length > 0 ? entry.applies_to_agents.map(lc).some((a) => a === lc(ctx.targetAgent)) : false;
|
|
15732
|
-
let score = 0;
|
|
15733
|
-
if (triggerHit)
|
|
15734
|
-
score += 0.5;
|
|
15735
|
-
if (actionHit)
|
|
15736
|
-
score += 0.35;
|
|
15737
|
-
if (agentHit)
|
|
15738
|
-
score += 0.25;
|
|
15739
|
-
if (entry.directive_priority === "critical")
|
|
15740
|
-
score += 0.4;
|
|
15741
|
-
else if (entry.directive_priority === "high")
|
|
15742
|
-
score += 0.2;
|
|
15743
|
-
else if (entry.directive_priority === "medium")
|
|
15744
|
-
score += 0.1;
|
|
15745
|
-
return { triggerHit, actionHit, agentHit, score: Math.min(1, score) };
|
|
16000
|
+
function sanitizeString(value, forbidden) {
|
|
16001
|
+
let result = redactSecrets(value);
|
|
16002
|
+
for (const secret of forbidden) {
|
|
16003
|
+
if (secret.length > 0)
|
|
16004
|
+
result = result.split(secret).join("[redacted]");
|
|
16005
|
+
}
|
|
16006
|
+
if (Buffer.byteLength(result, "utf8") <= MAX_RECEIPT_RESPONSE_BYTES)
|
|
16007
|
+
return result;
|
|
16008
|
+
const suffix = "...[bounded]";
|
|
16009
|
+
const bytes = Buffer.from(result, "utf8");
|
|
16010
|
+
const suffixBytes = Buffer.byteLength(suffix, "utf8");
|
|
16011
|
+
if (MAX_RECEIPT_RESPONSE_BYTES <= suffixBytes) {
|
|
16012
|
+
return bytes.subarray(0, MAX_RECEIPT_RESPONSE_BYTES).toString("utf8");
|
|
16013
|
+
}
|
|
16014
|
+
let low = 0;
|
|
16015
|
+
let high = Math.min(bytes.length, MAX_RECEIPT_RESPONSE_BYTES - suffixBytes);
|
|
16016
|
+
let best = "";
|
|
16017
|
+
while (low <= high) {
|
|
16018
|
+
const middle = Math.floor((low + high) / 2);
|
|
16019
|
+
const candidate = bytes.subarray(0, middle).toString("utf8");
|
|
16020
|
+
if (Buffer.byteLength(candidate, "utf8") <= MAX_RECEIPT_RESPONSE_BYTES - suffixBytes) {
|
|
16021
|
+
best = candidate;
|
|
16022
|
+
low = middle + 1;
|
|
16023
|
+
} else {
|
|
16024
|
+
high = middle - 1;
|
|
16025
|
+
}
|
|
16026
|
+
}
|
|
16027
|
+
const bounded = best + suffix;
|
|
16028
|
+
return Buffer.byteLength(bounded, "utf8") <= MAX_RECEIPT_RESPONSE_BYTES ? bounded : suffix;
|
|
15746
16029
|
}
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
|
|
15757
|
-
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
|
|
16030
|
+
var RECEIPT_RESULT_KEYS = new Set([
|
|
16031
|
+
"success",
|
|
16032
|
+
"error",
|
|
16033
|
+
"quarantined",
|
|
16034
|
+
"id",
|
|
16035
|
+
"reason",
|
|
16036
|
+
"hint",
|
|
16037
|
+
"message",
|
|
16038
|
+
"reinforced",
|
|
16039
|
+
"idempotent",
|
|
16040
|
+
"inactive",
|
|
16041
|
+
"category",
|
|
16042
|
+
"receipt_status",
|
|
16043
|
+
"replayed",
|
|
16044
|
+
"duplicate",
|
|
16045
|
+
"committed",
|
|
16046
|
+
"result"
|
|
16047
|
+
]);
|
|
16048
|
+
var RECEIPT_RESULT_UNREDACTED_VALUES = new Map([
|
|
16049
|
+
["category", new Set(KNOWLEDGE_CATEGORIES)]
|
|
16050
|
+
]);
|
|
16051
|
+
function sanitizeResultValue(value, forbidden, depth = 0, fieldName) {
|
|
16052
|
+
if (depth > 8)
|
|
16053
|
+
return "[depth-limited]";
|
|
16054
|
+
if (typeof value === "string") {
|
|
16055
|
+
const unredactedValues = fieldName ? RECEIPT_RESULT_UNREDACTED_VALUES.get(fieldName) : undefined;
|
|
16056
|
+
if (unredactedValues?.has(value))
|
|
16057
|
+
return value;
|
|
16058
|
+
return sanitizeString(value, forbidden);
|
|
16059
|
+
}
|
|
16060
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
16061
|
+
return value;
|
|
16062
|
+
}
|
|
16063
|
+
if (Array.isArray(value)) {
|
|
16064
|
+
return value.slice(0, 32).map((item) => sanitizeResultValue(item, forbidden, depth + 1));
|
|
16065
|
+
}
|
|
16066
|
+
if (typeof value === "object" && value !== null) {
|
|
16067
|
+
const record3 = value;
|
|
16068
|
+
const result = Object.create(null);
|
|
16069
|
+
for (const key of Object.keys(record3).slice(0, 64)) {
|
|
16070
|
+
if (!RECEIPT_RESULT_KEYS.has(key))
|
|
16071
|
+
continue;
|
|
16072
|
+
const descriptor = Object.getOwnPropertyDescriptor(record3, key);
|
|
16073
|
+
result[key] = sanitizeResultValue(descriptor && "value" in descriptor ? descriptor.value : undefined, forbidden, depth + 1, key);
|
|
16074
|
+
}
|
|
16075
|
+
return result;
|
|
16076
|
+
}
|
|
16077
|
+
return `[${typeof value}]`;
|
|
15761
16078
|
}
|
|
15762
|
-
function
|
|
15763
|
-
|
|
15764
|
-
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
16079
|
+
function collectForbiddenStrings(value, forbidden, depth = 0, nodes = { count: 0 }) {
|
|
16080
|
+
if (nodes.count >= MAX_CANONICAL_NODES || depth > 16)
|
|
16081
|
+
return;
|
|
16082
|
+
nodes.count += 1;
|
|
16083
|
+
if (typeof value === "string") {
|
|
16084
|
+
if (value.length > 0)
|
|
16085
|
+
forbidden.add(value);
|
|
16086
|
+
return;
|
|
16087
|
+
}
|
|
16088
|
+
if (Array.isArray(value)) {
|
|
16089
|
+
for (const item of value.slice(0, MAX_CANONICAL_ARRAY_ITEMS)) {
|
|
16090
|
+
collectForbiddenStrings(item, forbidden, depth + 1, nodes);
|
|
16091
|
+
}
|
|
16092
|
+
return;
|
|
16093
|
+
}
|
|
16094
|
+
if (typeof value !== "object" || value === null)
|
|
16095
|
+
return;
|
|
16096
|
+
for (const key of Object.keys(value).slice(0, MAX_CANONICAL_OBJECT_KEYS)) {
|
|
16097
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
16098
|
+
if (descriptor && "value" in descriptor) {
|
|
16099
|
+
collectForbiddenStrings(descriptor.value, forbidden, depth + 1, nodes);
|
|
16100
|
+
}
|
|
15770
16101
|
}
|
|
15771
|
-
return phases.size > 0 ? phases.size : cb.length;
|
|
15772
16102
|
}
|
|
15773
|
-
function
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
return
|
|
16103
|
+
function requestForbiddenStrings(request) {
|
|
16104
|
+
const forbidden = new Set([request.root, request.idempotencyKey]);
|
|
16105
|
+
collectForbiddenStrings(request.arguments, forbidden);
|
|
16106
|
+
return [...forbidden];
|
|
15777
16107
|
}
|
|
15778
|
-
function
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15786
|
-
bigrams.set(e.id, s);
|
|
16108
|
+
function sanitizeReceiptResponse(value, request) {
|
|
16109
|
+
const forbidden = request ? requestForbiddenStrings(request) : [];
|
|
16110
|
+
let parsed = value;
|
|
16111
|
+
if (typeof value === "string") {
|
|
16112
|
+
try {
|
|
16113
|
+
parsed = JSON.parse(value);
|
|
16114
|
+
} catch {
|
|
16115
|
+
return sanitizeString(value, forbidden);
|
|
15787
16116
|
}
|
|
15788
|
-
|
|
16117
|
+
}
|
|
16118
|
+
const sanitized = sanitizeResultValue(parsed, forbidden);
|
|
16119
|
+
let encoded;
|
|
16120
|
+
try {
|
|
16121
|
+
encoded = JSON.stringify(sanitized) || "null";
|
|
16122
|
+
} catch {
|
|
16123
|
+
encoded = '{"success":false,"error":"response unavailable"}';
|
|
16124
|
+
}
|
|
16125
|
+
if (Buffer.byteLength(encoded, "utf8") <= MAX_RECEIPT_RESPONSE_BYTES)
|
|
16126
|
+
return encoded;
|
|
16127
|
+
return '{"success":false,"error":"response exceeded receipt bound"}';
|
|
16128
|
+
}
|
|
16129
|
+
function parseReceiptResponse(response) {
|
|
16130
|
+
try {
|
|
16131
|
+
return JSON.parse(response);
|
|
16132
|
+
} catch {
|
|
16133
|
+
return response;
|
|
16134
|
+
}
|
|
16135
|
+
}
|
|
16136
|
+
async function readWriteReceiptStatus(root, idempotencyKey) {
|
|
16137
|
+
const request = {
|
|
16138
|
+
root,
|
|
16139
|
+
tool: "knowledge_add",
|
|
16140
|
+
idempotencyKey,
|
|
16141
|
+
arguments: {}
|
|
15789
16142
|
};
|
|
15790
|
-
const
|
|
15791
|
-
|
|
15792
|
-
const
|
|
15793
|
-
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
|
|
16143
|
+
const identity = computeWriteReceiptIdentity(request);
|
|
16144
|
+
assertReceiptPaths(root);
|
|
16145
|
+
const storage = await readReceiptStorage(root);
|
|
16146
|
+
const records = [...storage.archive, ...storage.active];
|
|
16147
|
+
const record3 = [...records].reverse().find((candidate) => candidate.tool === request.tool && candidate.root_hash === identity.root_hash && candidate.idempotency_hash === identity.idempotency_hash);
|
|
16148
|
+
if (!record3) {
|
|
16149
|
+
return { found: false, status: "NOT_FOUND", retryable: true };
|
|
16150
|
+
}
|
|
16151
|
+
return {
|
|
16152
|
+
found: true,
|
|
16153
|
+
status: record3.state,
|
|
16154
|
+
tool: record3.tool,
|
|
16155
|
+
receipt_id: record3.receipt_id,
|
|
16156
|
+
attempt_id: record3.attempt_id,
|
|
16157
|
+
updated_at: record3.updated_at,
|
|
16158
|
+
lease_expires_at: record3.lease_expires_at,
|
|
16159
|
+
archived: storage.archive.includes(record3),
|
|
16160
|
+
retryable: false
|
|
16161
|
+
};
|
|
16162
|
+
}
|
|
16163
|
+
async function readJournal(filePath, limits) {
|
|
16164
|
+
let content;
|
|
16165
|
+
let info;
|
|
16166
|
+
try {
|
|
16167
|
+
info = await lstat(filePath);
|
|
16168
|
+
} catch (error) {
|
|
16169
|
+
if (error?.code === "ENOENT")
|
|
16170
|
+
return [];
|
|
16171
|
+
throw new WriteReceiptError("JOURNAL_UNAVAILABLE", "Write receipt journal could not be read");
|
|
16172
|
+
}
|
|
16173
|
+
if (!info.isFile() || info.isSymbolicLink()) {
|
|
16174
|
+
throw new WriteReceiptError("JOURNAL_UNAVAILABLE", "Write receipt journal could not be read");
|
|
16175
|
+
}
|
|
16176
|
+
if (info.size > limits.maxBytes) {
|
|
16177
|
+
throw new WriteReceiptError("JOURNAL_CAPACITY", "Write receipt journal exceeds its bounded capacity");
|
|
16178
|
+
}
|
|
16179
|
+
let handle;
|
|
16180
|
+
try {
|
|
16181
|
+
handle = await open(filePath, "r");
|
|
16182
|
+
const buffer = Buffer.alloc(limits.maxBytes + 1);
|
|
16183
|
+
let offset = 0;
|
|
16184
|
+
while (offset < buffer.length) {
|
|
16185
|
+
const read = await handle.read(buffer, offset, buffer.length - offset, null);
|
|
16186
|
+
if (read.bytesRead === 0)
|
|
16187
|
+
break;
|
|
16188
|
+
offset += read.bytesRead;
|
|
15811
16189
|
}
|
|
15812
|
-
if (
|
|
15813
|
-
|
|
15814
|
-
|
|
15815
|
-
|
|
15816
|
-
|
|
16190
|
+
if (offset > limits.maxBytes) {
|
|
16191
|
+
throw new WriteReceiptError("JOURNAL_CAPACITY", "Write receipt journal exceeds its bounded capacity");
|
|
16192
|
+
}
|
|
16193
|
+
content = buffer.subarray(0, offset).toString("utf8");
|
|
16194
|
+
} catch (error) {
|
|
16195
|
+
if (error instanceof WriteReceiptError)
|
|
16196
|
+
throw error;
|
|
16197
|
+
throw new WriteReceiptError("JOURNAL_UNAVAILABLE", "Write receipt journal could not be read");
|
|
16198
|
+
} finally {
|
|
16199
|
+
try {
|
|
16200
|
+
await handle?.close();
|
|
16201
|
+
} catch {}
|
|
15817
16202
|
}
|
|
15818
|
-
|
|
16203
|
+
if (content.length > 0 && !content.endsWith(`
|
|
16204
|
+
`)) {
|
|
16205
|
+
throw new WriteReceiptError("JOURNAL_CORRUPT", "Write receipt journal has a truncated final record");
|
|
16206
|
+
}
|
|
16207
|
+
const lines = content.split(`
|
|
16208
|
+
`);
|
|
16209
|
+
if (lines.length > limits.maxRecords + 1) {
|
|
16210
|
+
throw new WriteReceiptError("JOURNAL_CAPACITY", "Write receipt journal exceeds its bounded record capacity");
|
|
16211
|
+
}
|
|
16212
|
+
const records = [];
|
|
16213
|
+
for (const line of lines) {
|
|
16214
|
+
if (!line)
|
|
16215
|
+
continue;
|
|
16216
|
+
if (Buffer.byteLength(line, "utf8") > MAX_RECEIPT_LINE_BYTES) {
|
|
16217
|
+
throw new WriteReceiptError("JOURNAL_CAPACITY", "Write receipt record exceeds its bounded line size");
|
|
16218
|
+
}
|
|
16219
|
+
let parsed;
|
|
16220
|
+
try {
|
|
16221
|
+
parsed = JSON.parse(line);
|
|
16222
|
+
} catch {
|
|
16223
|
+
throw new WriteReceiptError("JOURNAL_CORRUPT", "Write receipt journal contains invalid JSON");
|
|
16224
|
+
}
|
|
16225
|
+
const result = receiptRecordSchema.safeParse(parsed);
|
|
16226
|
+
if (!result.success) {
|
|
16227
|
+
throw new WriteReceiptError("JOURNAL_CORRUPT", "Write receipt journal contains an invalid record");
|
|
16228
|
+
}
|
|
16229
|
+
records.push(result.data);
|
|
16230
|
+
}
|
|
16231
|
+
return records;
|
|
15819
16232
|
}
|
|
15820
|
-
function
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
16233
|
+
async function readReceiptStorage(root) {
|
|
16234
|
+
const [archive, active] = await Promise.all([
|
|
16235
|
+
readJournal(getWriteReceiptArchivePath(root), {
|
|
16236
|
+
maxBytes: MAX_RECEIPT_ARCHIVE_BYTES,
|
|
16237
|
+
maxRecords: MAX_RECEIPT_ARCHIVE_RECORDS
|
|
16238
|
+
}),
|
|
16239
|
+
readJournal(journalPath(root), {
|
|
16240
|
+
maxBytes: MAX_RECEIPT_JOURNAL_BYTES,
|
|
16241
|
+
maxRecords: MAX_RECEIPT_RECORDS
|
|
16242
|
+
})
|
|
16243
|
+
]);
|
|
16244
|
+
return { archive, active };
|
|
15828
16245
|
}
|
|
15829
|
-
function
|
|
15830
|
-
|
|
15831
|
-
return 0.15;
|
|
15832
|
-
if (status === "established")
|
|
15833
|
-
return 0.1;
|
|
15834
|
-
return 0;
|
|
16246
|
+
function isUnresolved(record3) {
|
|
16247
|
+
return record3.state === "PREPARED" || record3.state === "IN_DOUBT";
|
|
15835
16248
|
}
|
|
15836
|
-
|
|
15837
|
-
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
|
|
15851
|
-
}
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
const
|
|
15857
|
-
|
|
15858
|
-
|
|
15859
|
-
|
|
15860
|
-
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
16249
|
+
function serializeReceiptRecords(records, limits) {
|
|
16250
|
+
if (records.length > limits.maxRecords) {
|
|
16251
|
+
throw new WriteReceiptError("JOURNAL_CAPACITY", "Write receipt journal exceeds its bounded record capacity");
|
|
16252
|
+
}
|
|
16253
|
+
const lines = records.map((item) => JSON.stringify(item));
|
|
16254
|
+
for (const line of lines) {
|
|
16255
|
+
if (Buffer.byteLength(line, "utf8") > MAX_RECEIPT_LINE_BYTES) {
|
|
16256
|
+
throw new WriteReceiptError("JOURNAL_CAPACITY", "Write receipt record exceeds its bounded line size");
|
|
16257
|
+
}
|
|
16258
|
+
}
|
|
16259
|
+
const content = lines.join(`
|
|
16260
|
+
`) + (lines.length > 0 ? `
|
|
16261
|
+
` : "");
|
|
16262
|
+
if (Buffer.byteLength(content, "utf8") > limits.maxBytes) {
|
|
16263
|
+
throw new WriteReceiptError("JOURNAL_CAPACITY", "Write receipt journal exceeds its bounded byte capacity");
|
|
16264
|
+
}
|
|
16265
|
+
return content;
|
|
16266
|
+
}
|
|
16267
|
+
async function writeReceiptStorage(root, records, hooks) {
|
|
16268
|
+
const latestByAttempt = new Map;
|
|
16269
|
+
for (const record3 of records)
|
|
16270
|
+
latestByAttempt.set(record3.attempt_id, record3);
|
|
16271
|
+
const unresolved = records.filter((record3) => isUnresolved(record3) && latestByAttempt.get(record3.attempt_id) === record3);
|
|
16272
|
+
const terminal = records.filter((record3) => !unresolved.includes(record3));
|
|
16273
|
+
const activeTerminalCount = Math.max(0, MAX_RECEIPT_RECORDS - unresolved.length);
|
|
16274
|
+
const activeRecords = new Set([
|
|
16275
|
+
...unresolved,
|
|
16276
|
+
...terminal.slice(-activeTerminalCount)
|
|
16277
|
+
]);
|
|
16278
|
+
const active = records.filter((record3) => activeRecords.has(record3));
|
|
16279
|
+
const archive = records.filter((record3) => !activeRecords.has(record3));
|
|
16280
|
+
const activeContent = serializeReceiptRecords(active, {
|
|
16281
|
+
maxBytes: MAX_RECEIPT_JOURNAL_BYTES,
|
|
16282
|
+
maxRecords: MAX_RECEIPT_RECORDS
|
|
16283
|
+
});
|
|
16284
|
+
const archiveContent = serializeReceiptRecords(archive, {
|
|
16285
|
+
maxBytes: MAX_RECEIPT_ARCHIVE_BYTES,
|
|
16286
|
+
maxRecords: MAX_RECEIPT_ARCHIVE_RECORDS
|
|
16287
|
+
});
|
|
16288
|
+
if (archive.length > 0 || archiveContent.length > 0) {
|
|
16289
|
+
await hooks?.persistStorage?.("archive", archive);
|
|
16290
|
+
await atomicWriteSwarmFile(getWriteReceiptArchivePath(root), archiveContent, {
|
|
16291
|
+
maxBytes: MAX_RECEIPT_ARCHIVE_BYTES
|
|
15875
16292
|
});
|
|
15876
|
-
|
|
15877
|
-
|
|
15878
|
-
|
|
15879
|
-
|
|
15880
|
-
|
|
15881
|
-
return false;
|
|
15882
|
-
if (tier === "hive" && e.tier !== "hive")
|
|
15883
|
-
return false;
|
|
15884
|
-
return true;
|
|
16293
|
+
}
|
|
16294
|
+
try {
|
|
16295
|
+
await hooks?.persistStorage?.("active", active);
|
|
16296
|
+
await atomicWriteSwarmFile(journalPath(root), activeContent, {
|
|
16297
|
+
maxBytes: MAX_RECEIPT_JOURNAL_BYTES
|
|
15885
16298
|
});
|
|
15886
|
-
|
|
15887
|
-
if (
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
if (role === "architect")
|
|
15892
|
-
return true;
|
|
15893
|
-
return e.applies_to_agents.map((a) => stripKnownSwarmPrefix(a).toLowerCase()).includes(role);
|
|
15894
|
-
});
|
|
15895
|
-
}
|
|
15896
|
-
const queryText = (query ?? "").trim();
|
|
15897
|
-
const queryBigrams = queryText ? wordBigrams(normalize(queryText)) : null;
|
|
15898
|
-
const hasQuery = queryBigrams !== null;
|
|
15899
|
-
const textWeight = hasQuery ? TEXT_WEIGHT : 0;
|
|
15900
|
-
const metaWeight = hasQuery ? META_WEIGHT : 1;
|
|
15901
|
-
const compositeRaw = [
|
|
15902
|
-
queryText,
|
|
15903
|
-
context?.taskTitle,
|
|
15904
|
-
context?.taskDescription,
|
|
15905
|
-
context?.lastUserMessage,
|
|
15906
|
-
context?.currentAction,
|
|
15907
|
-
...context?.recentReviewerFailures ?? [],
|
|
15908
|
-
...context?.recentTestFailures ?? [],
|
|
15909
|
-
...context?.recentToolErrors ?? []
|
|
15910
|
-
].filter((s) => typeof s === "string" && s.length > 0).join(" ");
|
|
15911
|
-
const compositeNorm = normalize(compositeRaw);
|
|
15912
|
-
const hasComposite = compositeNorm.length > 0;
|
|
15913
|
-
try {
|
|
15914
|
-
if (hasComposite) {
|
|
15915
|
-
const synMap = await readSynonymMap(directory, config.retrieval?.synonym_map_max_pairs);
|
|
15916
|
-
const synIndex = buildSynonymIndex(synMap, config.retrieval?.synonym_min_cooccurrence);
|
|
15917
|
-
const baseTokens = compositeNorm.split(" ").filter(Boolean);
|
|
15918
|
-
synonymTokens = expandTokens(synIndex, baseTokens);
|
|
16299
|
+
} catch (error) {
|
|
16300
|
+
if (unresolved.length > 0) {
|
|
16301
|
+
const recoveryByReceipt = new Map(archive.map((record3) => [record3.receipt_id, record3]));
|
|
16302
|
+
for (const record3 of unresolved) {
|
|
16303
|
+
recoveryByReceipt.set(record3.receipt_id, record3);
|
|
15919
16304
|
}
|
|
15920
|
-
|
|
15921
|
-
|
|
16305
|
+
try {
|
|
16306
|
+
const recoveryArchiveContent = serializeReceiptRecords([...recoveryByReceipt.values()], {
|
|
16307
|
+
maxBytes: MAX_RECEIPT_ARCHIVE_BYTES,
|
|
16308
|
+
maxRecords: MAX_RECEIPT_ARCHIVE_RECORDS
|
|
16309
|
+
});
|
|
16310
|
+
await atomicWriteSwarmFile(getWriteReceiptArchivePath(root), recoveryArchiveContent, { maxBytes: MAX_RECEIPT_ARCHIVE_BYTES });
|
|
16311
|
+
} catch {}
|
|
15922
16312
|
}
|
|
15923
|
-
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
|
|
15927
|
-
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
16313
|
+
throw error;
|
|
16314
|
+
}
|
|
16315
|
+
}
|
|
16316
|
+
function nowFrom(hooks) {
|
|
16317
|
+
const value = hooks?.now ? hooks.now() : Date.now();
|
|
16318
|
+
return Number.isFinite(value) && value >= 0 ? value : Date.now();
|
|
16319
|
+
}
|
|
16320
|
+
function responseForRecord(record3) {
|
|
16321
|
+
return record3.response === undefined ? {
|
|
16322
|
+
success: false,
|
|
16323
|
+
error: record3.error || "write receipt is unresolved"
|
|
16324
|
+
} : parseReceiptResponse(record3.response);
|
|
16325
|
+
}
|
|
16326
|
+
function addReplayMarker(value) {
|
|
16327
|
+
const marker = {
|
|
16328
|
+
receipt_status: "replayed",
|
|
16329
|
+
replayed: true,
|
|
16330
|
+
duplicate: true,
|
|
16331
|
+
committed: true
|
|
16332
|
+
};
|
|
16333
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
16334
|
+
return {
|
|
16335
|
+
...value,
|
|
16336
|
+
...marker
|
|
16337
|
+
};
|
|
16338
|
+
}
|
|
16339
|
+
return {
|
|
16340
|
+
result: value,
|
|
16341
|
+
...marker
|
|
16342
|
+
};
|
|
16343
|
+
}
|
|
16344
|
+
function copyRecord(record3) {
|
|
16345
|
+
return { ...record3 };
|
|
16346
|
+
}
|
|
16347
|
+
async function withReceiptLock(root, fn, hooks) {
|
|
16348
|
+
assertReceiptPaths(root);
|
|
16349
|
+
const lockResult = await tryAcquireLock(root, "mcp-write-receipts.jsonl", "mcp-write-receipts", "mcp-write-receipts");
|
|
16350
|
+
if (!lockResult.acquired) {
|
|
16351
|
+
throw new WriteReceiptError("JOURNAL_UNAVAILABLE", "Write receipt journal is busy; no write was attempted");
|
|
16352
|
+
}
|
|
16353
|
+
try {
|
|
16354
|
+
assertReceiptPaths(root);
|
|
16355
|
+
const storage = await readReceiptStorage(root);
|
|
16356
|
+
const records = [...storage.archive, ...storage.active];
|
|
16357
|
+
const result = await fn(records);
|
|
16358
|
+
if (result.changed) {
|
|
16359
|
+
await writeReceiptStorage(root, result.records, hooks);
|
|
16360
|
+
}
|
|
16361
|
+
return result.value;
|
|
16362
|
+
} finally {
|
|
16363
|
+
try {
|
|
16364
|
+
await lockResult.lock._release?.();
|
|
16365
|
+
} catch {}
|
|
16366
|
+
}
|
|
16367
|
+
}
|
|
16368
|
+
async function prepareReceipt(request, hooks) {
|
|
16369
|
+
const identity = computeWriteReceiptIdentity(request);
|
|
16370
|
+
const timestamp = nowFrom(hooks);
|
|
16371
|
+
const attempt = {
|
|
16372
|
+
version: 1,
|
|
16373
|
+
receipt_id: randomUUID2(),
|
|
16374
|
+
attempt_id: randomUUID2(),
|
|
16375
|
+
tool: request.tool,
|
|
16376
|
+
...identity,
|
|
16377
|
+
state: "PREPARED",
|
|
16378
|
+
prepared_at: timestamp,
|
|
16379
|
+
updated_at: timestamp,
|
|
16380
|
+
lease_expires_at: timestamp + PREPARED_LEASE_MS
|
|
16381
|
+
};
|
|
16382
|
+
try {
|
|
16383
|
+
return await withReceiptLock(request.root, async (records) => {
|
|
16384
|
+
const decision = {
|
|
16385
|
+
current: undefined
|
|
16386
|
+
};
|
|
16387
|
+
const nextRecords = (() => {
|
|
16388
|
+
const sameKey = records.filter((record3) => record3.root_hash === identity.root_hash && record3.idempotency_hash === identity.idempotency_hash);
|
|
16389
|
+
const sameRequest = sameKey.filter((record3) => record3.tool === request.tool && record3.arguments_digest === identity.arguments_digest && record3.policy_digest === identity.policy_digest);
|
|
16390
|
+
const latest = sameRequest.at(-1);
|
|
16391
|
+
const unresolved = latest && isUnresolved(latest) ? latest : undefined;
|
|
16392
|
+
if (unresolved) {
|
|
16393
|
+
if (unresolved.state === "PREPARED" && timestamp >= unresolved.lease_expires_at) {
|
|
16394
|
+
const inDoubt = {
|
|
16395
|
+
...unresolved,
|
|
16396
|
+
state: "IN_DOUBT",
|
|
16397
|
+
updated_at: timestamp,
|
|
16398
|
+
error: "previous attempt expired before settlement"
|
|
16399
|
+
};
|
|
16400
|
+
decision.current = {
|
|
16401
|
+
kind: "in_doubt",
|
|
16402
|
+
receipt: copyRecord(inDoubt)
|
|
16403
|
+
};
|
|
16404
|
+
return [...records, inDoubt];
|
|
16405
|
+
}
|
|
16406
|
+
decision.current = unresolved.state === "PREPARED" ? { kind: "in_progress", receipt: copyRecord(unresolved) } : { kind: "in_doubt", receipt: copyRecord(unresolved) };
|
|
16407
|
+
return records;
|
|
16408
|
+
}
|
|
16409
|
+
const exact = [...sameRequest].reverse().find((record3) => !isUnresolved(record3));
|
|
16410
|
+
if (exact) {
|
|
16411
|
+
decision.current = {
|
|
16412
|
+
kind: "replay",
|
|
16413
|
+
receipt: copyRecord(exact),
|
|
16414
|
+
response: responseForRecord(exact)
|
|
16415
|
+
};
|
|
16416
|
+
return records;
|
|
16417
|
+
}
|
|
16418
|
+
if (sameKey.length > 0) {
|
|
16419
|
+
const conflict = {
|
|
16420
|
+
...attempt,
|
|
16421
|
+
state: "FAILED_NO_EFFECT",
|
|
16422
|
+
updated_at: timestamp,
|
|
16423
|
+
lease_expires_at: timestamp,
|
|
16424
|
+
response: sanitizeReceiptResponse({ success: false, error: "idempotency key conflict" }, request),
|
|
16425
|
+
error: "idempotency key conflict"
|
|
16426
|
+
};
|
|
16427
|
+
decision.current = {
|
|
16428
|
+
kind: "conflict",
|
|
16429
|
+
receipt: copyRecord(conflict),
|
|
16430
|
+
response: responseForRecord(conflict)
|
|
16431
|
+
};
|
|
16432
|
+
return [...records, conflict];
|
|
16433
|
+
}
|
|
16434
|
+
decision.current = { kind: "prepared", receipt: copyRecord(attempt) };
|
|
16435
|
+
return [...records, attempt];
|
|
16436
|
+
})();
|
|
16437
|
+
if (!decision.current) {
|
|
16438
|
+
throw new WriteReceiptError("TRANSITION_REJECTED", "Write receipt preparation produced no decision");
|
|
16439
|
+
}
|
|
16440
|
+
const changed = nextRecords !== records;
|
|
16441
|
+
if (changed && hooks && hooks.persistReceipt && (decision.current.kind === "prepared" || decision.current.kind === "in_doubt" || decision.current.kind === "conflict")) {
|
|
16442
|
+
await hooks.persistReceipt(decision.current.receipt);
|
|
16443
|
+
}
|
|
16444
|
+
return { value: decision.current, records: nextRecords, changed };
|
|
16445
|
+
}, hooks);
|
|
16446
|
+
} catch (error) {
|
|
16447
|
+
if (error instanceof WriteReceiptError)
|
|
16448
|
+
throw error;
|
|
16449
|
+
throw new WriteReceiptError("JOURNAL_UNAVAILABLE", "Write receipt journal could not be prepared");
|
|
16450
|
+
}
|
|
16451
|
+
}
|
|
16452
|
+
function sameAttempt(record3, attempt) {
|
|
16453
|
+
return record3.attempt_id === attempt.attempt_id && record3.tool === attempt.tool && record3.root_hash === attempt.root_hash && record3.idempotency_hash === attempt.idempotency_hash && record3.arguments_digest === attempt.arguments_digest && record3.policy_digest === attempt.policy_digest;
|
|
16454
|
+
}
|
|
16455
|
+
async function transitionReceipt(request, attempt, state, options) {
|
|
16456
|
+
const timestamp = nowFrom(options.hooks);
|
|
16457
|
+
const nextRecord = {
|
|
16458
|
+
...attempt,
|
|
16459
|
+
state,
|
|
16460
|
+
updated_at: timestamp,
|
|
16461
|
+
lease_expires_at: state === "PREPARED" ? timestamp + PREPARED_LEASE_MS : attempt.lease_expires_at,
|
|
16462
|
+
...options.response === undefined ? state === "COMMITTED" ? { response: sanitizeReceiptResponse(undefined, request) } : {} : {
|
|
16463
|
+
response: sanitizeReceiptResponse(options.response, request)
|
|
16464
|
+
},
|
|
16465
|
+
...options.error === undefined ? {} : {
|
|
16466
|
+
error: sanitizeString(options.error, requestForbiddenStrings(request))
|
|
16467
|
+
}
|
|
16468
|
+
};
|
|
16469
|
+
const value = await withReceiptLock(request.root, async (records) => {
|
|
16470
|
+
const current = [...records].reverse().find((record3) => sameAttempt(record3, attempt));
|
|
16471
|
+
if (!current) {
|
|
16472
|
+
throw new WriteReceiptError("TRANSITION_REJECTED", "Write receipt attempt no longer exists");
|
|
16473
|
+
}
|
|
16474
|
+
if (current.state === "COMMITTED") {
|
|
16475
|
+
return { value: current, records, changed: false };
|
|
16476
|
+
}
|
|
16477
|
+
if (current.state === "IN_DOUBT" && state !== "COMMITTED" && state !== "IN_DOUBT") {
|
|
16478
|
+
return { value: current, records, changed: false };
|
|
16479
|
+
}
|
|
16480
|
+
if (current.state === "IN_DOUBT" && state === "COMMITTED") {
|
|
16481
|
+
if (options.hooks?.persistReceipt) {
|
|
16482
|
+
await options.hooks.persistReceipt(nextRecord);
|
|
15945
16483
|
}
|
|
15946
|
-
const triggerRecallHit = !ds.triggerHit && hasComposite && Array.isArray(entry.triggers) && entry.triggers.some((tr) => {
|
|
15947
|
-
const n = normalize(tr);
|
|
15948
|
-
return n.length >= TRIGGER_RECALL_MIN_LEN && compositeNorm.includes(n);
|
|
15949
|
-
});
|
|
15950
|
-
const triggerRecallBoost = triggerRecallHit ? TRIGGER_RECALL_BOOST : 0;
|
|
15951
|
-
const finalScore = Math.min(1, Math.max(0, textWeight * textScore + metaWeight * metaScore + ds.score + confBoost + generatedSkillBoost + outcomeBoost + coldStartBonus + synonymBoost + triggerRecallBoost + (hasQuery && !entry.confidence_floor_demoted ? statusBoost(entry.status) : 0)));
|
|
15952
|
-
const statusScoreBoost = hasQuery && !entry.confidence_floor_demoted ? statusBoost(entry.status) : 0;
|
|
15953
|
-
const isCritical = entry.directive_priority === "critical" && (ds.triggerHit || ds.actionHit || ds.agentHit);
|
|
15954
|
-
const isForceHive = forceReadHive && entry.tier === "hive";
|
|
15955
16484
|
return {
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
coldStartBoost: coldStartBonus,
|
|
15960
|
-
score_breakdown: {
|
|
15961
|
-
text_score: textScore,
|
|
15962
|
-
metadata_score: metaScore,
|
|
15963
|
-
directive_score: ds.score,
|
|
15964
|
-
confidence_boost: confBoost,
|
|
15965
|
-
generated_skill_boost: generatedSkillBoost,
|
|
15966
|
-
outcome_boost: outcomeBoost,
|
|
15967
|
-
cold_start_boost: coldStartBonus,
|
|
15968
|
-
synonym_boost: synonymBoost,
|
|
15969
|
-
trigger_recall_boost: triggerRecallBoost,
|
|
15970
|
-
status_boost: statusScoreBoost,
|
|
15971
|
-
final_score: finalScore,
|
|
15972
|
-
...synonymTrace[entry.id] ? { synonym_matches: synonymTrace[entry.id] } : {}
|
|
15973
|
-
},
|
|
15974
|
-
__critical: isCritical,
|
|
15975
|
-
__forceHive: isForceHive
|
|
16485
|
+
value: nextRecord,
|
|
16486
|
+
records: [...records, nextRecord],
|
|
16487
|
+
changed: true
|
|
15976
16488
|
};
|
|
15977
|
-
});
|
|
15978
|
-
scored.sort((a, b) => {
|
|
15979
|
-
const diff = b.finalScore - a.finalScore;
|
|
15980
|
-
if (Math.abs(diff) > 0.001)
|
|
15981
|
-
return diff;
|
|
15982
|
-
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
|
15983
|
-
});
|
|
15984
|
-
const top = [];
|
|
15985
|
-
const seen = new Set;
|
|
15986
|
-
for (const e of scored) {
|
|
15987
|
-
if (top.length >= max)
|
|
15988
|
-
break;
|
|
15989
|
-
if (e.__critical && !seen.has(e.id)) {
|
|
15990
|
-
top.push(e);
|
|
15991
|
-
seen.add(e.id);
|
|
15992
|
-
}
|
|
15993
16489
|
}
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
break;
|
|
15997
|
-
if (e.__forceHive && !seen.has(e.id)) {
|
|
15998
|
-
top.push(e);
|
|
15999
|
-
seen.add(e.id);
|
|
16000
|
-
}
|
|
16490
|
+
if (current.state !== "PREPARED") {
|
|
16491
|
+
return { value: current, records, changed: false };
|
|
16001
16492
|
}
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16493
|
+
if (options.hooks?.persistReceipt) {
|
|
16494
|
+
await options.hooks.persistReceipt(nextRecord);
|
|
16495
|
+
}
|
|
16496
|
+
return {
|
|
16497
|
+
value: nextRecord,
|
|
16498
|
+
records: [...records, nextRecord],
|
|
16499
|
+
changed: true
|
|
16500
|
+
};
|
|
16501
|
+
}, options.hooks);
|
|
16502
|
+
return value;
|
|
16503
|
+
}
|
|
16504
|
+
async function commitReceipt(request, attempt, response, hooks) {
|
|
16505
|
+
return transitionReceipt(request, attempt, "COMMITTED", {
|
|
16506
|
+
response,
|
|
16507
|
+
hooks
|
|
16508
|
+
});
|
|
16509
|
+
}
|
|
16510
|
+
async function markInDoubt(request, attempt, error, hooks) {
|
|
16511
|
+
return transitionReceipt(request, attempt, "IN_DOUBT", { error, hooks });
|
|
16512
|
+
}
|
|
16513
|
+
async function executeWithReceipt(request, operation, hooks) {
|
|
16514
|
+
const prepared = await prepareReceipt(request, hooks);
|
|
16515
|
+
if (prepared.kind === "conflict") {
|
|
16516
|
+
throw new WriteReceiptError("TRANSITION_REJECTED", "idempotency key conflict; no write was attempted");
|
|
16517
|
+
}
|
|
16518
|
+
if (prepared.kind === "replay") {
|
|
16519
|
+
if (prepared.receipt.state === "FAILED_NO_EFFECT") {
|
|
16520
|
+
throw new WriteReceiptError("TRANSITION_REJECTED", "idempotency key conflict; no write was attempted");
|
|
16521
|
+
}
|
|
16522
|
+
return addReplayMarker(prepared.response);
|
|
16523
|
+
}
|
|
16524
|
+
if (prepared.kind === "in_progress") {
|
|
16525
|
+
throw new WriteReceiptError("TRANSITION_REJECTED", "write already in progress; retry after the original attempt settles");
|
|
16526
|
+
}
|
|
16527
|
+
if (prepared.kind === "in_doubt") {
|
|
16528
|
+
throw new WriteReceiptError("TRANSITION_REJECTED", "write is IN_DOUBT and will not be retried automatically");
|
|
16529
|
+
}
|
|
16530
|
+
const attempt = prepared.receipt;
|
|
16531
|
+
try {
|
|
16532
|
+
if (hooks?.afterPrepare) {
|
|
16533
|
+
await hooks.afterPrepare(copyRecord(attempt));
|
|
16534
|
+
}
|
|
16535
|
+
} catch {
|
|
16536
|
+
throw new WriteReceiptError("TRANSITION_REJECTED", "write interrupted after durable preparation");
|
|
16537
|
+
}
|
|
16538
|
+
let result;
|
|
16539
|
+
try {
|
|
16540
|
+
result = await operation();
|
|
16541
|
+
} catch (error) {
|
|
16542
|
+
try {
|
|
16543
|
+
await markInDoubt(request, attempt, error instanceof Error ? error.message : "production write failed");
|
|
16544
|
+
} catch {}
|
|
16545
|
+
throw new WriteReceiptError("TRANSITION_REJECTED", "write outcome is IN_DOUBT and must not be retried automatically");
|
|
16546
|
+
}
|
|
16547
|
+
try {
|
|
16548
|
+
if (hooks?.afterMutation) {
|
|
16549
|
+
await hooks.afterMutation(copyRecord(attempt));
|
|
16550
|
+
}
|
|
16551
|
+
} catch {
|
|
16552
|
+
try {
|
|
16553
|
+
await markInDoubt(request, attempt, "write completed but settlement was interrupted", hooks);
|
|
16554
|
+
} catch {}
|
|
16555
|
+
throw new WriteReceiptError("TRANSITION_REJECTED", "write outcome is IN_DOUBT and must not be retried automatically");
|
|
16556
|
+
}
|
|
16557
|
+
try {
|
|
16558
|
+
await commitReceipt(request, attempt, result, hooks);
|
|
16559
|
+
} catch {
|
|
16560
|
+
try {
|
|
16561
|
+
await markInDoubt(request, attempt, "write completed but final receipt persistence failed");
|
|
16562
|
+
} catch {}
|
|
16563
|
+
throw new WriteReceiptError("TRANSITION_REJECTED", "write outcome is IN_DOUBT and must not be retried automatically");
|
|
16564
|
+
}
|
|
16565
|
+
return result;
|
|
16566
|
+
}
|
|
16567
|
+
var KNOWLEDGE_ARRAY_FIELDS = [
|
|
16568
|
+
"tags",
|
|
16569
|
+
"applies_to_agents",
|
|
16570
|
+
"applies_to_tools",
|
|
16571
|
+
"required_actions",
|
|
16572
|
+
"forbidden_actions",
|
|
16573
|
+
"verification_checks"
|
|
16574
|
+
];
|
|
16575
|
+
function assertBoundedKnowledgeAddInput(rawArgs) {
|
|
16576
|
+
if (!rawArgs || typeof rawArgs !== "object" || Array.isArray(rawArgs))
|
|
16577
|
+
return;
|
|
16578
|
+
const record3 = rawArgs;
|
|
16579
|
+
for (const field of KNOWLEDGE_ARRAY_FIELDS) {
|
|
16580
|
+
const descriptor = Object.getOwnPropertyDescriptor(record3, field);
|
|
16581
|
+
const value = descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
16582
|
+
if (value === undefined)
|
|
16583
|
+
continue;
|
|
16584
|
+
if (!Array.isArray(value) || value.length > MAX_KNOWLEDGE_ARRAY_ITEMS) {
|
|
16585
|
+
throw new WriteReceiptError("INVALID_REQUEST", "knowledge_add array fields exceed their bounded capacity");
|
|
16586
|
+
}
|
|
16587
|
+
for (const item of value) {
|
|
16588
|
+
if (typeof item !== "string" || item.length > MAX_KNOWLEDGE_OPTIONAL_TEXT_LENGTH) {
|
|
16589
|
+
throw new WriteReceiptError("INVALID_REQUEST", "knowledge_add array values are invalid or too long");
|
|
16011
16590
|
}
|
|
16012
16591
|
}
|
|
16013
|
-
results = top.map(({ __critical: _c, __forceHive: _f, ...rest }) => rest);
|
|
16014
|
-
} catch (err) {
|
|
16015
|
-
warn(`[search-knowledge] retrieval failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
16016
|
-
results = [];
|
|
16017
16592
|
}
|
|
16018
|
-
|
|
16019
|
-
const
|
|
16020
|
-
const
|
|
16021
|
-
|
|
16022
|
-
|
|
16023
|
-
|
|
16024
|
-
|
|
16025
|
-
|
|
16026
|
-
for (const e of results) {
|
|
16027
|
-
if (synonymTrace[e.id])
|
|
16028
|
-
synonymBreakdown[e.id] = synonymTrace[e.id];
|
|
16593
|
+
for (const field of ["idempotency_key", "lesson", "scope"]) {
|
|
16594
|
+
const descriptor = Object.getOwnPropertyDescriptor(record3, field);
|
|
16595
|
+
const value = descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
16596
|
+
if (value === undefined)
|
|
16597
|
+
continue;
|
|
16598
|
+
const max = field === "idempotency_key" ? MAX_IDEMPOTENCY_KEY_LENGTH : field === "lesson" ? MAX_KNOWLEDGE_TEXT_LENGTH : MAX_KNOWLEDGE_OPTIONAL_TEXT_LENGTH;
|
|
16599
|
+
if (typeof value !== "string" || value.length > max) {
|
|
16600
|
+
throw new WriteReceiptError("INVALID_REQUEST", "knowledge_add string fields are invalid or too long");
|
|
16029
16601
|
}
|
|
16030
|
-
const scoreBreakdown = synonymTokens.length > 0 ? {
|
|
16031
|
-
synonyms_expanded: synonymTokens,
|
|
16032
|
-
synonym_matches: synonymBreakdown
|
|
16033
|
-
} : undefined;
|
|
16034
|
-
await recordKnowledgeEvent(directory, {
|
|
16035
|
-
type: "retrieved",
|
|
16036
|
-
trace_id: traceId,
|
|
16037
|
-
session_id: sessionId,
|
|
16038
|
-
phase: context?.currentPhase,
|
|
16039
|
-
task_id: context?.taskId,
|
|
16040
|
-
agent,
|
|
16041
|
-
query: query ?? context?.lastUserMessage ?? context?.currentPhase ?? "",
|
|
16042
|
-
retrieval_mode: mode,
|
|
16043
|
-
result_ids: results.map((e) => e.id),
|
|
16044
|
-
ranks,
|
|
16045
|
-
scores,
|
|
16046
|
-
...scoreBreakdown ? { score_breakdown: scoreBreakdown } : {}
|
|
16047
|
-
});
|
|
16048
16602
|
}
|
|
16049
|
-
|
|
16603
|
+
}
|
|
16604
|
+
var boundedKnowledgeText = exports_external.string().max(MAX_KNOWLEDGE_OPTIONAL_TEXT_LENGTH);
|
|
16605
|
+
var knowledgeAddInput = exports_external.object({
|
|
16606
|
+
idempotency_key: exports_external.string().min(1).max(MAX_IDEMPOTENCY_KEY_LENGTH).refine((value) => !hasControlCharacters(value), {
|
|
16607
|
+
message: "idempotency_key must not contain ASCII control characters"
|
|
16608
|
+
}),
|
|
16609
|
+
lesson: exports_external.string().min(15).max(MAX_KNOWLEDGE_TEXT_LENGTH),
|
|
16610
|
+
category: exports_external.enum(KNOWLEDGE_CATEGORIES),
|
|
16611
|
+
tags: exports_external.array(boundedKnowledgeText).max(MAX_KNOWLEDGE_ARRAY_ITEMS).optional(),
|
|
16612
|
+
scope: boundedKnowledgeText.optional(),
|
|
16613
|
+
applies_to_agents: exports_external.array(boundedKnowledgeText).max(MAX_KNOWLEDGE_ARRAY_ITEMS).optional(),
|
|
16614
|
+
applies_to_tools: exports_external.array(boundedKnowledgeText).max(MAX_KNOWLEDGE_ARRAY_ITEMS).optional(),
|
|
16615
|
+
required_actions: exports_external.array(boundedKnowledgeText).max(MAX_KNOWLEDGE_ARRAY_ITEMS).optional(),
|
|
16616
|
+
forbidden_actions: exports_external.array(boundedKnowledgeText).max(MAX_KNOWLEDGE_ARRAY_ITEMS).optional(),
|
|
16617
|
+
verification_checks: exports_external.array(boundedKnowledgeText).max(MAX_KNOWLEDGE_ARRAY_ITEMS).optional()
|
|
16618
|
+
}).strict();
|
|
16619
|
+
function buildKnowledgeAddRequest(root, rawArgs, runtime) {
|
|
16620
|
+
assertBoundedKnowledgeAddInput(rawArgs);
|
|
16621
|
+
const args = knowledgeAddInput.parse(rawArgs);
|
|
16622
|
+
const idempotencyKey = args.idempotency_key;
|
|
16623
|
+
const productionArgs = {
|
|
16624
|
+
...args,
|
|
16625
|
+
scope: args.scope ?? "global"
|
|
16626
|
+
};
|
|
16627
|
+
delete productionArgs.idempotency_key;
|
|
16628
|
+
return {
|
|
16629
|
+
request: {
|
|
16630
|
+
root,
|
|
16631
|
+
tool: "knowledge_add",
|
|
16632
|
+
idempotencyKey,
|
|
16633
|
+
arguments: productionArgs,
|
|
16634
|
+
policy: runtime?.policy ?? KNOWLEDGE_ADD_POLICY_VERSION
|
|
16635
|
+
},
|
|
16636
|
+
productionArgs
|
|
16637
|
+
};
|
|
16050
16638
|
}
|
|
16051
16639
|
|
|
16052
|
-
// src/
|
|
16053
|
-
init_zod();
|
|
16054
|
-
var MEMORY_KINDS = [
|
|
16055
|
-
"user_preference",
|
|
16056
|
-
"project_fact",
|
|
16057
|
-
"architecture_decision",
|
|
16058
|
-
"repo_convention",
|
|
16059
|
-
"api_finding",
|
|
16060
|
-
"code_pattern",
|
|
16061
|
-
"test_pattern",
|
|
16062
|
-
"failure_pattern",
|
|
16063
|
-
"security_note",
|
|
16064
|
-
"evidence",
|
|
16065
|
-
"todo",
|
|
16066
|
-
"scratch"
|
|
16067
|
-
];
|
|
16068
|
-
var swarm_memory_recall = createSwarmTool({
|
|
16069
|
-
description: "Recall scoped Swarm memory for the current repository. Read-only; retrieved memory is untrusted background.",
|
|
16070
|
-
args: {
|
|
16071
|
-
query: exports_external.string().min(3).describe("Natural language recall query"),
|
|
16072
|
-
kinds: exports_external.array(exports_external.enum(MEMORY_KINDS)).optional().describe("Optional memory kinds to include"),
|
|
16073
|
-
maxItems: exports_external.number().int().min(1).max(20).optional().describe("Maximum memories to return")
|
|
16074
|
-
},
|
|
16075
|
-
execute: async (args, directory, ctx) => computeSwarmMemoryRecall(args, directory, ctx)
|
|
16076
|
-
});
|
|
16077
|
-
async function computeSwarmMemoryRecall(args, directory, ctx, options) {
|
|
16078
|
-
const { config } = _internals2.loadPluginConfigWithMeta(directory);
|
|
16079
|
-
if (config.memory?.enabled !== true) {
|
|
16080
|
-
return JSON.stringify({
|
|
16081
|
-
success: false,
|
|
16082
|
-
disabled: true,
|
|
16083
|
-
message: "Swarm memory is disabled. Set swarm.memory.enabled=true."
|
|
16084
|
-
});
|
|
16085
|
-
}
|
|
16086
|
-
const parsed = RecallArgsSchema.safeParse(args);
|
|
16087
|
-
if (!parsed.success) {
|
|
16088
|
-
return JSON.stringify({
|
|
16089
|
-
success: false,
|
|
16090
|
-
error: parsed.error.issues.map((issue) => issue.message).join("; ")
|
|
16091
|
-
});
|
|
16092
|
-
}
|
|
16093
|
-
const agent = getContextAgent(ctx);
|
|
16094
|
-
const unitId = ctx?.sessionID ? _internals2.getAgentSession(ctx.sessionID)?.currentTaskId ?? undefined : undefined;
|
|
16095
|
-
const gateway = _internals2.createMemoryGateway({
|
|
16096
|
-
directory,
|
|
16097
|
-
sessionID: ctx?.sessionID,
|
|
16098
|
-
agentRole: agent,
|
|
16099
|
-
agentId: agent,
|
|
16100
|
-
runId: ctx?.sessionID,
|
|
16101
|
-
unitId
|
|
16102
|
-
}, {
|
|
16103
|
-
config: config.memory
|
|
16104
|
-
});
|
|
16105
|
-
try {
|
|
16106
|
-
const bundle = await gateway.recall(parsed.data, {
|
|
16107
|
-
recordUsage: options?.recordUsage !== false
|
|
16108
|
-
});
|
|
16109
|
-
return JSON.stringify({
|
|
16110
|
-
success: true,
|
|
16111
|
-
bundle_id: bundle.id,
|
|
16112
|
-
memory_ids: bundle.items.map((item) => item.record.id),
|
|
16113
|
-
total: bundle.items.length,
|
|
16114
|
-
token_estimate: bundle.tokenEstimate,
|
|
16115
|
-
signals: bundle.items.map((item) => ({
|
|
16116
|
-
memory_id: item.record.id,
|
|
16117
|
-
relation: item.relation,
|
|
16118
|
-
...item.signals
|
|
16119
|
-
})),
|
|
16120
|
-
prompt_block: bundle.promptBlock
|
|
16121
|
-
}, null, 2);
|
|
16122
|
-
} finally {
|
|
16123
|
-
await gateway.dispose();
|
|
16124
|
-
}
|
|
16125
|
-
}
|
|
16126
|
-
var RecallArgsSchema = exports_external.object({
|
|
16127
|
-
query: exports_external.string().min(3),
|
|
16128
|
-
kinds: exports_external.array(exports_external.enum(MEMORY_KINDS)).optional(),
|
|
16129
|
-
maxItems: exports_external.number().int().min(1).max(20).optional()
|
|
16130
|
-
});
|
|
16131
|
-
var _internals2 = {
|
|
16132
|
-
loadPluginConfigWithMeta,
|
|
16133
|
-
createMemoryGateway,
|
|
16134
|
-
getAgentSession
|
|
16135
|
-
};
|
|
16136
|
-
function getContextAgent(ctx) {
|
|
16137
|
-
if (!ctx || typeof ctx !== "object")
|
|
16138
|
-
return;
|
|
16139
|
-
const value = ctx.agent;
|
|
16140
|
-
return typeof value === "string" ? value : undefined;
|
|
16141
|
-
}
|
|
16142
|
-
|
|
16143
|
-
// src/mcp/adapters/verification.ts
|
|
16640
|
+
// src/mcp/adapters/verification.ts
|
|
16144
16641
|
init_zod();
|
|
16145
16642
|
|
|
16146
16643
|
// src/tools/evidence-check.ts
|
|
@@ -17035,7 +17532,7 @@ async function computePlaceholderScan(input, directory, gateOverrides, options)
|
|
|
17035
17532
|
const addedLinesFiles = addedLinesValues.filter((lines) => (lines instanceof Set ? lines.size : lines.length) > 0).length;
|
|
17036
17533
|
const addedLinesTotal = addedLinesValues.reduce((sum, lines) => sum + (lines instanceof Set ? lines.size : lines.length), 0);
|
|
17037
17534
|
if (options?.persistEvidence === true) {
|
|
17038
|
-
await
|
|
17535
|
+
await _internals2.saveEvidence(directory, "placeholder_scan", {
|
|
17039
17536
|
task_id: "placeholder_scan",
|
|
17040
17537
|
type: "placeholder",
|
|
17041
17538
|
timestamp: new Date().toISOString(),
|
|
@@ -17075,11 +17572,11 @@ var placeholder_scan = createSwarmTool({
|
|
|
17075
17572
|
allow_sentinels: exports_external.array(exports_external.string()).optional().describe("Deprecated alias for sentinel_allowlist")
|
|
17076
17573
|
},
|
|
17077
17574
|
async execute(args, directory) {
|
|
17078
|
-
const result = await placeholderScan(args, directory,
|
|
17575
|
+
const result = await placeholderScan(args, directory, _internals2.loadGateOverrides(directory)?.placeholder_scan);
|
|
17079
17576
|
return JSON.stringify(result);
|
|
17080
17577
|
}
|
|
17081
17578
|
});
|
|
17082
|
-
var
|
|
17579
|
+
var _internals2 = {
|
|
17083
17580
|
collectNonStubBodyLines,
|
|
17084
17581
|
isStubSkeletonFunction,
|
|
17085
17582
|
isConstantLiteralNode,
|
|
@@ -17091,7 +17588,7 @@ var _internals3 = {
|
|
|
17091
17588
|
import * as fs3 from "fs";
|
|
17092
17589
|
import * as path4 from "path";
|
|
17093
17590
|
init_zod();
|
|
17094
|
-
var
|
|
17591
|
+
var _internals3 = {
|
|
17095
17592
|
loadGateOverrides
|
|
17096
17593
|
};
|
|
17097
17594
|
var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
|
|
@@ -17282,113 +17779,732 @@ async function computeSyntaxCheck(input, directory, gates, options) {
|
|
|
17282
17779
|
files: results
|
|
17283
17780
|
});
|
|
17284
17781
|
}
|
|
17285
|
-
return {
|
|
17286
|
-
verdict,
|
|
17287
|
-
files: results,
|
|
17288
|
-
summary
|
|
17289
|
-
};
|
|
17782
|
+
return {
|
|
17783
|
+
verdict,
|
|
17784
|
+
files: results,
|
|
17785
|
+
summary
|
|
17786
|
+
};
|
|
17787
|
+
}
|
|
17788
|
+
var syntax_check = createSwarmTool({
|
|
17789
|
+
allowWorkingDirectoryOverride: true,
|
|
17790
|
+
description: "Check syntax of source files using tree-sitter parsers. Supports JS/TS, Python, Go, Rust, Java, C/C++, C#, PHP, Ruby. Returns JSON with syntax errors found per file.",
|
|
17791
|
+
args: {
|
|
17792
|
+
changed_files: exports_external.array(exports_external.object({
|
|
17793
|
+
path: exports_external.string(),
|
|
17794
|
+
additions: exports_external.number()
|
|
17795
|
+
})).describe("Files to check (from diff gate)"),
|
|
17796
|
+
mode: exports_external.enum(["changed", "all"]).optional().describe("Check mode over the supplied changed_files. 'changed' (default) checks only entries with additions > 0, so deletion-only and pure-rename entries are excluded; 'all' checks every supplied entry. Neither mode enumerates the repository \u2014 this tool only ever inspects the files you pass in changed_files. If the filters leave nothing to check the verdict is 'skip', never 'pass'."),
|
|
17797
|
+
languages: exports_external.array(exports_external.string()).optional().describe("Optional: restrict to specific languages")
|
|
17798
|
+
},
|
|
17799
|
+
async execute(args, directory) {
|
|
17800
|
+
const gates = _internals3.loadGateOverrides(directory);
|
|
17801
|
+
const result = await syntaxCheck(args, directory, gates);
|
|
17802
|
+
return JSON.stringify(result);
|
|
17803
|
+
}
|
|
17804
|
+
});
|
|
17805
|
+
|
|
17806
|
+
// src/mcp/adapters/verification.ts
|
|
17807
|
+
function mcpToolContext(root) {
|
|
17808
|
+
return { directory: root };
|
|
17809
|
+
}
|
|
17810
|
+
function safeParseJson(text) {
|
|
17811
|
+
try {
|
|
17812
|
+
return JSON.parse(text);
|
|
17813
|
+
} catch {
|
|
17814
|
+
return { raw: text };
|
|
17815
|
+
}
|
|
17816
|
+
}
|
|
17817
|
+
var syntaxCheckSchema = exports_external.object({
|
|
17818
|
+
changed_files: exports_external.array(exports_external.object({
|
|
17819
|
+
path: exports_external.string().describe("Repository-relative file path"),
|
|
17820
|
+
additions: exports_external.number().describe("Added line count from the diff")
|
|
17821
|
+
})).describe("Files to check (from diff gate)"),
|
|
17822
|
+
mode: exports_external.enum(["changed", "all"]).optional().describe("Check mode: 'changed' checks only additions > 0 entries; 'all' checks every entry"),
|
|
17823
|
+
languages: exports_external.array(exports_external.string()).optional().describe("Restrict to specific languages")
|
|
17824
|
+
});
|
|
17825
|
+
var placeholderScanSchema = exports_external.object({
|
|
17826
|
+
changed_files: exports_external.array(exports_external.string()).describe("Repository-relative file paths to scan")
|
|
17827
|
+
});
|
|
17828
|
+
var sastScanSchema = exports_external.object({
|
|
17829
|
+
changed_files: exports_external.array(exports_external.string()).describe("Repository-relative file paths to scan"),
|
|
17830
|
+
severity_threshold: exports_external.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity to report (default: medium)")
|
|
17831
|
+
});
|
|
17832
|
+
var qualityBudgetSchema = exports_external.object({
|
|
17833
|
+
changed_files: exports_external.array(exports_external.string()).describe("Repository-relative file paths to analyze")
|
|
17834
|
+
});
|
|
17835
|
+
var evidenceCheckSchema = exports_external.object({
|
|
17836
|
+
required_types: exports_external.string().optional().describe('Comma-separated evidence types required per task (default: "reviewer,test_engineer")')
|
|
17837
|
+
});
|
|
17838
|
+
var syntaxCheckAdapter = {
|
|
17839
|
+
name: "syntax_check",
|
|
17840
|
+
kind: "read",
|
|
17841
|
+
pathFields: ["changed_files"],
|
|
17842
|
+
inputSchema: syntaxCheckSchema,
|
|
17843
|
+
execute: (rawArgs, root) => computeSyntaxCheck(syntaxCheckSchema.parse(rawArgs), root)
|
|
17844
|
+
};
|
|
17845
|
+
var placeholderScanAdapter = {
|
|
17846
|
+
name: "placeholder_scan",
|
|
17847
|
+
kind: "read",
|
|
17848
|
+
pathFields: ["changed_files"],
|
|
17849
|
+
inputSchema: placeholderScanSchema,
|
|
17850
|
+
execute: (rawArgs, root) => computePlaceholderScan({ changed_files: placeholderScanSchema.parse(rawArgs).changed_files }, root)
|
|
17851
|
+
};
|
|
17852
|
+
var sastScanAdapter = {
|
|
17853
|
+
name: "sast_scan",
|
|
17854
|
+
kind: "read",
|
|
17855
|
+
pathFields: ["changed_files"],
|
|
17856
|
+
inputSchema: sastScanSchema,
|
|
17857
|
+
execute: (rawArgs, root) => {
|
|
17858
|
+
const args = sastScanSchema.parse(rawArgs);
|
|
17859
|
+
return computeSastScan({
|
|
17860
|
+
changed_files: args.changed_files,
|
|
17861
|
+
severity_threshold: args.severity_threshold,
|
|
17862
|
+
offline_only: true
|
|
17863
|
+
}, root);
|
|
17864
|
+
}
|
|
17865
|
+
};
|
|
17866
|
+
var qualityBudgetAdapter = {
|
|
17867
|
+
name: "quality_budget",
|
|
17868
|
+
kind: "read",
|
|
17869
|
+
pathFields: ["changed_files"],
|
|
17870
|
+
inputSchema: qualityBudgetSchema,
|
|
17871
|
+
execute: (rawArgs, root) => computeQualityBudget({ changed_files: qualityBudgetSchema.parse(rawArgs).changed_files }, root)
|
|
17872
|
+
};
|
|
17873
|
+
var evidenceCheckAdapter = {
|
|
17874
|
+
name: "evidence_check",
|
|
17875
|
+
kind: "read",
|
|
17876
|
+
pathFields: [],
|
|
17877
|
+
inputSchema: evidenceCheckSchema,
|
|
17878
|
+
execute: async (rawArgs, root) => {
|
|
17879
|
+
const args = evidenceCheckSchema.parse(rawArgs);
|
|
17880
|
+
const result = await evidence_check.execute(args, mcpToolContext(root));
|
|
17881
|
+
return typeof result === "string" ? safeParseJson(result) : result;
|
|
17882
|
+
}
|
|
17883
|
+
};
|
|
17884
|
+
|
|
17885
|
+
// src/mcp/adapters/knowledge-add.ts
|
|
17886
|
+
async function executeKnowledgeAdd(rawArgs, root, runtime) {
|
|
17887
|
+
const { request, productionArgs } = buildKnowledgeAddRequest(root, rawArgs, runtime);
|
|
17888
|
+
return executeWithReceipt(request, async () => {
|
|
17889
|
+
const result = await knowledge_add.execute(productionArgs, mcpToolContext(root));
|
|
17890
|
+
return typeof result === "string" ? safeParseJson(result) : result;
|
|
17891
|
+
}, runtime?.hooks);
|
|
17892
|
+
}
|
|
17893
|
+
var knowledgeAddAdapter = {
|
|
17894
|
+
name: "knowledge_add",
|
|
17895
|
+
kind: "write",
|
|
17896
|
+
pathFields: [],
|
|
17897
|
+
inputSchema: knowledgeAddInput,
|
|
17898
|
+
execute: executeKnowledgeAdd
|
|
17899
|
+
};
|
|
17900
|
+
|
|
17901
|
+
// src/mcp/adapters/knowledge-memory.ts
|
|
17902
|
+
init_zod();
|
|
17903
|
+
import { existsSync as existsSync4 } from "fs";
|
|
17904
|
+
import path6 from "path";
|
|
17905
|
+
init_schema();
|
|
17906
|
+
|
|
17907
|
+
// src/hooks/search-knowledge.ts
|
|
17908
|
+
init_schema();
|
|
17909
|
+
import { existsSync as existsSync3, statSync as statSync4 } from "fs";
|
|
17910
|
+
import path5 from "path";
|
|
17911
|
+
init_logger();
|
|
17912
|
+
|
|
17913
|
+
// src/hooks/knowledge-reader.ts
|
|
17914
|
+
init_logger();
|
|
17915
|
+
init_knowledge_types();
|
|
17916
|
+
var JACCARD_THRESHOLD = 0.6;
|
|
17917
|
+
var HIVE_TIER_BOOST = 0.05;
|
|
17918
|
+
var DEFAULT_SAME_PROJECT_PENALTY = -0.05;
|
|
17919
|
+
function inferCategoriesFromPhase(phaseDescription) {
|
|
17920
|
+
const lower = phaseDescription.toLowerCase();
|
|
17921
|
+
const patterns = [
|
|
17922
|
+
{
|
|
17923
|
+
pattern: /\b(?:test|qa|quality|verification|validation)\b/,
|
|
17924
|
+
categories: ["testing", "debugging"]
|
|
17925
|
+
},
|
|
17926
|
+
{
|
|
17927
|
+
pattern: /\b(?:implement|build|develop|coding|code)\b/,
|
|
17928
|
+
categories: ["tooling", "architecture", "debugging"]
|
|
17929
|
+
},
|
|
17930
|
+
{
|
|
17931
|
+
pattern: /\b(?:integrat|deploy|ci|cd|release|publish)\b/,
|
|
17932
|
+
categories: ["integration", "tooling", "performance"]
|
|
17933
|
+
},
|
|
17934
|
+
{
|
|
17935
|
+
pattern: /\b(?:plan|design|architect|spec|requirement)\b/,
|
|
17936
|
+
categories: ["architecture", "process"]
|
|
17937
|
+
},
|
|
17938
|
+
{
|
|
17939
|
+
pattern: /\b(?:review|refactor|cleanup|polish|optimi)\b/,
|
|
17940
|
+
categories: ["performance", "architecture", "process"]
|
|
17941
|
+
},
|
|
17942
|
+
{
|
|
17943
|
+
pattern: /\b(?:secur|audit|harden|compliance)\b/,
|
|
17944
|
+
categories: ["security", "testing"]
|
|
17945
|
+
},
|
|
17946
|
+
{
|
|
17947
|
+
pattern: /\b(?:setup|config|scaffold|init|bootstrap)\b/,
|
|
17948
|
+
categories: ["tooling", "other"]
|
|
17949
|
+
},
|
|
17950
|
+
{
|
|
17951
|
+
pattern: /\b(?:doc|readme|changelog)\b/,
|
|
17952
|
+
categories: ["process", "tooling"]
|
|
17953
|
+
}
|
|
17954
|
+
];
|
|
17955
|
+
for (const { pattern, categories } of patterns) {
|
|
17956
|
+
if (pattern.test(lower)) {
|
|
17957
|
+
return categories;
|
|
17958
|
+
}
|
|
17959
|
+
}
|
|
17960
|
+
return ["process", "tooling"];
|
|
17961
|
+
}
|
|
17962
|
+
async function readMergedKnowledge(directory, config, context, opts) {
|
|
17963
|
+
const swarmPath = resolveSwarmKnowledgePath(directory);
|
|
17964
|
+
const swarmEntries = await readKnowledge(swarmPath);
|
|
17965
|
+
let hiveEntries = [];
|
|
17966
|
+
if (config.hive_enabled !== false) {
|
|
17967
|
+
const hivePath = resolveHiveKnowledgePath();
|
|
17968
|
+
hiveEntries = await readKnowledge(hivePath);
|
|
17969
|
+
}
|
|
17970
|
+
const seenLessons = new Set;
|
|
17971
|
+
const merged = [];
|
|
17972
|
+
for (const entry of hiveEntries) {
|
|
17973
|
+
const normalized = normalize(entry.lesson);
|
|
17974
|
+
seenLessons.add(normalized);
|
|
17975
|
+
merged.push({
|
|
17976
|
+
...entry,
|
|
17977
|
+
relevanceScore: { category: 0, confidence: 0, keywords: 0 },
|
|
17978
|
+
finalScore: 0
|
|
17979
|
+
});
|
|
17980
|
+
}
|
|
17981
|
+
for (const entry of swarmEntries) {
|
|
17982
|
+
const normalized = normalize(entry.lesson);
|
|
17983
|
+
if (seenLessons.has(normalized)) {
|
|
17984
|
+
continue;
|
|
17985
|
+
}
|
|
17986
|
+
const swarmBigrams = wordBigrams(normalized);
|
|
17987
|
+
const isHiveNearDup = hiveEntries.some((hiveEntry) => jaccardBigram(swarmBigrams, wordBigrams(normalize(hiveEntry.lesson))) >= JACCARD_THRESHOLD);
|
|
17988
|
+
if (isHiveNearDup)
|
|
17989
|
+
continue;
|
|
17990
|
+
const isSwarmNearDup = merged.some((m) => m.tier === "swarm" && jaccardBigram(swarmBigrams, wordBigrams(normalize(m.lesson))) >= JACCARD_THRESHOLD);
|
|
17991
|
+
if (isSwarmNearDup)
|
|
17992
|
+
continue;
|
|
17993
|
+
seenLessons.add(normalized);
|
|
17994
|
+
merged.push({
|
|
17995
|
+
...entry,
|
|
17996
|
+
relevanceScore: { category: 0, confidence: 0, keywords: 0 },
|
|
17997
|
+
finalScore: 0
|
|
17998
|
+
});
|
|
17999
|
+
}
|
|
18000
|
+
const retractionRecords = await readRetractionRecords(directory);
|
|
18001
|
+
const suppressedLessons = new Set(retractionRecords.map((record3) => record3.normalized_lesson).filter((value) => typeof value === "string" && value.length > 0));
|
|
18002
|
+
const scopeFilter = config.scope_filter ?? ["global"];
|
|
18003
|
+
const filtered = merged.filter((entry) => (opts?.skipScopeFilter || scopeFilter.some((pattern) => (entry.scope ?? "global") === pattern)) && isActiveStatus(entry.status) && !suppressedLessons.has(normalize(entry.lesson)));
|
|
18004
|
+
const ranked = filtered.map((entry) => {
|
|
18005
|
+
let categoryScore = 0;
|
|
18006
|
+
if (context?.currentPhase) {
|
|
18007
|
+
const phaseCategories = inferCategoriesFromPhase(context.currentPhase);
|
|
18008
|
+
if (phaseCategories.includes(entry.category)) {
|
|
18009
|
+
categoryScore = 1;
|
|
18010
|
+
} else if (entry.category === "process") {
|
|
18011
|
+
categoryScore = 0.5;
|
|
18012
|
+
}
|
|
18013
|
+
} else {
|
|
18014
|
+
categoryScore = 0.5;
|
|
18015
|
+
}
|
|
18016
|
+
const confidenceScore = entry.confidence;
|
|
18017
|
+
let keywordsScore = 0;
|
|
18018
|
+
if (context?.techStack && entry.tags.length > 0) {
|
|
18019
|
+
const matchingTags = entry.tags.filter((t) => context.techStack.some((s) => t.toLowerCase().includes(s.toLowerCase()) || s.toLowerCase().includes(t.toLowerCase()))).length;
|
|
18020
|
+
keywordsScore = Math.min(matchingTags / Math.max(entry.tags.length, 1), 1);
|
|
18021
|
+
} else if (entry.tags.length === 0) {
|
|
18022
|
+
keywordsScore = 0.5;
|
|
18023
|
+
}
|
|
18024
|
+
let tierBoost = 0;
|
|
18025
|
+
let isSameProject = false;
|
|
18026
|
+
if (entry.tier === "hive" && "source_project" in entry) {
|
|
18027
|
+
const sourceProject = entry.source_project;
|
|
18028
|
+
isSameProject = !!(context?.projectName && sourceProject === context.projectName);
|
|
18029
|
+
tierBoost = HIVE_TIER_BOOST;
|
|
18030
|
+
}
|
|
18031
|
+
const sameProjectPenalty = isSameProject ? DEFAULT_SAME_PROJECT_PENALTY : 0;
|
|
18032
|
+
const finalScore = categoryScore * 0.4 + confidenceScore * 0.35 + keywordsScore * 0.25 + tierBoost + sameProjectPenalty;
|
|
18033
|
+
const relevanceScore = {
|
|
18034
|
+
category: categoryScore,
|
|
18035
|
+
confidence: confidenceScore,
|
|
18036
|
+
keywords: keywordsScore
|
|
18037
|
+
};
|
|
18038
|
+
return {
|
|
18039
|
+
...entry,
|
|
18040
|
+
relevanceScore,
|
|
18041
|
+
finalScore: Math.min(Math.max(finalScore, 0), 1)
|
|
18042
|
+
};
|
|
18043
|
+
});
|
|
18044
|
+
ranked.sort((a, b) => {
|
|
18045
|
+
const scoreDiff = b.finalScore - a.finalScore;
|
|
18046
|
+
if (Math.abs(scoreDiff) > 0.001)
|
|
18047
|
+
return scoreDiff;
|
|
18048
|
+
const dateA = new Date(a.created_at).getTime();
|
|
18049
|
+
const dateB = new Date(b.created_at).getTime();
|
|
18050
|
+
return dateB - dateA;
|
|
18051
|
+
});
|
|
18052
|
+
const maxInject = config.max_inject_count ?? 5;
|
|
18053
|
+
const topN = ranked.slice(0, maxInject);
|
|
18054
|
+
return topN;
|
|
18055
|
+
}
|
|
18056
|
+
function lc(s) {
|
|
18057
|
+
return (s ?? "").toLowerCase();
|
|
18058
|
+
}
|
|
18059
|
+
function anyMatch(haystack, needles) {
|
|
18060
|
+
if (needles.length === 0)
|
|
18061
|
+
return false;
|
|
18062
|
+
const hay = haystack.map(lc);
|
|
18063
|
+
return needles.some((n) => hay.some((h) => h.includes(lc(n))));
|
|
18064
|
+
}
|
|
18065
|
+
function tokenizeContext(ctx) {
|
|
18066
|
+
const parts = [];
|
|
18067
|
+
if (ctx.taskTitle)
|
|
18068
|
+
parts.push(ctx.taskTitle);
|
|
18069
|
+
if (ctx.taskDescription)
|
|
18070
|
+
parts.push(ctx.taskDescription);
|
|
18071
|
+
if (ctx.lastUserMessage)
|
|
18072
|
+
parts.push(ctx.lastUserMessage);
|
|
18073
|
+
if (ctx.currentAction)
|
|
18074
|
+
parts.push(ctx.currentAction);
|
|
18075
|
+
if (ctx.currentTool)
|
|
18076
|
+
parts.push(ctx.currentTool);
|
|
18077
|
+
if (ctx.targetAgent)
|
|
18078
|
+
parts.push(ctx.targetAgent);
|
|
18079
|
+
if (ctx.declaredScope)
|
|
18080
|
+
parts.push(ctx.declaredScope);
|
|
18081
|
+
if (ctx.recentReviewerFailures)
|
|
18082
|
+
parts.push(...ctx.recentReviewerFailures);
|
|
18083
|
+
if (ctx.recentTestFailures)
|
|
18084
|
+
parts.push(...ctx.recentTestFailures);
|
|
18085
|
+
if (ctx.recentToolErrors)
|
|
18086
|
+
parts.push(...ctx.recentToolErrors);
|
|
18087
|
+
if (ctx.planConstraints)
|
|
18088
|
+
parts.push(...ctx.planConstraints);
|
|
18089
|
+
if (ctx.filePaths)
|
|
18090
|
+
parts.push(...ctx.filePaths);
|
|
18091
|
+
return parts.map(lc);
|
|
18092
|
+
}
|
|
18093
|
+
function scoreDirectiveAgainstContext(entry, ctx) {
|
|
18094
|
+
const haystack = tokenizeContext(ctx);
|
|
18095
|
+
const triggerHit = entry.triggers && entry.triggers.length > 0 ? anyMatch(haystack, entry.triggers) : false;
|
|
18096
|
+
const actionHit = entry.applies_to_tools && entry.applies_to_tools.length > 0 ? entry.applies_to_tools.map(lc).some((t) => t === lc(ctx.currentTool) || t === lc(ctx.currentAction)) : false;
|
|
18097
|
+
const agentHit = entry.applies_to_agents && entry.applies_to_agents.length > 0 ? entry.applies_to_agents.map(lc).some((a) => a === lc(ctx.targetAgent)) : false;
|
|
18098
|
+
let score = 0;
|
|
18099
|
+
if (triggerHit)
|
|
18100
|
+
score += 0.5;
|
|
18101
|
+
if (actionHit)
|
|
18102
|
+
score += 0.35;
|
|
18103
|
+
if (agentHit)
|
|
18104
|
+
score += 0.25;
|
|
18105
|
+
if (entry.directive_priority === "critical")
|
|
18106
|
+
score += 0.4;
|
|
18107
|
+
else if (entry.directive_priority === "high")
|
|
18108
|
+
score += 0.2;
|
|
18109
|
+
else if (entry.directive_priority === "medium")
|
|
18110
|
+
score += 0.1;
|
|
18111
|
+
return { triggerHit, actionHit, agentHit, score: Math.min(1, score) };
|
|
18112
|
+
}
|
|
18113
|
+
|
|
18114
|
+
// src/hooks/search-knowledge.ts
|
|
18115
|
+
init_knowledge_types();
|
|
18116
|
+
var TEXT_WEIGHT = 0.6;
|
|
18117
|
+
var META_WEIGHT = 0.4;
|
|
18118
|
+
var DIRECTIVE_BOOST_MIN_CONFIDENCE = 0.75;
|
|
18119
|
+
var SYNONYM_TEXT_BOOST = 0.15;
|
|
18120
|
+
var TRIGGER_RECALL_BOOST = 0.3;
|
|
18121
|
+
var TRIGGER_RECALL_MIN_LEN = 3;
|
|
18122
|
+
var DEFAULT_COLD_START_BONUS = 0.08;
|
|
18123
|
+
var DEFAULT_COLD_START_MAX_AGE_PHASES = 3;
|
|
18124
|
+
var OUTCOME_RANK_WEIGHT = 0.1;
|
|
18125
|
+
function entryText(e) {
|
|
18126
|
+
return `${e.lesson} ${e.tags.join(" ")} ${e.category}`;
|
|
18127
|
+
}
|
|
18128
|
+
function entryAgePhases(e) {
|
|
18129
|
+
const cb = e.confirmed_by;
|
|
18130
|
+
if (!Array.isArray(cb) || cb.length === 0)
|
|
18131
|
+
return 0;
|
|
18132
|
+
const phases = new Set;
|
|
18133
|
+
for (const c of cb) {
|
|
18134
|
+
if (typeof c?.phase_number === "number")
|
|
18135
|
+
phases.add(c.phase_number);
|
|
18136
|
+
}
|
|
18137
|
+
return phases.size > 0 ? phases.size : cb.length;
|
|
18138
|
+
}
|
|
18139
|
+
function clampLambda(v) {
|
|
18140
|
+
if (typeof v !== "number" || Number.isNaN(v))
|
|
18141
|
+
return 0.5;
|
|
18142
|
+
return Math.min(1, Math.max(0, v));
|
|
18143
|
+
}
|
|
18144
|
+
function mmrRerank(pool, pinned, max, lambda) {
|
|
18145
|
+
if (pool.length === 0)
|
|
18146
|
+
return [];
|
|
18147
|
+
const bigrams = new Map;
|
|
18148
|
+
const bg = (e) => {
|
|
18149
|
+
let s = bigrams.get(e.id);
|
|
18150
|
+
if (!s) {
|
|
18151
|
+
s = wordBigrams(normalize(e.lesson ?? ""));
|
|
18152
|
+
bigrams.set(e.id, s);
|
|
18153
|
+
}
|
|
18154
|
+
return s;
|
|
18155
|
+
};
|
|
18156
|
+
const selected = [...pinned];
|
|
18157
|
+
const candidates = [...pool];
|
|
18158
|
+
const out = [];
|
|
18159
|
+
while (selected.length < max && candidates.length > 0) {
|
|
18160
|
+
let bestIdx = -1;
|
|
18161
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
18162
|
+
let best = null;
|
|
18163
|
+
for (let i = 0;i < candidates.length; i++) {
|
|
18164
|
+
const c = candidates[i];
|
|
18165
|
+
let maxSim = 0;
|
|
18166
|
+
for (const s of selected) {
|
|
18167
|
+
const sim = jaccardBigram(bg(c), bg(s));
|
|
18168
|
+
if (sim > maxSim)
|
|
18169
|
+
maxSim = sim;
|
|
18170
|
+
}
|
|
18171
|
+
const score = lambda * c.finalScore - (1 - lambda) * maxSim;
|
|
18172
|
+
if (score > bestScore + 0.000000001 || Math.abs(score - bestScore) <= 0.000000001 && best !== null && tieBreak(c, best) < 0) {
|
|
18173
|
+
bestScore = score;
|
|
18174
|
+
bestIdx = i;
|
|
18175
|
+
best = c;
|
|
18176
|
+
}
|
|
18177
|
+
}
|
|
18178
|
+
if (bestIdx < 0 || best === null)
|
|
18179
|
+
break;
|
|
18180
|
+
out.push(best);
|
|
18181
|
+
selected.push(best);
|
|
18182
|
+
candidates.splice(bestIdx, 1);
|
|
18183
|
+
}
|
|
18184
|
+
return out;
|
|
18185
|
+
}
|
|
18186
|
+
function tieBreak(a, b) {
|
|
18187
|
+
if (Math.abs(a.finalScore - b.finalScore) > 0.000000001)
|
|
18188
|
+
return b.finalScore - a.finalScore;
|
|
18189
|
+
const at = new Date(a.created_at).getTime();
|
|
18190
|
+
const bt = new Date(b.created_at).getTime();
|
|
18191
|
+
if (at !== bt)
|
|
18192
|
+
return bt - at;
|
|
18193
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
18194
|
+
}
|
|
18195
|
+
function statusBoost(status) {
|
|
18196
|
+
if (status === "promoted")
|
|
18197
|
+
return 0.15;
|
|
18198
|
+
if (status === "established")
|
|
18199
|
+
return 0.1;
|
|
18200
|
+
return 0;
|
|
18201
|
+
}
|
|
18202
|
+
async function searchKnowledge(params) {
|
|
18203
|
+
const {
|
|
18204
|
+
directory,
|
|
18205
|
+
config,
|
|
18206
|
+
query,
|
|
18207
|
+
context,
|
|
18208
|
+
mode,
|
|
18209
|
+
agent = "unknown",
|
|
18210
|
+
sessionId = "unknown",
|
|
18211
|
+
tier = "all",
|
|
18212
|
+
emitEvent = false,
|
|
18213
|
+
applyScopeFilter = true,
|
|
18214
|
+
forceReadHive = false,
|
|
18215
|
+
applyRoleScope = true,
|
|
18216
|
+
skipLedgerGenesis = false
|
|
18217
|
+
} = params;
|
|
18218
|
+
const traceId = newTraceId();
|
|
18219
|
+
const max = params.maxResults ?? config.max_inject_count ?? 5;
|
|
18220
|
+
let results = [];
|
|
18221
|
+
let synonymTokens = [];
|
|
18222
|
+
const synonymTrace = {};
|
|
18223
|
+
try {
|
|
18224
|
+
const mergeConfig = {
|
|
18225
|
+
...config,
|
|
18226
|
+
hive_enabled: tier === "swarm" ? false : forceReadHive ? true : config.hive_enabled,
|
|
18227
|
+
max_inject_count: Math.max(20, max)
|
|
18228
|
+
};
|
|
18229
|
+
const projected = {
|
|
18230
|
+
projectName: context?.projectName ?? "unknown",
|
|
18231
|
+
currentPhase: context?.currentPhase ?? "",
|
|
18232
|
+
techStack: context?.techStack,
|
|
18233
|
+
recentErrors: [
|
|
18234
|
+
...context?.recentReviewerFailures ?? [],
|
|
18235
|
+
...context?.recentTestFailures ?? [],
|
|
18236
|
+
...context?.recentToolErrors ?? []
|
|
18237
|
+
]
|
|
18238
|
+
};
|
|
18239
|
+
let candidates = await readMergedKnowledge(directory, mergeConfig, projected, {
|
|
18240
|
+
skipScopeFilter: !applyScopeFilter
|
|
18241
|
+
});
|
|
18242
|
+
const journalPath2 = path5.join(directory, ".swarm", "knowledge-receipts-v2.jsonl");
|
|
18243
|
+
const journalReadable = existsSync3(journalPath2) && statSync4(journalPath2).size > 0;
|
|
18244
|
+
const counterRollups = skipLedgerGenesis && !journalReadable ? new Map : await readAuthoritativeKnowledgeCounterRollups(directory);
|
|
18245
|
+
candidates = candidates.filter((e) => {
|
|
18246
|
+
if (!isActiveStatus(e.status))
|
|
18247
|
+
return false;
|
|
18248
|
+
if (tier === "hive" && e.tier !== "hive")
|
|
18249
|
+
return false;
|
|
18250
|
+
return true;
|
|
18251
|
+
});
|
|
18252
|
+
const role = stripKnownSwarmPrefix(agent).toLowerCase();
|
|
18253
|
+
if (applyRoleScope && role && role !== "unknown") {
|
|
18254
|
+
candidates = candidates.filter((e) => {
|
|
18255
|
+
if (!e.applies_to_agents || e.applies_to_agents.length === 0)
|
|
18256
|
+
return true;
|
|
18257
|
+
if (role === "architect")
|
|
18258
|
+
return true;
|
|
18259
|
+
return e.applies_to_agents.map((a) => stripKnownSwarmPrefix(a).toLowerCase()).includes(role);
|
|
18260
|
+
});
|
|
18261
|
+
}
|
|
18262
|
+
const queryText = (query ?? "").trim();
|
|
18263
|
+
const queryBigrams = queryText ? wordBigrams(normalize(queryText)) : null;
|
|
18264
|
+
const hasQuery = queryBigrams !== null;
|
|
18265
|
+
const textWeight = hasQuery ? TEXT_WEIGHT : 0;
|
|
18266
|
+
const metaWeight = hasQuery ? META_WEIGHT : 1;
|
|
18267
|
+
const compositeRaw = [
|
|
18268
|
+
queryText,
|
|
18269
|
+
context?.taskTitle,
|
|
18270
|
+
context?.taskDescription,
|
|
18271
|
+
context?.lastUserMessage,
|
|
18272
|
+
context?.currentAction,
|
|
18273
|
+
...context?.recentReviewerFailures ?? [],
|
|
18274
|
+
...context?.recentTestFailures ?? [],
|
|
18275
|
+
...context?.recentToolErrors ?? []
|
|
18276
|
+
].filter((s) => typeof s === "string" && s.length > 0).join(" ");
|
|
18277
|
+
const compositeNorm = normalize(compositeRaw);
|
|
18278
|
+
const hasComposite = compositeNorm.length > 0;
|
|
18279
|
+
try {
|
|
18280
|
+
if (hasComposite) {
|
|
18281
|
+
const synMap = await readSynonymMap(directory, config.retrieval?.synonym_map_max_pairs);
|
|
18282
|
+
const synIndex = buildSynonymIndex(synMap, config.retrieval?.synonym_min_cooccurrence);
|
|
18283
|
+
const baseTokens = compositeNorm.split(" ").filter(Boolean);
|
|
18284
|
+
synonymTokens = expandTokens(synIndex, baseTokens);
|
|
18285
|
+
}
|
|
18286
|
+
} catch {
|
|
18287
|
+
synonymTokens = [];
|
|
18288
|
+
}
|
|
18289
|
+
const minConf = typeof config.directive_min_confidence === "number" ? config.directive_min_confidence : DIRECTIVE_BOOST_MIN_CONFIDENCE;
|
|
18290
|
+
const scored = candidates.map((entry) => {
|
|
18291
|
+
const retrievalOutcomes = effectiveRetrievalOutcomes(entry.retrieval_outcomes, counterRollups.get(entry.id));
|
|
18292
|
+
const textScore = queryBigrams ? jaccardBigram(queryBigrams, wordBigrams(normalize(entryText(entry)))) : 0;
|
|
18293
|
+
const metaScore = entry.finalScore;
|
|
18294
|
+
const ds = context ? scoreDirectiveAgainstContext(entry, context) : { triggerHit: false, actionHit: false, agentHit: false, score: 0 };
|
|
18295
|
+
const confBoost = context && entry.confidence >= minConf && (ds.actionHit || ds.agentHit) ? 0.25 : 0;
|
|
18296
|
+
const generatedSkillBoost = entry.generated_skill_path && entry.status !== "archived" ? 0.05 : 0;
|
|
18297
|
+
const outcomeBoost = computeOutcomeSignal(retrievalOutcomes) * OUTCOME_RANK_WEIGHT;
|
|
18298
|
+
const coldStartBonus = (retrievalOutcomes.applied_explicit_count ?? 0) === 0 && entryAgePhases(entry) < (config.retrieval?.cold_start_max_age_phases ?? DEFAULT_COLD_START_MAX_AGE_PHASES) ? config.retrieval?.cold_start_bonus ?? DEFAULT_COLD_START_BONUS : 0;
|
|
18299
|
+
let synonymBoost = 0;
|
|
18300
|
+
if (synonymTokens.length > 0) {
|
|
18301
|
+
const entryHay = normalize(entryText(entry));
|
|
18302
|
+
const matched = [];
|
|
18303
|
+
for (const t of synonymTokens) {
|
|
18304
|
+
if (t.length > 0 && entryHay.includes(t))
|
|
18305
|
+
matched.push(t);
|
|
18306
|
+
}
|
|
18307
|
+
if (matched.length > 0) {
|
|
18308
|
+
synonymBoost = SYNONYM_TEXT_BOOST;
|
|
18309
|
+
synonymTrace[entry.id] = matched;
|
|
18310
|
+
}
|
|
18311
|
+
}
|
|
18312
|
+
const triggerRecallHit = !ds.triggerHit && hasComposite && Array.isArray(entry.triggers) && entry.triggers.some((tr) => {
|
|
18313
|
+
const n = normalize(tr);
|
|
18314
|
+
return n.length >= TRIGGER_RECALL_MIN_LEN && compositeNorm.includes(n);
|
|
18315
|
+
});
|
|
18316
|
+
const triggerRecallBoost = triggerRecallHit ? TRIGGER_RECALL_BOOST : 0;
|
|
18317
|
+
const finalScore = Math.min(1, Math.max(0, textWeight * textScore + metaWeight * metaScore + ds.score + confBoost + generatedSkillBoost + outcomeBoost + coldStartBonus + synonymBoost + triggerRecallBoost + (hasQuery && !entry.confidence_floor_demoted ? statusBoost(entry.status) : 0)));
|
|
18318
|
+
const statusScoreBoost = hasQuery && !entry.confidence_floor_demoted ? statusBoost(entry.status) : 0;
|
|
18319
|
+
const isCritical = entry.directive_priority === "critical" && (ds.triggerHit || ds.actionHit || ds.agentHit);
|
|
18320
|
+
const isForceHive = forceReadHive && entry.tier === "hive";
|
|
18321
|
+
return {
|
|
18322
|
+
...entry,
|
|
18323
|
+
retrieval_outcomes: retrievalOutcomes,
|
|
18324
|
+
finalScore,
|
|
18325
|
+
coldStartBoost: coldStartBonus,
|
|
18326
|
+
score_breakdown: {
|
|
18327
|
+
text_score: textScore,
|
|
18328
|
+
metadata_score: metaScore,
|
|
18329
|
+
directive_score: ds.score,
|
|
18330
|
+
confidence_boost: confBoost,
|
|
18331
|
+
generated_skill_boost: generatedSkillBoost,
|
|
18332
|
+
outcome_boost: outcomeBoost,
|
|
18333
|
+
cold_start_boost: coldStartBonus,
|
|
18334
|
+
synonym_boost: synonymBoost,
|
|
18335
|
+
trigger_recall_boost: triggerRecallBoost,
|
|
18336
|
+
status_boost: statusScoreBoost,
|
|
18337
|
+
final_score: finalScore,
|
|
18338
|
+
...synonymTrace[entry.id] ? { synonym_matches: synonymTrace[entry.id] } : {}
|
|
18339
|
+
},
|
|
18340
|
+
__critical: isCritical,
|
|
18341
|
+
__forceHive: isForceHive
|
|
18342
|
+
};
|
|
18343
|
+
});
|
|
18344
|
+
scored.sort((a, b) => {
|
|
18345
|
+
const diff = b.finalScore - a.finalScore;
|
|
18346
|
+
if (Math.abs(diff) > 0.001)
|
|
18347
|
+
return diff;
|
|
18348
|
+
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
|
18349
|
+
});
|
|
18350
|
+
const top = [];
|
|
18351
|
+
const seen = new Set;
|
|
18352
|
+
for (const e of scored) {
|
|
18353
|
+
if (top.length >= max)
|
|
18354
|
+
break;
|
|
18355
|
+
if (e.__critical && !seen.has(e.id)) {
|
|
18356
|
+
top.push(e);
|
|
18357
|
+
seen.add(e.id);
|
|
18358
|
+
}
|
|
18359
|
+
}
|
|
18360
|
+
for (const e of scored) {
|
|
18361
|
+
if (top.length >= max)
|
|
18362
|
+
break;
|
|
18363
|
+
if (e.__forceHive && !seen.has(e.id)) {
|
|
18364
|
+
top.push(e);
|
|
18365
|
+
seen.add(e.id);
|
|
18366
|
+
}
|
|
18367
|
+
}
|
|
18368
|
+
const lambda = clampLambda(config.retrieval?.mmr_lambda);
|
|
18369
|
+
const remaining = scored.filter((e) => !seen.has(e.id));
|
|
18370
|
+
const selected = mmrRerank(remaining, top, max, lambda);
|
|
18371
|
+
for (const e of selected) {
|
|
18372
|
+
if (top.length >= max)
|
|
18373
|
+
break;
|
|
18374
|
+
if (!seen.has(e.id)) {
|
|
18375
|
+
top.push(e);
|
|
18376
|
+
seen.add(e.id);
|
|
18377
|
+
}
|
|
18378
|
+
}
|
|
18379
|
+
results = top.map(({ __critical: _c, __forceHive: _f, ...rest }) => rest);
|
|
18380
|
+
} catch (err) {
|
|
18381
|
+
warn(`[search-knowledge] retrieval failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
18382
|
+
results = [];
|
|
18383
|
+
}
|
|
18384
|
+
if (emitEvent) {
|
|
18385
|
+
const ranks = {};
|
|
18386
|
+
const scores = {};
|
|
18387
|
+
results.forEach((e, idx) => {
|
|
18388
|
+
ranks[e.id] = idx + 1;
|
|
18389
|
+
scores[e.id] = e.finalScore;
|
|
18390
|
+
});
|
|
18391
|
+
const synonymBreakdown = {};
|
|
18392
|
+
for (const e of results) {
|
|
18393
|
+
if (synonymTrace[e.id])
|
|
18394
|
+
synonymBreakdown[e.id] = synonymTrace[e.id];
|
|
18395
|
+
}
|
|
18396
|
+
const scoreBreakdown = synonymTokens.length > 0 ? {
|
|
18397
|
+
synonyms_expanded: synonymTokens,
|
|
18398
|
+
synonym_matches: synonymBreakdown
|
|
18399
|
+
} : undefined;
|
|
18400
|
+
await recordKnowledgeEvent(directory, {
|
|
18401
|
+
type: "retrieved",
|
|
18402
|
+
trace_id: traceId,
|
|
18403
|
+
session_id: sessionId,
|
|
18404
|
+
phase: context?.currentPhase,
|
|
18405
|
+
task_id: context?.taskId,
|
|
18406
|
+
agent,
|
|
18407
|
+
query: query ?? context?.lastUserMessage ?? context?.currentPhase ?? "",
|
|
18408
|
+
retrieval_mode: mode,
|
|
18409
|
+
result_ids: results.map((e) => e.id),
|
|
18410
|
+
ranks,
|
|
18411
|
+
scores,
|
|
18412
|
+
...scoreBreakdown ? { score_breakdown: scoreBreakdown } : {}
|
|
18413
|
+
});
|
|
18414
|
+
}
|
|
18415
|
+
return { trace_id: traceId, results };
|
|
17290
18416
|
}
|
|
17291
|
-
|
|
17292
|
-
|
|
17293
|
-
|
|
18417
|
+
|
|
18418
|
+
// src/tools/swarm-memory-recall.ts
|
|
18419
|
+
init_zod();
|
|
18420
|
+
var MEMORY_KINDS = [
|
|
18421
|
+
"user_preference",
|
|
18422
|
+
"project_fact",
|
|
18423
|
+
"architecture_decision",
|
|
18424
|
+
"repo_convention",
|
|
18425
|
+
"api_finding",
|
|
18426
|
+
"code_pattern",
|
|
18427
|
+
"test_pattern",
|
|
18428
|
+
"failure_pattern",
|
|
18429
|
+
"security_note",
|
|
18430
|
+
"evidence",
|
|
18431
|
+
"todo",
|
|
18432
|
+
"scratch"
|
|
18433
|
+
];
|
|
18434
|
+
var swarm_memory_recall = createSwarmTool({
|
|
18435
|
+
description: "Recall scoped Swarm memory for the current repository. Read-only; retrieved memory is untrusted background.",
|
|
17294
18436
|
args: {
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
})).describe("Files to check (from diff gate)"),
|
|
17299
|
-
mode: exports_external.enum(["changed", "all"]).optional().describe("Check mode over the supplied changed_files. 'changed' (default) checks only entries with additions > 0, so deletion-only and pure-rename entries are excluded; 'all' checks every supplied entry. Neither mode enumerates the repository \u2014 this tool only ever inspects the files you pass in changed_files. If the filters leave nothing to check the verdict is 'skip', never 'pass'."),
|
|
17300
|
-
languages: exports_external.array(exports_external.string()).optional().describe("Optional: restrict to specific languages")
|
|
18437
|
+
query: exports_external.string().min(3).describe("Natural language recall query"),
|
|
18438
|
+
kinds: exports_external.array(exports_external.enum(MEMORY_KINDS)).optional().describe("Optional memory kinds to include"),
|
|
18439
|
+
maxItems: exports_external.number().int().min(1).max(20).optional().describe("Maximum memories to return")
|
|
17301
18440
|
},
|
|
17302
|
-
async
|
|
17303
|
-
const gates = _internals4.loadGateOverrides(directory);
|
|
17304
|
-
const result = await syntaxCheck(args, directory, gates);
|
|
17305
|
-
return JSON.stringify(result);
|
|
17306
|
-
}
|
|
18441
|
+
execute: async (args, directory, ctx) => computeSwarmMemoryRecall(args, directory, ctx)
|
|
17307
18442
|
});
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
18443
|
+
async function computeSwarmMemoryRecall(args, directory, ctx, options) {
|
|
18444
|
+
const { config } = _internals4.loadPluginConfigWithMeta(directory);
|
|
18445
|
+
if (config.memory?.enabled !== true) {
|
|
18446
|
+
return JSON.stringify({
|
|
18447
|
+
success: false,
|
|
18448
|
+
disabled: true,
|
|
18449
|
+
message: "Swarm memory is disabled. Set swarm.memory.enabled=true."
|
|
18450
|
+
});
|
|
18451
|
+
}
|
|
18452
|
+
const parsed = RecallArgsSchema.safeParse(args);
|
|
18453
|
+
if (!parsed.success) {
|
|
18454
|
+
return JSON.stringify({
|
|
18455
|
+
success: false,
|
|
18456
|
+
error: parsed.error.issues.map((issue) => issue.message).join("; ")
|
|
18457
|
+
});
|
|
18458
|
+
}
|
|
18459
|
+
const agent = getContextAgent(ctx);
|
|
18460
|
+
const unitId = ctx?.sessionID ? _internals4.getAgentSession(ctx.sessionID)?.currentTaskId ?? undefined : undefined;
|
|
18461
|
+
const gateway = _internals4.createMemoryGateway({
|
|
18462
|
+
directory,
|
|
18463
|
+
sessionID: ctx?.sessionID,
|
|
18464
|
+
agentRole: agent,
|
|
18465
|
+
agentId: agent,
|
|
18466
|
+
runId: ctx?.sessionID,
|
|
18467
|
+
unitId
|
|
18468
|
+
}, {
|
|
18469
|
+
config: config.memory
|
|
18470
|
+
});
|
|
17314
18471
|
try {
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
18472
|
+
const bundle = await gateway.recall(parsed.data, {
|
|
18473
|
+
recordUsage: options?.recordUsage !== false
|
|
18474
|
+
});
|
|
18475
|
+
return JSON.stringify({
|
|
18476
|
+
success: true,
|
|
18477
|
+
bundle_id: bundle.id,
|
|
18478
|
+
memory_ids: bundle.items.map((item) => item.record.id),
|
|
18479
|
+
total: bundle.items.length,
|
|
18480
|
+
token_estimate: bundle.tokenEstimate,
|
|
18481
|
+
signals: bundle.items.map((item) => ({
|
|
18482
|
+
memory_id: item.record.id,
|
|
18483
|
+
relation: item.relation,
|
|
18484
|
+
...item.signals
|
|
18485
|
+
})),
|
|
18486
|
+
prompt_block: bundle.promptBlock
|
|
18487
|
+
}, null, 2);
|
|
18488
|
+
} finally {
|
|
18489
|
+
await gateway.dispose();
|
|
17318
18490
|
}
|
|
17319
18491
|
}
|
|
17320
|
-
var
|
|
17321
|
-
|
|
17322
|
-
|
|
17323
|
-
|
|
17324
|
-
})).describe("Files to check (from diff gate)"),
|
|
17325
|
-
mode: exports_external.enum(["changed", "all"]).optional().describe("Check mode: 'changed' checks only additions > 0 entries; 'all' checks every entry"),
|
|
17326
|
-
languages: exports_external.array(exports_external.string()).optional().describe("Restrict to specific languages")
|
|
17327
|
-
});
|
|
17328
|
-
var placeholderScanSchema = exports_external.object({
|
|
17329
|
-
changed_files: exports_external.array(exports_external.string()).describe("Repository-relative file paths to scan")
|
|
17330
|
-
});
|
|
17331
|
-
var sastScanSchema = exports_external.object({
|
|
17332
|
-
changed_files: exports_external.array(exports_external.string()).describe("Repository-relative file paths to scan"),
|
|
17333
|
-
severity_threshold: exports_external.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity to report (default: medium)")
|
|
17334
|
-
});
|
|
17335
|
-
var qualityBudgetSchema = exports_external.object({
|
|
17336
|
-
changed_files: exports_external.array(exports_external.string()).describe("Repository-relative file paths to analyze")
|
|
17337
|
-
});
|
|
17338
|
-
var evidenceCheckSchema = exports_external.object({
|
|
17339
|
-
required_types: exports_external.string().optional().describe('Comma-separated evidence types required per task (default: "reviewer,test_engineer")')
|
|
18492
|
+
var RecallArgsSchema = exports_external.object({
|
|
18493
|
+
query: exports_external.string().min(3),
|
|
18494
|
+
kinds: exports_external.array(exports_external.enum(MEMORY_KINDS)).optional(),
|
|
18495
|
+
maxItems: exports_external.number().int().min(1).max(20).optional()
|
|
17340
18496
|
});
|
|
17341
|
-
var
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
pathFields: ["changed_files"],
|
|
17346
|
-
inputSchema: syntaxCheckSchema,
|
|
17347
|
-
execute: (rawArgs, root) => computeSyntaxCheck(syntaxCheckSchema.parse(rawArgs), root)
|
|
17348
|
-
};
|
|
17349
|
-
var placeholderScanAdapter = {
|
|
17350
|
-
name: "placeholder_scan",
|
|
17351
|
-
description: "Scan changed files for placeholder content: TODO/FIXME comments, stub implementations, and placeholder markers that indicate unfinished work.",
|
|
17352
|
-
kind: "read",
|
|
17353
|
-
pathFields: ["changed_files"],
|
|
17354
|
-
inputSchema: placeholderScanSchema,
|
|
17355
|
-
execute: (rawArgs, root) => computePlaceholderScan({ changed_files: placeholderScanSchema.parse(rawArgs).changed_files }, root)
|
|
17356
|
-
};
|
|
17357
|
-
var sastScanAdapter = {
|
|
17358
|
-
name: "sast_scan",
|
|
17359
|
-
description: "static analysis security scan",
|
|
17360
|
-
kind: "read",
|
|
17361
|
-
pathFields: ["changed_files"],
|
|
17362
|
-
inputSchema: sastScanSchema,
|
|
17363
|
-
execute: (rawArgs, root) => {
|
|
17364
|
-
const args = sastScanSchema.parse(rawArgs);
|
|
17365
|
-
return computeSastScan({
|
|
17366
|
-
changed_files: args.changed_files,
|
|
17367
|
-
severity_threshold: args.severity_threshold,
|
|
17368
|
-
offline_only: true
|
|
17369
|
-
}, root);
|
|
17370
|
-
}
|
|
17371
|
-
};
|
|
17372
|
-
var qualityBudgetAdapter = {
|
|
17373
|
-
name: "quality_budget",
|
|
17374
|
-
description: "check quality budgets (complexity, API surface, duplication, test ratio) for changed files",
|
|
17375
|
-
kind: "read",
|
|
17376
|
-
pathFields: ["changed_files"],
|
|
17377
|
-
inputSchema: qualityBudgetSchema,
|
|
17378
|
-
execute: (rawArgs, root) => computeQualityBudget({ changed_files: qualityBudgetSchema.parse(rawArgs).changed_files }, root)
|
|
17379
|
-
};
|
|
17380
|
-
var evidenceCheckAdapter = {
|
|
17381
|
-
name: "evidence_check",
|
|
17382
|
-
description: "Verify completed tasks in the plan have required evidence. Reads .swarm/plan.md for completed tasks and .swarm/evidence/ for evidence files. Returns JSON with completeness ratio and gaps for tasks missing required evidence types.",
|
|
17383
|
-
kind: "read",
|
|
17384
|
-
pathFields: [],
|
|
17385
|
-
inputSchema: evidenceCheckSchema,
|
|
17386
|
-
execute: async (rawArgs, root) => {
|
|
17387
|
-
const args = evidenceCheckSchema.parse(rawArgs);
|
|
17388
|
-
const result = await evidence_check.execute(args, mcpToolContext(root));
|
|
17389
|
-
return typeof result === "string" ? safeParseJson(result) : result;
|
|
17390
|
-
}
|
|
18497
|
+
var _internals4 = {
|
|
18498
|
+
loadPluginConfigWithMeta,
|
|
18499
|
+
createMemoryGateway,
|
|
18500
|
+
getAgentSession
|
|
17391
18501
|
};
|
|
18502
|
+
function getContextAgent(ctx) {
|
|
18503
|
+
if (!ctx || typeof ctx !== "object")
|
|
18504
|
+
return;
|
|
18505
|
+
const value = ctx.agent;
|
|
18506
|
+
return typeof value === "string" ? value : undefined;
|
|
18507
|
+
}
|
|
17392
18508
|
|
|
17393
18509
|
// src/mcp/adapters/knowledge-memory.ts
|
|
17394
18510
|
var knowledgeRecallSchema = exports_external.object({
|
|
@@ -17401,13 +18517,12 @@ var memoryRecallSchema = exports_external.object({
|
|
|
17401
18517
|
});
|
|
17402
18518
|
var knowledgeRecallAdapter = {
|
|
17403
18519
|
name: "knowledge_recall",
|
|
17404
|
-
description: "Performs semantic natural-language search across the knowledge base for relevant past decisions, patterns, and lessons learned.",
|
|
17405
18520
|
kind: "read",
|
|
17406
18521
|
pathFields: [],
|
|
17407
18522
|
inputSchema: knowledgeRecallSchema,
|
|
17408
18523
|
execute: async (rawArgs, root) => {
|
|
17409
18524
|
const args = knowledgeRecallSchema.parse(rawArgs);
|
|
17410
|
-
const knowledgePath =
|
|
18525
|
+
const knowledgePath = path6.join(root, ".swarm", "knowledge");
|
|
17411
18526
|
if (!existsSync4(knowledgePath)) {
|
|
17412
18527
|
return {
|
|
17413
18528
|
results: [],
|
|
@@ -17441,7 +18556,6 @@ var knowledgeRecallAdapter = {
|
|
|
17441
18556
|
};
|
|
17442
18557
|
var swarmMemoryRecallAdapter = {
|
|
17443
18558
|
name: "swarm_memory_recall",
|
|
17444
|
-
description: "recall scoped Swarm memory for the current repository as untrusted background",
|
|
17445
18559
|
kind: "read",
|
|
17446
18560
|
pathFields: [],
|
|
17447
18561
|
inputSchema: memoryRecallSchema,
|
|
@@ -17455,7 +18569,7 @@ var swarmMemoryRecallAdapter = {
|
|
|
17455
18569
|
message: "Swarm memory is disabled. Set swarm.memory.enabled=true."
|
|
17456
18570
|
};
|
|
17457
18571
|
}
|
|
17458
|
-
const storeArtifact = (config.memory.provider ?? "sqlite") === "sqlite" ?
|
|
18572
|
+
const storeArtifact = (config.memory.provider ?? "sqlite") === "sqlite" ? path6.join(root, ".swarm", "memory", "memory.db") : path6.join(root, ".swarm", "memory", "memories.jsonl");
|
|
17459
18573
|
if (!existsSync4(storeArtifact)) {
|
|
17460
18574
|
return {
|
|
17461
18575
|
available: false,
|
|
@@ -17468,6 +18582,18 @@ var swarmMemoryRecallAdapter = {
|
|
|
17468
18582
|
}
|
|
17469
18583
|
};
|
|
17470
18584
|
|
|
18585
|
+
// src/mcp/adapters/receipt-status.ts
|
|
18586
|
+
var receiptStatusAdapter = {
|
|
18587
|
+
name: "knowledge_receipt_status",
|
|
18588
|
+
kind: "read",
|
|
18589
|
+
pathFields: [],
|
|
18590
|
+
inputSchema: writeReceiptStatusInput,
|
|
18591
|
+
execute: async (rawArgs, root) => {
|
|
18592
|
+
const args = writeReceiptStatusInput.parse(rawArgs);
|
|
18593
|
+
return readWriteReceiptStatus(root, args.idempotency_key);
|
|
18594
|
+
}
|
|
18595
|
+
};
|
|
18596
|
+
|
|
17471
18597
|
// src/mcp/adapters/scope-repo.ts
|
|
17472
18598
|
init_zod();
|
|
17473
18599
|
|
|
@@ -17475,7 +18601,7 @@ init_zod();
|
|
|
17475
18601
|
init_zod();
|
|
17476
18602
|
import * as child_process from "child_process";
|
|
17477
18603
|
import * as fs4 from "fs";
|
|
17478
|
-
import * as
|
|
18604
|
+
import * as path7 from "path";
|
|
17479
18605
|
|
|
17480
18606
|
// src/diff/summary-generator.ts
|
|
17481
18607
|
var RISK_LEVELS = ["Critical", "High", "Medium", "Low"];
|
|
@@ -17572,20 +18698,20 @@ function validateBase(base) {
|
|
|
17572
18698
|
function validatePaths(paths) {
|
|
17573
18699
|
if (!paths)
|
|
17574
18700
|
return null;
|
|
17575
|
-
for (const
|
|
17576
|
-
if (!
|
|
18701
|
+
for (const path8 of paths) {
|
|
18702
|
+
if (!path8 || path8.length === 0) {
|
|
17577
18703
|
return "empty path not allowed";
|
|
17578
18704
|
}
|
|
17579
|
-
if (
|
|
18705
|
+
if (path8.length > MAX_PATH_LENGTH) {
|
|
17580
18706
|
return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
|
|
17581
18707
|
}
|
|
17582
|
-
if (SHELL_METACHARACTERS.test(
|
|
18708
|
+
if (SHELL_METACHARACTERS.test(path8)) {
|
|
17583
18709
|
return "path contains shell metacharacters";
|
|
17584
18710
|
}
|
|
17585
|
-
if (
|
|
18711
|
+
if (path8.startsWith("-")) {
|
|
17586
18712
|
return 'path cannot start with "-" (option-like arguments not allowed)';
|
|
17587
18713
|
}
|
|
17588
|
-
if (CONTROL_CHAR_PATTERN.test(
|
|
18714
|
+
if (CONTROL_CHAR_PATTERN.test(path8)) {
|
|
17589
18715
|
return "path contains control characters";
|
|
17590
18716
|
}
|
|
17591
18717
|
}
|
|
@@ -17689,8 +18815,8 @@ var diff = createSwarmTool({
|
|
|
17689
18815
|
if (parts.length >= 3) {
|
|
17690
18816
|
const additions = parseInt(parts[0], 10) || 0;
|
|
17691
18817
|
const deletions = parseInt(parts[1], 10) || 0;
|
|
17692
|
-
const
|
|
17693
|
-
files.push({ path:
|
|
18818
|
+
const path8 = parts[2];
|
|
18819
|
+
files.push({ path: path8, additions, deletions });
|
|
17694
18820
|
}
|
|
17695
18821
|
}
|
|
17696
18822
|
if (summaryOnly) {
|
|
@@ -17773,7 +18899,7 @@ var diff = createSwarmTool({
|
|
|
17773
18899
|
} else if (base === "unstaged") {
|
|
17774
18900
|
const oldRef = `:${file.path}`;
|
|
17775
18901
|
oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
|
|
17776
|
-
newContent = fs4.readFileSync(
|
|
18902
|
+
newContent = fs4.readFileSync(path7.join(directory, file.path), "utf-8");
|
|
17777
18903
|
} else {
|
|
17778
18904
|
const oldRef = `${base}:${file.path}`;
|
|
17779
18905
|
oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
|
|
@@ -17925,7 +19051,6 @@ var symbolsSchema = exports_external.object({
|
|
|
17925
19051
|
});
|
|
17926
19052
|
var planConflictCheckAdapter = {
|
|
17927
19053
|
name: "plan_conflict_check",
|
|
17928
|
-
description: "read-only advisory check (#1656): compute a pairwise file-conflict matrix for N proposed parallel task groups using declared scopes and optional git co-change; returns a verdict (all_disjoint / conflicts_present / unknown_scopes), per-pair evidence, and a suggested serialization order. Writes nothing \u2014 the execution gate independently recomputes the verdict inline at dispatch time via the same helper. Call BEFORE attempting parallel dispatch to confirm disjointness.",
|
|
17929
19054
|
kind: "read",
|
|
17930
19055
|
pathFields: [],
|
|
17931
19056
|
inputSchema: planConflictCheckSchema,
|
|
@@ -17933,7 +19058,6 @@ var planConflictCheckAdapter = {
|
|
|
17933
19058
|
};
|
|
17934
19059
|
var diffAdapter = {
|
|
17935
19060
|
name: "diff",
|
|
17936
|
-
description: "Analyze git diff for changed files, exports, interfaces, and function signatures. Returns structured output with contract change detection.",
|
|
17937
19061
|
kind: "read",
|
|
17938
19062
|
pathFields: ["paths"],
|
|
17939
19063
|
inputSchema: diffSchema,
|
|
@@ -17958,7 +19082,6 @@ var diffAdapter = {
|
|
|
17958
19082
|
};
|
|
17959
19083
|
var symbolsAdapter = {
|
|
17960
19084
|
name: "symbols",
|
|
17961
|
-
description: "Extract and search code symbols (functions, classes, methods, interfaces) across the repository using tree-sitter. Use for understanding code structure and finding definitions.",
|
|
17962
19085
|
kind: "read",
|
|
17963
19086
|
pathFields: ["file", "workspace"],
|
|
17964
19087
|
inputSchema: symbolsSchema,
|
|
@@ -17969,8 +19092,169 @@ var symbolsAdapter = {
|
|
|
17969
19092
|
}
|
|
17970
19093
|
};
|
|
17971
19094
|
|
|
19095
|
+
// src/tools/scope-validate.ts
|
|
19096
|
+
init_zod();
|
|
19097
|
+
import path8 from "path";
|
|
19098
|
+
init_path_identity();
|
|
19099
|
+
var scopeValidateArgs = {
|
|
19100
|
+
command: exports_external.string().min(1).max(64 * 1024).describe("Shell command to inspect; it is never executed"),
|
|
19101
|
+
shell: exports_external.enum(["posix", "powershell", "cmd"]).describe("Shell grammar used to inspect the command"),
|
|
19102
|
+
scope_files: exports_external.array(exports_external.string().min(1).max(4096)).min(1).max(1e4).describe("Workspace-relative paths the command is allowed to write")
|
|
19103
|
+
};
|
|
19104
|
+
var scopeValidateSchema = exports_external.object(scopeValidateArgs);
|
|
19105
|
+
|
|
19106
|
+
class ScopeValidationError extends Error {
|
|
19107
|
+
code;
|
|
19108
|
+
constructor(code, message) {
|
|
19109
|
+
super(`${code}: ${message}`);
|
|
19110
|
+
this.name = "ScopeValidationError";
|
|
19111
|
+
this.code = code;
|
|
19112
|
+
}
|
|
19113
|
+
}
|
|
19114
|
+
function fail(code, message) {
|
|
19115
|
+
throw new ScopeValidationError(code, message);
|
|
19116
|
+
}
|
|
19117
|
+
function detectWrites(args) {
|
|
19118
|
+
if (args.shell === "posix")
|
|
19119
|
+
return detectPosixWrites(args.command);
|
|
19120
|
+
return detectWindowsWrites(args.command, args.shell);
|
|
19121
|
+
}
|
|
19122
|
+
function createScopeMatcher(scopeEntries, directory) {
|
|
19123
|
+
const flavor = getPathFlavor();
|
|
19124
|
+
const pathImpl = flavor === "win32" ? path8.win32 : path8.posix;
|
|
19125
|
+
const scopeIdentities = new Set(scopeEntries.map((scope) => normalizePathIdentity(pathImpl.resolve(directory, scope), flavor)));
|
|
19126
|
+
return (filePath) => {
|
|
19127
|
+
let candidate = pathImpl.resolve(directory, filePath);
|
|
19128
|
+
while (true) {
|
|
19129
|
+
if (scopeIdentities.has(normalizePathIdentity(candidate, flavor))) {
|
|
19130
|
+
return true;
|
|
19131
|
+
}
|
|
19132
|
+
const parent = pathImpl.dirname(candidate);
|
|
19133
|
+
if (parent === candidate)
|
|
19134
|
+
return false;
|
|
19135
|
+
candidate = parent;
|
|
19136
|
+
}
|
|
19137
|
+
};
|
|
19138
|
+
}
|
|
19139
|
+
function evaluateScopeValidate(args, directory) {
|
|
19140
|
+
validateWorkspaceRoot(directory);
|
|
19141
|
+
if (!args || typeof args.command !== "string") {
|
|
19142
|
+
fail("INVALID_INPUT", "command must be a string");
|
|
19143
|
+
}
|
|
19144
|
+
if (!["posix", "powershell", "cmd"].includes(args.shell)) {
|
|
19145
|
+
fail("INVALID_INPUT", "shell must be posix, powershell, or cmd");
|
|
19146
|
+
}
|
|
19147
|
+
if (!Array.isArray(args.scope_files)) {
|
|
19148
|
+
fail("INVALID_SCOPE", "scope_files must be a non-empty array");
|
|
19149
|
+
}
|
|
19150
|
+
const normalizedScope = normalizeScopeFiles(args.scope_files);
|
|
19151
|
+
if (!normalizedScope) {
|
|
19152
|
+
fail("INVALID_SCOPE", "scope_files must contain safe workspace-relative paths");
|
|
19153
|
+
}
|
|
19154
|
+
const validateScopeTarget = createTargetWithinRootValidator(directory);
|
|
19155
|
+
for (const scopeFile of normalizedScope) {
|
|
19156
|
+
const unsafeReason = unsafePathTextReason(scopeFile);
|
|
19157
|
+
if (unsafeReason)
|
|
19158
|
+
fail("INVALID_SCOPE", unsafeReason);
|
|
19159
|
+
const reason = validateScopeTarget(scopeFile);
|
|
19160
|
+
if (reason)
|
|
19161
|
+
fail("INVALID_SCOPE", reason);
|
|
19162
|
+
}
|
|
19163
|
+
const isInScope = createScopeMatcher(normalizedScope, directory);
|
|
19164
|
+
const classification = classifyCommand(args.command);
|
|
19165
|
+
if (classification.ambiguous) {
|
|
19166
|
+
fail("AMBIGUOUS_COMMAND", "command syntax could not be verified safely");
|
|
19167
|
+
}
|
|
19168
|
+
if (classification.aggregate === "catastrophic" || classification.aggregate === "destructive") {
|
|
19169
|
+
fail("DESTRUCTIVE_COMMAND", "destructive command intent is not permitted");
|
|
19170
|
+
}
|
|
19171
|
+
const analysis = detectWrites(args);
|
|
19172
|
+
if (analysis.parseError) {
|
|
19173
|
+
fail("UNPARSEABLE_COMMAND", "command could not be parsed safely");
|
|
19174
|
+
}
|
|
19175
|
+
if (!analysis.hasWrites) {
|
|
19176
|
+
return {
|
|
19177
|
+
allowed: true,
|
|
19178
|
+
shell: args.shell,
|
|
19179
|
+
command_digest: classification.originalDigest,
|
|
19180
|
+
targets: []
|
|
19181
|
+
};
|
|
19182
|
+
}
|
|
19183
|
+
const resolved = resolveWriteTargets(args.command, analysis.writes, directory);
|
|
19184
|
+
const targets = [];
|
|
19185
|
+
for (const target of resolved) {
|
|
19186
|
+
if (!target.resolved || !target.resolvedPath) {
|
|
19187
|
+
fail("UNRESOLVED_TARGET", "a write target could not be resolved statically");
|
|
19188
|
+
}
|
|
19189
|
+
const resolvedPath = path8.resolve(target.resolvedPath);
|
|
19190
|
+
if (!isCanonicalPathWithinRoot(resolvedPath, directory)) {
|
|
19191
|
+
fail("ROOT_ESCAPE", "a write target resolves outside the configured root");
|
|
19192
|
+
}
|
|
19193
|
+
const symlinkReason = checkWriteTargetForSymlink(resolvedPath, directory);
|
|
19194
|
+
if (symlinkReason) {
|
|
19195
|
+
fail("SYMLINK_ESCAPE", "a write target uses an unverifiable symlink or junction");
|
|
19196
|
+
}
|
|
19197
|
+
const relativeTarget = path8.relative(directory, resolvedPath);
|
|
19198
|
+
if (!isInScope(relativeTarget)) {
|
|
19199
|
+
fail("SCOPE_VIOLATION", "a write target is outside the inline declared scope");
|
|
19200
|
+
}
|
|
19201
|
+
const authority = checkFileAuthority("coder", relativeTarget, directory, undefined, { declaredScope: normalizedScope });
|
|
19202
|
+
if (!authority.allowed) {
|
|
19203
|
+
fail("AUTHORITY_DENIED", "a write target is protected by file authority policy");
|
|
19204
|
+
}
|
|
19205
|
+
targets.push({
|
|
19206
|
+
category: target.original.category,
|
|
19207
|
+
operator: target.original.operator,
|
|
19208
|
+
path: relativeTarget.replace(/\\/g, "/")
|
|
19209
|
+
});
|
|
19210
|
+
}
|
|
19211
|
+
return {
|
|
19212
|
+
allowed: true,
|
|
19213
|
+
shell: args.shell,
|
|
19214
|
+
command_digest: classification.originalDigest,
|
|
19215
|
+
targets
|
|
19216
|
+
};
|
|
19217
|
+
}
|
|
19218
|
+
var scope_validate = createSwarmTool({
|
|
19219
|
+
description: "Validate a shell command against an inline workspace scope without executing it. Rejects destructive, ambiguous, unresolved, escaping, symlinked, protected, and out-of-scope writes.",
|
|
19220
|
+
args: scopeValidateArgs,
|
|
19221
|
+
execute: async (args, directory) => JSON.stringify(evaluateScopeValidate(scopeValidateSchema.parse(args), directory))
|
|
19222
|
+
});
|
|
19223
|
+
|
|
19224
|
+
// src/mcp/adapters/scope-validation.ts
|
|
19225
|
+
var scopeValidationAdapter = {
|
|
19226
|
+
name: "scope_validate",
|
|
19227
|
+
kind: "read",
|
|
19228
|
+
pathFields: ["scope_files"],
|
|
19229
|
+
inputSchema: scopeValidateSchema,
|
|
19230
|
+
execute: async (rawArgs, root) => evaluateScopeValidate(scopeValidateSchema.parse(rawArgs), root)
|
|
19231
|
+
};
|
|
19232
|
+
|
|
17972
19233
|
// src/mcp/registry.ts
|
|
17973
19234
|
var WRITE_TOOL_NAME_PATTERN = /write_|record_|submit_|repair_|prepare_|rebind_|invalidate_|authorize_|external_skill_(promote|reject|delete|revoke)|swarm_apply_patch|save_plan|update_task_status|declare_scope|set_qa_gates|knowledge_add|knowledge_remove|knowledge_archive|checkpoint|phase_complete|complete_pr_workflow|abort_pr_workflow|approve_plan_critic|swarm_memory_propose|swarm_memory_outcome|spec_write|lint_spec|skill_generate|skill_regenerate|skill_retire|skill_improve|skill_apply|run_stale_reconciliation|epic_record_divergence|lean_turbo_acquire_locks|lean_turbo_plan_lanes|lean_turbo_critic|lean_turbo_review|lean_turbo_run_phase|convene_general_council|swarm_command/;
|
|
19235
|
+
var REVIEWED_MCP_WRITE_TOOLS = ["knowledge_add"];
|
|
19236
|
+
var REVIEWED_MCP_WRITE_TOOL_SET = new Set(REVIEWED_MCP_WRITE_TOOLS);
|
|
19237
|
+
function validateWritePolicy(allowWrite, writeTools) {
|
|
19238
|
+
if (writeTools === undefined)
|
|
19239
|
+
return [];
|
|
19240
|
+
if (!Array.isArray(writeTools)) {
|
|
19241
|
+
throw new Error("MCP write policy must be an array");
|
|
19242
|
+
}
|
|
19243
|
+
if (!allowWrite && writeTools.length > 0) {
|
|
19244
|
+
throw new Error("MCP write tools require the explicit allowWrite startup capability");
|
|
19245
|
+
}
|
|
19246
|
+
const seen = new Set;
|
|
19247
|
+
for (const name of writeTools) {
|
|
19248
|
+
if (typeof name !== "string" || name.length === 0 || !REVIEWED_MCP_WRITE_TOOL_SET.has(name)) {
|
|
19249
|
+
throw new Error(`unknown or unauthorized MCP write tool: ${String(name)}`);
|
|
19250
|
+
}
|
|
19251
|
+
if (seen.has(name)) {
|
|
19252
|
+
throw new Error(`duplicate MCP write tool: ${name}`);
|
|
19253
|
+
}
|
|
19254
|
+
seen.add(name);
|
|
19255
|
+
}
|
|
19256
|
+
return writeTools;
|
|
19257
|
+
}
|
|
17974
19258
|
var READ_ADAPTERS = [
|
|
17975
19259
|
knowledgeRecallAdapter,
|
|
17976
19260
|
swarmMemoryRecallAdapter,
|
|
@@ -17981,13 +19265,16 @@ var READ_ADAPTERS = [
|
|
|
17981
19265
|
qualityBudgetAdapter,
|
|
17982
19266
|
planConflictCheckAdapter,
|
|
17983
19267
|
diffAdapter,
|
|
17984
|
-
symbolsAdapter
|
|
19268
|
+
symbolsAdapter,
|
|
19269
|
+
scopeValidationAdapter,
|
|
19270
|
+
receiptStatusAdapter
|
|
17985
19271
|
];
|
|
17986
19272
|
function buildMcpToolRegistry(options) {
|
|
17987
19273
|
const root = options.root || options.directory || "";
|
|
17988
19274
|
if (!root) {
|
|
17989
19275
|
throw new Error("buildMcpToolRegistry: a project root is required");
|
|
17990
19276
|
}
|
|
19277
|
+
const requestedWrites = validateWritePolicy(options.allowWrite, options.writeTools);
|
|
17991
19278
|
const tools = [];
|
|
17992
19279
|
for (const adapter of READ_ADAPTERS) {
|
|
17993
19280
|
const metadata = TOOL_METADATA[adapter.name];
|
|
@@ -18009,6 +19296,26 @@ function buildMcpToolRegistry(options) {
|
|
|
18009
19296
|
execute: adapter.execute
|
|
18010
19297
|
});
|
|
18011
19298
|
}
|
|
19299
|
+
if (requestedWrites.includes("knowledge_add")) {
|
|
19300
|
+
const metadata = TOOL_METADATA.knowledge_add;
|
|
19301
|
+
if (!metadata) {
|
|
19302
|
+
throw new Error("buildMcpToolRegistry: knowledge_add is not a registered tool (TOOL_METADATA parity violated)");
|
|
19303
|
+
}
|
|
19304
|
+
if (WRITE_TOOL_NAME_PATTERN.test(knowledgeAddAdapter.name) === false) {
|
|
19305
|
+
throw new Error("buildMcpToolRegistry: reviewed knowledge_add adapter is not classified as write-capable");
|
|
19306
|
+
}
|
|
19307
|
+
if (knowledgeAddAdapter.kind !== "write") {
|
|
19308
|
+
throw new Error("buildMcpToolRegistry: knowledge_add adapter must be write-capable");
|
|
19309
|
+
}
|
|
19310
|
+
tools.push({
|
|
19311
|
+
name: knowledgeAddAdapter.name,
|
|
19312
|
+
description: metadata.description,
|
|
19313
|
+
kind: "write",
|
|
19314
|
+
pathFields: knowledgeAddAdapter.pathFields,
|
|
19315
|
+
inputSchema: knowledgeAddAdapter.inputSchema,
|
|
19316
|
+
execute: knowledgeAddAdapter.execute
|
|
19317
|
+
});
|
|
19318
|
+
}
|
|
18012
19319
|
return { tools };
|
|
18013
19320
|
}
|
|
18014
19321
|
|
|
@@ -18030,10 +19337,10 @@ class McpContainmentError extends Error {
|
|
|
18030
19337
|
function validatePathValue(field, value, root) {
|
|
18031
19338
|
if (typeof value !== "string" || value === "")
|
|
18032
19339
|
return;
|
|
18033
|
-
if (
|
|
18034
|
-
const resolved2 =
|
|
18035
|
-
const relative2 =
|
|
18036
|
-
if (relative2.startsWith("..") ||
|
|
19340
|
+
if (path9.isAbsolute(value) || /^[A-Za-z]:[/\\]/.test(value)) {
|
|
19341
|
+
const resolved2 = path9.resolve(value);
|
|
19342
|
+
const relative2 = path9.relative(root, resolved2);
|
|
19343
|
+
if (relative2.startsWith("..") || path9.isAbsolute(relative2)) {
|
|
18037
19344
|
throw new McpContainmentError(field, value, root);
|
|
18038
19345
|
}
|
|
18039
19346
|
if (!isCanonicalPathWithinRoot(resolved2, root)) {
|
|
@@ -18045,7 +19352,7 @@ function validatePathValue(field, value, root) {
|
|
|
18045
19352
|
if (reason !== null) {
|
|
18046
19353
|
throw new McpContainmentError(field, value, root);
|
|
18047
19354
|
}
|
|
18048
|
-
const resolved =
|
|
19355
|
+
const resolved = path9.resolve(root, value);
|
|
18049
19356
|
if (!isCanonicalPathWithinRoot(resolved, root)) {
|
|
18050
19357
|
throw new McpContainmentError(field, value, root);
|
|
18051
19358
|
}
|
|
@@ -18080,7 +19387,8 @@ function createMcpServer(options) {
|
|
|
18080
19387
|
const root = options.root;
|
|
18081
19388
|
const registry = buildMcpToolRegistry({
|
|
18082
19389
|
root,
|
|
18083
|
-
allowWrite: options.allowWrite
|
|
19390
|
+
allowWrite: options.allowWrite,
|
|
19391
|
+
writeTools: options.writeTools
|
|
18084
19392
|
});
|
|
18085
19393
|
const server = new McpServer({
|
|
18086
19394
|
name: SERVER_NAME,
|
|
@@ -18095,10 +19403,11 @@ function createMcpServer(options) {
|
|
|
18095
19403
|
for (const field of tool.pathFields) {
|
|
18096
19404
|
validatePathField(field, args[field], root);
|
|
18097
19405
|
}
|
|
18098
|
-
const raw = await tool.execute(args, root);
|
|
19406
|
+
const raw = tool.kind === "write" ? await tool.execute(args, root, { hooks: options.writeHooks }) : await tool.execute(args, root);
|
|
18099
19407
|
const { text } = applyResponsePipeline(raw);
|
|
18100
19408
|
return {
|
|
18101
|
-
content: [{ type: "text", text }]
|
|
19409
|
+
content: [{ type: "text", text }],
|
|
19410
|
+
...tool.kind === "write" && isStructuredWriteFailure(raw) ? { isError: true } : {}
|
|
18102
19411
|
};
|
|
18103
19412
|
} catch (error) {
|
|
18104
19413
|
const raw = error instanceof Error ? error.message : String(error);
|
|
@@ -18112,6 +19421,16 @@ function createMcpServer(options) {
|
|
|
18112
19421
|
}
|
|
18113
19422
|
return server;
|
|
18114
19423
|
}
|
|
19424
|
+
function isStructuredWriteFailure(value) {
|
|
19425
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
19426
|
+
return false;
|
|
19427
|
+
const record3 = value;
|
|
19428
|
+
if (record3.success === false)
|
|
19429
|
+
return true;
|
|
19430
|
+
if (record3.success !== true && typeof record3.error === "string")
|
|
19431
|
+
return true;
|
|
19432
|
+
return false;
|
|
19433
|
+
}
|
|
18115
19434
|
async function runMcpServer(options) {
|
|
18116
19435
|
const server = createMcpServer(options);
|
|
18117
19436
|
const transport = options.transport !== undefined ? options.transport : new StdioServerTransport;
|