blun-king-cli 9.1.567 → 9.1.570

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 (263) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
  3. package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
  4. package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
  5. package/agent-spine-plugin/CHANGELOG.md +1581 -0
  6. package/agent-spine-plugin/README.md +30 -4
  7. package/agent-spine-plugin/blun.plugin.json +3 -3
  8. package/agent-spine-plugin/docs/acceptance.md +61 -0
  9. package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
  10. package/agent-spine-plugin/docs/host-integration.md +178 -0
  11. package/agent-spine-plugin/docs/preflight-recall.md +69 -0
  12. package/agent-spine-plugin/docs/preservation-contract.md +53 -0
  13. package/agent-spine-plugin/docs/quality-gates.md +50 -0
  14. package/agent-spine-plugin/docs/releasing.md +85 -0
  15. package/agent-spine-plugin/docs/session-timeline.md +251 -0
  16. package/agent-spine-plugin/docs/source-roots.md +113 -0
  17. package/agent-spine-plugin/docs/structured-completion.md +67 -0
  18. package/agent-spine-plugin/docs/world-model.md +94 -0
  19. package/agent-spine-plugin/hooks/codex.json +2 -2
  20. package/agent-spine-plugin/hooks/hooks.json +1 -1
  21. package/agent-spine-plugin/hooks/version.json +1 -1
  22. package/agent-spine-plugin/package.json +13 -3
  23. package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
  24. package/agent-spine-plugin/scripts/check-hosts.js +14 -5
  25. package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
  26. package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
  27. package/agent-spine-plugin/scripts/check-install.js +478 -0
  28. package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
  29. package/agent-spine-plugin/scripts/check-syntax.js +29 -0
  30. package/agent-spine-plugin/scripts/github-actions.js +11 -0
  31. package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
  32. package/agent-spine-plugin/scripts/release-check.js +145 -0
  33. package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
  34. package/agent-spine-plugin/scripts/run-checks.js +47 -0
  35. package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
  36. package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
  37. package/agent-spine-plugin/spine-example/1-identity.md +12 -0
  38. package/agent-spine-plugin/spine-example/2-voice.md +6 -0
  39. package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
  40. package/agent-spine-plugin/spine-example/4-history.md +4 -0
  41. package/agent-spine-plugin/src/cli-agent.js +296 -0
  42. package/agent-spine-plugin/src/cli-attention.js +95 -0
  43. package/agent-spine-plugin/src/cli-autonomy.js +36 -0
  44. package/agent-spine-plugin/src/cli-common.js +71 -0
  45. package/agent-spine-plugin/src/cli-continuity.js +116 -0
  46. package/agent-spine-plugin/src/cli-core.js +128 -0
  47. package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
  48. package/agent-spine-plugin/src/cli-host.js +21 -0
  49. package/agent-spine-plugin/src/cli-learning.js +305 -0
  50. package/agent-spine-plugin/src/cli-premortem.js +16 -0
  51. package/agent-spine-plugin/src/cli-sharing.js +230 -0
  52. package/agent-spine-plugin/src/cli.js +40 -1350
  53. package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
  54. package/agent-spine-plugin/src/hook.js +185 -567
  55. package/agent-spine-plugin/src/index.js +16 -2
  56. package/agent-spine-plugin/src/lib/acceptance.js +113 -11
  57. package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
  58. package/agent-spine-plugin/src/lib/attention-context.js +167 -0
  59. package/agent-spine-plugin/src/lib/attention-events.js +113 -0
  60. package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
  61. package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
  62. package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
  63. package/agent-spine-plugin/src/lib/attention.js +9 -600
  64. package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
  65. package/agent-spine-plugin/src/lib/audit.js +56 -6
  66. package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
  67. package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
  68. package/agent-spine-plugin/src/lib/autonomy.js +8 -0
  69. package/agent-spine-plugin/src/lib/briefing.js +67 -4
  70. package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
  71. package/agent-spine-plugin/src/lib/catalog.js +1 -1
  72. package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
  73. package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
  74. package/agent-spine-plugin/src/lib/context.js +3 -1
  75. package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
  76. package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
  77. package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
  78. package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
  79. package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
  80. package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
  81. package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
  82. package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
  83. package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
  84. package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
  85. package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
  86. package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
  87. package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
  88. package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
  89. package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
  90. package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
  91. package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
  92. package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
  93. package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
  94. package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
  95. package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
  96. package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
  97. package/agent-spine-plugin/src/lib/documents.js +27 -5
  98. package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
  99. package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
  100. package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
  101. package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
  102. package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
  103. package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
  104. package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
  105. package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
  106. package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
  107. package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
  108. package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
  109. package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
  110. package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
  111. package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
  112. package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
  113. package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
  114. package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
  115. package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
  116. package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
  117. package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
  118. package/agent-spine-plugin/src/lib/hook-context.js +424 -0
  119. package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
  120. package/agent-spine-plugin/src/lib/hook-input.js +27 -0
  121. package/agent-spine-plugin/src/lib/hook-output.js +140 -0
  122. package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
  123. package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
  124. package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
  125. package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
  126. package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
  127. package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
  128. package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
  129. package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
  130. package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
  131. package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
  132. package/agent-spine-plugin/src/lib/learning-context.js +130 -0
  133. package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
  134. package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
  135. package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
  136. package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
  137. package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
  138. package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
  139. package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
  140. package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
  141. package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
  142. package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
  143. package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
  144. package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
  145. package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
  146. package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
  147. package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
  148. package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
  149. package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
  150. package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
  151. package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
  152. package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
  153. package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
  154. package/agent-spine-plugin/src/lib/learning.js +36 -6923
  155. package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
  156. package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
  157. package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
  158. package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
  159. package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
  160. package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
  161. package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
  162. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
  163. package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
  164. package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
  165. package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
  166. package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
  167. package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
  168. package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
  169. package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
  170. package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
  171. package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
  172. package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
  173. package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
  174. package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
  175. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
  176. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
  177. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
  178. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
  179. package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
  180. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
  181. package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
  182. package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
  183. package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
  184. package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
  185. package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
  186. package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
  187. package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
  188. package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
  189. package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
  190. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
  191. package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
  192. package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
  193. package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
  194. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
  195. package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
  196. package/agent-spine-plugin/src/lib/source-roots.js +69 -136
  197. package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
  198. package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
  199. package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
  200. package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
  201. package/agent-spine-plugin/src/lib/world-model.js +278 -0
  202. package/agent-spine-plugin/src/mcp.js +20 -161
  203. package/agent-spine-plugin/src/version.js +1 -1
  204. package/agent-spine-plugin/src/worker.js +22 -5
  205. package/bin/agent-resume-snapshot.cjs +2 -2
  206. package/bin/agentspine-king-goal-inbox.mjs +111 -0
  207. package/bin/agentspine-king-goal-intake.mjs +106 -0
  208. package/bin/baseline-skill-performance-policy.cjs +1 -16
  209. package/bin/core-bootstrap.js +3 -0
  210. package/bin/curiosity-scout-policy.cjs +5 -1
  211. package/bin/input-draft-persistence.cjs +2 -2
  212. package/bin/king-tui-function-contract.json +33 -0
  213. package/bin/launcher-restart-policy.cjs +150 -0
  214. package/bin/launcher-runtime.js +56 -44
  215. package/bin/managed-context-startup-policy.cjs +27 -0
  216. package/bin/managed-plugin-selection.cjs +116 -0
  217. package/bin/mistake-relevance-policy.cjs +1 -1
  218. package/bin/observer-hooks.cjs +14 -0
  219. package/bin/oversized-context-offload-policy.cjs +86 -0
  220. package/bin/plugin-bootstrap.js +7 -40
  221. package/bin/proactive-compaction-policy.cjs +1 -1
  222. package/bin/provider-model-refresh-deadline.cjs +53 -0
  223. package/bin/provider-model-refresh-policy.cjs +107 -0
  224. package/bin/release-artifact-freeze-policy.cjs +30 -0
  225. package/bin/repeated-user-message-projection.cjs +3 -126
  226. package/bin/research-page-result.cjs +74 -0
  227. package/bin/runtime-exit-ledger.cjs +1 -0
  228. package/bin/session-compaction-policy.cjs +84 -0
  229. package/bin/skill-listing-performance-policy.cjs +2 -2
  230. package/bin/standard-tools-bootstrap.js +0 -37
  231. package/bin/subagent-skill-policy.cjs +3 -1
  232. package/bin/telegram-approval-relay.cjs +12 -7
  233. package/bin/telegram-private-conversation-policy.cjs +3 -2
  234. package/bin/telegram-queue-handoff-policy.cjs +24 -0
  235. package/bin/thinking-activity-status-policy.cjs +1 -1
  236. package/bin/thinking-only-guard.cjs +16 -12
  237. package/bin/tool-call-loop-policy.cjs +0 -2
  238. package/bin/tool-result-offload-policy.cjs +11 -2
  239. package/bin/tui-functional-contract.cjs +55 -0
  240. package/bin/update-notice.js +18 -14
  241. package/bin/user-message-offload-policy.cjs +1 -1
  242. package/bin/user-prompt-hook-origin-policy.cjs +34 -0
  243. package/bin/windows-node-crash-dump.cjs +110 -0
  244. package/blun.mjs +2861 -1173
  245. package/codebase-index/codebase_index.py +4 -3
  246. package/package.json +5 -18
  247. package/standard-skills/research-evidence/SKILL.md +39 -0
  248. package/standard-skills/research-evidence/references/evidence-format.md +82 -0
  249. package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
  250. package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
  251. package/standard-skills/web-lesen/SKILL.md +37 -22
  252. package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
  253. package/telegram-plugin/DELIVERY.md +36 -0
  254. package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
  255. package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
  256. package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
  257. package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
  258. package/telegram-plugin/dist/bridge.mjs +7 -56
  259. package/telegram-plugin/dist/mcp-server.mjs +33 -4
  260. package/agent-spine-plugin/skill/SKILL.md +0 -76
  261. package/bin/mnemo-connect-heartbeat.cjs +0 -204
  262. package/bin/mnemo-tool-agent-policy.cjs +0 -22
  263. package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
