blun-king-cli 9.1.567 → 9.1.569

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 (262) hide show
  1. package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
  2. package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
  3. package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
  4. package/agent-spine-plugin/CHANGELOG.md +1581 -0
  5. package/agent-spine-plugin/README.md +30 -4
  6. package/agent-spine-plugin/blun.plugin.json +3 -3
  7. package/agent-spine-plugin/docs/acceptance.md +61 -0
  8. package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
  9. package/agent-spine-plugin/docs/host-integration.md +178 -0
  10. package/agent-spine-plugin/docs/preflight-recall.md +69 -0
  11. package/agent-spine-plugin/docs/preservation-contract.md +53 -0
  12. package/agent-spine-plugin/docs/quality-gates.md +50 -0
  13. package/agent-spine-plugin/docs/releasing.md +85 -0
  14. package/agent-spine-plugin/docs/session-timeline.md +251 -0
  15. package/agent-spine-plugin/docs/source-roots.md +113 -0
  16. package/agent-spine-plugin/docs/structured-completion.md +67 -0
  17. package/agent-spine-plugin/docs/world-model.md +94 -0
  18. package/agent-spine-plugin/hooks/codex.json +2 -2
  19. package/agent-spine-plugin/hooks/hooks.json +1 -1
  20. package/agent-spine-plugin/hooks/version.json +1 -1
  21. package/agent-spine-plugin/package.json +13 -3
  22. package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
  23. package/agent-spine-plugin/scripts/check-hosts.js +14 -5
  24. package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
  25. package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
  26. package/agent-spine-plugin/scripts/check-install.js +478 -0
  27. package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
  28. package/agent-spine-plugin/scripts/check-syntax.js +29 -0
  29. package/agent-spine-plugin/scripts/github-actions.js +11 -0
  30. package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
  31. package/agent-spine-plugin/scripts/release-check.js +145 -0
  32. package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
  33. package/agent-spine-plugin/scripts/run-checks.js +47 -0
  34. package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
  35. package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
  36. package/agent-spine-plugin/spine-example/1-identity.md +12 -0
  37. package/agent-spine-plugin/spine-example/2-voice.md +6 -0
  38. package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
  39. package/agent-spine-plugin/spine-example/4-history.md +4 -0
  40. package/agent-spine-plugin/src/cli-agent.js +296 -0
  41. package/agent-spine-plugin/src/cli-attention.js +95 -0
  42. package/agent-spine-plugin/src/cli-autonomy.js +36 -0
  43. package/agent-spine-plugin/src/cli-common.js +71 -0
  44. package/agent-spine-plugin/src/cli-continuity.js +116 -0
  45. package/agent-spine-plugin/src/cli-core.js +128 -0
  46. package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
  47. package/agent-spine-plugin/src/cli-host.js +21 -0
  48. package/agent-spine-plugin/src/cli-learning.js +305 -0
  49. package/agent-spine-plugin/src/cli-premortem.js +16 -0
  50. package/agent-spine-plugin/src/cli-sharing.js +230 -0
  51. package/agent-spine-plugin/src/cli.js +40 -1350
  52. package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
  53. package/agent-spine-plugin/src/hook.js +185 -567
  54. package/agent-spine-plugin/src/index.js +16 -2
  55. package/agent-spine-plugin/src/lib/acceptance.js +113 -11
  56. package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
  57. package/agent-spine-plugin/src/lib/attention-context.js +167 -0
  58. package/agent-spine-plugin/src/lib/attention-events.js +113 -0
  59. package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
  60. package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
  61. package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
  62. package/agent-spine-plugin/src/lib/attention.js +9 -600
  63. package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
  64. package/agent-spine-plugin/src/lib/audit.js +56 -6
  65. package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
  66. package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
  67. package/agent-spine-plugin/src/lib/autonomy.js +8 -0
  68. package/agent-spine-plugin/src/lib/briefing.js +67 -4
  69. package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
  70. package/agent-spine-plugin/src/lib/catalog.js +1 -1
  71. package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
  72. package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
  73. package/agent-spine-plugin/src/lib/context.js +3 -1
  74. package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
  75. package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
  76. package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
  77. package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
  78. package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
  79. package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
  80. package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
  81. package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
  82. package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
  83. package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
  84. package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
  85. package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
  86. package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
  87. package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
  88. package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
  89. package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
  90. package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
  91. package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
  92. package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
  93. package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
  94. package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
  95. package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
  96. package/agent-spine-plugin/src/lib/documents.js +27 -5
  97. package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
  98. package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
  99. package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
  100. package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
  101. package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
  102. package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
  103. package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
  104. package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
  105. package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
  106. package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
  107. package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
  108. package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
  109. package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
  110. package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
  111. package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
  112. package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
  113. package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
  114. package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
  115. package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
  116. package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
  117. package/agent-spine-plugin/src/lib/hook-context.js +424 -0
  118. package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
  119. package/agent-spine-plugin/src/lib/hook-input.js +27 -0
  120. package/agent-spine-plugin/src/lib/hook-output.js +140 -0
  121. package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
  122. package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
  123. package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
  124. package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
  125. package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
  126. package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
  127. package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
  128. package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
  129. package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
  130. package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
  131. package/agent-spine-plugin/src/lib/learning-context.js +130 -0
  132. package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
  133. package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
  134. package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
  135. package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
  136. package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
  137. package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
  138. package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
  139. package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
  140. package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
  141. package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
  142. package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
  143. package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
  144. package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
  145. package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
  146. package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
  147. package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
  148. package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
  149. package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
  150. package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
  151. package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
  152. package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
  153. package/agent-spine-plugin/src/lib/learning.js +36 -6923
  154. package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
  155. package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
  156. package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
  157. package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
  158. package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
  159. package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
  160. package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
  161. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
  162. package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
  163. package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
  164. package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
  165. package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
  166. package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
  167. package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
  168. package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
  169. package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
  170. package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
  171. package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
  172. package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
  173. package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
  174. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
  175. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
  176. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
  177. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
  178. package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
  179. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
  180. package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
  181. package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
  182. package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
  183. package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
  184. package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
  185. package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
  186. package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
  187. package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
  188. package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
  189. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
  190. package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
  191. package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
  192. package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
  193. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
  194. package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
  195. package/agent-spine-plugin/src/lib/source-roots.js +69 -136
  196. package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
  197. package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
  198. package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
  199. package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
  200. package/agent-spine-plugin/src/lib/world-model.js +278 -0
  201. package/agent-spine-plugin/src/mcp.js +20 -161
  202. package/agent-spine-plugin/src/version.js +1 -1
  203. package/agent-spine-plugin/src/worker.js +22 -5
  204. package/bin/agent-resume-snapshot.cjs +2 -2
  205. package/bin/agentspine-king-goal-inbox.mjs +111 -0
  206. package/bin/agentspine-king-goal-intake.mjs +106 -0
  207. package/bin/baseline-skill-performance-policy.cjs +1 -16
  208. package/bin/core-bootstrap.js +2 -0
  209. package/bin/curiosity-scout-policy.cjs +5 -1
  210. package/bin/input-draft-persistence.cjs +2 -2
  211. package/bin/king-tui-function-contract.json +33 -0
  212. package/bin/launcher-restart-policy.cjs +150 -0
  213. package/bin/launcher-runtime.js +56 -44
  214. package/bin/managed-context-startup-policy.cjs +27 -0
  215. package/bin/managed-plugin-selection.cjs +116 -0
  216. package/bin/mistake-relevance-policy.cjs +1 -1
  217. package/bin/observer-hooks.cjs +14 -0
  218. package/bin/oversized-context-offload-policy.cjs +86 -0
  219. package/bin/plugin-bootstrap.js +7 -40
  220. package/bin/proactive-compaction-policy.cjs +1 -1
  221. package/bin/provider-model-refresh-deadline.cjs +53 -0
  222. package/bin/provider-model-refresh-policy.cjs +107 -0
  223. package/bin/release-artifact-freeze-policy.cjs +30 -0
  224. package/bin/repeated-user-message-projection.cjs +3 -126
  225. package/bin/research-page-result.cjs +74 -0
  226. package/bin/runtime-exit-ledger.cjs +1 -0
  227. package/bin/session-compaction-policy.cjs +84 -0
  228. package/bin/skill-listing-performance-policy.cjs +2 -2
  229. package/bin/standard-tools-bootstrap.js +0 -37
  230. package/bin/subagent-skill-policy.cjs +3 -1
  231. package/bin/telegram-approval-relay.cjs +12 -7
  232. package/bin/telegram-private-conversation-policy.cjs +3 -2
  233. package/bin/telegram-queue-handoff-policy.cjs +24 -0
  234. package/bin/thinking-activity-status-policy.cjs +1 -1
  235. package/bin/thinking-only-guard.cjs +16 -12
  236. package/bin/tool-call-loop-policy.cjs +0 -2
  237. package/bin/tool-result-offload-policy.cjs +11 -2
  238. package/bin/tui-functional-contract.cjs +55 -0
  239. package/bin/update-notice.js +18 -14
  240. package/bin/user-message-offload-policy.cjs +1 -1
  241. package/bin/user-prompt-hook-origin-policy.cjs +34 -0
  242. package/bin/windows-node-crash-dump.cjs +110 -0
  243. package/blun.mjs +2517 -1048
  244. package/codebase-index/codebase_index.py +4 -3
  245. package/package.json +3 -17
  246. package/standard-skills/research-evidence/SKILL.md +39 -0
  247. package/standard-skills/research-evidence/references/evidence-format.md +82 -0
  248. package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
  249. package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
  250. package/standard-skills/web-lesen/SKILL.md +37 -22
  251. package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
  252. package/telegram-plugin/DELIVERY.md +36 -0
  253. package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
  254. package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
  255. package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
  256. package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
  257. package/telegram-plugin/dist/bridge.mjs +7 -56
  258. package/telegram-plugin/dist/mcp-server.mjs +33 -4
  259. package/agent-spine-plugin/skill/SKILL.md +0 -76
  260. package/bin/mnemo-connect-heartbeat.cjs +0 -204
  261. package/bin/mnemo-tool-agent-policy.cjs +0 -22
  262. package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
