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.
Files changed (118) hide show
  1. package/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
  2. package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
  3. package/dist/agents/architect.d.ts +9 -1
  4. package/dist/cli/{capability-probe-pdwwz8gs.js → capability-probe-8k8ebeyp.js} +2 -2
  5. package/dist/cli/{coder-settlement-pm07553t.js → coder-settlement-d2bfb25j.js} +13 -13
  6. package/dist/cli/{config-doctor-3mwx2rwq.js → config-doctor-905x2s1c.js} +4 -4
  7. package/dist/cli/{core-ty985ae1.js → core-k7wfx1wx.js} +4 -4
  8. package/dist/cli/{curation-policy-d8s9w1z8.js → curation-policy-n4hd2wsa.js} +5 -5
  9. package/dist/cli/{curator-drift-4m984tvy.js → curator-drift-smqmej5v.js} +4 -4
  10. package/dist/cli/curator-llm-factory-qhhxev17.js +72 -0
  11. package/dist/cli/{curator-pch7ww87.js → curator-wyadq69k.js} +40 -40
  12. package/dist/cli/{evidence-summary-service-cxed89n3.js → evidence-summary-service-r3wcprfk.js} +19 -19
  13. package/dist/cli/{gate-evidence-ymye7yew.js → gate-evidence-tq9rb2aj.js} +8 -8
  14. package/dist/cli/guardrail-explain-kvgejh24.js +73 -0
  15. package/dist/cli/{guardrail-log-pgggycf9.js → guardrail-log-m6twga6x.js} +9 -9
  16. package/dist/cli/{guardrail-reset-aghymhr5.js → guardrail-reset-86esv8cr.js} +40 -40
  17. package/dist/cli/hive-promoter-s87hkb40.js +87 -0
  18. package/dist/cli/{index-zzr7f594.js → index-18hjycr1.js} +1 -1
  19. package/dist/cli/{index-w7aedfce.js → index-29begjkp.js} +8 -8
  20. package/dist/cli/{index-7nsvszwn.js → index-2b1whj28.js} +2 -2
  21. package/dist/cli/{index-7bvzssz0.js → index-2bmhe2jn.js} +4 -4
  22. package/dist/cli/{index-hxdswvtv.js → index-2mdk6nt9.js} +7 -7
  23. package/dist/cli/{index-rz4gpt3n.js → index-2vhkwy6v.js} +3 -3
  24. package/dist/cli/{index-fpbcxykc.js → index-6b295cas.js} +87 -1
  25. package/dist/cli/{index-yzjaj53h.js → index-9rwhas09.js} +1 -1
  26. package/dist/cli/{index-8dy3qf4q.js → index-at0ygwwp.js} +5 -5
  27. package/dist/cli/{index-q1jterxw.js → index-azy3tw6c.js} +2 -2
  28. package/dist/cli/{index-eefz8ghz.js → index-bfz1jvcn.js} +1 -1
  29. package/dist/cli/{index-gwnbprzk.js → index-d5dexrv1.js} +4 -4
  30. package/dist/cli/{index-wemtrnch.js → index-ffqy30e5.js} +2 -2
  31. package/dist/cli/{index-5w51tawb.js → index-gg44hytk.js} +9 -9
  32. package/dist/cli/{index-ebmep9me.js → index-heg4em8j.js} +591 -261
  33. package/dist/cli/{index-z8j7jqxt.js → index-j1v54ahd.js} +4 -3
  34. package/dist/cli/{index-tj0sdxg9.js → index-j3rkh9mp.js} +2 -2
  35. package/dist/cli/{index-zbq9091c.js → index-jzxeymr0.js} +13 -13
  36. package/dist/cli/{index-zwam9stk.js → index-ke3m026z.js} +6 -6
  37. package/dist/cli/{index-r18n5f17.js → index-kfk403mc.js} +5 -5
  38. package/dist/cli/{index-5frh43fj.js → index-m2n1rhgv.js} +12 -0
  39. package/dist/cli/{index-k6bjkqw8.js → index-mpcp6rmj.js} +1 -1
  40. package/dist/cli/{index-fnmt1ema.js → index-p27az2hc.js} +4 -4
  41. package/dist/cli/{index-5d2avnj9.js → index-pm7c6f66.js} +2 -2
  42. package/dist/cli/{index-vfp9hjgv.js → index-q9vb5e7n.js} +1 -1
  43. package/dist/cli/{index-0z5s71kn.js → index-qk29b7s4.js} +42 -42
  44. package/dist/cli/{index-by7xpk8f.js → index-qs8eth8h.js} +37 -6
  45. package/dist/cli/{index-ef2fa774.js → index-rp0jk48x.js} +7 -7
  46. package/dist/cli/{index-9ftc8saz.js → index-sqngtwyq.js} +1 -1
  47. package/dist/cli/{index-tzxk6ct7.js → index-swxaa0zs.js} +40 -40
  48. package/dist/cli/{index-xnednxvm.js → index-tnjmn0dn.js} +1 -1
  49. package/dist/cli/{index-rgapg88w.js → index-vfc7d22n.js} +7 -3
  50. package/dist/cli/{index-1bq6hkw5.js → index-w4yjy3vp.js} +1 -1
  51. package/dist/cli/{index-73arkdgf.js → index-wbkxbgk8.js} +9 -5
  52. package/dist/cli/{index-2jtmevrr.js → index-wfbh1bx8.js} +3 -3
  53. package/dist/cli/{index-4chr4gbq.js → index-wkde35ej.js} +9 -9
  54. package/dist/cli/{index-76qm2c2y.js → index-wvghb3x6.js} +4 -4
  55. package/dist/cli/{index-v2he8xfj.js → index-y6a2bkp1.js} +2 -2
  56. package/dist/cli/{index-eya03h5m.js → index-y74n1sy8.js} +7 -7
  57. package/dist/cli/{index-tbswvz8s.js → index-yeh78c1g.js} +2 -2
  58. package/dist/cli/{index-en0p0hqp.js → index-yj1kr0ft.js} +1 -1
  59. package/dist/cli/{index-nzz58tbs.js → index-zex7644g.js} +2 -2
  60. package/dist/cli/{index-exhm2ztr.js → index-zmb8fjp5.js} +1 -1
  61. package/dist/cli/{index-mcq6xs4k.js → index-zsm8h8y1.js} +4 -4
  62. package/dist/cli/{index-yhaxn5r7.js → index-zsp9de3k.js} +2 -2
  63. package/dist/cli/index.js +41 -41
  64. package/dist/cli/{knowledge-escalator-praedffr.js → knowledge-escalator-rgz471df.js} +13 -13
  65. package/dist/cli/{knowledge-events-sn5gs5q9.js → knowledge-events-4mq0ypx9.js} +11 -11
  66. package/dist/cli/{knowledge-link-9z3fha35.js → knowledge-link-npk50523.js} +4 -4
  67. package/dist/cli/{knowledge-store-838sgd97.js → knowledge-store-gft25s5t.js} +5 -5
  68. package/dist/cli/{knowledge-validator-bsxgarqv.js → knowledge-validator-ejqacbx4.js} +7 -7
  69. package/dist/cli/{mcp-skh29erk.js → mcp-emaeeq4s.js} +48 -7
  70. package/dist/cli/mcp.d.ts +9 -0
  71. package/dist/cli/{model-preflight-gk1jysa8.js → model-preflight-kh9dwts2.js} +1 -1
  72. package/dist/cli/{pending-delegations-6zr5es4b.js → pending-delegations-4bhkcykk.js} +11 -11
  73. package/dist/cli/{pr-subscriptions-x2rr3gva.js → pr-subscriptions-h1n6dbds.js} +11 -11
  74. package/dist/cli/{pr-workflow-gate-jx30c9vz.js → pr-workflow-gate-wn6v5za8.js} +40 -40
  75. package/dist/cli/{runner-yhfmejzb.js → runner-ffy5ahhz.js} +5 -5
  76. package/dist/cli/{scan-cursor-6h85e2g0.js → scan-cursor-e63t0g5b.js} +6 -6
  77. package/dist/cli/{schema-8h1b602y.js → schema-4k4hr33y.js} +4 -2
  78. package/dist/cli/{scope-persistence-rb99xjr0.js → scope-persistence-qcw3rtxq.js} +16 -16
  79. package/dist/cli/{server-kdjc5aw8.js → server-58s1wwap.js} +2055 -736
  80. package/dist/cli/{skill-generator-nz7wjabh.js → skill-generator-fhj13jmq.js} +15 -15
  81. package/dist/cli/{snapshot-coordination-init-hexx2msp.js → snapshot-coordination-init-5fawrj35.js} +40 -40
  82. package/dist/cli/{telemetry-w774xb8g.js → telemetry-ca8dmn26.js} +1 -1
  83. package/dist/cli/{workspace-snapshot-9gqna7gq.js → workspace-snapshot-v5qyjxg0.js} +3 -3
  84. package/dist/cli/{worktree-collision-ownership-ecwj3ehp.js → worktree-collision-ownership-hjk0jx19.js} +12 -12
  85. package/dist/cli/{worktree-isolation-0x9fx30v.js → worktree-isolation-aj20bs6q.js} +40 -40
  86. package/dist/commands/dashboard.d.ts +15 -0
  87. package/dist/commands/registry.d.ts +7 -0
  88. package/dist/config/schema.d.ts +16 -0
  89. package/dist/dashboard/assets.d.ts +12 -0
  90. package/dist/dashboard/index.d.ts +74 -0
  91. package/dist/dashboard/server.d.ts +94 -0
  92. package/dist/dashboard/views.d.ts +131 -0
  93. package/dist/events/core-events.d.ts +1 -1
  94. package/dist/hooks/delegation-gate.d.ts +26 -0
  95. package/dist/hooks/index.d.ts +1 -1
  96. package/dist/hooks/tool-summarizer.d.ts +7 -2
  97. package/dist/index.js +437 -276
  98. package/dist/mcp/adapters/knowledge-add.d.ts +19 -0
  99. package/dist/mcp/adapters/receipt-status.d.ts +3 -0
  100. package/dist/mcp/adapters/scope-validation.d.ts +3 -0
  101. package/dist/mcp/registry.d.ts +26 -14
  102. package/dist/mcp/server.d.ts +5 -0
  103. package/dist/mcp/write-receipts.d.ts +168 -0
  104. package/dist/summaries/index.d.ts +1 -1
  105. package/dist/summaries/manager.d.ts +27 -1
  106. package/dist/tools/approve-retry-sounding-board.d.ts +5 -0
  107. package/dist/tools/index.d.ts +3 -0
  108. package/dist/tools/knowledge-receipt-status.d.ts +8 -0
  109. package/dist/tools/manifest.d.ts +3 -0
  110. package/dist/tools/scope-validate.d.ts +55 -0
  111. package/dist/tools/tool-metadata.d.ts +12 -0
  112. package/dist/types/events.d.ts +5 -1
  113. package/dist/utils/path-security.d.ts +21 -0
  114. package/opencode-swarm.schema.json +14 -0
  115. package/package.json +1 -1
  116. package/dist/cli/curator-llm-factory-zbs6hm60.js +0 -72
  117. package/dist/cli/guardrail-explain-1zy8c3vn.js +0 -73
  118. 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-ebmep9me.js";
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-vfp9hjgv.js";
31
- import"./index-by7xpk8f.js";
32
- import"./index-eefz8ghz.js";
33
- import"./index-rgapg88w.js";
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-zbq9091c.js";
39
- import"./index-k6bjkqw8.js";
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
- validateTargetWithinRoot
46
- } from "./index-fpbcxykc.js";
47
- import"./index-mcq6xs4k.js";
48
- import"./index-8dy3qf4q.js";
49
- import"./index-nzz58tbs.js";
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-eya03h5m.js";
64
+ import"./index-y74n1sy8.js";
52
65
  import"./index-549wxbe1.js";
