@gajae-code/coding-agent 0.11.11 → 0.12.1

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 (344) hide show
  1. package/CHANGELOG.md +131 -4
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/cli/read-cli.d.ts +1 -0
  4. package/dist/types/commands/deep-interview.d.ts +9 -0
  5. package/dist/types/commands/harness.d.ts +22 -0
  6. package/dist/types/commands/read.d.ts +6 -0
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/provider-auth-health.d.ts +14 -0
  9. package/dist/types/config/provider-ranking.d.ts +59 -0
  10. package/dist/types/config/settings-schema.d.ts +83 -1
  11. package/dist/types/config/settings.d.ts +2 -0
  12. package/dist/types/exa/mcp-client.d.ts +10 -1
  13. package/dist/types/exec/bash-executor.d.ts +45 -12
  14. package/dist/types/extensibility/extensions/types.d.ts +38 -4
  15. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  16. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  17. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  18. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  19. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  20. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  21. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  22. package/dist/types/extensibility/shared-events.d.ts +6 -0
  23. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  24. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  25. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  26. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  27. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  28. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  29. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  30. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +70 -1
  31. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  32. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  33. package/dist/types/gjc-runtime/team-runtime.d.ts +27 -1
  34. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  35. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  36. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  37. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +17 -0
  38. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  39. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  40. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  41. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  42. package/dist/types/main.d.ts +1 -1
  43. package/dist/types/memories/index.d.ts +2 -0
  44. package/dist/types/modes/acp/acp-agent.d.ts +10 -19
  45. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  46. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  47. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  48. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  49. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  50. package/dist/types/modes/components/session-observer-overlay.d.ts +22 -0
  51. package/dist/types/modes/components/settings-selector.d.ts +11 -0
  52. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  53. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  54. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  55. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  56. package/dist/types/modes/interactive-mode.d.ts +2 -0
  57. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  58. package/dist/types/modes/types.d.ts +2 -0
  59. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  60. package/dist/types/reminders/star-reminder.d.ts +3 -13
  61. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  62. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  63. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  64. package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
  65. package/dist/types/sdk/broker/transport.d.ts +2 -0
  66. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  68. package/dist/types/sdk/bus/config.d.ts +6 -2
  69. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  70. package/dist/types/sdk/bus/index.d.ts +44 -1
  71. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +44 -0
  72. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  73. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  74. package/dist/types/sdk/bus/reconciliation-store.d.ts +69 -0
  75. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  76. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  77. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  78. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  79. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  80. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  81. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  82. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  83. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  84. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  85. package/dist/types/sdk/host/host.d.ts +2 -0
  86. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  87. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  88. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  89. package/dist/types/sdk/prompt-status.d.ts +23 -3
  90. package/dist/types/sdk/session.d.ts +6 -0
  91. package/dist/types/session/agent-session.d.ts +82 -12
  92. package/dist/types/session/artifacts.d.ts +8 -0
  93. package/dist/types/session/blob-store.d.ts +57 -4
  94. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  95. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  96. package/dist/types/session/session-manager.d.ts +48 -19
  97. package/dist/types/session/session-storage.d.ts +4 -0
  98. package/dist/types/session/streaming-output.d.ts +96 -12
  99. package/dist/types/setup/credential-import.d.ts +12 -0
  100. package/dist/types/skill-state/active-state.d.ts +12 -4
  101. package/dist/types/skill-state/workflow-hud.d.ts +9 -0
  102. package/dist/types/task/executor.d.ts +3 -2
  103. package/dist/types/task/index.d.ts +3 -1
  104. package/dist/types/task/provider-retry-status.d.ts +8 -0
  105. package/dist/types/task/receipt.d.ts +7 -1
  106. package/dist/types/task/types.d.ts +23 -0
  107. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  108. package/dist/types/tools/bash-interactive.d.ts +4 -1
  109. package/dist/types/tools/bash.d.ts +3 -1
  110. package/dist/types/tools/browser/launch.d.ts +7 -0
  111. package/dist/types/tools/fetch.d.ts +17 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -0
  113. package/dist/types/tools/index.d.ts +4 -0
  114. package/dist/types/tools/output-meta.d.ts +44 -3
  115. package/dist/types/tools/path-utils.d.ts +1 -0
  116. package/dist/types/tools/read-internals.d.ts +12 -0
  117. package/dist/types/tools/read.d.ts +45 -7
  118. package/dist/types/tools/resource-gc.d.ts +4 -0
  119. package/dist/types/tools/review.d.ts +5 -7
  120. package/dist/types/tools/subagent.d.ts +2 -0
  121. package/dist/types/tools/tool-result.d.ts +7 -1
  122. package/dist/types/utils/gh.d.ts +13 -0
  123. package/dist/types/utils/image-resize.d.ts +1 -0
  124. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  125. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  126. package/dist/types/web/search/providers/xai.d.ts +2 -0
  127. package/package.json +11 -13
  128. package/scripts/acp-conformance-agent.ts +218 -0
  129. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  130. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  131. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  132. package/scripts/generate-sdk-operation-inventory.ts +5 -0
  133. package/scripts/resident-memory-bench.ts +1341 -0
  134. package/scripts/run-acp-conformance.ts +285 -0
  135. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  136. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  137. package/src/async/job-manager.ts +78 -20
  138. package/src/capability/index.ts +8 -0
  139. package/src/cli/fast-help.ts +1 -1
  140. package/src/cli/plugin-cli.ts +176 -18
  141. package/src/cli/read-cli.ts +2 -1
  142. package/src/cli.ts +1 -0
  143. package/src/commands/deep-interview.ts +29 -9
  144. package/src/commands/harness.ts +35 -12
  145. package/src/commands/read.ts +11 -2
  146. package/src/commands/team.ts +20 -2
  147. package/src/config/file-lock.ts +11 -2
  148. package/src/config/model-registry.ts +21 -2
  149. package/src/config/provider-auth-health.ts +42 -0
  150. package/src/config/provider-ranking.ts +119 -0
  151. package/src/config/settings-schema.ts +74 -1
  152. package/src/config/settings.ts +246 -108
  153. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  154. package/src/coordinator-mcp/server.ts +5 -12
  155. package/src/defaults/gjc/skills/deep-interview/SKILL.md +230 -82
  156. package/src/defaults/gjc/skills/ralplan/SKILL.md +77 -41
  157. package/src/defaults/gjc/skills/team/SKILL.md +3 -1
  158. package/src/defaults/gjc/skills/ultragoal/SKILL.md +62 -39
  159. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  160. package/src/defaults/gjc-defaults.ts +4 -4
  161. package/src/eval/py/executor.ts +7 -2
  162. package/src/exa/mcp-client.ts +12 -3
  163. package/src/exec/bash-executor.ts +144 -27
  164. package/src/extensibility/extensions/runner.ts +15 -0
  165. package/src/extensibility/extensions/types.ts +46 -5
  166. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  167. package/src/extensibility/gjc-plugins/index.ts +19 -2
  168. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  169. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  170. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  171. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  172. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  173. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  174. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  175. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  176. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  177. package/src/extensibility/gjc-plugins/types.ts +141 -0
  178. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  179. package/src/extensibility/shared-events.ts +5 -0
  180. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
  181. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  182. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  183. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  184. package/src/gjc-runtime/deep-interview-state.ts +12 -943
  185. package/src/gjc-runtime/gc-render.ts +1 -0
  186. package/src/gjc-runtime/gc-runtime.ts +11 -1
  187. package/src/gjc-runtime/launch-tmux.ts +25 -6
  188. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  189. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  190. package/src/gjc-runtime/ralplan-runtime.ts +1149 -98
  191. package/src/gjc-runtime/state-runtime.ts +2 -1
  192. package/src/gjc-runtime/state-writer.ts +70 -222
  193. package/src/gjc-runtime/team-launch.ts +42 -5
  194. package/src/gjc-runtime/team-runtime.ts +1085 -40
  195. package/src/gjc-runtime/team-store.ts +32 -0
  196. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  197. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  198. package/src/gjc-runtime/team-workers.ts +1 -0
  199. package/src/gjc-runtime/tmux-owner-isolation.ts +28 -0
  200. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  201. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  202. package/src/gjc-runtime/ultragoal-runtime.ts +414 -618
  203. package/src/gjc-runtime/workflow-manifest.generated.json +105 -449
  204. package/src/gjc-runtime/workflow-manifest.ts +38 -247
  205. package/src/hooks/native-skill-hook.ts +17 -4
  206. package/src/hooks/skill-state.ts +1 -23
  207. package/src/internal-urls/docs-index.generated.ts +21 -22
  208. package/src/internal-urls/local-root-gc.ts +127 -0
  209. package/src/main.ts +9 -5
  210. package/src/memories/index.ts +10 -0
  211. package/src/modes/DESIGN.md +98 -0
  212. package/src/modes/acp/acp-agent.ts +339 -191
  213. package/src/modes/acp/acp-event-mapper.ts +14 -7
  214. package/src/modes/acp/acp-mode.ts +39 -3
  215. package/src/modes/components/assistant-message.ts +123 -22
  216. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  217. package/src/modes/components/irc-sidebar.ts +176 -62
  218. package/src/modes/components/model-selector.ts +61 -4
  219. package/src/modes/components/notifications-settings-editor.ts +12 -1
  220. package/src/modes/components/oauth-selector.ts +96 -28
  221. package/src/modes/components/read-tool-group.ts +82 -14
  222. package/src/modes/components/session-observer-overlay.ts +457 -83
  223. package/src/modes/components/settings-selector.ts +116 -23
  224. package/src/modes/components/status-line/gh.ts +16 -0
  225. package/src/modes/components/tool-execution.ts +138 -27
  226. package/src/modes/components/tool-status-header.ts +3 -14
  227. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  228. package/src/modes/controllers/command-controller.ts +4 -0
  229. package/src/modes/controllers/event-controller.ts +109 -27
  230. package/src/modes/controllers/extension-ui-controller.ts +26 -2
  231. package/src/modes/controllers/input-controller.ts +18 -3
  232. package/src/modes/controllers/selector-controller.ts +127 -36
  233. package/src/modes/interactive-mode.ts +62 -3
  234. package/src/modes/irc-observation-ledger.ts +71 -15
  235. package/src/modes/runtime-init.ts +22 -2
  236. package/src/modes/types.ts +2 -0
  237. package/src/modes/utils/ui-helpers.ts +94 -1
  238. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  239. package/src/prompts/agents/architect.md +9 -1
  240. package/src/prompts/agents/critic.md +9 -0
  241. package/src/prompts/system/eager-todo.md +8 -4
  242. package/src/prompts/system/system-prompt.md +11 -5
  243. package/src/prompts/tools/bash.md +5 -5
  244. package/src/prompts/tools/read.md +4 -2
  245. package/src/prompts/tools/search-tool-bm25.md +5 -0
  246. package/src/prompts/tools/task.md +1 -1
  247. package/src/reminders/star-reminder.ts +4 -42
  248. package/src/runtime-mcp/manager.ts +19 -4
  249. package/src/runtime-mcp/smithery-auth.ts +17 -2
  250. package/src/runtime-mcp/smithery-connect.ts +11 -1
  251. package/src/sdk/acp/adapter.ts +22 -6
  252. package/src/sdk/broker/lifecycle.ts +25 -6
  253. package/src/sdk/broker/transport.ts +35 -3
  254. package/src/sdk/bus/attachment-registry.ts +5 -1
  255. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  256. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  257. package/src/sdk/bus/config.ts +18 -2
  258. package/src/sdk/bus/control-drain-lease.ts +104 -0
  259. package/src/sdk/bus/index.ts +984 -146
  260. package/src/sdk/bus/kind-aware-reconciliation.ts +317 -0
  261. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  262. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  263. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  264. package/src/sdk/bus/reconciliation-store.ts +303 -0
  265. package/src/sdk/bus/rich-draft.ts +1 -1
  266. package/src/sdk/bus/rich-render.ts +2 -2
  267. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  268. package/src/sdk/bus/telegram-cli.ts +33 -8
  269. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  270. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  271. package/src/sdk/bus/telegram-daemon.ts +916 -23
  272. package/src/sdk/bus/telegram-reference.ts +40 -3
  273. package/src/sdk/bus/threaded-render.ts +48 -3
  274. package/src/sdk/bus/topic-registry.ts +20 -0
  275. package/src/sdk/host/control/dispatch.ts +5 -1
  276. package/src/sdk/host/control/operations.ts +1 -1
  277. package/src/sdk/host/host.ts +23 -1
  278. package/src/sdk/host/query/handlers.ts +55 -0
  279. package/src/sdk/host/query/revision-store.ts +36 -2
  280. package/src/sdk/host/reverse-leases.ts +44 -7
  281. package/src/sdk/mcp/server.ts +46 -18
  282. package/src/sdk/prompt-status.ts +16 -3
  283. package/src/sdk/protocol/operation-inventory.generated.json +51 -0
  284. package/src/sdk/protocol/operation-registry.ts +7 -2
  285. package/src/sdk/session.ts +101 -10
  286. package/src/session/agent-session.ts +1286 -220
  287. package/src/session/artifacts.ts +22 -0
  288. package/src/session/auth-broker-config.ts +10 -3
  289. package/src/session/blob-store.ts +915 -24
  290. package/src/session/internal/managed-session-scope.ts +44 -6
  291. package/src/session/internal/managed-session-storage.ts +48 -6
  292. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  293. package/src/session/session-manager.ts +2364 -438
  294. package/src/session/session-storage.ts +9 -1
  295. package/src/session/streaming-output.ts +500 -111
  296. package/src/setup/credential-import.ts +75 -13
  297. package/src/setup/provider-onboarding.ts +19 -4
  298. package/src/skill-state/active-state.ts +32 -10
  299. package/src/skill-state/workflow-hud.ts +42 -2
  300. package/src/skill-state/workflow-mutation-guard.ts +560 -8
  301. package/src/task/executor.ts +122 -39
  302. package/src/task/index.ts +23 -14
  303. package/src/task/provider-retry-status.ts +16 -0
  304. package/src/task/receipt.ts +23 -7
  305. package/src/task/render.ts +6 -2
  306. package/src/task/types.ts +108 -0
  307. package/src/tools/ask-answer-registry.ts +12 -6
  308. package/src/tools/ask.ts +169 -118
  309. package/src/tools/ast-edit.ts +1 -0
  310. package/src/tools/ast-grep.ts +1 -0
  311. package/src/tools/bash-interactive.ts +8 -3
  312. package/src/tools/bash.ts +503 -60
  313. package/src/tools/browser/launch.ts +41 -6
  314. package/src/tools/fetch.ts +114 -23
  315. package/src/tools/find.ts +1 -0
  316. package/src/tools/image-gen.ts +29 -5
  317. package/src/tools/index.ts +4 -0
  318. package/src/tools/output-meta.ts +281 -20
  319. package/src/tools/path-utils.ts +8 -3
  320. package/src/tools/read-internals.ts +44 -0
  321. package/src/tools/read.ts +1560 -310
  322. package/src/tools/resource-gc.ts +182 -18
  323. package/src/tools/review.ts +5 -7
  324. package/src/tools/search.ts +1 -0
  325. package/src/tools/subagent-render.ts +6 -2
  326. package/src/tools/subagent.ts +6 -0
  327. package/src/tools/telegram-send.ts +6 -3
  328. package/src/tools/tool-result.ts +14 -1
  329. package/src/utils/gh.ts +39 -0
  330. package/src/utils/image-resize.ts +3 -0
  331. package/src/web/search/providers/anthropic.ts +4 -4
  332. package/src/web/search/providers/codex.ts +1 -1
  333. package/src/web/search/providers/kimi.ts +15 -5
  334. package/src/web/search/providers/searxng.ts +47 -4
  335. package/src/web/search/providers/xai.ts +7 -2
  336. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  337. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  338. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  339. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  340. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  341. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  342. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  343. package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
  344. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