@@ -0,0 +1,453 @@
1
+ import { MAX_SUBSTITUTION_DEPTH, shellSubstitutions } from "./delivery-shell-substitutions.js";
2
+ import { maskHeredocBodies } from "./delivery-shell-heredoc.js";
3
+ import { shellExecutableName, unwrapLeadingShellWrapper } from "./delivery-shell-wrapper.js";
4
+
5
+ const WRITE_TOOLS = /(^|__)(apply_patch|edit|write|delete|move|rename)(_|$)/i;
6
+ const SHELL_TOOLS = /(^|__)(bash|exec_command|shell|powershell|pwsh)(_|$)/i;
7
+ const MAX_COMMAND_CHARS = 64 * 1024;
8
+ const SHELL_CONTROL_PREFIXES = new Set([
9
+ "!", "{", "if", "then", "elif", "else", "while", "until", "do", "time", "coproc", "nohup"
10
+ ]);
11
+ const SHELL_STDIN_EXECUTORS = new Set(["bash", "sh", "zsh"]);
12
+ const MUTATION_COMMANDS = new Set([
13
+ "apply_patch", "rm", "mv", "cp", "install", "mkdir", "rmdir", "touch", "truncate", "tee", "patch",
14
+ "set-content", "add-content", "out-file", "new-item", "remove-item", "move-item", "copy-item",
15
+ "rename-item", "clear-content"
16
+ ]);
17
+ const TEST_COMMANDS = [
18
+ { family: "node-test", expression: /^node\s+--test(?:\s+[^;&|\n]+)?$/i },
19
+ { family: "npm-test", expression: /^npm\s+(?:run\s+)?test(?:[\s:][^;&|\n]*)?$/i },
20
+ { family: "npm-check", expression: /^npm\s+run\s+check(?:\s+[^;&|\n]+)?$/i },
21
+ { family: "pytest", expression: /^(?:python(?:3)?\s+-m\s+)?pytest(?:\s+[^;&|\n]+)?$/i }
22
+ ];
23
+
24
+ export function commandFromInput(input) {
25
+ const toolInput = input?.tool_input ?? input?.tool_args;
26
+ if (typeof toolInput === "string") return toolInput;
27
+ if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) return "";
28
+ for (const key of ["command", "cmd", "script"]) {
29
+ if (typeof toolInput[key] === "string") return toolInput[key];
30
+ }
31
+ return "";
32
+ }
33
+
34
+ export function isSuccessMarkerClause(value) {
35
+ return [
36
+ "printf AGENTSPINE_TEST_OK", "printf 'AGENTSPINE_TEST_OK\\n'",
37
+ "printf \"AGENTSPINE_TEST_OK\\n\"", "node -e \"console.log('AGENTSPINE_TEST_OK')\"",
38
+ "node -e 'console.log(\"AGENTSPINE_TEST_OK\")'"
39
+ ].includes(value.trim());
40
+ }
41
+
42
+ function shellSegments(command) {
43
+ const segments = [];
44
+ let quote = null;
45
+ let escaped = false;
46
+ let start = 0;
47
+ let separatorBefore = "";
48
+ const push = (end) => {
49
+ const raw = command.slice(start, end);
50
+ const left = raw.search(/\S/);
51
+ if (left >= 0) segments.push({ text: raw.slice(left), index: start + left, separatorBefore });
52
+ };
53
+ for (let index = 0; index < command.length; index += 1) {
54
+ const character = command[index];
55
+ if (escaped) { escaped = false; continue; }
56
+ if (character === "\\" && quote !== "'") { escaped = true; continue; }
57
+ if (quote) {
58
+ if (character === quote) quote = null;
59
+ continue;
60
+ }
61
+ if (character === "'" || character === '"') { quote = character; continue; }
62
+ if (character === ";" || character === "\n" || character === "|" || character === "&") {
63
+ push(index);
64
+ const separatorStart = index;
65
+ while (command[index + 1] === character) index += 1;
66
+ separatorBefore = command.slice(separatorStart, index + 1);
67
+ start = index + 1;
68
+ }
69
+ }
70
+ push(command.length);
71
+ return segments;
72
+ }
73
+
74
+ function shellTokens(segment) {
75
+ const tokens = [];
76
+ let value = "";
77
+ let start = -1;
78
+ let plain = true;
79
+ let quote = null;
80
+ let escaped = false;
81
+ const push = (end) => {
82
+ if (start >= 0) tokens.push({ value, index: start, end, plain });
83
+ value = "";
84
+ start = -1;
85
+ plain = true;
86
+ };
87
+ for (let index = 0; index < segment.length; index += 1) {
88
+ const character = segment[index];
89
+ if (escaped) { value += character; escaped = false; continue; }
90
+ if (character === "\\" && quote !== "'") {
91
+ if (start < 0) start = index;
92
+ plain = false;
93
+ escaped = true;
94
+ continue;
95
+ }
96
+ if (quote) {
97
+ if (character === quote) quote = null;
98
+ else value += character;
99
+ continue;
100
+ }
101
+ if (character === "'" || character === '"') {
102
+ if (start < 0) start = index;
103
+ plain = false;
104
+ quote = character;
105
+ } else if (/\s/.test(character)) push(index);
106
+ else {
107
+ if (start < 0) start = index;
108
+ value += character;
109
+ }
110
+ }
111
+ push(segment.length);
112
+ return tokens;
113
+ }
114
+
115
+ function executableTokens(segment) {
116
+ const tokens = shellTokens(segment);
117
+ let subshellDepth = 0;
118
+ while (tokens[0]) {
119
+ const first = tokens[0];
120
+ const lower = first.value.toLowerCase();
121
+ if (first.plain && lower === "(") {
122
+ subshellDepth += 1;
123
+ tokens.shift();
124
+ continue;
125
+ }
126
+ if (first.plain && /^[A-Za-z_][A-Za-z0-9_]*=/.test(first.value)) {
127
+ tokens.shift();
128
+ continue;
129
+ }
130
+ if (unwrapLeadingShellWrapper(tokens, shellTokens)) continue;
131
+ if (first.plain && SHELL_CONTROL_PREFIXES.has(lower)) {
132
+ tokens.shift();
133
+ continue;
134
+ }
135
+ if (first.plain && first.value.startsWith("(") && !first.value.startsWith("((")) {
136
+ tokens[0] = { ...first, value: first.value.slice(1), index: first.index + 1 };
137
+ subshellDepth += 1;
138
+ if (!tokens[0].value) tokens.shift();
139
+ continue;
140
+ }
141
+ break;
142
+ }
143
+ for (let index = tokens.length - 1; index >= 0 && subshellDepth > 0; index -= 1) {
144
+ while (tokens[index].value.endsWith(")") && subshellDepth > 0) {
145
+ tokens[index] = { ...tokens[index], value: tokens[index].value.slice(0, -1) };
146
+ subshellDepth -= 1;
147
+ }
148
+ }
149
+ return tokens;
150
+ }
151
+
152
+ function bodyAfterCasePattern(segment, start) {
153
+ let quote = null;
154
+ let escaped = false;
155
+ let parentheses = 0;
156
+ for (let index = start; index < segment.length; index += 1) {
157
+ const character = segment[index];
158
+ if (escaped) { escaped = false; continue; }
159
+ if (character === "\\" && quote !== "'") { escaped = true; continue; }
160
+ if (quote) {
161
+ if (character === quote) quote = null;
162
+ continue;
163
+ }
164
+ if (character === "'" || character === '"' || character === "`") {
165
+ quote = character;
166
+ continue;
167
+ }
168
+ if (character === "$" && segment[index + 1] === "(") {
169
+ parentheses += segment[index + 2] === "(" ? 2 : 1;
170
+ index += segment[index + 2] === "(" ? 2 : 1;
171
+ continue;
172
+ }
173
+ if (character === "(") { parentheses += 1; continue; }
174
+ if (character !== ")") continue;
175
+ if (parentheses > 0) { parentheses -= 1; continue; }
176
+ return segment.slice(index + 1);
177
+ }
178
+ return null;
179
+ }
180
+
181
+ function caseClauseBody(segment, separatorBefore) {
182
+ const tokens = shellTokens(segment);
183
+ if (separatorBefore === ";;") return bodyAfterCasePattern(segment, 0);
184
+ if (!(tokens[0]?.plain && tokens[0].value.toLowerCase() === "case"
185
+ && tokens[2]?.plain && tokens[2].value.toLowerCase() === "in")) return null;
186
+ return bodyAfterCasePattern(segment, tokens[2].end);
187
+ }
188
+
189
+ function optionTokens(tokens, start = 1) {
190
+ const values = tokens.slice(start).map((token) => token.value);
191
+ const separator = values.indexOf("--");
192
+ return separator < 0 ? values : values.slice(0, separator);
193
+ }
194
+
195
+ function isInspectionOnly(executable, tokens) {
196
+ const options = optionTokens(tokens);
197
+ if (options.some((value) => value === "--help" || value === "--version")) return true;
198
+ if (executable === "git" && tokens[1]?.value.toLowerCase() === "apply") {
199
+ return optionTokens(tokens, 2).includes("--check");
200
+ }
201
+ return executable === "npm" && tokens[1]?.value.toLowerCase() === "install"
202
+ && optionTokens(tokens, 2).includes("--dry-run");
203
+ }
204
+
205
+ function maskCodeLiterals(code, language) {
206
+ const output = [...code];
207
+ let quote = null;
208
+ let triple = false;
209
+ let lineComment = false;
210
+ let blockComment = false;
211
+ let escaped = false;
212
+ for (let index = 0; index < code.length; index += 1) {
213
+ const character = code[index];
214
+ const next = code[index + 1];
215
+ if (lineComment) {
216
+ if (character === "\n") lineComment = false;
217
+ else output[index] = " ";
218
+ continue;
219
+ }
220
+ if (blockComment) {
221
+ output[index] = " ";
222
+ if (character === "*" && next === "/") { output[index + 1] = " "; index += 1; blockComment = false; }
223
+ continue;
224
+ }
225
+ if (quote) {
226
+ output[index] = " ";
227
+ if (escaped) { escaped = false; continue; }
228
+ if (character === "\\") { escaped = true; continue; }
229
+ if (triple && character === quote && next === quote && code[index + 2] === quote) {
230
+ output[index + 1] = output[index + 2] = " ";
231
+ index += 2;
232
+ quote = null;
233
+ triple = false;
234
+ } else if (!triple && character === quote) quote = null;
235
+ continue;
236
+ }
237
+ if (language === "javascript" && character === "/" && next === "/") {
238
+ output[index] = output[index + 1] = " "; index += 1; lineComment = true; continue;
239
+ }
240
+ if (language === "javascript" && character === "/" && next === "*") {
241
+ output[index] = output[index + 1] = " "; index += 1; blockComment = true; continue;
242
+ }
243
+ if (language === "python" && character === "#") {
244
+ output[index] = " "; lineComment = true; continue;
245
+ }
246
+ const quotes = language === "javascript" ? new Set(["'", '"', "`"]) : new Set(["'", '"']);
247
+ if (quotes.has(character)) {
248
+ quote = character;
249
+ triple = language === "python" && next === character && code[index + 2] === character;
250
+ output[index] = " ";
251
+ if (triple) { output[index + 1] = output[index + 2] = " "; index += 2; }
252
+ }
253
+ }
254
+ return output.join("");
255
+ }
256
+
257
+ function pythonOpenWrites(code, masked) {
258
+ for (const match of masked.matchAll(/\bopen\s*\(/g)) {
259
+ const open = masked.indexOf("(", match.index);
260
+ let depth = 0;
261
+ let end = -1;
262
+ for (let index = open; index < masked.length; index += 1) {
263
+ if (masked[index] === "(") depth += 1;
264
+ else if (masked[index] === ")" && --depth === 0) { end = index; break; }
265
+ }
266
+ if (end < 0) continue;
267
+ const args = code.slice(open + 1, end);
268
+ const mode = args.match(/(?:^|,)\s*(?:mode\s*=\s*)?([rubf]*)(['"])([wax][bt+]{0,3})\2\s*(?:,|$)/i);
269
+ if (mode) return true;
270
+ }
271
+ return false;
272
+ }
273
+
274
+ function inlineProgramWrites(executable, code) {
275
+ if (/^node(?:\.exe)?$/i.test(executable)) {
276
+ return /\b(?:writeFileSync|appendFileSync|writeFile|appendFile)\s*\(/.test(maskCodeLiterals(code, "javascript"));
277
+ }
278
+ if (/^(?:python(?:3(?:\.\d+)?)?|py)(?:\.exe)?$/i.test(executable)) {
279
+ const masked = maskCodeLiterals(code, "python");
280
+ return /\.(?:write_text|write_bytes)\s*\(/.test(masked) || pythonOpenWrites(code, masked);
281
+ }
282
+ return false;
283
+ }
284
+
285
+ function segmentWrites(segment) {
286
+ const caseBody = caseClauseBody(segment.text, segment.separatorBefore);
287
+ if (caseBody !== null && segmentWrites({ text: caseBody })) return true;
288
+ const tokens = executableTokens(segment.text);
289
+ const executable = shellExecutableName(tokens[0]?.value);
290
+ if (!executable) return false;
291
+ if (isInspectionOnly(executable, tokens)) return false;
292
+ if (MUTATION_COMMANDS.has(executable)) return true;
293
+ if (new Set(["sed", "perl"]).has(executable)) return tokens.slice(1).some((token) => /^-.*i/.test(token.value));
294
+ if (executable === "git") return /^(?:apply|checkout|restore|reset|merge|rebase|cherry-pick)$/i.test(tokens[1]?.value || "");
295
+ if (new Set(["npm", "pnpm", "yarn"]).has(executable)) {
296
+ return /^(?:install|update|add|remove)$/i.test(tokens[1]?.value || "");
297
+ }
298
+ if (executable === "dd") return tokens.slice(1).some((token) => /^of=/.test(token.value));
299
+ if (new Set(["bash", "sh", "zsh", "pwsh", "powershell"]).has(executable)
300
+ && new Set(["-c", "-command"]).has(tokens[1]?.value.toLowerCase()) && tokens[2]) {
301
+ return commandWriteActions(tokens[2].value).length > 0;
302
+ }
303
+ if (executable === "eval" && tokens[1]) {
304
+ return commandWriteActions(tokens.slice(1).map((token) => token.value).join(" ")).length > 0;
305
+ }
306
+ const inlineFlag = tokens.findIndex((token, index) => index > 0 && new Set(["-e", "--eval", "-c"]).has(token.value));
307
+ if (inlineFlag > 0 && tokens[inlineFlag + 1]) return inlineProgramWrites(executable, tokens[inlineFlag + 1].value);
308
+ return false;
309
+ }
310
+
311
+ function startsShellCommand(command, index) {
312
+ return index === 0 || /[\s;&|(!{}]/.test(command[index - 1]);
313
+ }
314
+
315
+ function structuredComparisonIndexes(command) {
316
+ const indexes = new Set();
317
+ const frames = [];
318
+ let quote = null;
319
+ let escaped = false;
320
+ let lineComment = false;
321
+ for (let index = 0; index < command.length; index += 1) {
322
+ const character = command[index];
323
+ const next = command[index + 1];
324
+ if (lineComment) {
325
+ if (character === "\n") lineComment = false;
326
+ continue;
327
+ }
328
+ if (escaped) { escaped = false; continue; }
329
+ if (character === "\\" && quote !== "'") { escaped = true; continue; }
330
+ if (quote) {
331
+ if (character === quote) quote = null;
332
+ continue;
333
+ }
334
+ if (character === "'" || character === '"') { quote = character; continue; }
335
+ if (character === "#" && (index === 0 || /[\s;&|()]/.test(command[index - 1]))) {
336
+ lineComment = true;
337
+ continue;
338
+ }
339
+ const top = frames.at(-1);
340
+ if (top?.type === "conditional" && character === "]" && next === "]") {
341
+ frames.pop(); index += 1; continue;
342
+ }
343
+ if (top?.type === "arithmetic" && character === ")" && next === ")") {
344
+ if (top.depth <= 1) frames.pop();
345
+ else top.depth -= 2;
346
+ index += 1;
347
+ continue;
348
+ }
349
+ if (top?.type === "command" && character === ")") {
350
+ if (top.depth === 0) frames.pop();
351
+ else top.depth -= 1;
352
+ continue;
353
+ }
354
+ if (top?.type === "backtick" && character === "`") {
355
+ frames.pop();
356
+ continue;
357
+ }
358
+ if (character === "[" && next === "[" && startsShellCommand(command, index)
359
+ && /\s/.test(command[index + 2] || "")) {
360
+ frames.push({ type: "conditional", depth: 0 }); index += 1; continue;
361
+ }
362
+ if (character === "$" && next === "(" && command[index + 2] === "(") {
363
+ frames.push({ type: "arithmetic", depth: 0 }); index += 2; continue;
364
+ }
365
+ if (character === "(" && next === "(" && startsShellCommand(command, index)) {
366
+ frames.push({ type: "arithmetic", depth: 0 }); index += 1; continue;
367
+ }
368
+ if (character === "$" && next === "(") {
369
+ frames.push({ type: "command", depth: 0 }); index += 1; continue;
370
+ }
371
+ if (character === "`") { frames.push({ type: "backtick", depth: 0 }); continue; }
372
+ if (top?.type === "command" && character === "(") { top.depth += 1; continue; }
373
+ if (top?.type === "arithmetic" && character === "(") { top.depth += 1; continue; }
374
+ if (character === ">" && ["conditional", "arithmetic"].includes(frames.at(-1)?.type)) {
375
+ indexes.add(index);
376
+ }
377
+ }
378
+ return indexes;
379
+ }
380
+
381
+ function redirectionActions(command) {
382
+ const actions = [];
383
+ const comparisons = structuredComparisonIndexes(command);
384
+ let quote = null;
385
+ let lineComment = false;
386
+ for (let index = 0; index < command.length; index += 1) {
387
+ const character = command[index];
388
+ if (lineComment) {
389
+ if (character === "\n") lineComment = false;
390
+ continue;
391
+ }
392
+ if (character === "\\" && quote !== "'") { index += 1; continue; }
393
+ if (quote) {
394
+ if (character === quote) quote = null;
395
+ continue;
396
+ }
397
+ if (character === "'" || character === '"') { quote = character; continue; }
398
+ if (character === "#" && (index === 0 || /[\s;&|()]/.test(command[index - 1]))) {
399
+ lineComment = true;
400
+ continue;
401
+ }
402
+ if (character !== ">") continue;
403
+ if (comparisons.has(index)) continue;
404
+ const next = command[index + 1];
405
+ if (next === ">") index += 1;
406
+ let target = index + 1;
407
+ while (/\s/.test(command[target] || "")) target += 1;
408
+ if (!["&", "="].includes(command[target])) actions.push({ kind: "write", family: null, index });
409
+ }
410
+ return actions;
411
+ }
412
+
413
+ function commandWriteActions(command, depth = 0) {
414
+ const heredocs = maskHeredocBodies(command);
415
+ const analyzable = heredocs.command;
416
+ const actions = shellSegments(analyzable).filter(segmentWrites)
417
+ .map((segment) => ({ kind: "write", family: null, index: segment.index }));
418
+ actions.push(...redirectionActions(analyzable));
419
+ if (depth < MAX_SUBSTITUTION_DEPTH) {
420
+ for (const heredoc of heredocs.bodies) {
421
+ const executable = shellExecutableName(executableTokens(heredoc.opener)[0]?.value);
422
+ if (!SHELL_STDIN_EXECUTORS.has(executable)) continue;
423
+ actions.push(...commandWriteActions(heredoc.text, depth + 1)
424
+ .map((action) => ({ ...action, index: heredoc.index + action.index })));
425
+ }
426
+ for (const substitution of shellSubstitutions(analyzable, depth)) {
427
+ actions.push(...commandWriteActions(substitution.text, depth + 1)
428
+ .map((action) => ({ ...action, index: substitution.index + action.index })));
429
+ }
430
+ }
431
+ return actions;
432
+ }
433
+
434
+ function trailingTestAction(command) {
435
+ if (typeof command !== "string" || /\|\||[;|\n]/.test(command)) return null;
436
+ const parts = command.split(/\s*&&\s*/);
437
+ const tail = parts.at(-1)?.trim() || "";
438
+ const candidate = (isSuccessMarkerClause(tail) ? parts.at(-2) : tail)?.trim() || "";
439
+ const match = TEST_COMMANDS.find(({ expression }) => expression.test(candidate));
440
+ return match ? { kind: "test", family: match.family, index: command.lastIndexOf(candidate) } : null;
441
+ }
442
+
443
+ export function deliveryToolActions(input) {
444
+ const toolName = String(input?.tool_name || "").slice(0, 128);
445
+ if (WRITE_TOOLS.test(toolName)) return [{ kind: "write", family: null, index: 0 }];
446
+ if (!SHELL_TOOLS.test(toolName)) return [];
447
+ const command = commandFromInput(input);
448
+ if (command.length > MAX_COMMAND_CHARS) return [];
449
+ const actions = commandWriteActions(command);
450
+ const test = trailingTestAction(command);
451
+ if (test) actions.push(test);
452
+ return actions.sort((left, right) => left.index - right.index || left.kind.localeCompare(right.kind));
453
+ }
@@ -0,0 +1,78 @@
1
+ import { createHash } from "node:crypto";
2
+ import { sessionBriefing } from "./briefing.js";
3
+ import { readDocument } from "./context.js";
4
+ import { inspectPremortemState } from "./delivery-premortem.js";
5
+ import { canonicalPath } from "./paths.js";
6
+ import { resolveMcpSources } from "./mcp-source-context.js";
7
+ import { targetSnapshot } from "./delivery-target.js";
8
+
9
+ const MAX_TARGETS = 32;
10
+ const MAX_CONTRACTS = 8;
11
+ const MAX_TERMS = 16;
12
+ const AUTHORITY = "context-only";
13
+
14
+ function boundedStrings(value, name, maximum) {
15
+ if (!Array.isArray(value) || value.length < 1 || value.length > maximum
16
+ || value.some((item) => typeof item !== "string" || !item.trim()
17
+ || item.length > 512 || /[\0\r\n]/.test(item))) {
18
+ throw new Error(`${name} must contain one to ${maximum} bounded strings`);
19
+ }
20
+ return [...new Set(value.map((item) => item.trim()))];
21
+ }
22
+
23
+ function contractMatches(document, terms) {
24
+ const content = document.content.toLowerCase();
25
+ return terms.filter((term) => content.includes(term.toLowerCase()));
26
+ }
27
+
28
+ async function settledValues(work) {
29
+ const results = await Promise.allSettled(work);
30
+ const failed = results.find(result => result.status === "rejected");
31
+ if (failed) throw failed.reason;
32
+ return results.map(result => result.value);
33
+ }
34
+
35
+ export async function deliveryKnowledgeQuery({ root, requirementId, targetPaths,
36
+ contractPaths, recentErrorTerms, maxBytes = 16384 }) {
37
+ const canonicalRoot = await canonicalPath(root);
38
+ const targets = boundedStrings(targetPaths, "targetPaths", MAX_TARGETS);
39
+ const contracts = boundedStrings(contractPaths, "contractPaths", MAX_CONTRACTS);
40
+ const terms = boundedStrings(recentErrorTerms, "recentErrorTerms", MAX_TERMS);
41
+ const current = await inspectPremortemState({ root: canonicalRoot, requirementId });
42
+ if (current.blocked || current.status === "degraded" || current.status === "absent") return current;
43
+ const binding = current.binding;
44
+ const sources = await resolveMcpSources({ root: canonicalRoot, host: binding.host,
45
+ entityId: binding.entityId, groupId: binding.groupId, projectId: binding.projectId,
46
+ currentTaskId: binding.taskId, required: true });
47
+ // A rejected query must own all started work until it settles. Otherwise its
48
+ // MCP error can precede catalog/state activity from a sibling operation.
49
+ const [targetResults, contractDocuments, briefing] = await settledValues([
50
+ settledValues(targets.map((path) => targetSnapshot(canonicalRoot, path))),
51
+ settledValues(contracts.map((path) => readDocument({ root: canonicalRoot,
52
+ path, offset: 0, length: 65536, catalog: sources.catalog }))),
53
+ sessionBriefing({ root: canonicalRoot, cwd: canonicalRoot,
54
+ host: binding.host, entityId: binding.entityId, groupId: binding.groupId,
55
+ projectId: binding.projectId, currentTaskId: binding.taskId,
56
+ includePrivate: false, focusActive: true, includeSourceContent: false, maxBytes,
57
+ catalog: sources.catalog, userStateRoot: sources.userStateRoot,
58
+ sourceDiagnostics: sources.diagnostics })
59
+ ]);
60
+ const result = {
61
+ schema: "agentspine.delivery-knowledge-query/v1",
62
+ requirementId,
63
+ binding: {
64
+ host: binding.host, projectId: binding.projectId, groupId: binding.groupId,
65
+ taskId: binding.taskId, assignmentId: binding.assignmentId,
66
+ goalId: binding.goalId, goalStepId: binding.goalStepId,
67
+ queueId: binding.queueId, gatewayAttempt: binding.gatewayAttempt
68
+ },
69
+ targets: targetResults,
70
+ contracts: contractDocuments.map((document) => ({ path: document.path,
71
+ sha256: document.sha256, bytes: document.totalBytes,
72
+ matchedRecentErrorTerms: contractMatches(document, terms), authority: AUTHORITY })),
73
+ recentErrorTerms: terms,
74
+ context: briefing,
75
+ authority: AUTHORITY
76
+ };
77
+ return { ...result, digest: createHash("sha256").update(JSON.stringify(result)).digest("hex") };
78
+ }
@@ -0,0 +1,107 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ const DIGEST_RE = /^[a-f0-9]{64}$/;
4
+ const REQUIREMENT_RE = /^premortem-requirement:([a-f0-9]{64}):([a-f0-9]{64})$/;
5
+ const VALUE_LIMIT = 512;
6
+
7
+ function canonical(value) {
8
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
9
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
10
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
11
+ }
12
+
13
+ function sha256(value) {
14
+ return createHash("sha256").update(typeof value === "string" ? value : canonical(value)).digest("hex");
15
+ }
16
+
17
+ function bounded(value, name, limit = VALUE_LIMIT, optional = false) {
18
+ if (optional && (value === undefined || value === null || value === "")) return null;
19
+ if (typeof value !== "string" || !value || value.length > limit || /[\0\r\n]/.test(value)) {
20
+ throw new Error(`premortem ${name} is invalid`);
21
+ }
22
+ return value;
23
+ }
24
+
25
+ export function normalizePremortemBinding(input) {
26
+ if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("premortem binding is missing");
27
+ const suppliedTask = input.taskId ?? input.currentTaskId ?? null;
28
+ if (input.taskId && input.currentTaskId && input.taskId !== input.currentTaskId) {
29
+ throw new Error("premortem task binding is inconsistent");
30
+ }
31
+ const binding = {
32
+ host: bounded(input.host, "host"), sessionId: bounded(input.sessionId, "sessionId"),
33
+ projectId: bounded(input.projectId, "projectId"),
34
+ entityId: bounded(input.entityId, "entityId", VALUE_LIMIT, true),
35
+ groupId: bounded(input.groupId, "groupId", VALUE_LIMIT, true),
36
+ taskId: bounded(suppliedTask, "taskId", VALUE_LIMIT, true),
37
+ assignmentId: bounded(input.assignmentId, "assignmentId", VALUE_LIMIT, true),
38
+ goalId: bounded(input.goalId, "goalId", VALUE_LIMIT, true),
39
+ goalStepId: bounded(input.goalStepId, "goalStepId", VALUE_LIMIT, true),
40
+ queueId: bounded(input.queueId, "queueId", VALUE_LIMIT, true),
41
+ gatewayAttempt: input.gatewayAttempt ?? null,
42
+ planDefinitionsDigest: bounded(input.planDefinitionsDigest, "planDefinitionsDigest", 64, true)?.toLowerCase() ?? null
43
+ };
44
+ if (Boolean(binding.goalId) !== Boolean(binding.goalStepId)) throw new Error("premortem goal and step must be paired");
45
+ if (binding.planDefinitionsDigest && !binding.goalId) throw new Error("premortem plan digest requires a goal step");
46
+ if (binding.gatewayAttempt !== null && (!Number.isInteger(binding.gatewayAttempt)
47
+ || binding.gatewayAttempt < 1 || !binding.queueId)) {
48
+ throw new Error("premortem gatewayAttempt must be a positive integer bound to a queue");
49
+ }
50
+ if (binding.planDefinitionsDigest && !DIGEST_RE.test(binding.planDefinitionsDigest)) {
51
+ throw new Error("premortem planDefinitionsDigest must be a SHA-256 digest");
52
+ }
53
+ return binding;
54
+ }
55
+
56
+ export function premortemLaneDigest(binding) {
57
+ const { assignmentId, ...legacyBinding } = binding;
58
+ const exact = binding.queueId || binding.goalId ? legacyBinding
59
+ : assignmentId ? binding
60
+ : { host: binding.host, sessionId: binding.sessionId, projectId: binding.projectId };
61
+ return sha256({ schema: "agentspine.delivery-premortem-lane/v1", binding: exact });
62
+ }
63
+
64
+ export function validPremortemStateBinding(state, expectedDigest) {
65
+ try {
66
+ return premortemLaneDigest(normalizePremortemBinding(state?.binding)) === expectedDigest;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ export function formatPremortemRequirementId(laneDigest, generationDigest = null) {
73
+ return generationDigest
74
+ ? `premortem-requirement:${laneDigest}:${generationDigest}`
75
+ : `premortem-requirement:${laneDigest}`;
76
+ }
77
+
78
+ export function parsePremortemRequirementId(value) {
79
+ const match = typeof value === "string" ? value.match(REQUIREMENT_RE) : null;
80
+ if (!match) throw new Error("premortem requirementId is invalid");
81
+ return { laneDigest: match[1], generationDigest: match[2] };
82
+ }
83
+
84
+ export function deliveryWriteIdDigest(input) {
85
+ const supplied = input?.tool_use_id ?? input?.event_id ?? input?.hook_event_id;
86
+ return sha256(typeof supplied === "string" && supplied ? supplied : canonical(input ?? null));
87
+ }
88
+
89
+ export function premortemWriteIdentity(input, phase) {
90
+ const toolInput = input?.tool_input ?? input?.toolInput;
91
+ const material = { schema: "agentspine.delivery-premortem-write/v1",
92
+ idDigest: deliveryWriteIdDigest(input),
93
+ inputDigest: sha256(toolInput ?? null), inputKnown: toolInput !== undefined,
94
+ phase, authority: "context-only" };
95
+ return { ...material, digest: sha256(material) };
96
+ }
97
+
98
+ export function premortemGoalBindingSummary(state) {
99
+ const { binding } = state;
100
+ const value = { goalId: binding.goalId, goalStepId: binding.goalStepId,
101
+ queueId: binding.queueId, gatewayAttempt: binding.gatewayAttempt,
102
+ planDefinitionsDigest: binding.planDefinitionsDigest, host: binding.host,
103
+ projectId: binding.projectId, entityId: binding.entityId, groupId: binding.groupId,
104
+ taskId: binding.taskId, laneDigest: state.laneDigest,
105
+ sessionDigest: sha256(binding.sessionId), authority: "context-only" };
106
+ return { ...value, digest: sha256(value) };
107
+ }