53
66
  import"./index-zsrtt42s.js";
54
- import"./index-4chr4gbq.js";
55
- import"./index-5d2avnj9.js";
56
- import"./index-7nsvszwn.js";
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-r18n5f17.js";
63
- import"./index-fnmt1ema.js";
64
- import"./index-ef2fa774.js";
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-w7aedfce.js";
71
- import"./index-exhm2ztr.js";
83
+ } from "./index-29begjkp.js";
84
+ import"./index-zmb8fjp5.js";
72
85
  import"./index-2s7dvzff.js";
73
- import"./index-gwnbprzk.js";
74
- import"./index-wemtrnch.js";
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-rz4gpt3n.js";
96
+ import"./index-2vhkwy6v.js";
77
97
  import"./index-frcemc0b.js";
78
98
  import"./index-wrt875kh.js";
79
- import"./index-5w51tawb.js";
80
- import"./index-zwam9stk.js";
81
- import"./index-z8j7jqxt.js";
82
- import"./index-v2he8xfj.js";
83
- import"./index-1bq6hkw5.js";
84
- import"./index-xnednxvm.js";
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-tbswvz8s.js";
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-hxdswvtv.js";
126
+ } from "./index-2mdk6nt9.js";
98
127
  import"./index-vzm6zre7.js";