@@ -1,12 +1,4 @@
1
1
  import { createHash } from "node:crypto";
2
- import {
3
- clampReportedAmbiguity,
4
- computeAmbiguityFloor,
5
- type DeepInterviewAmbiguityMilestone,
6
- deriveAmbiguityMilestone,
7
- scoreToUnits,
8
- weightedAmbiguityUnits,
9
- } from "./deep-interview-ambiguity";
10
2
 
11
3
  /**
12
4
  * Pure, dependency-free foundation for deep-interview state shape.
@@ -54,6 +46,10 @@ export interface DeepInterviewTriggerMetadata {
54
46
  status: DeepInterviewTriggerStatus;
55
47
  component: string;
56
48
  dimension: string;
49
+ priorDimensionScore?: number;
50
+ newDimensionScore?: number;
51
+ priorAmbiguity?: number;
52
+ newAmbiguity?: number;
57
53
  evidence?: string;
58
54
  contradictedFactId?: string;
59
55
  /** Required when status is `disputed` or `unresolved` to exempt the invariant. */
@@ -84,12 +80,10 @@ export interface DeepInterviewRoundRecord {
84
80
  /** Deterministic floor in effect when this round was scored, when it clamped. */
85
81
  ambiguity_floor?: number;
86
82
  triggers?: DeepInterviewTriggerMetadata[];
87
- round_result_digest?: string;
88
83
  }