@@ -37,7 +37,7 @@
37
37
  ],
38
38
  "PreToolUse": [
39
39
  {
40
- "matcher": "Edit|Write|apply_patch|Bash",
40
+ "matcher": "^(?:Edit|Write|apply_patch|Bash|PowerShell|mcp__plugin_agent-spine_agent-spine__session_timeline_(?:index|search))$",
41
41
  "hooks": [
42
42
  {
43
43
  "type": "command",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "schema": "agentspine.hook-bundle/v1",
3
- "version": "0.59.1",
3
+ "version": "0.73.0",
4
4
  "contract": "agentspine.preflight/v2"
5
5
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-spine",
3
- "version": "0.59.1",
3
+ "version": "0.73.0",
4
4
  "description": "A non-destructive identity and memory spine for AI agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,10 +18,19 @@
18
18
  ".mcp.json",
19
19
  "assets",
20
20
  "bin",
21
- "docs",
21
+ "docs/acceptance.md",
22
+ "docs/assignment-continuation.md",
23
+ "docs/host-integration.md",
24
+ "docs/preflight-recall.md",
25
+ "docs/preservation-contract.md",
26
+ "docs/quality-gates.md",
27
+ "docs/releasing.md",
28
+ "docs/session-timeline.md",
29
+ "docs/source-roots.md",
30
+ "docs/structured-completion.md",
31
+ "docs/world-model.md",
22
32
  "hooks",
23
33
  "scripts",
24
- "skill",
25
34
  "skills",
26
35
  "spine-example",
27
36
  "src",
@@ -36,6 +45,7 @@
36
45
  "host:check": "node scripts/check-hosts.js --json",
37
46
  "host:install-check": "node scripts/check-install.js --json",
38
47
  "lint": "node scripts/check-syntax.js",
48
+ "line-budget": "node scripts/check-line-budget.js",
39
49
  "release:check": "node scripts/release-check.js --json",
40
50
  "release:sbom": "npm sbom --sbom-format cyclonedx",
41
51
  "smoke": "node bin/agentspine.js doctor --json",
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ import { spawn, spawnSync } from "node:child_process";
3
+ import { createHash } from "node:crypto";
4
+ import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
5
+ import { basename, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { installCodexMcp, stripManagedCodexBlock } from "../src/lib/codex-installation.js";
8
+ import { VERSION } from "../src/version.js";
9
+
10
+ function sha256(value) {
11
+ return createHash("sha256").update(value).digest("hex");
12
+ }
13
+
14
+ function copyFilter(source) {
15
+ const name = basename(source);
16
+ return !new Set([".git", "node_modules"]).has(name) && !name.endsWith(".tgz");
17
+ }
18
+
19
+ function toolValue(response) {
20
+ const text = response.result?.content?.[0]?.text;
21
+ return text ? JSON.parse(text) : null;
22
+ }
23
+
24
+ async function invokeLauncher(launcher, cwd, env, requests, timeoutMs = 10000) {
25
+ return new Promise((resolveResult, reject) => {
26
+ const child = spawn(process.execPath, [launcher], {
27
+ cwd, env: { ...process.env, ...env }, stdio: ["pipe", "pipe", "pipe"]
28
+ });
29
+ const expected = new Set(requests.map((request) => request.id));
30
+ const responses = new Map();
31
+ let stdout = "";
32
+ let stderr = "";
33
+ let settled = false;
34
+ let complete = false;
35
+ const finish = (error) => {
36
+ if (settled) return;
37
+ settled = true;
38
+ clearTimeout(timer);
39
+ if (error) child.kill();
40
+ if (error) reject(error);
41
+ else resolveResult(responses);
42
+ };
43
+ const timer = setTimeout(() => finish(new Error(`Codex launcher probe timed out${stderr ? `: ${stderr}` : ""}`)), timeoutMs);
44
+ child.stderr.setEncoding("utf8");
45
+ child.stderr.on("data", (chunk) => { stderr += chunk; });
46
+ child.stdout.setEncoding("utf8");
47
+ child.stdout.on("data", (chunk) => {
48
+ stdout += chunk;
49
+ let newline;
50
+ while ((newline = stdout.indexOf("\n")) >= 0) {
51
+ const line = stdout.slice(0, newline).trim();
52
+ stdout = stdout.slice(newline + 1);
53
+ if (!line) continue;
54
+ let response;
55
+ try { response = JSON.parse(line); } catch (error) { return finish(error); }
56
+ if (expected.has(response.id)) responses.set(response.id, response);
57
+ if (responses.size === expected.size && !complete) {
58
+ complete = true;
59
+ child.stdin.end();
60
+ }
61
+ }
62
+ });
63
+ child.on("error", finish);
64
+ child.on("close", (code) => {
65
+ if (!settled) finish(complete ? null
66
+ : new Error(`Codex launcher exited with ${code}${stderr ? `: ${stderr}` : ""}`));
67
+ });
68
+ for (const request of requests) child.stdin.write(`${JSON.stringify(request)}\n`);
69
+ });
70
+ }
71
+
72
+ function hookRequirement(packageRoot, projectRoot, stateRoot) {
73
+ const input = {
74
+ host: "codex", cwd: projectRoot, session_id: "session:synthetic-codex-install",
75
+ hook_event_name: "UserPromptSubmit", event_id: "prompt:synthetic-codex-install",
76
+ prompt: "Continue the synthetic installed delivery.",
77
+ agent_spine_scope: { project_id: "project:synthetic-codex-install" }
78
+ };
79
+ const result = spawnSync(process.execPath, [join(packageRoot, "src", "hook.js")], {
80
+ cwd: projectRoot, env: { ...process.env, AGENTSPINE_STATE_DIR: stateRoot },
81
+ encoding: "utf8", input: JSON.stringify(input)
82
+ });
83
+ if (result.status !== 0) throw new Error(`installed Codex hook failed: ${result.stderr}`);
84
+ const context = JSON.parse(JSON.parse(result.stdout).hookSpecificOutput.additionalContext);
85
+ const requirementId = context.preflight?.premortem?.requirementId;
86
+ if (!requirementId) throw new Error("installed Codex hook did not issue a delivery requirement");
87
+ return requirementId;
88
+ }
89
+
90
+ async function oldPackage(root, target) {
91
+ await cp(root, target, { recursive: true, filter: copyFilter });
92
+ const pkgPath = join(target, "package.json");
93
+ const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
94
+ pkg.version = "0.72.5";
95
+ await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
96
+ await writeFile(join(target, "src", "version.js"), 'export const VERSION = "0.72.5";\n', "utf8");
97
+ }
98
+
99
+ export async function checkCodexInstall(root, workspace) {
100
+ root = resolve(root);
101
+ const codexHome = join(workspace, "codex-home");
102
+ const skillsRoot = join(workspace, "user-home", ".agents", "skills");
103
+ const projectRoot = join(workspace, "project");
104
+ const stateRoot = join(workspace, "state");
105
+ const previousRoot = join(workspace, "cache-0.72.5");
106
+ await Promise.all([
107
+ mkdir(codexHome, { recursive: true }), mkdir(join(projectRoot, ".git"), { recursive: true }),
108
+ mkdir(stateRoot, { recursive: true }), oldPackage(root, previousRoot)
109
+ ]);
110
+ const foreignConfig = [
111
+ 'model = "gpt-synthetic"',
112
+ "[mcp_servers.foreign]",
113
+ 'command = "foreign-server"',
114
+ 'args = ["--keep-byte-exact"]',
115
+ ""
116
+ ].join("\r\n");
117
+ const source = "# Synthetic Codex project\n\nKeep this source byte-exact.\n";
118
+ const unknown = '{"schema":"external.unknown-event/v99","decision":"not-authority"}\n';
119
+ const configPath = join(codexHome, "config.toml");
120
+ const sourcePath = join(projectRoot, "AGENTS.md");
121
+ const unknownPath = join(stateRoot, "external-event.json");
122
+ await Promise.all([
123
+ writeFile(configPath, foreignConfig, "utf8"),
124
+ writeFile(sourcePath, source, "utf8"),
125
+ writeFile(unknownPath, unknown, "utf8")
126
+ ]);
127
+ const initial = await installCodexMcp({ codexHome, skillsRoot, packageRoot: previousRoot });
128
+ const requirementId = hookRequirement(previousRoot, projectRoot, stateRoot);
129
+ const oldResponses = await invokeLauncher(initial.launcherPath, projectRoot, {
130
+ AGENTSPINE_STATE_DIR: stateRoot, PLUGIN_ROOT: join(workspace, "wrong-plugin-root")
131
+ }, [
132
+ { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18" } },
133
+ { jsonrpc: "2.0", id: 2, method: "tools/list", params: {} },
134
+ { jsonrpc: "2.0", id: 3, method: "tools/call", params: { name: "session_briefing",
135
+ arguments: { root: projectRoot, cwd: projectRoot, host: "codex", maxBytes: 4096,
136
+ includeSourceContent: false } } }
137
+ ]);
138
+ if (oldResponses.get(1)?.result?.serverInfo?.version !== "0.72.5"
139
+ || (oldResponses.get(2)?.result?.tools || []).length < 3 || oldResponses.get(3)?.result?.isError) {
140
+ throw new Error("pre-update Codex process did not load the registered reader");
141
+ }
142
+
143
+ const updated = await installCodexMcp({ codexHome, skillsRoot, packageRoot: root });
144
+ await rm(previousRoot, { recursive: true, force: true });
145
+ const requests = [
146
+ { jsonrpc: "2.0", id: 10, method: "initialize", params: { protocolVersion: "2025-06-18" } },
147
+ { jsonrpc: "2.0", id: 11, method: "tools/list", params: {} },
148
+ { jsonrpc: "2.0", id: 12, method: "tools/call", params: { name: "read_document",
149
+ arguments: { root: projectRoot, host: "codex", path: "AGENTS.md", offset: 0, length: 4096 } } },
150
+ { jsonrpc: "2.0", id: 13, method: "tools/call", params: { name: "session_briefing",
151
+ arguments: { root: projectRoot, cwd: projectRoot, host: "codex", projectId: "project:synthetic-codex-install",
152
+ requirementId, maxBytes: 4096, includeSourceContent: false } } },
153
+ { jsonrpc: "2.0", id: 14, method: "tools/call", params: { name: "delivery_knowledge_query",
154
+ arguments: { root: projectRoot, requirementId, targetPaths: ["artifact.txt"], contractPaths: ["AGENTS.md"],
155
+ recentErrorTerms: ["stale reader", "missing registration"], maxBytes: 8192 } } },
156
+ { jsonrpc: "2.0", id: 15, method: "tools/call", params: { name: "record_delivery_premortem",
157
+ arguments: { root: projectRoot, requirementId, items: [
158
+ { category: "baseline-environment", failure: "this delivery fails because the installed reader is stale", check: "Check the loaded reader version." },
159
+ { category: "contract-tests", failure: "this delivery fails because required tools are missing", check: "List and call the required tools." },
160
+ { category: "delivery-path", failure: "this delivery fails because configuration bytes changed", check: "Hash unmanaged configuration and sources." }
161
+ ] } } }
162
+ ];
163
+ const responses = await invokeLauncher(updated.launcherPath, projectRoot, {
164
+ AGENTSPINE_STATE_DIR: stateRoot, PLUGIN_ROOT: join(workspace, "wrong-plugin-root")
165
+ }, requests);
166
+ const toolNames = new Set((responses.get(11)?.result?.tools || []).map((tool) => tool.name));
167
+ const read = toolValue(responses.get(12));
168
+ const briefing = toolValue(responses.get(13));
169
+ const knowledge = toolValue(responses.get(14));
170
+ const premortem = toolValue(responses.get(15));
171
+ for (const name of ["session_briefing", "delivery_knowledge_query", "record_delivery_premortem"]) {
172
+ if (!toolNames.has(name)) throw new Error(`updated Codex reader is missing ${name}`);
173
+ }
174
+ if (responses.get(10)?.result?.serverInfo?.version !== VERSION || read?.content !== source
175
+ || briefing?.deliveryUseReceipt?.blocked || knowledge?.deliveryUseReceipt?.blocked
176
+ || premortem?.blocked || premortem?.status !== "recorded") {
177
+ throw new Error("updated Codex reader did not preserve the existing session and required calls");
178
+ }
179
+ const config = await readFile(configPath, "utf8");
180
+ const installedSkill = await readFile(updated.skill.skillPath);
181
+ const sourceSkill = await readFile(join(root, "skills", "agent-spine", "SKILL.md"));
182
+ if (stripManagedCodexBlock(config) !== foreignConfig
183
+ || (config.match(/^\[mcp_servers\.agent-spine\]$/gm) || []).length !== 1
184
+ || !installedSkill.equals(sourceSkill)
185
+ || await readFile(sourcePath, "utf8") !== source || await readFile(unknownPath, "utf8") !== unknown) {
186
+ throw new Error("Codex installation changed foreign configuration, source bytes, or unknown state");
187
+ }
188
+ return {
189
+ ok: true,
190
+ version: VERSION,
191
+ oldVersion: "0.72.5",
192
+ configuredOnce: true,
193
+ foreignConfigDigest: sha256(foreignConfig),
194
+ unmanagedConfigDigest: updated.unmanagedAfterDigest,
195
+ sourceDigest: sha256(source),
196
+ unknownStateDigest: sha256(unknown),
197
+ tools: [...toolNames].filter((name) => REQUIRED_TOOL_NAMES.has(name)).sort(),
198
+ existingSessionReadable: true,
199
+ boundRequiredCalls: true,
200
+ nativeSkillInstalled: true,
201
+ wrongPluginRootIgnored: true,
202
+ authority: "installation-check-only"
203
+ };
204
+ }
205
+
206
+ const REQUIRED_TOOL_NAMES = new Set([
207
+ "session_briefing", "delivery_knowledge_query", "record_delivery_premortem", "read_document"
208
+ ]);
209
+
210
+ async function main() {
211
+ const root = resolve(process.argv[2] || process.cwd());
212
+ const workspace = resolve(process.argv[3] || join(root, ".synthetic-codex-install"));
213
+ await mkdir(workspace, { recursive: true });
214
+ try {
215
+ process.stdout.write(`${JSON.stringify(await checkCodexInstall(root, workspace), null, 2)}\n`);
216
+ } finally {
217
+ if (!process.argv[3]) await rm(workspace, { recursive: true, force: true });
218
+ }
219
+ }
220
+
221
+ if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
222
+ main().catch((error) => {
223
+ process.stderr.write(`AgentSpine Codex install check failed: ${error.message}\n`);
224
+ process.exitCode = 1;
225
+ });
226
+ }
@@ -3,6 +3,10 @@ import { spawn } from "node:child_process";
3
3
  import { readFile, stat } from "node:fs/promises";
4
4
  import { isAbsolute, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { installedHostEnvironment } from "./check-install-hook.js";
7
+
8
+ const TIMELINE_PRE_TOOL_MATCHER = "^(?:Edit|Write|apply_patch|Bash|PowerShell|mcp__plugin_agent-spine_agent-spine__session_timeline_(?:index|search))$";
9
+ const BLUN_TIMELINE_PRE_TOOL_MATCHER = "^(?:Edit|Write|apply_patch|Bash|PowerShell|exec_command|mcp__(?:agent-spine|plugin-agent-spine_agent-spine)__session_timeline_(?:index|search))$";
6
10
 
7
11
  function assert(condition, message) {
8
12
  if (!condition) throw new Error(message);
@@ -25,7 +29,7 @@ async function validateEntrypoint(root, value) {
25
29
  assert(metadata.isFile(), "MCP entrypoint must be a regular file");
26
30
  }
27
31
 
28
- function validateHooks(root, hooks, { required, commandRoot }) {
32
+ function validateHooks(root, hooks, { required, commandRoot, matcher = TIMELINE_PRE_TOOL_MATCHER }) {
29
33
  assert(hooks && typeof hooks === "object" && !Array.isArray(hooks), "hook bundle is missing");
30
34
  assert(Object.keys(hooks).every((key) => ["description", "hooks"].includes(key)), "hook bundle contains unsupported top-level metadata");
31
35
  assert(hooks.description && typeof hooks.description === "string", "hook bundle description is missing");
@@ -39,6 +43,8 @@ function validateHooks(root, hooks, { required, commandRoot }) {
39
43
  ? " --silent-oversize-post-tool-use" : ""}`;
40
44
  assert(command.command === expected, `${event} must use the bundled lifecycle adapter`);
41
45
  assert(Number.isInteger(command.timeout) && command.timeout > 0 && command.timeout <= 15, `${event} timeout is unsafe`);
46
+ if (event === "PreToolUse") assert(registrations[0].matcher === matcher,
47
+ "PreToolUse must route only exact mutations and timeline MCP calls through one guard");
42
48
  }
43
49
  const extras = Object.keys(hooks.hooks || {}).filter((event) => !required.includes(event));
44
50
  assert(extras.length === 0, `unknown hook events: ${extras.join(", ")}`);
@@ -62,6 +68,8 @@ function validateBlunHooks(root, hooks) {
62
68
  ? " --silent-oversize-post-tool-use" : ""}`;
63
69
  assert(command.command === expected, `${event} must use the bundled BLUN lifecycle adapter`);
64
70
  assert(Number.isInteger(command.timeout) && command.timeout > 0 && command.timeout <= 15, `${event} BLUN timeout is unsafe`);
71
+ if (event === "PreToolUse") assert(command.matcher === BLUN_TIMELINE_PRE_TOOL_MATCHER,
72
+ "BLUN PreToolUse must route only exact mutations and timeline MCP calls through one guard");
65
73
  }
66
74
  return { events: required, commands: required.length, entrypoint: relative(root, resolve(root, "src/hook.js")) };
67
75
  }
@@ -81,7 +89,7 @@ async function initializeServer({ label, root, variable, server, version }) {
81
89
  return await new Promise((resolveResult, reject) => {
82
90
  const child = spawn(command, args, {
83
91
  cwd: root,
84
- env: { ...process.env, CLAUDE_PLUGIN_ROOT: root, PLUGIN_ROOT: root },
92
+ env: installedHostEnvironment(label, root),
85
93
  stdio: ["pipe", "pipe", "pipe"]
86
94
  });
87
95
  let stdout = "";
@@ -152,14 +160,15 @@ export async function checkHosts(root = process.cwd()) {
152
160
  await validateEntrypoint(root, resolve(root, pkg.bin["agentspine-worker"]));
153
161
  assert(claudeManifest.mcpServers === "./.mcp.json", "Claude manifest must explicitly reference ./.mcp.json");
154
162
  assert(claudeManifest.hooks === undefined, "default hooks/hooks.json must not also be registered through a supplemental manifest path");
155
- assert(codexManifest.hooks === undefined, "Codex manifest must omit unsupported hook registration fields");
163
+ assert(codexManifest.hooks === "./hooks/codex.json", "Codex manifest must select its host-specific hook adapter");
156
164
  assert(claudeMcp.mcpServers && Object.keys(claudeMcp.mcpServers).length === 1, "Claude MCP file must contain one mcpServers registration");
157
165
  assert(codexManifest.mcpServers && Object.keys(codexManifest.mcpServers).length === 1, "Codex manifest must contain one MCP registration");
158
166
  const commonEvents = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PreCompact", "PostCompact", "Stop", "SubagentStop"];
159
167
  const claudeHookInventory = validateHooks(root, claudeHooks, {
160
168
  required: [...commonEvents, "InstructionsLoaded"], commandRoot: "CLAUDE_PLUGIN_ROOT"
161
169
  });
162
- const codexHookInventory = validateHooks(root, codexHooks, { required: commonEvents, commandRoot: "PLUGIN_ROOT" });
170
+ const codexHookInventory = validateHooks(root, codexHooks, { required: commonEvents, commandRoot: "PLUGIN_ROOT",
171
+ matcher: "^(?:Edit|Write|apply_patch|Bash|PowerShell|mcp__(?:plugin_agent-spine_agent-spine|agent-spine)__session_timeline_(?:index|search))$" });
163
172
  const blunHookInventory = validateBlunHooks(root, blunManifest.hooks);
164
173
  const registrations = await Promise.all([
165
174
  initializeServer({ label: "blun", root, variable: "BLUN_PLUGIN_ROOT", server: blunManifest.mcpServers["agent-spine"], version: pkg.version }),
@@ -170,7 +179,7 @@ export async function checkHosts(root = process.cwd()) {
170
179
  ok: true, root, version: pkg.version, registrations,
171
180
  hooks: { blun: blunHookInventory, claude: claudeHookInventory, codex: codexHookInventory },
172
181
  hookDiscovery: {
173
- blun: "plugin-manifest", claude: "default-hooks-directory", codex: "bundled-host-adapter",
182
+ blun: "plugin-manifest", claude: "default-hooks-directory", codex: "plugin-manifest",
174
183
  trust: "host-user-required", liveTrustVerified: false
175
184
  },
176
185
  worker: { entrypoint: pkg.bin["agentspine-worker"], setsPerInstall: 1 },
@@ -0,0 +1,226 @@
1
+ import { spawn } from "node:child_process";
2
+ import { writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ const HOST_ROOTS = {
7
+ blun: ["BLUN_PLUGIN_ROOT"],
8
+ claude: ["CLAUDE_PLUGIN_ROOT"],
9
+ codex: ["PLUGIN_ROOT", "CLAUDE_PLUGIN_ROOT"]
10
+ };
11
+ const HOST_ROOT_KEYS = ["BLUN_PLUGIN_ROOT", "CLAUDE_PLUGIN_ROOT", "PLUGIN_ROOT"];
12
+
13
+ export function installedHostEnvironment(host, pluginRoot, extraEnv = {}) {
14
+ const rootKeys = HOST_ROOTS[host];
15
+ if (!rootKeys) throw new Error(`unsupported installed host: ${host}`);
16
+ const env = { ...process.env, ...extraEnv };
17
+ for (const key of HOST_ROOT_KEYS) delete env[key];
18
+ for (const key of rootKeys) env[key] = pluginRoot;
19
+ return env;
20
+ }
21
+
22
+ function parseProtocol(stdout, host) {
23
+ try {
24
+ return JSON.parse(stdout.trim());
25
+ } catch (error) {
26
+ throw new Error(`${host} installed hook returned invalid protocol JSON: ${stdout.slice(0, 2048)}`, { cause: error });
27
+ }
28
+ }
29
+
30
+ function parseContext(protocol, host, requireBriefing) {
31
+ const raw = protocol.hookSpecificOutput?.additionalContext;
32
+ if (typeof raw !== "string" || !raw) return null;
33
+ try {
34
+ return JSON.parse(raw);
35
+ } catch (error) {
36
+ if (!requireBriefing) return null;
37
+ throw new Error(`${host} installed hook returned invalid briefing context JSON`, { cause: error });
38
+ }
39
+ }
40
+
41
+ function protocolShape(protocol) {
42
+ const specific = protocol.hookSpecificOutput;
43
+ return {
44
+ protocolKeys: Object.keys(protocol).sort(),
45
+ hookSpecificKeys: specific && typeof specific === "object" ? Object.keys(specific).sort() : [],
46
+ permissionDecision: specific?.permissionDecision || null,
47
+ permissionDecisionReason: specific?.permissionDecisionReason || null
48
+ };
49
+ }
50
+
51
+ export async function invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, payload = null,
52
+ { requireBriefing = true, extraEnv = {} } = {}) {
53
+ return await new Promise((resolveResult, reject) => {
54
+ const child = spawn(process.execPath, [join(pluginRoot, "src/hook.js")], {
55
+ cwd: projectRoot,
56
+ env: installedHostEnvironment(host, pluginRoot, {
57
+ AGENTSPINE_HOST: host,
58
+ AGENTSPINE_STATE_DIR: stateRoot,
59
+ ...extraEnv
60
+ }),
61
+ stdio: ["pipe", "pipe", "pipe"]
62
+ });
63
+ let stdout = "";
64
+ let stderr = "";
65
+ const timer = setTimeout(() => {
66
+ child.kill();
67
+ reject(new Error(`${host} installed hook timed out`));
68
+ }, 5000);
69
+ child.stdout.setEncoding("utf8");
70
+ child.stderr.setEncoding("utf8");
71
+ child.stdout.on("data", (chunk) => { stdout += chunk; });
72
+ child.stderr.on("data", (chunk) => { stderr += chunk; });
73
+ child.once("error", reject);
74
+ child.once("exit", (code) => {
75
+ clearTimeout(timer);
76
+ if (code !== 0) {
77
+ reject(new Error(`${host} installed hook exited with ${code}: ${stderr.slice(0, 2048)}`));
78
+ return;
79
+ }
80
+ try {
81
+ const protocol = parseProtocol(stdout, host);
82
+ const context = parseContext(protocol, host, requireBriefing);
83
+ if (requireBriefing && (context?.briefing?.host !== host
84
+ || !Array.isArray(context?.briefing?.sources?.documents))) {
85
+ throw new Error(`${host} installed hook did not inject a real session briefing: `
86
+ + `${protocol.reason || context?.error || context?.sourceResolution?.reason || "missing context"}`);
87
+ }
88
+ resolveResult({
89
+ event: protocol.hookSpecificOutput?.hookEventName || payload?.hook_event_name || null, host,
90
+ sources: context?.briefing?.sources?.documents?.length ?? null,
91
+ sourceIds: context?.briefing?.sources?.documents?.map((item) => item.path) || [],
92
+ sourceContents: context?.briefing?.sources?.documents?.map((item) => item.content).filter(Boolean) || [],
93
+ sourceResolution: context?.sourceResolution || null,
94
+ learningClaims: context?.briefing?.learning?.map((item) => item.claim) || [],
95
+ attentionKinds: context?.briefing?.attention?.items?.map((item) => item.kind) || [],
96
+ capturedAttentionKind: context?.attentionEvent?.kind || null,
97
+ selfstarter: context?.selfstarter || null,
98
+ channelEvent: context?.channelEvent || null,
99
+ voiceBrief: context?.briefing?.voiceBrief || null,
100
+ preflight: context?.preflight || null,
101
+ decision: protocol.decision || null,
102
+ reason: protocol.reason || null,
103
+ message: protocol.hookSpecificOutput?.message || null,
104
+ ...protocolShape(protocol)
105
+ });
106
+ } catch (error) {
107
+ reject(error);
108
+ }
109
+ });
110
+ child.stdin.end(`${JSON.stringify(payload || {
111
+ hook_event_name: "SessionStart", cwd: projectRoot, host
112
+ })}\n`);
113
+ });
114
+ }
115
+
116
+ export async function invokeInstalledBlunPostWriteDigest(pluginRoot, projectRoot, stateRoot) {
117
+ const sessionId = `session:installed-blun-post-${stateRoot.split(/[\\/]/).at(-1)}`;
118
+ const shared = {
119
+ hook_event_name: "UserPromptSubmit", cwd: projectRoot, host: "blun",
120
+ project_id: "project:installed-blun-protocol", session_id: sessionId,
121
+ event_id: `prompt:${sessionId}`, prompt: "Change the synthetic installed BLUN artifact safely."
122
+ };
123
+ const prompted = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, "blun", shared,
124
+ { requireBriefing: false });
125
+ const requirementId = prompted.message?.match(
126
+ /Requirement: (premortem-requirement:[a-f0-9]{64}:[a-f0-9]{64})/
127
+ )?.[1];
128
+ if (!requirementId) throw new Error("installed BLUN prompt did not expose its premortem requirement");
129
+ const previousStateRoot = process.env.AGENTSPINE_STATE_DIR;
130
+ try {
131
+ process.env.AGENTSPINE_STATE_DIR = stateRoot;
132
+ const premortem = await import(pathToFileURL(join(pluginRoot, "src/lib/delivery-premortem.js")).href);
133
+ const usage = await import(pathToFileURL(join(pluginRoot, "src/lib/delivery-agent-usage.js")).href);
134
+ await usage.recordDeliveryBriefingUse({ root: projectRoot, requirementId,
135
+ input: { root: projectRoot }, result: { schema: "synthetic-installed-briefing" } });
136
+ await usage.recordDeliveryKnowledgeUse({ root: projectRoot, requirementId,
137
+ input: { targets: ["AGENTS.md"] }, result: { schema: "synthetic-installed-knowledge" } });
138
+ const recorded = await premortem.recordDeliveryPremortem({
139
+ root: projectRoot, requirementId, items: [
140
+ { category: "baseline-environment", failure: "this delivery fails because the installed baseline is stale",
141
+ check: "Compare the installed synthetic snapshot digest." },
142
+ { category: "contract-tests", failure: "this delivery fails because the installed contract regresses",
143
+ check: "Run the installed synthetic Node test." },
144
+ { category: "delivery-path", failure: "this delivery fails because the installed artifact is misplaced",
145
+ check: "Verify the installed synthetic artifact path and digest." }
146
+ ]
147
+ });
148
+ if (recorded.blocked) throw new Error(`installed BLUN premortem registration failed: ${recorded.reason}`);
149
+ } finally {
150
+ if (previousStateRoot === undefined) delete process.env.AGENTSPINE_STATE_DIR;
151
+ else process.env.AGENTSPINE_STATE_DIR = previousStateRoot;
152
+ }
153
+ const filePath = join(projectRoot, "installed-blun-post-write.txt");
154
+ const tool = { tool_name: "Write", tool_use_id: `write:${sessionId}`,
155
+ tool_input: { file_path: filePath, content: "synthetic installed BLUN write\n" } };
156
+ const pre = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, "blun", {
157
+ ...shared, ...tool, hook_event_name: "PreToolUse"
158
+ }, { requireBriefing: false });
159
+ if (pre.decision === "block" || pre.permissionDecision === "deny") {
160
+ throw new Error(`installed BLUN PreToolUse denied its registered premortem: ${pre.reason}`);
161
+ }
162
+ await writeFile(filePath, tool.tool_input.content, "utf8");
163
+ const post = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, "blun", {
164
+ ...shared, ...tool, hook_event_name: "PostToolUse", success: true, tool_response: { ok: true }
165
+ }, { requireBriefing: false });
166
+ const digest = post.message?.match(/Premortem latest write sha256 ([a-f0-9]{64})/)?.[1];
167
+ if (!digest || !exactKeys(post.protocolKeys, ["hookSpecificOutput"])
168
+ || !exactKeys(post.hookSpecificKeys, ["hookEventName", "message"])) {
169
+ throw new Error("installed BLUN PostToolUse did not return its latest-write digest through message");
170
+ }
171
+ return { contextField: "message", latestWriteDigest: digest };
172
+ }
173
+
174
+ function exactKeys(value, expected) {
175
+ return JSON.stringify(value) === JSON.stringify([...expected].sort());
176
+ }
177
+
178
+ export async function invokeInstalledBlockingProtocols(pluginRoot, projectRoot, stateRoot) {
179
+ const writePayload = (host) => ({
180
+ hook_event_name: "PreToolUse", cwd: projectRoot, host,
181
+ project_id: "project:installed-protocol",
182
+ session_id: `session:installed-${host}-protocol`, tool_name: "Write",
183
+ tool_use_id: `tool:installed-${host}-protocol`,
184
+ tool_input: { file_path: "AGENTS.md", content: "synthetic\n" }
185
+ });
186
+ const claudePreTool = await invokeInstalledHook(
187
+ pluginRoot, projectRoot, stateRoot, "claude", writePayload("claude"), { requireBriefing: false }
188
+ );
189
+ const claudeStop = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, "claude", {
190
+ hook_event_name: "Stop", cwd: projectRoot, host: "claude",
191
+ project_id: "project:installed-protocol",
192
+ session_id: "session:installed-claude-stop-protocol",
193
+ agent_spine_exchange_directory: projectRoot,
194
+ final_assistant_message: "missing-installed-artifact.txt sha256 0000000000000000"
195
+ }, { requireBriefing: false });
196
+ const codexPreTool = await invokeInstalledHook(
197
+ pluginRoot, projectRoot, stateRoot, "codex", writePayload("codex"), { requireBriefing: false }
198
+ );
199
+ if (claudePreTool.decision !== null || claudePreTool.permissionDecision !== "deny"
200
+ || !claudePreTool.permissionDecisionReason?.includes("protected source")
201
+ || !exactKeys(claudePreTool.protocolKeys, ["hookSpecificOutput"])
202
+ || !exactKeys(claudePreTool.hookSpecificKeys,
203
+ ["hookEventName", "permissionDecision", "permissionDecisionReason"])) {
204
+ throw new Error("installed Claude PreToolUse denial did not use its nested permissionDecision schema");
205
+ }
206
+ if (claudeStop.decision !== null || claudeStop.permissionDecision !== null) {
207
+ throw new Error("installed Claude Stop must not block replies for artifact evidence");
208
+ }
209
+ if (codexPreTool.decision !== "block" || !codexPreTool.reason?.includes("protected source")
210
+ || codexPreTool.permissionDecision !== null
211
+ || !exactKeys(codexPreTool.protocolKeys, ["decision", "reason"])) {
212
+ const observed = {
213
+ decision: codexPreTool.decision, reason: codexPreTool.reason,
214
+ protocolKeys: codexPreTool.protocolKeys, hookSpecificKeys: codexPreTool.hookSpecificKeys,
215
+ permissionDecision: codexPreTool.permissionDecision,
216
+ sourceResolution: codexPreTool.sourceResolution, preflight: codexPreTool.preflight,
217
+ selfstarter: codexPreTool.selfstarter
218
+ };
219
+ throw new Error("installed Codex denial did not preserve its strict top-level decision/reason schema: "
220
+ + JSON.stringify(observed).slice(0, 8192));
221
+ }
222
+ return {
223
+ claude: { preTool: "nested-permission-deny", stop: "advisory" },
224
+ codex: { preTool: "top-level-block" }
225
+ };
226
+ }