99
128
  import"./index-ae75rja9.js";
100
- import"./index-en0p0hqp.js";
101
- import"./index-tj0sdxg9.js";
129
+ import"./index-yj1kr0ft.js";
130
+ import {
131
+ tryAcquireLock
132
+ } from "./index-j3rkh9mp.js";
102
133
  import"./index-7g4c7s5r.js";
103
- import"./index-2jtmevrr.js";
104
- import"./index-yhaxn5r7.js";
105
- import"./index-1xnse449.js";
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-zzr7f594.js";
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-73arkdgf.js";
158
+ } from "./index-wbkxbgk8.js";
123
159
  import {
124
160
  TOOL_METADATA,
125
161
  init_tool_metadata
126
- } from "./index-5frh43fj.js";
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-76qm2c2y.js";
171
- import"./index-yzjaj53h.js";
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 path7 from "path";
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/mcp/adapters/knowledge-memory.ts
15577
+ // src/tools/knowledge-add.ts
15536
15578
  init_zod();
15537
- import { existsSync as existsSync4 } from "fs";
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 JACCARD_THRESHOLD = 0.6;
15551
- var HIVE_TIER_BOOST = 0.05;
15552
- var DEFAULT_SAME_PROJECT_PENALTY = -0.05;
15553
- function inferCategoriesFromPhase(phaseDescription) {
15554
- const lower = phaseDescription.toLowerCase();
15555
- const patterns = [
15556
- {
15557
- pattern: /\b(?:test|qa|quality|verification|validation)\b/,
15558
- categories: ["testing", "debugging"]
15559
- },
15560
- {
15561
- pattern: /\b(?:implement|build|develop|coding|code)\b/,
15562
- categories: ["tooling", "architecture", "debugging"]
15563
- },
15564
- {
15565
- pattern: /\b(?:integrat|deploy|ci|cd|release|publish)\b/,
15566
- categories: ["integration", "tooling", "performance"]
15567
- },
15568
- {
15569
- pattern: /\b(?:plan|design|architect|spec|requirement)\b/,
15570
- categories: ["architecture", "process"]
15571
- },
15572
- {
15573
- pattern: /\b(?:review|refactor|cleanup|polish|optimi)\b/,
15574
- categories: ["performance", "architecture", "process"]
15575
- },
15576
- {
15577
- pattern: /\b(?:secur|audit|harden|compliance)\b/,
15578
- categories: ["security", "testing"]
15579
- },
15580
- {
15581
- pattern: /\b(?:setup|config|scaffold|init|bootstrap)\b/,
15582
- categories: ["tooling", "other"]
15583
- },
15584
- {
15585
- pattern: /\b(?:doc|readme|changelog)\b/,
15586
- categories: ["process", "tooling"]
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
- for (const { pattern, categories } of patterns) {
15590
- if (pattern.test(lower)) {
15591
- return categories;
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
- async function readMergedKnowledge(directory, config, context, opts) {
15597
- const swarmPath = resolveSwarmKnowledgePath(directory);
15598
- const swarmEntries = await readKnowledge(swarmPath);
15599
- let hiveEntries = [];
15600
- if (config.hive_enabled !== false) {
15601
- const hivePath = resolveHiveKnowledgePath();
15602
- hiveEntries = await readKnowledge(hivePath);
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
- const seenLessons = new Set;
15605
- const merged = [];
15606
- for (const entry of hiveEntries) {
15607
- const normalized = normalize(entry.lesson);
15608
- seenLessons.add(normalized);
15609
- merged.push({
15610
- ...entry,
15611
- relevanceScore: { category: 0, confidence: 0, keywords: 0 },
15612
- finalScore: 0
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
- for (const entry of swarmEntries) {
15616
- const normalized = normalize(entry.lesson);
15617
- if (seenLessons.has(normalized)) {
15618
- continue;
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
- const swarmBigrams = wordBigrams(normalized);
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
- const retractionRecords = await readRetractionRecords(directory);
15635
- const suppressedLessons = new Set(retractionRecords.map((record3) => record3.normalized_lesson).filter((value) => typeof value === "string" && value.length > 0));
15636
- const scopeFilter = config.scope_filter ?? ["global"];
15637
- const filtered = merged.filter((entry) => (opts?.skipScopeFilter || scopeFilter.some((pattern) => (entry.scope ?? "global") === pattern)) && isActiveStatus(entry.status) && !suppressedLessons.has(normalize(entry.lesson)));
15638
- const ranked = filtered.map((entry) => {
15639
- let categoryScore = 0;
15640
- if (context?.currentPhase) {
15641
- const phaseCategories = inferCategoriesFromPhase(context.currentPhase);
15642
- if (phaseCategories.includes(entry.category)) {
15643
- categoryScore = 1;
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
- const confidenceScore = entry.confidence;
15651
- let keywordsScore = 0;
15652
- if (context?.techStack && entry.tags.length > 0) {
15653
- const matchingTags = entry.tags.filter((t) => context.techStack.some((s) => t.toLowerCase().includes(s.toLowerCase()) || s.toLowerCase().includes(t.toLowerCase()))).length;
15654
- keywordsScore = Math.min(matchingTags / Math.max(entry.tags.length, 1), 1);
15655
- } else if (entry.tags.length === 0) {
15656
- keywordsScore = 0.5;
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
- let tierBoost = 0;
15659
- let isSameProject = false;
15660
- if (entry.tier === "hive" && "source_project" in entry) {
15661
- const sourceProject = entry.source_project;
15662
- isSameProject = !!(context?.projectName && sourceProject === context.projectName);
15663
- tierBoost = HIVE_TIER_BOOST;
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
- const sameProjectPenalty = isSameProject ? DEFAULT_SAME_PROJECT_PENALTY : 0;
15666
- const finalScore = categoryScore * 0.4 + confidenceScore * 0.35 + keywordsScore * 0.25 + tierBoost + sameProjectPenalty;
15667
- const relevanceScore = {
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 lc(s) {
15691
- return (s ?? "").toLowerCase();
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 anyMatch(haystack, needles) {
15694
- if (needles.length === 0)
15695
- return false;
15696
- const hay = haystack.map(lc);
15697
- return needles.some((n) => hay.some((h) => h.includes(lc(n))));
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 tokenizeContext(ctx) {
15700
- const parts = [];
15701
- if (ctx.taskTitle)
15702
- parts.push(ctx.taskTitle);
15703
- if (ctx.taskDescription)
15704
- parts.push(ctx.taskDescription);
15705
- if (ctx.lastUserMessage)
15706
- parts.push(ctx.lastUserMessage);
15707
- if (ctx.currentAction)
15708
- parts.push(ctx.currentAction);
15709
- if (ctx.currentTool)
15710
- parts.push(ctx.currentTool);
15711
- if (ctx.targetAgent)
15712
- parts.push(ctx.targetAgent);
15713
- if (ctx.declaredScope)
15714
- parts.push(ctx.declaredScope);
15715
- if (ctx.recentReviewerFailures)
15716
- parts.push(...ctx.recentReviewerFailures);
15717
- if (ctx.recentTestFailures)
15718
- parts.push(...ctx.recentTestFailures);
15719
- if (ctx.recentToolErrors)
15720
- parts.push(...ctx.recentToolErrors);
15721
- if (ctx.planConstraints)
15722
- parts.push(...ctx.planConstraints);
15723
- if (ctx.filePaths)
15724
- parts.push(...ctx.filePaths);
15725
- return parts.map(lc);
15726
- }
15727
- function scoreDirectiveAgainstContext(entry, ctx) {
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
- // src/hooks/search-knowledge.ts
15749
- init_knowledge_types();
15750
- var TEXT_WEIGHT = 0.6;
15751
- var META_WEIGHT = 0.4;
15752
- var DIRECTIVE_BOOST_MIN_CONFIDENCE = 0.75;
15753
- var SYNONYM_TEXT_BOOST = 0.15;
15754
- var TRIGGER_RECALL_BOOST = 0.3;
15755
- var TRIGGER_RECALL_MIN_LEN = 3;
15756
- var DEFAULT_COLD_START_BONUS = 0.08;
15757
- var DEFAULT_COLD_START_MAX_AGE_PHASES = 3;
15758
- var OUTCOME_RANK_WEIGHT = 0.1;
15759
- function entryText(e) {
15760
- return `${e.lesson} ${e.tags.join(" ")} ${e.category}`;
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 entryAgePhases(e) {
15763
- const cb = e.confirmed_by;
15764
- if (!Array.isArray(cb) || cb.length === 0)
15765
- return 0;
15766
- const phases = new Set;
15767
- for (const c of cb) {
15768
- if (typeof c?.phase_number === "number")
15769
- phases.add(c.phase_number);
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 clampLambda(v) {
15774
- if (typeof v !== "number" || Number.isNaN(v))
15775
- return 0.5;
15776
- return Math.min(1, Math.max(0, v));
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 mmrRerank(pool, pinned, max, lambda) {
15779
- if (pool.length === 0)
15780
- return [];
15781
- const bigrams = new Map;
15782
- const bg = (e) => {
15783
- let s = bigrams.get(e.id);
15784
- if (!s) {
15785
- s = wordBigrams(normalize(e.lesson ?? ""));
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
- return s;
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 selected = [...pinned];
15791
- const candidates = [...pool];
15792
- const out = [];
15793
- while (selected.length < max && candidates.length > 0) {
15794
- let bestIdx = -1;
15795
- let bestScore = Number.NEGATIVE_INFINITY;
15796
- let best = null;
15797
- for (let i = 0;i < candidates.length; i++) {
15798
- const c = candidates[i];
15799
- let maxSim = 0;
15800
- for (const s of selected) {
15801
- const sim = jaccardBigram(bg(c), bg(s));
15802
- if (sim > maxSim)
15803
- maxSim = sim;
15804
- }
15805
- const score = lambda * c.finalScore - (1 - lambda) * maxSim;
15806
- if (score > bestScore + 0.000000001 || Math.abs(score - bestScore) <= 0.000000001 && best !== null && tieBreak(c, best) < 0) {
15807
- bestScore = score;
15808
- bestIdx = i;
15809
- best = c;
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 (bestIdx < 0 || best === null)
15813
- break;
15814
- out.push(best);
15815
- selected.push(best);
15816
- candidates.splice(bestIdx, 1);
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
- return out;
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 tieBreak(a, b) {
15821
- if (Math.abs(a.finalScore - b.finalScore) > 0.000000001)
15822
- return b.finalScore - a.finalScore;
15823
- const at = new Date(a.created_at).getTime();
15824
- const bt = new Date(b.created_at).getTime();
15825
- if (at !== bt)
15826
- return bt - at;
15827
- return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
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 statusBoost(status) {
15830
- if (status === "promoted")
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
- async function searchKnowledge(params) {
15837
- const {
15838
- directory,
15839
- config,
15840
- query,
15841
- context,
15842
- mode,
15843
- agent = "unknown",
15844
- sessionId = "unknown",
15845
- tier = "all",
15846
- emitEvent = false,
15847
- applyScopeFilter = true,
15848
- forceReadHive = false,
15849
- applyRoleScope = true,
15850
- skipLedgerGenesis = false
15851
- } = params;
15852
- const traceId = newTraceId();
15853
- const max = params.maxResults ?? config.max_inject_count ?? 5;
15854
- let results = [];
15855
- let synonymTokens = [];
15856
- const synonymTrace = {};
15857
- try {
15858
- const mergeConfig = {
15859
- ...config,
15860
- hive_enabled: tier === "swarm" ? false : forceReadHive ? true : config.hive_enabled,
15861
- max_inject_count: Math.max(20, max)
15862
- };
15863
- const projected = {
15864
- projectName: context?.projectName ?? "unknown",
15865
- currentPhase: context?.currentPhase ?? "",
15866
- techStack: context?.techStack,
15867
- recentErrors: [
15868
- ...context?.recentReviewerFailures ?? [],
15869
- ...context?.recentTestFailures ?? [],
15870
- ...context?.recentToolErrors ?? []
15871
- ]
15872
- };
15873
- let candidates = await readMergedKnowledge(directory, mergeConfig, projected, {
15874
- skipScopeFilter: !applyScopeFilter
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
- const journalPath = path.join(directory, ".swarm", "knowledge-receipts-v2.jsonl");
15877
- const journalReadable = existsSync(journalPath) && statSync(journalPath).size > 0;
15878
- const counterRollups = skipLedgerGenesis && !journalReadable ? new Map : await readAuthoritativeKnowledgeCounterRollups(directory);
15879
- candidates = candidates.filter((e) => {
15880
- if (!isActiveStatus(e.status))
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
- const role = stripKnownSwarmPrefix(agent).toLowerCase();
15887
- if (applyRoleScope && role && role !== "unknown") {
15888
- candidates = candidates.filter((e) => {
15889
- if (!e.applies_to_agents || e.applies_to_agents.length === 0)
15890
- return true;
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
- } catch {
15921
- synonymTokens = [];
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
- const minConf = typeof config.directive_min_confidence === "number" ? config.directive_min_confidence : DIRECTIVE_BOOST_MIN_CONFIDENCE;
15924
- const scored = candidates.map((entry) => {
15925
- const retrievalOutcomes = effectiveRetrievalOutcomes(entry.retrieval_outcomes, counterRollups.get(entry.id));
15926
- const textScore = queryBigrams ? jaccardBigram(queryBigrams, wordBigrams(normalize(entryText(entry)))) : 0;
15927
- const metaScore = entry.finalScore;
15928
- const ds = context ? scoreDirectiveAgainstContext(entry, context) : { triggerHit: false, actionHit: false, agentHit: false, score: 0 };
15929
- const confBoost = context && entry.confidence >= minConf && (ds.actionHit || ds.agentHit) ? 0.25 : 0;
15930
- const generatedSkillBoost = entry.generated_skill_path && entry.status !== "archived" ? 0.05 : 0;
15931
- const outcomeBoost = computeOutcomeSignal(retrievalOutcomes) * OUTCOME_RANK_WEIGHT;
15932
- 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;
15933
- let synonymBoost = 0;
15934
- if (synonymTokens.length > 0) {
15935
- const entryHay = normalize(entryText(entry));
15936
- const matched = [];
15937
- for (const t of synonymTokens) {
15938
- if (t.length > 0 && entryHay.includes(t))
15939
- matched.push(t);
15940
- }
15941
- if (matched.length > 0) {
15942
- synonymBoost = SYNONYM_TEXT_BOOST;
15943
- synonymTrace[entry.id] = matched;
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
- ...entry,
15957
- retrieval_outcomes: retrievalOutcomes,
15958
- finalScore,
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
- for (const e of scored) {
15995
- if (top.length >= max)
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
- const lambda = clampLambda(config.retrieval?.mmr_lambda);
16003
- const remaining = scored.filter((e) => !seen.has(e.id));
16004
- const selected = mmrRerank(remaining, top, max, lambda);
16005
- for (const e of selected) {
16006
- if (top.length >= max)
16007
- break;
16008
- if (!seen.has(e.id)) {
16009
- top.push(e);
16010
- seen.add(e.id);
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
- if (emitEvent) {
16019
- const ranks = {};
16020
- const scores = {};
16021
- results.forEach((e, idx) => {
16022
- ranks[e.id] = idx + 1;
16023
- scores[e.id] = e.finalScore;
16024
- });
16025
- const synonymBreakdown = {};
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
- return { trace_id: traceId, results };
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/tools/swarm-memory-recall.ts
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 _internals3.saveEvidence(directory, "placeholder_scan", {
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, _internals3.loadGateOverrides(directory)?.placeholder_scan);
17575
+ const result = await placeholderScan(args, directory, _internals2.loadGateOverrides(directory)?.placeholder_scan);
17079
17576
  return JSON.stringify(result);
17080
17577
  }
17081
17578
  });
17082
- var _internals3 = {
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 _internals4 = {
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
- var syntax_check = createSwarmTool({
17292
- allowWorkingDirectoryOverride: true,
17293
- 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.",
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
- changed_files: exports_external.array(exports_external.object({
17296
- path: exports_external.string(),
17297
- additions: exports_external.number()
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 execute(args, directory) {
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
- // src/mcp/adapters/verification.ts
17310
- function mcpToolContext(root) {
17311
- return { directory: root };
17312
- }
17313
- function safeParseJson(text) {
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
- return JSON.parse(text);
17316
- } catch {
17317
- return { raw: text };
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 syntaxCheckSchema = exports_external.object({
17321
- changed_files: exports_external.array(exports_external.object({
17322
- path: exports_external.string().describe("Repository-relative file path"),
17323
- additions: exports_external.number().describe("Added line count from the diff")
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 syntaxCheckAdapter = {
17342
- name: "syntax_check",
17343
- 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.",
17344
- kind: "read",
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 = path5.join(root, ".swarm", "knowledge");
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" ? path5.join(root, ".swarm", "memory", "memory.db") : path5.join(root, ".swarm", "memory", "memories.jsonl");
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 path6 from "path";
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 path7 of paths) {
17576
- if (!path7 || path7.length === 0) {
18701
+ for (const path8 of paths) {
18702
+ if (!path8 || path8.length === 0) {
17577
18703
  return "empty path not allowed";
17578
18704
  }
17579
- if (path7.length > MAX_PATH_LENGTH) {
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(path7)) {
18708
+ if (SHELL_METACHARACTERS.test(path8)) {
17583
18709
  return "path contains shell metacharacters";
17584
18710
  }
17585
- if (path7.startsWith("-")) {
18711
+ if (path8.startsWith("-")) {
17586
18712
  return 'path cannot start with "-" (option-like arguments not allowed)';
17587
18713
  }
17588
- if (CONTROL_CHAR_PATTERN.test(path7)) {
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 path7 = parts[2];
17693
- files.push({ path: path7, additions, deletions });
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(path6.join(directory, file.path), "utf-8");
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 (path7.isAbsolute(value) || /^[A-Za-z]:[/\\]/.test(value)) {
18034
- const resolved2 = path7.resolve(value);
18035
- const relative2 = path7.relative(root, resolved2);
18036
- if (relative2.startsWith("..") || path7.isAbsolute(relative2)) {
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 = path7.resolve(root, value);
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;