89
84
 
90
85
  export interface DeepInterviewStateEnvelope {
91
86
  threshold?: number;
92
- threshold_units?: number;
93
87
  threshold_source?: string;
94
88
  state?: Record<string, unknown>;
95
89
  [key: string]: unknown;
@@ -168,7 +162,6 @@ const HOISTED_STATE_FIELDS = [
168
162
  "type",
169
163
  "language",
170
164
  "threshold",
171
- "threshold_units",
172
165
  "threshold_source",
173
166
  ] as const;
174
167
 
@@ -490,20 +483,23 @@ export function assertDeepInterviewEnvelopeInputLimits(envelope: Record<string,
490
483
  typeof envelope.state === "object" && envelope.state !== null && !Array.isArray(envelope.state)
491
484
  ? (envelope.state as Record<string, unknown>)
492
485
  : {};
486
+ // `null` is legal everywhere prose is optional: the skill template seeds
487
+ // `initial_context_summary: null` and the merge treats `null` as a deletion
488
+ // marker, so only present non-null values are bounded.
493
489
  for (const field of ["initial_idea", "initial_context", "initial_context_summary"] as const) {
494
490
  const nestedValue = state[field];
495
- if (nestedValue !== undefined)
491
+ if (nestedValue !== undefined && nestedValue !== null)
496
492
  assertDeepInterviewInputWithinLimit(nestedValue as string, MAX_INITIAL_CONTEXT_LENGTH, `state.${field}`);
497
493
  const topLevelValue = envelope[field];
498
- if (topLevelValue !== undefined)
494
+ if (topLevelValue !== undefined && topLevelValue !== null)
499
495
  assertDeepInterviewInputWithinLimit(topLevelValue as string, MAX_INITIAL_CONTEXT_LENGTH, field);
500
496
  }
501
497
  for (const field of ["user_response", "answer"] as const) {
502
498
  const nestedValue = state[field];
503
- if (nestedValue !== undefined)
499
+ if (nestedValue !== undefined && nestedValue !== null)
504
500
  assertDeepInterviewInputWithinLimit(nestedValue as string, MAX_USER_RESPONSE_LENGTH, `state.${field}`);
505
501
  const topLevelValue = envelope[field];
506
- if (topLevelValue !== undefined)
502
+ if (topLevelValue !== undefined && topLevelValue !== null)
507
503
  assertDeepInterviewInputWithinLimit(topLevelValue as string, MAX_USER_RESPONSE_LENGTH, field);
508
504
  }
509
505
  if (!Array.isArray(state.rounds)) return;
@@ -514,7 +510,7 @@ export function assertDeepInterviewEnvelopeInputLimits(envelope: Record<string,
514
510
  const value = record[field];
515
511
  // A structured scorer may use an unrelated object-valued `answer`; only prose
516
512
  // values in a legacy answer slot are user input subject to this cap.
517
- if (value === undefined || (field === "answer" && typeof value !== "string")) continue;
513
+ if (value === undefined || value === null || (field === "answer" && typeof value !== "string")) continue;
518
514
  assertDeepInterviewInputWithinLimit(
519
515
  value as string,
520
516
  MAX_USER_RESPONSE_LENGTH,
@@ -807,930 +803,3 @@ export function assertDeepInterviewIntentReview(
807
803
  )
808
804
  throw new Error("intent review approval evidence is invalid");
809
805
  }
810
- export type DeepInterviewDimension = "goal" | "constraints" | "criteria" | "context";
811
- export type DeepInterviewResolution =
812
- | "auto_research_accepted"
813
- | "auto_answer"
814
- | "direct"
815
- | "refined"
816
- | "cited_confirmation";
817
-
818
- export interface DeepInterviewFactOperation {
819
- op: "add" | "dispute" | "supersede";
820
- id: string;
821
- statement?: string;
822
- component?: string;
823
- dimension?: DeepInterviewDimension;
824
- evidence?: string;
825
- target_id?: string;
826
- }
827
-
828
- export interface DeepInterviewOntologyEntity {
829
- id: string;
830
- name: string;
831
- type: string;
832
- fields: string[];
833
- }
834
-
835
- export interface DeepInterviewOntologyRelationship {
836
- id: string;
837
- from_entity_id: string;
838
- to_entity_id: string;
839
- type: string;
840
- }
841
-
842
- export interface DeepInterviewOntologyReasoning {
843
- statement: string;
844
- evidence?: string;
845
- }
846
-
847
- export interface DeepInterviewOntologyInput {
848
- entities: DeepInterviewOntologyEntity[];
849
- relationships: DeepInterviewOntologyRelationship[];
850
- reasoning: DeepInterviewOntologyReasoning[];
851
- }
852
-
853
- export interface DeepInterviewComponentUpdate {
854
- component_id: string;
855
- scores: Record<DeepInterviewDimension, number>;
856
- }
857
-
858
- /**
859
- * Optional legacy assertions accepted only when they agree with the native
860
- * target. The persisted target is always derived from component scores.
861
- */
862
- export interface DeepInterviewTargetingInput {
863
- target_component_id?: string;
864
- target_dimension?: DeepInterviewDimension;
865
- weakest_component_id?: string;
866
- weakest_dimension?: DeepInterviewDimension;
867
- last_targeted_component_id?: string | null;
868
- }
869
-
870
- export interface DeepInterviewRoundBookkeeping {
871
- resolution: DeepInterviewResolution;
872
- round_ids?: string[];
873
- counter_deltas?: Record<string, number>;
874
- }
875
-
876
- /**
877
- * Closed evidence submitted for one scoring transaction. All convergence values
878
- * (ambiguity, floor, milestone, ontology counts, rotation, and streak) are
879
- * native-derived and intentionally cannot be supplied here.
880
- */
881
- export interface DeepInterviewRoundResultV1 {
882
- global_scores: Record<DeepInterviewDimension, number>;
883
- component_updates?: DeepInterviewComponentUpdate[];
884
- targeting?: DeepInterviewTargetingInput;
885
- triggers?: DeepInterviewTriggerMetadata[];
886
- fact_ops?: DeepInterviewFactOperation[];
887
- ontology?: DeepInterviewOntologyInput | DeepInterviewOntologyEntity[];
888
- bookkeeping?: DeepInterviewRoundBookkeeping;
889
- /** Compatibility adapter for recorder callers; never persisted as the v1 wire shape. */
890
- component_scores?: Record<string, Record<DeepInterviewDimension, number>>;
891
- auto_answered?: boolean;
892
- }
893
-
894
- /**
895
- * Version 1 native projection returned by `apply-round-result` as
896
- * `native_projection`. This is the complete renderable convergence surface;
897
- * callers must not derive or supplement it from candidate scorer input.
898
- */
899
- export interface DeepInterviewRoundResultProjection {
900
- score_units: Partial<Record<DeepInterviewDimension, number>>;
901
- weighted_ambiguity: number;
902
- weighted_ambiguity_units: number;
903
- floor: number;
904
- floor_units: number;
905
- floor_cause: ReturnType<typeof computeAmbiguityFloor>;
906
- effective_ambiguity: number;
907
- effective_ambiguity_units: number;
908
- prior_effective_ambiguity: number | null;
909
- direction: "increased" | "decreased" | "unchanged" | "initial";
910
- ambiguity_milestone: DeepInterviewAmbiguityMilestone;
911
- topology: unknown;
912
- topology_counts: { active: number; deferred: number; total: number };
913
- ontology: unknown;
914
- ontology_counts: { stable: number; changed: number; new: number; basis: string };
915
- targeting: {
916
- target_component_id: string | null;
917
- target_dimension: DeepInterviewDimension | null;
918
- last_targeted_component_id: string | null;
919
- };
920
- transition: {
921
- round_key: string;
922
- lifecycle: "scored";
923
- auto_answer_streak: number;
924
- };
925
- }
926
-
927
- export type DeepInterviewApplyRoundResult =
928
- | { kind: "noop"; envelope: DeepInterviewStateEnvelope; projection?: DeepInterviewRoundResultProjection }
929
- | { kind: "write"; envelope: DeepInterviewStateEnvelope; projection: DeepInterviewRoundResultProjection };
930
-
931
- function canonicalJsonValue(value: unknown): unknown {
932
- if (Array.isArray(value)) return value.map(canonicalJsonValue);
933
- if (!isPlainObject(value)) {
934
- if (typeof value === "number" && !Number.isFinite(value)) {
935
- throw new TypeError("canonical JSON rejects non-finite numbers");
936
- }
937
- if (value === undefined) throw new TypeError("canonical JSON rejects undefined");
938
- return value;
939
- }
940
- /**
941
- * Object properties set to `undefined` are dropped, matching `JSON.stringify`:
942
- * an omitted optional key and a present-but-undefined one must digest
943
- * identically. Decoded requests routinely materialize every optional key (an
944
- * omitted `targeting` becomes `targeting: undefined`), so rejecting them here
945
- * would make every request carrying an absent optional field unserializable.
946
- * Array elements and the top-level value stay strict: `undefined` there is a
947
- * real encoding bug, not an absent field.
948
- */
949
- const output: Record<string, unknown> = {};
950
- for (const key of Object.keys(value).sort()) {
951
- if (value[key] === undefined) continue;
952
- output[key] = canonicalJsonValue(value[key]);
953
- }
954
- return output;
955
- }
956
-
957
- /** Strict UTF-8 JSON serialization used by v1 replay digests. */
958
- export function canonicalDeepInterviewJson(value: unknown): string {
959
- return JSON.stringify(canonicalJsonValue(value));
960
- }
961
-
962
- export function deepInterviewRoundResultDigest(input: {
963
- round: number;
964
- question_id: string;
965
- round_id?: string | null;
966
- result: unknown;
967
- }): string {
968
- return createHash("sha256")
969
- .update(
970
- canonicalDeepInterviewJson({
971
- v: 1,
972
- round: input.round,
973
- question_id: input.question_id,
974
- round_id: input.round_id ?? null,
975
- result: input.result,
976
- }),
977
- )
978
- .digest("hex");
979
- }
980
-
981
- /** Complete persisted answer identity; omitted optional values normalize to null. */
982
- export function deepInterviewAnswerIdentityEqual(
983
- a: Pick<
984
- DeepInterviewRoundRecord,
985
- | "round"
986
- | "round_key"
987
- | "round_id"
988
- | "question_id"
989
- | "component"
990
- | "dimension"
991
- | "question_text"
992
- | "question_hash"
993
- | "selected_options"
994
- | "custom_input"
995
- >,
996
- b: Pick<
997
- DeepInterviewRoundRecord,
998
- | "round"
999
- | "round_key"
1000
- | "round_id"
1001
- | "question_id"
1002
- | "component"
1003
- | "dimension"
1004
- | "question_text"
1005
- | "question_hash"
1006
- | "selected_options"
1007
- | "custom_input"
1008
- >,
1009
- ): boolean {
1010
- return (
1011
- canonicalDeepInterviewJson({
1012
- round: a.round,
1013
- round_key: a.round_key,
1014
- round_id: a.round_id ?? null,
1015
- question_id: a.question_id ?? null,
1016
- component: a.component ?? null,
1017
- dimension: a.dimension ?? null,
1018
- question_text: a.question_text ?? null,
1019
- question_hash: a.question_hash ?? null,
1020
- selected_options: a.selected_options ?? [],
1021
- custom_input: a.custom_input ?? null,
1022
- }) ===
1023
- canonicalDeepInterviewJson({
1024
- round: b.round,
1025
- round_key: b.round_key,
1026
- round_id: b.round_id ?? null,
1027
- question_id: b.question_id ?? null,
1028
- component: b.component ?? null,
1029
- dimension: b.dimension ?? null,
1030
- question_text: b.question_text ?? null,
1031
- question_hash: b.question_hash ?? null,
1032
- selected_options: b.selected_options ?? [],
1033
- custom_input: b.custom_input ?? null,
1034
- })
1035
- );
1036
- }
1037
-
1038
- /** Apply one closed v1 scoring round. The input contains evidence only; all convergence state is derived here. */
1039
- export function applyDeepInterviewRoundResultV1(
1040
- envelopeValue: unknown,
1041
- roundKey: string,
1042
- result: DeepInterviewRoundResultV1,
1043
- now: string,
1044
- ): DeepInterviewApplyRoundResult {
1045
- const envelope = normalizeDeepInterviewEnvelope(envelopeValue);
1046
- const state = { ...(envelope.state ?? {}) };
1047
- const rounds = asRecordArray(state.rounds) as unknown as DeepInterviewRoundRecord[];
1048
- const index = rounds.findIndex(round => round.round_key === roundKey);
1049
- if (index < 0) throw new Error("DI_ROUND_NOT_FOUND");
1050
- const shell = rounds[index];
1051
- if (shell.lifecycle !== "answered" && shell.lifecycle !== "pending_scoring" && shell.lifecycle !== "scored")
1052
- throw new Error("DI_STATE_SCHEMA_INVALID");
1053
- if (typeof shell.question_id !== "string" || shell.question_id === "") throw new Error("DI_STATE_SCHEMA_INVALID");
1054
- const digest = deepInterviewRoundResultDigest({
1055
- round: shell.round,
1056
- question_id: shell.question_id,
1057
- round_id: shell.round_id ?? null,
1058
- result,
1059
- });
1060
- if (shell.lifecycle === "scored") {
1061
- if (shell.round_result_digest !== digest) throw new Error("DI_ROUND_RESULT_CONFLICT");
1062
- return { kind: "noop", envelope };
1063
- }
1064
-
1065
- const type = state.type;
1066
- if (type !== "greenfield" && type !== "brownfield") throw new Error("DI_STATE_SCHEMA_INVALID");
1067
- const dimensions: DeepInterviewDimension[] =
1068
- type === "brownfield" ? ["goal", "constraints", "criteria", "context"] : ["goal", "constraints", "criteria"];
1069
- const componentScores =
1070
- result.component_scores ??
1071
- Object.fromEntries((result.component_updates ?? []).map(update => [update.component_id, update.scores]));
1072
- const finiteScore = (value: unknown): value is number =>
1073
- typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
1074
- if (!dimensions.every(dimension => finiteScore(result.global_scores[dimension])))
1075
- throw new Error("DI_STATE_SCHEMA_INVALID");
1076
- /**
1077
- * Rounds must be scored in order, but the Round 0 topology gate is not a
1078
- * scorable round: the recorder persists it as an `answered` shell to bind the
1079
- * locked intent contract, `apply-round-result` rejects `--round 0`, and
1080
- * `validateDeepInterviewV1Envelope` refuses to persist a round-0 record that
1081
- * is anything other than an answered, score-less shell. Counting it here would
1082
- * deadlock every later round forever, so the gate is excluded by identity
1083
- * rather than by an ordering range.
1084
- */
1085
- if (
1086
- rounds.some(
1087
- round =>
1088
- round.lifecycle !== "scored" &&
1089
- round.round !== 0 &&
1090
- (round.round < shell.round || (round.round === shell.round && round.round_key < shell.round_key)),
1091
- )
1092
- )
1093
- throw new Error("DI_STATE_SCHEMA_INVALID");
1094
-
1095
- const topology = isPlainObject(state.topology) ? state.topology : undefined;
1096
- const activeComponents =
1097
- topology && Array.isArray(topology.components)
1098
- ? topology.components.filter(
1099
- component => isPlainObject(component) && component.active !== false && component.status !== "deferred",
1100
- )
1101
- : [];
1102
- if (activeComponents.length > 0) {
1103
- for (const component of activeComponents) {
1104
- const id = component.id;
1105
- if (
1106
- typeof id !== "string" ||
1107
- !isPlainObject(componentScores[id]) ||
1108
- !dimensions.every(d => finiteScore(componentScores[id][d]))
1109
- )
1110
- throw new Error("DI_STATE_SCHEMA_INVALID");
1111
- }
1112
- for (const dimension of dimensions) {
1113
- const weakest = Math.min(
1114
- ...activeComponents.map(component => componentScores[String(component.id)][dimension]),
1115
- );
1116
- if (result.global_scores[dimension] !== weakest) throw new Error("DI_STATE_SCHEMA_INVALID");
1117
- }
1118
- }
1119
- const facts = asRecordArray(state.established_facts);
1120
- for (const operation of result.fact_ops ?? []) {
1121
- if (!operation || typeof operation.id !== "string" || operation.id === "")
1122
- throw new Error("DI_STATE_SCHEMA_INVALID");
1123
- const factIndex = facts.findIndex(fact => fact.id === operation.id);
1124
- if (operation.op === "add") {
1125
- if (!operation.statement || factIndex >= 0) throw new Error("DI_STATE_SCHEMA_INVALID");
1126
- facts.push({
1127
- id: operation.id,
1128
- statement: operation.statement,
1129
- round: shell.round,
1130
- component: operation.component,
1131
- dimension: operation.dimension,
1132
- evidence: operation.evidence,
1133
- disputed: false,
1134
- });
1135
- } else if (operation.op === "dispute") {
1136
- if (factIndex < 0) throw new Error("DI_STATE_SCHEMA_INVALID");
1137
- facts[factIndex] = { ...facts[factIndex], disputed: true };
1138
- } else {
1139
- if (factIndex < 0 || !operation.target_id || !facts.some(fact => fact.id === operation.target_id))
1140
- throw new Error("DI_STATE_SCHEMA_INVALID");
1141
- facts[factIndex] = { ...facts[factIndex], disputed: true, superseded_by: operation.target_id };
1142
- }
1143
- }
1144
- for (const trigger of result.triggers ?? []) {
1145
- if (
1146
- !trigger ||
1147
- !["A", "B", "C", "D"].includes(trigger.kind) ||
1148
- !["active", "disputed", "unresolved"].includes(trigger.status) ||
1149
- typeof trigger.name !== "string" ||
1150
- trigger.name === "" ||
1151
- typeof trigger.component !== "string" ||
1152
- trigger.component === "" ||
1153
- !dimensions.includes(trigger.dimension as DeepInterviewDimension)
1154
- )
1155
- throw new Error("DI_STATE_SCHEMA_INVALID");
1156
- if ((trigger.status === "disputed" || trigger.status === "unresolved") && !trigger.rationale)
1157
- throw new Error("DI_STATE_SCHEMA_INVALID");
1158
- if (trigger.contradictedFactId && !facts.some(fact => fact.id === trigger.contradictedFactId))
1159
- throw new Error("DI_STATE_SCHEMA_INVALID");
1160
- if (
1161
- topology &&
1162
- Array.isArray(topology.components) &&
1163
- topology.components.length > 0 &&
1164
- !topology.components.some(component => isPlainObject(component) && component.id === trigger.component)
1165
- )
1166
- throw new Error("DI_STATE_SCHEMA_INVALID");
1167
- }
1168
- const priorSnapshot = Array.isArray(state.ontology_snapshots) ? state.ontology_snapshots.at(-1) : undefined;
1169
- const priorEntities =
1170
- isPlainObject(priorSnapshot) && Array.isArray(priorSnapshot.entities)
1171
- ? priorSnapshot.entities.filter(isPlainObject)
1172
- : [];
1173
- const ontologyInput = result.ontology;
1174
- const entities = (Array.isArray(ontologyInput) ? ontologyInput : (ontologyInput?.entities ?? [])).map(entity => ({
1175
- ...entity,
1176
- relationships: Array.isArray(ontologyInput)
1177
- ? []
1178
- : (ontologyInput?.relationships ?? [])
1179
- .filter(
1180
- relationship => relationship.from_entity_id === entity.id || relationship.to_entity_id === entity.id,
1181
- )
1182
- .map(relationship => relationship.id),
1183
- }));
1184
- if (
1185
- !entities.every(
1186
- entity =>
1187
- entity &&
1188
- typeof entity.id === "string" &&
1189
- typeof entity.name === "string" &&
1190
- entity.name !== "" &&
1191
- typeof entity.type === "string" &&
1192
- Array.isArray(entity.fields) &&
1193
- Array.isArray(entity.relationships),
1194
- )
1195
- )
1196
- throw new Error("DI_STATE_SCHEMA_INVALID");
1197
- if (isPlainObject(priorSnapshot)) {
1198
- const priorSnapshotRound = priorSnapshot.round;
1199
- if (
1200
- typeof priorSnapshotRound !== "number" ||
1201
- !Number.isSafeInteger(priorSnapshotRound) ||
1202
- priorSnapshotRound >= shell.round
1203
- )
1204
- throw new Error("DI_STATE_SCHEMA_INVALID");
1205
- }
1206
- const basis = entities.length === 0 ? "no_entities" : priorSnapshot === undefined ? "first_round" : "compared";
1207
- const unmatchedPrior = new Set(priorEntities.map((_, index) => index));
1208
- const stableMatches = new Set<number>();
1209
- const changedMatches = new Set<number>();
1210
- if (basis === "compared") {
1211
- for (let current = 0; current < entities.length; current += 1) {
1212
- const prior = priorEntities.findIndex(
1213
- (entity, index) =>
1214
- unmatchedPrior.has(index) &&
1215
- entity.name === entities[current].name &&
1216
- entity.type === entities[current].type,
1217
- );
1218
- if (prior >= 0) {
1219
- unmatchedPrior.delete(prior);
1220
- stableMatches.add(current);
1221
- }
1222
- }
1223
- const candidates: { prior: number; current: number }[] = [];
1224
- for (let current = 0; current < entities.length; current += 1) {
1225
- if (stableMatches.has(current)) continue;
1226
- const fields = new Set(entities[current].fields);
1227
- for (const prior of unmatchedPrior) {
1228
- if (priorEntities[prior].type !== entities[current].type) continue;
1229
- const priorFields = new Set(priorEntities[prior].fields as unknown[]);
1230
- const overlap = [...fields].filter(field => priorFields.has(field)).length;
1231
- if (overlap * 2 > Math.max(fields.size, priorFields.size)) candidates.push({ prior, current });
1232
- }
1233
- }
1234
- candidates.sort(
1235
- (a, b) =>
1236
- String(priorEntities[a.prior].id).localeCompare(String(priorEntities[b.prior].id)) ||
1237
- String(entities[a.current].id).localeCompare(String(entities[b.current].id)),
1238
- );
1239
- const matchedCurrent = new Set<number>();
1240
- for (const candidate of candidates) {
1241
- if (unmatchedPrior.has(candidate.prior) && !matchedCurrent.has(candidate.current)) {
1242
- unmatchedPrior.delete(candidate.prior);
1243
- matchedCurrent.add(candidate.current);
1244
- changedMatches.add(candidate.current);
1245
- }
1246
- }
1247
- }
1248
- const stable = stableMatches.size;
1249
- const changed = changedMatches.size;
1250
- const fresh = entities.length - stable - changed;
1251
- const stabilityRatio =
1252
- basis === "compared" ? Math.floor(((stable + changed) * 10_000) / entities.length + 0.5) / 10_000 : null;
1253
- const ontologySnapshots = [
1254
- ...(Array.isArray(state.ontology_snapshots) ? state.ontology_snapshots : []),
1255
- {
1256
- round: shell.round,
1257
- captured_at: now,
1258
- entities,
1259
- basis,
1260
- stable_entities: stable,
1261
- new_entities: fresh,
1262
- changed_entities: changed,
1263
- stability_ratio: stabilityRatio,
1264
- },
1265
- ];
1266
-
1267
- const nextTopology =
1268
- topology && Array.isArray(topology.components)
1269
- ? {
1270
- ...topology,
1271
- components: topology.components.map(component => {
1272
- if (!isPlainObject(component) || typeof component.id !== "string") return component;
1273
- const scores = componentScores[component.id];
1274
- return scores ? { ...component, clarity_scores: { ...scores } } : component;
1275
- }),
1276
- }
1277
- : state.topology;
1278
- const activeIds = activeComponents.map(component => String(component.id));
1279
- const componentWeakness = (id: string) => Math.min(...dimensions.map(dimension => componentScores[id][dimension]));
1280
- const weakestValue = activeIds.length === 0 ? undefined : Math.min(...activeIds.map(componentWeakness));
1281
- const tiedWeakest = activeIds.filter(id => componentWeakness(id) === weakestValue);
1282
- const previousTarget =
1283
- isPlainObject(topology) && typeof topology.last_targeted_component_id === "string"
1284
- ? topology.last_targeted_component_id
1285
- : null;
1286
- const rotatedCandidates =
1287
- tiedWeakest.length > 1 && previousTarget !== null
1288
- ? [...tiedWeakest].sort(
1289
- (a, b) =>
1290
- ((activeIds.indexOf(a) - activeIds.indexOf(previousTarget) + activeIds.length) % activeIds.length) -
1291
- ((activeIds.indexOf(b) - activeIds.indexOf(previousTarget) + activeIds.length) % activeIds.length),
1292
- )
1293
- : tiedWeakest;
1294
- const targetComponent = rotatedCandidates[0] ?? null;
1295
- const targetDimension =
1296
- targetComponent === null
1297
- ? null
1298
- : dimensions.reduce((weakest, dimension) =>
1299
- componentScores[targetComponent][dimension] < componentScores[targetComponent][weakest]
1300
- ? dimension
1301
- : weakest,
1302
- );
1303
- if (
1304
- result.targeting &&
1305
- ((result.targeting.target_component_id !== undefined &&
1306
- result.targeting.target_component_id !== targetComponent) ||
1307
- (result.targeting.weakest_component_id !== undefined &&
1308
- result.targeting.weakest_component_id !== targetComponent) ||
1309
- (result.targeting.target_dimension !== undefined && result.targeting.target_dimension !== targetDimension) ||
1310
- (result.targeting.weakest_dimension !== undefined && result.targeting.weakest_dimension !== targetDimension))
1311
- )
1312
- throw new Error("DI_STATE_SCHEMA_INVALID");
1313
- const weightedUnits = weightedAmbiguityUnits(result.global_scores, type);
1314
- const weightedAmbiguity = weightedUnits / 10_000;
1315
- const resolution = result.bookkeeping?.resolution ?? (result.auto_answered ? "auto_answer" : "direct");
1316
- const priorStreak = typeof state.auto_answer_streak === "number" ? state.auto_answer_streak : 0;
1317
- const autoAnswerStreak =
1318
- resolution === "auto_answer" || resolution === "auto_research_accepted" ? priorStreak + 1 : 0;
1319
- const requestedRoundIds = result.bookkeeping?.round_ids ?? [roundKey];
1320
- const durableRoundReferences = new Set(
1321
- rounds.flatMap(round =>
1322
- typeof round.round_id === "string" && round.round_id !== ""
1323
- ? [round.round_key, round.round_id]
1324
- : [round.round_key],
1325
- ),
1326
- );
1327
- if (
1328
- new Set(requestedRoundIds).size !== requestedRoundIds.length ||
1329
- !requestedRoundIds.every(id => typeof id === "string" && durableRoundReferences.has(id))
1330
- )
1331
- throw new Error("DI_STATE_SCHEMA_INVALID");
1332
- const appendRoundIds = (field: string): string[] => [
1333
- ...(Array.isArray(state[field]) ? state[field].filter((id): id is string => typeof id === "string") : []),
1334
- ...requestedRoundIds.filter(id => !(Array.isArray(state[field]) ? state[field] : []).includes(id)),
1335
- ];
1336
- const counters = { ...(isPlainObject(state.counters) ? state.counters : {}) };
1337
- for (const [key, delta] of Object.entries(result.bookkeeping?.counter_deltas ?? {})) {
1338
- if (
1339
- !Number.isSafeInteger(delta) ||
1340
- (typeof counters[key] !== "undefined" && !Number.isSafeInteger(counters[key]))
1341
- )
1342
- throw new Error("DI_STATE_SCHEMA_INVALID");
1343
- counters[key] = (typeof counters[key] === "number" ? counters[key] : 0) + delta;
1344
- }
1345
- const nextState: Record<string, unknown> = {
1346
- ...state,
1347
- topology:
1348
- isPlainObject(nextTopology) && targetComponent !== null
1349
- ? { ...nextTopology, last_targeted_component_id: targetComponent }
1350
- : nextTopology,
1351
- established_facts: facts,
1352
- ontology_snapshots: ontologySnapshots,
1353
- auto_answer_streak: autoAnswerStreak,
1354
- counters,
1355
- auto_answered_rounds:
1356
- resolution === "auto_answer" ? appendRoundIds("auto_answered_rounds") : state.auto_answered_rounds,
1357
- auto_research_accepted_rounds:
1358
- resolution === "auto_research_accepted"
1359
- ? appendRoundIds("auto_research_accepted_rounds")
1360
- : state.auto_research_accepted_rounds,
1361
- refined_rounds: resolution === "refined" ? appendRoundIds("refined_rounds") : state.refined_rounds,
1362
- };
1363
- const floorBreakdown = computeAmbiguityFloor({
1364
- ...nextState,
1365
- rounds: [...rounds, { ...shell, lifecycle: "scored" }],
1366
- });
1367
- const floor = floorBreakdown.floor;
1368
- const effectiveAmbiguity = clampReportedAmbiguity(weightedAmbiguity, floor).effective;
1369
- const threshold = state.threshold ?? envelope.threshold;
1370
- if (!finiteScore(threshold)) throw new Error("DI_STATE_SCHEMA_INVALID");
1371
- const thresholdUnits = state.threshold_units ?? envelope.threshold_units ?? scoreToUnits(threshold);
1372
- if (
1373
- typeof thresholdUnits !== "number" ||
1374
- !Number.isSafeInteger(thresholdUnits) ||
1375
- thresholdUnits < 1 ||
1376
- thresholdUnits > 10_000 ||
1377
- scoreToUnits(threshold) !== thresholdUnits
1378
- )
1379
- throw new Error("DI_STATE_SCHEMA_INVALID");
1380
- const milestone = deriveAmbiguityMilestone(scoreToUnits(effectiveAmbiguity), thresholdUnits);
1381
- nextState.threshold_units = thresholdUnits;
1382
- const priorScoredRounds = rounds
1383
- .filter(
1384
- round =>
1385
- round.lifecycle === "scored" &&
1386
- (round.round < shell.round || (round.round === shell.round && round.round_key < shell.round_key)),
1387
- )
1388
- .sort((a, b) => b.round - a.round || b.round_key.localeCompare(a.round_key));
1389
- const priorRound = priorScoredRounds[0];
1390
- const priorEffectiveAmbiguity = typeof priorRound?.ambiguity === "number" ? priorRound.ambiguity : null;
1391
- for (const trigger of result.triggers ?? []) {
1392
- if (trigger.status !== "active") continue;
1393
- const topologyComponents: unknown[] =
1394
- isPlainObject(topology) && Array.isArray(topology.components) ? topology.components : [];
1395
- const hasTopologyComponents = topologyComponents.length > 0;
1396
- const priorComponent = topologyComponents.find(
1397
- component => isPlainObject(component) && component.id === trigger.component,
1398
- );
1399
- const priorScores = isPlainObject(priorComponent) ? priorComponent.clarity_scores : undefined;
1400
- const priorDimension = hasTopologyComponents
1401
- ? isPlainObject(priorScores)
1402
- ? priorScores[trigger.dimension]
1403
- : undefined
1404
- : priorRound?.scores?.[trigger.dimension];
1405
- const candidateDimension = hasTopologyComponents
1406
- ? componentScores[trigger.component]?.[trigger.dimension as DeepInterviewDimension]
1407
- : result.global_scores[trigger.dimension as DeepInterviewDimension];
1408
- if (
1409
- !priorRound ||
1410
- !finiteScore(priorDimension) ||
1411
- !finiteScore(candidateDimension) ||
1412
- !finiteScore(priorRound.ambiguity) ||
1413
- candidateDimension > priorDimension ||
1414
- effectiveAmbiguity <= priorRound.ambiguity
1415
- )
1416
- throw new Error("DI_STATE_SCHEMA_INVALID");
1417
- }
1418
- rounds[index] = {
1419
- ...shell,
1420
- lifecycle: "scored",
1421
- scored_at: now,
1422
- scores: result.global_scores,
1423
- ambiguity: effectiveAmbiguity,
1424
- reported_ambiguity: weightedAmbiguity,
1425
- ambiguity_floor: floor,
1426
- triggers: result.triggers ?? [],
1427
- round_result_digest: digest,
1428
- };
1429
- nextState.rounds = rounds;
1430
- nextState.current_ambiguity = effectiveAmbiguity;
1431
- nextState.weighted_ambiguity = weightedAmbiguity;
1432
- nextState.effective_ambiguity = effectiveAmbiguity;
1433
- nextState.floor = floor;
1434
- nextState.ambiguity_milestone = milestone;
1435
- return {
1436
- kind: "write",
1437
- envelope: { ...envelope, state: nextState },
1438
- projection: {
1439
- score_units: Object.fromEntries(
1440
- dimensions.map(dimension => [dimension, scoreToUnits(result.global_scores[dimension])]),
1441
- ) as Partial<Record<DeepInterviewDimension, number>>,
1442
- weighted_ambiguity: weightedAmbiguity,
1443
- weighted_ambiguity_units: weightedUnits,
1444
- floor,
1445
- floor_units: scoreToUnits(floor),
1446
- floor_cause: floorBreakdown,
1447
- effective_ambiguity: effectiveAmbiguity,
1448
- effective_ambiguity_units: scoreToUnits(effectiveAmbiguity),
1449
- prior_effective_ambiguity: priorEffectiveAmbiguity,
1450
- direction:
1451
- priorEffectiveAmbiguity === null
1452
- ? "initial"
1453
- : effectiveAmbiguity > priorEffectiveAmbiguity
1454
- ? "increased"
1455
- : effectiveAmbiguity < priorEffectiveAmbiguity
1456
- ? "decreased"
1457
- : "unchanged",
1458
- ambiguity_milestone: milestone,
1459
- topology: nextState.topology,
1460
- topology_counts: {
1461
- active: activeComponents.length,
1462
- deferred: Array.isArray(topology?.components)
1463
- ? topology.components.filter(component => isPlainObject(component) && component.status === "deferred")
1464
- .length
1465
- : 0,
1466
- total: Array.isArray(topology?.components) ? topology.components.length : 0,
1467
- },
1468
- ontology: ontologySnapshots.at(-1),
1469
- ontology_counts: { stable, changed, new: fresh, basis },
1470
- targeting: {
1471
- target_component_id: targetComponent,
1472
- target_dimension: targetDimension,
1473
- last_targeted_component_id: targetComponent,
1474
- },
1475
- transition: { round_key: roundKey, lifecycle: "scored", auto_answer_streak: autoAnswerStreak },
1476
- },
1477
- };
1478
- }
1479
- export function validateDeepInterviewV1Envelope(value: Record<string, unknown>): void {
1480
- const invalid = (): never => {
1481
- throw new Error("DI_STATE_SCHEMA_INVALID");
1482
- };
1483
- const validDate = (candidate: unknown): candidate is string =>
1484
- typeof candidate === "string" && Number.isFinite(Date.parse(candidate));
1485
- const validScore = (candidate: unknown): candidate is number => {
1486
- if (typeof candidate !== "number") return false;
1487
- try {
1488
- scoreToUnits(candidate);
1489
- return true;
1490
- } catch {
1491
- return false;
1492
- }
1493
- };
1494
- const stateValue = value.state;
1495
- if (value.skill !== "deep-interview" || value.schema_version !== 1 || !isPlainObject(stateValue)) return invalid();
1496
- const state = stateValue as Record<string, unknown>;
1497
- const type = state.type;
1498
- if (type !== "greenfield" && type !== "brownfield") return invalid();
1499
- const dimensions: DeepInterviewDimension[] =
1500
- type === "brownfield" ? ["goal", "constraints", "criteria", "context"] : ["goal", "constraints", "criteria"];
1501
- const isDimension = (candidate: unknown): candidate is DeepInterviewDimension =>
1502
- typeof candidate === "string" && dimensions.includes(candidate as DeepInterviewDimension);
1503
- const roundsValue = state.rounds;
1504
- const factsValue = state.established_facts;
1505
- const threshold = state.threshold ?? value.threshold;
1506
- const thresholdUnits = state.threshold_units;
1507
- if (
1508
- !validScore(threshold) ||
1509
- typeof thresholdUnits !== "number" ||
1510
- !Number.isSafeInteger(thresholdUnits) ||
1511
- thresholdUnits < 1 ||
1512
- thresholdUnits > 10_000 ||
1513
- scoreToUnits(threshold) !== thresholdUnits ||
1514
- !Array.isArray(roundsValue) ||
1515
- !Array.isArray(factsValue)
1516
- )
1517
- return invalid();
1518
- const rounds = roundsValue as unknown[];
1519
- const facts = factsValue as unknown[];
1520
-
1521
- const topology = state.topology;
1522
- const componentIds = new Set<string>();
1523
- let hasTopologyComponents = false;
1524
- if (topology !== undefined) {
1525
- if (!isPlainObject(topology) || !Array.isArray(topology.components) || typeof topology.status !== "string")
1526
- return invalid();
1527
- const topologyRecord = topology as Record<string, unknown>;
1528
- const components = topologyRecord.components as unknown[];
1529
- for (const component of components) {
1530
- if (!isPlainObject(component)) return invalid();
1531
- const componentRecord = component as Record<string, unknown>;
1532
- const componentIdValue = componentRecord.id;
1533
- if (typeof componentIdValue !== "string" || componentIdValue === "" || componentIds.has(componentIdValue))
1534
- return invalid();
1535
- const componentId = componentIdValue;
1536
- componentIds.add(componentId);
1537
- if (componentRecord.active !== undefined && typeof componentRecord.active !== "boolean") return invalid();
1538
- if (componentRecord.clarity_scores !== undefined) {
1539
- const clarityScoresValue = componentRecord.clarity_scores;
1540
- if (!isPlainObject(clarityScoresValue)) return invalid();
1541
- const clarityScores = clarityScoresValue as Record<string, unknown>;
1542
- for (const dimension of dimensions) {
1543
- const score = clarityScores[dimension];
1544
- if (score !== undefined && score !== null && !validScore(score)) return invalid();
1545
- }
1546
- }
1547
- }
1548
- hasTopologyComponents = componentIds.size > 0;
1549
- }
1550
-
1551
- const factIds = new Set<string>();
1552
- for (const fact of facts) {
1553
- if (!isPlainObject(fact)) return invalid();
1554
- const factRecord = fact as Record<string, unknown>;
1555
- const factIdValue = factRecord.id;
1556
- const factRoundValue = factRecord.round;
1557
- if (
1558
- typeof factIdValue !== "string" ||
1559
- factIdValue === "" ||
1560
- factIds.has(factIdValue) ||
1561
- typeof factRecord.statement !== "string" ||
1562
- factRecord.statement === "" ||
1563
- typeof factRoundValue !== "number" ||
1564
- !Number.isSafeInteger(factRoundValue) ||
1565
- factRoundValue < 1 ||
1566
- typeof factRecord.disputed !== "boolean"
1567
- )
1568
- return invalid();
1569
- const factId = factIdValue;
1570
- factIds.add(factId);
1571
- if (
1572
- factRecord.component !== undefined &&
1573
- (typeof factRecord.component !== "string" ||
1574
- factRecord.component === "" ||
1575
- (hasTopologyComponents && !componentIds.has(factRecord.component)))
1576
- )
1577
- return invalid();
1578
- if (factRecord.dimension !== undefined && !isDimension(factRecord.dimension)) return invalid();
1579
- }
1580
- for (const fact of facts) {
1581
- if (!isPlainObject(fact)) return invalid();
1582
- const factRecord = fact as Record<string, unknown>;
1583
- if (
1584
- factRecord.superseded_by !== undefined &&
1585
- (factRecord.disputed !== true || !factIds.has(String(factRecord.superseded_by)))
1586
- )
1587
- return invalid();
1588
- }
1589
-
1590
- if (state.ontology_snapshots !== undefined) {
1591
- const ontologySnapshotsValue = state.ontology_snapshots;
1592
- if (!Array.isArray(ontologySnapshotsValue)) return invalid();
1593
- const ontologySnapshots = ontologySnapshotsValue as unknown[];
1594
- let priorRound = 0;
1595
- for (const snapshot of ontologySnapshots) {
1596
- if (!isPlainObject(snapshot)) return invalid();
1597
- const snapshotRecord = snapshot as Record<string, unknown>;
1598
- const snapshotRound = snapshotRecord.round;
1599
- const stableEntities = snapshotRecord.stable_entities;
1600
- const newEntities = snapshotRecord.new_entities;
1601
- const changedEntities = snapshotRecord.changed_entities;
1602
- const stabilityRatio = snapshotRecord.stability_ratio;
1603
- if (
1604
- typeof snapshotRound !== "number" ||
1605
- !Number.isSafeInteger(snapshotRound) ||
1606
- snapshotRound <= priorRound ||
1607
- !validDate(snapshotRecord.captured_at) ||
1608
- !Array.isArray(snapshotRecord.entities) ||
1609
- !["no_entities", "first_round", "compared"].includes(String(snapshotRecord.basis)) ||
1610
- typeof stableEntities !== "number" ||
1611
- !Number.isSafeInteger(stableEntities) ||
1612
- typeof newEntities !== "number" ||
1613
- !Number.isSafeInteger(newEntities) ||
1614
- typeof changedEntities !== "number" ||
1615
- !Number.isSafeInteger(changedEntities) ||
1616
- (stabilityRatio !== null && !validScore(stabilityRatio))
1617
- )
1618
- return invalid();
1619
- const entities = snapshotRecord.entities as unknown[];
1620
- if (
1621
- stableEntities < 0 ||
1622
- newEntities < 0 ||
1623
- changedEntities < 0 ||
1624
- stableEntities + newEntities + changedEntities !== entities.length ||
1625
- ((snapshotRecord.basis === "no_entities" || snapshotRecord.basis === "first_round") &&
1626
- stabilityRatio !== null) ||
1627
- (snapshotRecord.basis === "compared" && stabilityRatio === null)
1628
- )
1629
- return invalid();
1630
- priorRound = snapshotRound;
1631
- for (const entity of entities) {
1632
- if (!isPlainObject(entity)) return invalid();
1633
- const entityRecord = entity as Record<string, unknown>;
1634
- if (
1635
- typeof entityRecord.id !== "string" ||
1636
- typeof entityRecord.name !== "string" ||
1637
- entityRecord.name === "" ||
1638
- typeof entityRecord.type !== "string" ||
1639
- !Array.isArray(entityRecord.fields) ||
1640
- !entityRecord.fields.every((field: unknown) => typeof field === "string") ||
1641
- !Array.isArray(entityRecord.relationships) ||
1642
- !entityRecord.relationships.every((relationship: unknown) => typeof relationship === "string")
1643
- )
1644
- return invalid();
1645
- }
1646
- }
1647
- }
1648
- if (state.auto_answered_rounds !== undefined) {
1649
- const autoAnsweredRoundsValue = state.auto_answered_rounds;
1650
- if (!Array.isArray(autoAnsweredRoundsValue)) return invalid();
1651
- const autoAnsweredRounds = autoAnsweredRoundsValue as unknown[];
1652
- if (!autoAnsweredRounds.every((key: unknown) => typeof key === "string")) return invalid();
1653
- }
1654
- for (const key of ["current_ambiguity", "weighted_ambiguity", "effective_ambiguity", "floor"] as const) {
1655
- if (state[key] !== undefined && !validScore(state[key])) return invalid();
1656
- }
1657
-
1658
- for (const round of rounds) {
1659
- if (!isPlainObject(round)) return invalid();
1660
- const roundRecord = round as Record<string, unknown>;
1661
- const roundNumberValue = roundRecord.round;
1662
- const isRoundZeroIntentShell =
1663
- roundNumberValue === 0 && roundRecord.lifecycle === "answered" && roundRecord.scores === undefined;
1664
- if (
1665
- typeof roundRecord.round_key !== "string" ||
1666
- roundRecord.round_key === "" ||
1667
- typeof roundNumberValue !== "number" ||
1668
- !Number.isSafeInteger(roundNumberValue) ||
1669
- roundNumberValue < 0 ||
1670
- (roundNumberValue === 0 && !isRoundZeroIntentShell) ||
1671
- typeof roundRecord.question_id !== "string" ||
1672
- roundRecord.question_id === "" ||
1673
- typeof roundRecord.question_text !== "string" ||
1674
- typeof roundRecord.question_hash !== "string" ||
1675
- roundRecord.question_hash === "" ||
1676
- typeof roundRecord.answer_hash !== "string" ||
1677
- roundRecord.answer_hash === "" ||
1678
- !validDate(roundRecord.answered_at) ||
1679
- !["answered", "pending_scoring", "scored"].includes(String(roundRecord.lifecycle))
1680
- )
1681
- return invalid();
1682
- if (
1683
- roundRecord.component !== undefined &&
1684
- (typeof roundRecord.component !== "string" ||
1685
- roundRecord.component === "" ||
1686
- (hasTopologyComponents &&
1687
- !componentIds.has(roundRecord.component) &&
1688
- !(isRoundZeroIntentShell && roundRecord.component === "review-topology")))
1689
- )
1690
- return invalid();
1691
- if (
1692
- roundRecord.dimension !== undefined &&
1693
- !isDimension(roundRecord.dimension) &&
1694
- !(isRoundZeroIntentShell && roundRecord.dimension === "topology")
1695
- )
1696
- return invalid();
1697
- if (roundRecord.lifecycle !== "scored") continue;
1698
- const scoresValue = roundRecord.scores;
1699
- const triggersValue = roundRecord.triggers;
1700
- if (
1701
- !validDate(roundRecord.scored_at) ||
1702
- !isPlainObject(scoresValue) ||
1703
- !validScore(roundRecord.ambiguity) ||
1704
- !validScore(roundRecord.reported_ambiguity) ||
1705
- !validScore(roundRecord.ambiguity_floor) ||
1706
- typeof roundRecord.round_result_digest !== "string" ||
1707
- !/^[0-9a-f]{64}$/.test(roundRecord.round_result_digest) ||
1708
- !Array.isArray(triggersValue)
1709
- )
1710
- return invalid();
1711
- const scores = scoresValue as Record<string, unknown>;
1712
- if (!dimensions.every(dimension => validScore(scores[dimension]))) return invalid();
1713
- const triggers = triggersValue as unknown[];
1714
- for (const trigger of triggers) {
1715
- if (!isPlainObject(trigger)) return invalid();
1716
- const triggerRecord = trigger as Record<string, unknown>;
1717
- if (
1718
- !["A", "B", "C", "D"].includes(String(triggerRecord.kind)) ||
1719
- !["active", "disputed", "unresolved"].includes(String(triggerRecord.status)) ||
1720
- typeof triggerRecord.name !== "string" ||
1721
- typeof triggerRecord.component !== "string" ||
1722
- triggerRecord.component === "" ||
1723
- (hasTopologyComponents && !componentIds.has(triggerRecord.component)) ||
1724
- !isDimension(triggerRecord.dimension) ||
1725
- (triggerRecord.contradictedFactId !== undefined && !factIds.has(String(triggerRecord.contradictedFactId)))
1726
- )
1727
- return invalid();
1728
- if (
1729
- (triggerRecord.status === "disputed" || triggerRecord.status === "unresolved") &&
1730
- typeof triggerRecord.rationale !== "string"
1731
- )
1732
- return invalid();
1733
- }
1734
- void scores;
1735
- }
1736
- }