supipowers 1.5.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/README.md +14 -8
  2. package/bin/install.mjs +20 -5
  3. package/bin/install.ts +95 -0
  4. package/package.json +8 -4
  5. package/skills/context-mode/SKILL.md +17 -10
  6. package/skills/harness/SKILL.md +94 -0
  7. package/skills/ui-design/SKILL.md +63 -0
  8. package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
  9. package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
  10. package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
  11. package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
  12. package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
  13. package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
  14. package/skills/ultraplan-discover/SKILL.md +96 -0
  15. package/skills/ultraplan-intake/SKILL.md +89 -0
  16. package/skills/ultraplan-research/SKILL.md +129 -0
  17. package/skills/ultraplan-review/SKILL.md +86 -0
  18. package/skills/ultraplan-review-scope/SKILL.md +111 -0
  19. package/skills/ultraplan-review-structure/SKILL.md +120 -0
  20. package/skills/ultraplan-review-tdd/SKILL.md +142 -0
  21. package/skills/ultraplan-scout/SKILL.md +110 -0
  22. package/skills/ultraplan-synthesize/SKILL.md +124 -0
  23. package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
  24. package/src/ai/schema-text.ts +129 -0
  25. package/src/ai/structured-output.ts +274 -0
  26. package/src/ai/template.ts +27 -0
  27. package/src/bootstrap.ts +63 -28
  28. package/src/commands/agents.ts +149 -45
  29. package/src/commands/ai-review.ts +251 -30
  30. package/src/commands/clear.ts +434 -0
  31. package/src/commands/commit.ts +1 -0
  32. package/src/commands/config.ts +242 -44
  33. package/src/commands/context.ts +55 -28
  34. package/src/commands/doctor.ts +234 -6
  35. package/src/commands/fix-pr.ts +306 -131
  36. package/src/commands/generate.ts +111 -21
  37. package/src/commands/memory.ts +192 -0
  38. package/src/commands/model-picker.ts +28 -21
  39. package/src/commands/model.ts +19 -9
  40. package/src/commands/optimize-context.ts +408 -29
  41. package/src/commands/plan.ts +2 -0
  42. package/src/commands/qa.ts +312 -137
  43. package/src/commands/release.ts +259 -76
  44. package/src/commands/review.ts +293 -59
  45. package/src/commands/status.ts +200 -13
  46. package/src/commands/supi.ts +3 -35
  47. package/src/commands/ui-design.ts +394 -0
  48. package/src/commands/ultraplan.ts +1518 -0
  49. package/src/commands/update.ts +86 -0
  50. package/src/config/defaults.ts +62 -0
  51. package/src/config/loader.ts +448 -60
  52. package/src/config/schema.ts +108 -2
  53. package/src/context/optimizer.ts +25 -33
  54. package/src/context/rule-renderer.ts +223 -0
  55. package/src/context/savings.ts +258 -0
  56. package/src/context/startup-check.ts +380 -0
  57. package/src/context/startup-optimizer.ts +355 -0
  58. package/src/context/tokenignore.ts +146 -0
  59. package/src/context-mode/cache-handle.ts +49 -0
  60. package/src/context-mode/cache-preview.ts +71 -0
  61. package/src/context-mode/cache-store.ts +738 -0
  62. package/src/context-mode/compressor.ts +131 -26
  63. package/src/context-mode/dedup.ts +108 -0
  64. package/src/context-mode/detector.ts +35 -4
  65. package/src/context-mode/event-extractor.ts +14 -12
  66. package/src/context-mode/event-store.ts +91 -36
  67. package/src/context-mode/hooks.ts +798 -56
  68. package/src/context-mode/knowledge/store.ts +255 -11
  69. package/src/context-mode/memory-store.ts +325 -0
  70. package/src/context-mode/metrics-recorder.ts +158 -0
  71. package/src/context-mode/metrics-store.ts +765 -0
  72. package/src/context-mode/model.ts +24 -0
  73. package/src/context-mode/processor-keys.ts +29 -0
  74. package/src/context-mode/processors/build.ts +66 -0
  75. package/src/context-mode/processors/docker.ts +57 -0
  76. package/src/context-mode/processors/git.ts +111 -0
  77. package/src/context-mode/processors/json.ts +112 -0
  78. package/src/context-mode/processors/k8s.ts +67 -0
  79. package/src/context-mode/processors/lint.ts +67 -0
  80. package/src/context-mode/processors/log.ts +86 -0
  81. package/src/context-mode/processors/registry.ts +116 -0
  82. package/src/context-mode/processors/test-runner.ts +102 -0
  83. package/src/context-mode/processors/types.ts +20 -0
  84. package/src/context-mode/repomap.ts +400 -0
  85. package/src/context-mode/routing.ts +97 -24
  86. package/src/context-mode/sandbox/runners.ts +5 -1
  87. package/src/context-mode/snapshot-builder.ts +106 -11
  88. package/src/context-mode/source-hash.ts +173 -0
  89. package/src/context-mode/tool-name.ts +11 -0
  90. package/src/context-mode/tools.ts +654 -22
  91. package/src/context-mode/web/fetcher.ts +31 -12
  92. package/src/debug/logger.ts +2 -1
  93. package/src/deps/registry.ts +1 -1
  94. package/src/discipline/failure-summarizer.ts +170 -0
  95. package/src/discipline/failure-taxonomy.ts +131 -0
  96. package/src/discipline/workflow-invariants.ts +125 -0
  97. package/src/discovery/index.ts +31 -0
  98. package/src/discovery/lsp.ts +87 -0
  99. package/src/discovery/rank.ts +144 -0
  100. package/src/discovery/sources.ts +89 -0
  101. package/src/discovery/workflow.ts +87 -0
  102. package/src/docs/contracts.ts +39 -0
  103. package/src/docs/drift.ts +117 -87
  104. package/src/fix-pr/assessment.ts +200 -0
  105. package/src/fix-pr/contracts.ts +47 -0
  106. package/src/fix-pr/fetch-comments.ts +80 -0
  107. package/src/fix-pr/prompt-builder.ts +58 -40
  108. package/src/fix-pr/scripts/exec.ts +34 -0
  109. package/src/fix-pr/scripts/trigger-review.ts +106 -0
  110. package/src/fix-pr/scripts/wait-and-check.ts +108 -0
  111. package/src/fix-pr/types.ts +4 -0
  112. package/src/git/branch-finish.ts +5 -0
  113. package/src/git/commit-contract.ts +83 -0
  114. package/src/git/commit.ts +121 -184
  115. package/src/git/status.ts +62 -8
  116. package/src/harness/anti_slop/architecture-parser.ts +210 -0
  117. package/src/harness/anti_slop/backend-factory.ts +30 -0
  118. package/src/harness/anti_slop/backend.ts +140 -0
  119. package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
  120. package/src/harness/anti_slop/fallow-adapter.ts +305 -0
  121. package/src/harness/anti_slop/installer.ts +227 -0
  122. package/src/harness/anti_slop/queue.ts +216 -0
  123. package/src/harness/anti_slop/recommend.ts +84 -0
  124. package/src/harness/anti_slop/score.ts +180 -0
  125. package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
  126. package/src/harness/artifacts/agents-md.ts +88 -0
  127. package/src/harness/artifacts/checks-wiring.ts +57 -0
  128. package/src/harness/artifacts/docs-tree.ts +79 -0
  129. package/src/harness/artifacts/lint-configs.ts +136 -0
  130. package/src/harness/artifacts/review-agents.ts +67 -0
  131. package/src/harness/bare-entry.ts +108 -0
  132. package/src/harness/command.ts +1010 -0
  133. package/src/harness/default-agents/design.md +23 -0
  134. package/src/harness/default-agents/discover.md +18 -0
  135. package/src/harness/default-agents/implement.md +24 -0
  136. package/src/harness/default-agents/plan.md +19 -0
  137. package/src/harness/default-agents/research.md +21 -0
  138. package/src/harness/default-agents/validate.md +22 -0
  139. package/src/harness/gc/reporter.ts +28 -0
  140. package/src/harness/gc/runner.ts +136 -0
  141. package/src/harness/hooks/layer-context-inject.ts +155 -0
  142. package/src/harness/hooks/post-session-sweep.ts +130 -0
  143. package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
  144. package/src/harness/hooks/register.ts +118 -0
  145. package/src/harness/model.ts +117 -0
  146. package/src/harness/pipeline.ts +348 -0
  147. package/src/harness/project-paths.ts +235 -0
  148. package/src/harness/stage-runner.ts +107 -0
  149. package/src/harness/stages/design.ts +386 -0
  150. package/src/harness/stages/discover.ts +454 -0
  151. package/src/harness/stages/implement.ts +162 -0
  152. package/src/harness/stages/plan.ts +335 -0
  153. package/src/harness/stages/research.ts +263 -0
  154. package/src/harness/stages/validate.ts +684 -0
  155. package/src/harness/storage.ts +467 -0
  156. package/src/harness/tools.ts +426 -0
  157. package/src/lsp/bridge.ts +56 -95
  158. package/src/lsp/capabilities.ts +108 -0
  159. package/src/lsp/contracts.ts +35 -0
  160. package/src/lsp/detector.ts +8 -12
  161. package/src/markdown-frontmatter.ts +68 -0
  162. package/src/mempalace/bridge.ts +129 -0
  163. package/src/mempalace/config.ts +75 -0
  164. package/src/mempalace/format.ts +163 -0
  165. package/src/mempalace/hooks.ts +370 -0
  166. package/src/mempalace/installer-helper.ts +194 -0
  167. package/src/mempalace/python/mempalace_bridge.py +440 -0
  168. package/src/mempalace/runtime.ts +565 -0
  169. package/src/mempalace/schema.ts +264 -0
  170. package/src/mempalace/session-summary.ts +198 -0
  171. package/src/mempalace/tool.ts +186 -0
  172. package/src/mempalace/uv.ts +256 -0
  173. package/src/migrate/runner.ts +354 -0
  174. package/src/planning/approval-flow.ts +206 -9
  175. package/src/planning/plan-writer-prompt.ts +4 -3
  176. package/src/planning/planning-ask-tool.ts +39 -0
  177. package/src/planning/render-markdown.ts +74 -0
  178. package/src/planning/spec.ts +42 -0
  179. package/src/planning/system-prompt.ts +11 -8
  180. package/src/planning/validate.ts +84 -0
  181. package/src/platform/omp.ts +15 -2
  182. package/src/platform/system-prompt.ts +37 -0
  183. package/src/platform/test-utils.ts +3 -0
  184. package/src/platform/types.ts +6 -1
  185. package/src/qa/config.ts +12 -6
  186. package/src/qa/detect-app-type.ts +13 -6
  187. package/src/qa/matrix.ts +12 -6
  188. package/src/qa/prompt-builder.ts +28 -30
  189. package/src/qa/scripts/dev-server-utils.ts +72 -0
  190. package/src/qa/scripts/run-e2e-tests.ts +226 -0
  191. package/src/qa/scripts/start-dev-server.ts +138 -0
  192. package/src/qa/scripts/stop-dev-server.ts +77 -0
  193. package/src/qa/session.ts +13 -7
  194. package/src/quality/ai-setup.ts +27 -25
  195. package/src/quality/contracts.ts +34 -0
  196. package/src/quality/gates/ai-review.ts +20 -58
  197. package/src/quality/gates/command.ts +249 -46
  198. package/src/quality/review-gates.ts +18 -2
  199. package/src/quality/runner.ts +63 -22
  200. package/src/quality/schemas.ts +37 -2
  201. package/src/quality/setup.ts +96 -16
  202. package/src/release/changelog.ts +1 -1
  203. package/src/release/channels/custom.ts +13 -3
  204. package/src/release/channels/types.ts +5 -0
  205. package/src/release/contracts.ts +90 -0
  206. package/src/release/executor.ts +122 -45
  207. package/src/release/prompt.ts +18 -2
  208. package/src/release/targets.ts +86 -0
  209. package/src/release/version.ts +96 -71
  210. package/src/review/agent-loader.ts +298 -127
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +115 -14
  213. package/src/review/output.ts +12 -139
  214. package/src/review/runner.ts +12 -6
  215. package/src/review/scope.ts +144 -24
  216. package/src/review/types.ts +11 -20
  217. package/src/review/validator.ts +12 -6
  218. package/src/storage/fix-pr-sessions.ts +21 -14
  219. package/src/storage/plans.ts +14 -5
  220. package/src/storage/qa-sessions.ts +25 -19
  221. package/src/storage/reliability-metrics.ts +180 -0
  222. package/src/storage/reports.ts +8 -7
  223. package/src/storage/review-sessions.ts +55 -20
  224. package/src/tool-catalog/active-tool-controller.ts +164 -0
  225. package/src/tool-catalog/active-tool-planner.ts +212 -0
  226. package/src/tool-catalog/tool-groups.ts +102 -0
  227. package/src/types.ts +1401 -5
  228. package/src/ui-design/backend-adapter.ts +78 -0
  229. package/src/ui-design/backends/local-html.ts +82 -0
  230. package/src/ui-design/backends/pencil-mcp.ts +111 -0
  231. package/src/ui-design/components-scanner.ts +124 -0
  232. package/src/ui-design/config.ts +55 -0
  233. package/src/ui-design/pen-scanner.ts +95 -0
  234. package/src/ui-design/pen-selector.ts +72 -0
  235. package/src/ui-design/prompt-builder.ts +73 -0
  236. package/src/ui-design/scanner.ts +136 -0
  237. package/src/ui-design/session.ts +974 -0
  238. package/src/ui-design/system-prompt.ts +312 -0
  239. package/src/ui-design/tokens-scanner.ts +181 -0
  240. package/src/ui-design/types.ts +96 -0
  241. package/src/ultraplan/agent-catalog.ts +522 -0
  242. package/src/ultraplan/authoring/agent-catalog.ts +310 -0
  243. package/src/ultraplan/authoring/authoring-tools.ts +552 -0
  244. package/src/ultraplan/authoring/command-handlers.ts +339 -0
  245. package/src/ultraplan/authoring/markdown.ts +510 -0
  246. package/src/ultraplan/authoring/model.ts +162 -0
  247. package/src/ultraplan/authoring/pipeline.ts +319 -0
  248. package/src/ultraplan/authoring/stage-runner.ts +141 -0
  249. package/src/ultraplan/authoring/stages/approve.ts +249 -0
  250. package/src/ultraplan/authoring/stages/discover.ts +289 -0
  251. package/src/ultraplan/authoring/stages/intake.ts +203 -0
  252. package/src/ultraplan/authoring/stages/research.ts +399 -0
  253. package/src/ultraplan/authoring/stages/review.ts +333 -0
  254. package/src/ultraplan/authoring/stages/scout.ts +188 -0
  255. package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
  256. package/src/ultraplan/authoring/storage.ts +594 -0
  257. package/src/ultraplan/authoring/synth-gate.ts +165 -0
  258. package/src/ultraplan/authoring-draft.ts +653 -0
  259. package/src/ultraplan/authoring-persist.ts +180 -0
  260. package/src/ultraplan/authoring-tool.ts +608 -0
  261. package/src/ultraplan/authoring-wizard.ts +587 -0
  262. package/src/ultraplan/batch/merge.ts +98 -0
  263. package/src/ultraplan/batch/planner.ts +150 -0
  264. package/src/ultraplan/batch/presenter.ts +97 -0
  265. package/src/ultraplan/batch/storage.ts +420 -0
  266. package/src/ultraplan/batch/supervisor.ts +317 -0
  267. package/src/ultraplan/batch/worker.ts +26 -0
  268. package/src/ultraplan/batch/worktree.ts +110 -0
  269. package/src/ultraplan/contracts.ts +1593 -0
  270. package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
  271. package/src/ultraplan/default-agents/authoring/intake.md +12 -0
  272. package/src/ultraplan/default-agents/authoring/planner.md +12 -0
  273. package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
  274. package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
  275. package/src/ultraplan/default-agents/authoring/scout.md +12 -0
  276. package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
  277. package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
  278. package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
  279. package/src/ultraplan/default-agents/backend-executor.md +10 -0
  280. package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
  281. package/src/ultraplan/default-agents/backend-tester.md +10 -0
  282. package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
  283. package/src/ultraplan/default-agents/frontend-executor.md +10 -0
  284. package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
  285. package/src/ultraplan/default-agents/frontend-tester.md +10 -0
  286. package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
  287. package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
  288. package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
  289. package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
  290. package/src/ultraplan/execution/contract.ts +71 -0
  291. package/src/ultraplan/execution/policy.ts +217 -0
  292. package/src/ultraplan/execution/runtime-tools.ts +107 -0
  293. package/src/ultraplan/execution/session-runner.ts +281 -0
  294. package/src/ultraplan/next-router.ts +85 -0
  295. package/src/ultraplan/presenter.ts +359 -0
  296. package/src/ultraplan/project-paths.ts +342 -0
  297. package/src/ultraplan/runtime/active-execution.ts +72 -0
  298. package/src/ultraplan/runtime/apply-mutation.ts +416 -0
  299. package/src/ultraplan/runtime/blockers.ts +243 -0
  300. package/src/ultraplan/runtime/hook-bridge.ts +486 -0
  301. package/src/ultraplan/runtime/launch-context.ts +207 -0
  302. package/src/ultraplan/runtime/migration.ts +524 -0
  303. package/src/ultraplan/runtime/normalize.ts +281 -0
  304. package/src/ultraplan/runtime/proof.ts +260 -0
  305. package/src/ultraplan/runtime/reducer.ts +416 -0
  306. package/src/ultraplan/runtime/repair.ts +251 -0
  307. package/src/ultraplan/runtime/tracker-storage.ts +368 -0
  308. package/src/ultraplan/session-selection.ts +291 -0
  309. package/src/ultraplan/storage.ts +374 -0
  310. package/src/utils/editor.ts +38 -0
  311. package/src/utils/executable.ts +80 -0
  312. package/src/utils/paths.ts +1 -20
  313. package/src/utils/shell.ts +31 -0
  314. package/src/visual/companion.ts +2 -1
  315. package/src/visual/scripts/frame-template.html +60 -0
  316. package/src/visual/scripts/index.js +59 -13
  317. package/src/visual/scripts/package.json +3 -0
  318. package/src/visual/start-server.ts +2 -1
  319. package/src/workspace/git-scope.ts +64 -0
  320. package/src/workspace/locks.ts +23 -0
  321. package/src/workspace/package-manager.ts +117 -0
  322. package/src/workspace/path-mapping.ts +75 -0
  323. package/src/workspace/project-slug.ts +92 -0
  324. package/src/workspace/repo-root.ts +137 -0
  325. package/src/workspace/selector.ts +115 -0
  326. package/src/workspace/state-paths.ts +118 -0
  327. package/src/workspace/targets.ts +313 -0
  328. package/src/fix-pr/scripts/diff-comments.sh +0 -33
  329. package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
  330. package/src/fix-pr/scripts/trigger-review.sh +0 -36
  331. package/src/fix-pr/scripts/wait-and-check.sh +0 -37
  332. package/src/qa/scripts/detect-app-type.sh +0 -68
  333. package/src/qa/scripts/discover-routes.sh +0 -143
  334. package/src/qa/scripts/run-e2e-tests.sh +0 -131
  335. package/src/qa/scripts/start-dev-server.sh +0 -46
  336. package/src/qa/scripts/stop-dev-server.sh +0 -36
  337. package/src/review/prompts/fix-output-schema.md +0 -18
  338. package/src/review/prompts/review-output-schema.md +0 -38
  339. package/src/review/template.ts +0 -15
  340. /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
@@ -0,0 +1,281 @@
1
+ import { createHash } from "node:crypto";
2
+ import type {
3
+ UltraPlanActorKind,
4
+ UltraPlanAttemptRecord,
5
+ UltraPlanCursorTargetType,
6
+ UltraPlanExecutionPhase,
7
+ UltraPlanHookEventName,
8
+ UltraPlanHookObservation,
9
+ UltraPlanObservationTarget,
10
+ UltraPlanScenarioLevel,
11
+ UltraPlanSourceAgent,
12
+ UltraPlanStackId,
13
+ } from "../../types.js";
14
+ import { recoverLaunchContextFromEvent, recoverTargetHintFromPrompt } from "./launch-context.js";
15
+
16
+ /**
17
+ * Slice-2 normalization seam.
18
+ *
19
+ * Converts raw platform hook events into typed `UltraPlanHookObservation` values. This module is
20
+ * the gate between raw platform behavior and UltraPlan runtime truth — by the time an observation
21
+ * reaches the reducer, its attempt identity, target, replay fingerprint, and correlation status
22
+ * are all fully resolved. Pure.
23
+ */
24
+
25
+ export interface UltraPlanTargetHint {
26
+ targetType?: UltraPlanCursorTargetType;
27
+ stack?: UltraPlanStackId | null;
28
+ domainId?: string | null;
29
+ level?: UltraPlanScenarioLevel | null;
30
+ scenarioId?: string | null;
31
+ phase?: UltraPlanExecutionPhase;
32
+ resolvedSlot?: string | null;
33
+ actorKind?: UltraPlanActorKind;
34
+ sourceAgent?: UltraPlanSourceAgent;
35
+ }
36
+
37
+ export interface NormalizeHookEventInput {
38
+ hookEvent: UltraPlanHookEventName;
39
+ sessionId: string;
40
+ nowIso: string;
41
+
42
+ /** Structured platform metadata (may carry `ultraplanLaunchContext`). */
43
+ metadata?: Record<string, unknown> | null;
44
+ /** Prompt/assignment/system-prompt text (may carry `ULTRAPLAN_LAUNCH_CONTEXT=<json>`). */
45
+ prompt?: string | null;
46
+ /** The currently-persisted active attempt from the tracker (last-resort carrier). */
47
+ persistedActiveAttempt?: UltraPlanAttemptRecord | null;
48
+
49
+ /** Raw platform payload (tool args, results, exit reasons, etc.) — used for audit + fingerprint. */
50
+ payload?: Record<string, unknown>;
51
+
52
+ /**
53
+ * Hints about the slot-backed target derived from the platform event (e.g. before_agent_start
54
+ * args). When absent or the hook is a pure session event, the observation is classified as
55
+ * session-scope.
56
+ */
57
+ targetHint?: UltraPlanTargetHint;
58
+ /** Fallback hint from active-execution state when prompt text did not carry one. */
59
+ fallbackTargetHint?: UltraPlanTargetHint;
60
+
61
+ /** Optional platform-native event id (tool call id, turn id, agent run id). */
62
+ nativeEventId?: string | null;
63
+ /** Optional causation id grouping related hook activity. */
64
+ causationId?: string | null;
65
+ /** Optional override for when the event actually occurred; defaults to `nowIso`. */
66
+ occurredAt?: string;
67
+ }
68
+
69
+ /**
70
+ * Hook events that are session-scope regardless of target hint. Main-orchestrator-only
71
+ * before_agent_start / tool_call / tool_result / agent_end events that arrive with no slot-backed
72
+ * target hint are also treated as session-scope via the classification logic below.
73
+ */
74
+ const SESSION_SCOPE_HOOKS: readonly UltraPlanHookEventName[] = [
75
+ "session_start",
76
+ "session_shutdown",
77
+ ];
78
+
79
+ export function normalizeHookEvent(input: NormalizeHookEventInput): UltraPlanHookObservation {
80
+ const occurredAt = input.occurredAt ?? input.nowIso;
81
+ const targetHint = resolveTargetHint(input);
82
+ const launchContext = SESSION_SCOPE_HOOKS.includes(input.hookEvent)
83
+ ? null
84
+ : recoverLaunchContextFromEvent({
85
+ metadata: input.metadata ?? null,
86
+ prompt: input.prompt ?? null,
87
+ persistedActiveAttempt: input.persistedActiveAttempt ?? null,
88
+ });
89
+ const { actorKind, isSessionScope } = classifyActor(input, targetHint, launchContext);
90
+ const sourceAgent = targetHint?.sourceAgent
91
+ ?? launchContext?.sourceAgent
92
+ ?? (isSessionScope ? "main" : "sub-agent");
93
+
94
+ if (isSessionScope) {
95
+ return {
96
+ sessionId: input.sessionId,
97
+ hookEvent: input.hookEvent,
98
+ actorKind: "main-orchestrator",
99
+ attemptId: null,
100
+ attemptKey: null,
101
+ sourceAgent,
102
+ occurredAt,
103
+ causationId: input.causationId ?? null,
104
+ fingerprint: computeFingerprint({
105
+ attemptId: null,
106
+ hookEvent: input.hookEvent,
107
+ nativeEventId: input.nativeEventId ?? null,
108
+ payload: input.payload ?? {},
109
+ }),
110
+ target: null,
111
+ correlationFailure: null,
112
+ payloadSummary: summarizePayload(input.hookEvent, input.payload),
113
+ };
114
+ }
115
+
116
+ if (!launchContext) {
117
+ return {
118
+ sessionId: input.sessionId,
119
+ hookEvent: input.hookEvent,
120
+ actorKind,
121
+ attemptId: null,
122
+ attemptKey: null,
123
+ sourceAgent,
124
+ occurredAt,
125
+ causationId: input.causationId ?? null,
126
+ fingerprint: computeFingerprint({
127
+ attemptId: null,
128
+ hookEvent: input.hookEvent,
129
+ nativeEventId: input.nativeEventId ?? null,
130
+ payload: input.payload ?? {},
131
+ }),
132
+ target: buildTargetFromHint(targetHint),
133
+ correlationFailure: {
134
+ reason: "slot-backed hook event without a resolvable UltraPlan launch context",
135
+ },
136
+ payloadSummary: summarizePayload(input.hookEvent, input.payload),
137
+ };
138
+ }
139
+
140
+ if (!targetHint) {
141
+ return {
142
+ sessionId: input.sessionId,
143
+ hookEvent: input.hookEvent,
144
+ actorKind,
145
+ attemptId: launchContext.attemptId,
146
+ attemptKey: launchContext.attemptKey,
147
+ sourceAgent,
148
+ occurredAt,
149
+ causationId: input.causationId ?? null,
150
+ fingerprint: computeFingerprint({
151
+ attemptId: launchContext.attemptId,
152
+ hookEvent: input.hookEvent,
153
+ nativeEventId: input.nativeEventId ?? null,
154
+ payload: input.payload ?? {},
155
+ }),
156
+ target: null,
157
+ correlationFailure: {
158
+ reason: "slot-backed hook event without a resolvable UltraPlan target hint",
159
+ },
160
+ payloadSummary: summarizePayload(input.hookEvent, input.payload),
161
+ };
162
+ }
163
+
164
+ return {
165
+ sessionId: input.sessionId,
166
+ hookEvent: input.hookEvent,
167
+ actorKind,
168
+ attemptId: launchContext.attemptId,
169
+ attemptKey: launchContext.attemptKey,
170
+ sourceAgent,
171
+ occurredAt,
172
+ causationId: input.causationId ?? null,
173
+ fingerprint: computeFingerprint({
174
+ attemptId: launchContext.attemptId,
175
+ hookEvent: input.hookEvent,
176
+ nativeEventId: input.nativeEventId ?? null,
177
+ payload: input.payload ?? {},
178
+ }),
179
+ target: buildTargetFromHint(targetHint),
180
+ correlationFailure: null,
181
+ payloadSummary: summarizePayload(input.hookEvent, input.payload),
182
+ };
183
+ }
184
+
185
+ // --- helpers ---------------------------------------------------------------
186
+
187
+ function classifyActor(
188
+ input: NormalizeHookEventInput,
189
+ targetHint: UltraPlanTargetHint | undefined,
190
+ launchContext: UltraPlanAttemptRecord["launchContext"] | null,
191
+ ): { actorKind: UltraPlanActorKind; isSessionScope: boolean } {
192
+ if (SESSION_SCOPE_HOOKS.includes(input.hookEvent)) {
193
+ return { actorKind: "main-orchestrator", isSessionScope: true };
194
+ }
195
+ if (targetHint?.actorKind === "main-orchestrator") {
196
+ return { actorKind: "main-orchestrator", isSessionScope: true };
197
+ }
198
+ if (targetHint || launchContext) {
199
+ return { actorKind: targetHint?.actorKind ?? "slot", isSessionScope: false };
200
+ }
201
+ return { actorKind: "main-orchestrator", isSessionScope: true };
202
+ }
203
+
204
+ function buildTargetFromHint(hint: UltraPlanTargetHint | undefined): UltraPlanObservationTarget | null {
205
+ if (!hint) return null;
206
+ const hasAnyField = hint.targetType !== undefined
207
+ || hint.stack !== undefined
208
+ || hint.domainId !== undefined
209
+ || hint.level !== undefined
210
+ || hint.scenarioId !== undefined
211
+ || hint.phase !== undefined
212
+ || hint.resolvedSlot !== undefined;
213
+ if (!hasAnyField) return null;
214
+ return {
215
+ targetType: hint.targetType ?? "scenario",
216
+ stack: hint.stack ?? null,
217
+ domainId: hint.domainId ?? null,
218
+ level: hint.level ?? null,
219
+ scenarioId: hint.scenarioId ?? null,
220
+ phase: hint.phase ?? "red",
221
+ resolvedSlot: hint.resolvedSlot ?? null,
222
+ };
223
+ }
224
+
225
+ function resolveTargetHint(input: NormalizeHookEventInput): UltraPlanTargetHint | undefined {
226
+ return input.targetHint
227
+ ?? recoverTargetHintFromPrompt(input.prompt ?? null)
228
+ ?? input.fallbackTargetHint;
229
+ }
230
+
231
+ interface FingerprintComponents {
232
+ attemptId: string | null;
233
+ hookEvent: UltraPlanHookEventName;
234
+ nativeEventId: string | null;
235
+ payload: unknown;
236
+ }
237
+
238
+ /**
239
+ * Replay fingerprint per spec §cross-hook carrier line 459:
240
+ * `attemptId + hook name + native event id + normalized payload`
241
+ *
242
+ * The payload is canonicalized (sorted keys) so equivalent payloads with different key ordering
243
+ * produce the same fingerprint. This is what makes replay dedupe durable across reloads.
244
+ */
245
+ function computeFingerprint(parts: FingerprintComponents): string {
246
+ const canonical = JSON.stringify({
247
+ attemptId: parts.attemptId,
248
+ hookEvent: parts.hookEvent,
249
+ nativeEventId: parts.nativeEventId,
250
+ payload: canonicalize(parts.payload),
251
+ });
252
+ return createHash("sha256").update(canonical).digest("hex");
253
+ }
254
+
255
+ function canonicalize(value: unknown): unknown {
256
+ if (Array.isArray(value)) {
257
+ return value.map((item) => canonicalize(item));
258
+ }
259
+ if (value && typeof value === "object") {
260
+ const entries = Object.entries(value as Record<string, unknown>)
261
+ .filter(([, v]) => v !== undefined)
262
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
263
+ .map(([k, v]) => [k, canonicalize(v)] as const);
264
+ return Object.fromEntries(entries);
265
+ }
266
+ return value;
267
+ }
268
+
269
+ function summarizePayload(hookEvent: UltraPlanHookEventName, payload: Record<string, unknown> | undefined): string {
270
+ if (!payload) return hookEvent;
271
+ const tool = typeof payload.toolName === "string" ? payload.toolName : undefined;
272
+ const exit = typeof payload.exitCode === "number" ? payload.exitCode : undefined;
273
+ const reason = typeof payload.exitReason === "string" ? payload.exitReason : undefined;
274
+ const summary = typeof payload.summary === "string" ? payload.summary : undefined;
275
+ const bits: string[] = [hookEvent];
276
+ if (tool) bits.push(`tool=${tool}`);
277
+ if (exit !== undefined) bits.push(`exit=${exit}`);
278
+ if (reason) bits.push(`reason=${reason}`);
279
+ if (summary) bits.push(summary);
280
+ return bits.join(" ");
281
+ }
@@ -0,0 +1,260 @@
1
+ import { createHash } from "node:crypto";
2
+ import type {
3
+ UltraPlanBlockerCandidate,
4
+ UltraPlanExecutionPhase,
5
+ UltraPlanHookObservation,
6
+ UltraPlanProofCandidate,
7
+ UltraPlanProofCandidateTarget,
8
+ UltraPlanStackId,
9
+ } from "../../types.js";
10
+ import {
11
+ isUltraPlanDomainReview,
12
+ isUltraPlanStackReview,
13
+ } from "../contracts.js";
14
+ import {
15
+ buildProofInvalidBlocker,
16
+ } from "./blockers.js";
17
+
18
+ /**
19
+ * Slice-2 proof extraction.
20
+ *
21
+ * Converts `tool_result` / `agent_end` observations into typed proof candidates. Fail-closed:
22
+ * phase or target mismatches become `proof-invalid` blocker candidates, never silent advancement.
23
+ * Pure — no I/O.
24
+ */
25
+
26
+ export interface ExtractProofInput {
27
+ observation: UltraPlanHookObservation;
28
+ /**
29
+ * Raw platform payload. When the payload has a structured `proof` object of shape
30
+ * `{ type, phase, evidence, artifactRef? }`, that value is treated as a proof candidate.
31
+ */
32
+ payload: Record<string, unknown>;
33
+ expectedTarget: UltraPlanProofCandidateTarget;
34
+ expectedPhase: UltraPlanExecutionPhase;
35
+ }
36
+
37
+ export type ExtractProofResult =
38
+ | { kind: "proof"; proof: UltraPlanProofCandidate }
39
+ | { kind: "blocker-candidate"; blocker: UltraPlanBlockerCandidate }
40
+ | { kind: "none" };
41
+
42
+ export function extractProofCandidate(input: ExtractProofInput): ExtractProofResult {
43
+ const raw = input.payload?.proof;
44
+ if (raw === undefined || raw === null) {
45
+ return { kind: "none" };
46
+ }
47
+
48
+ const parsed = parseProofShape(raw);
49
+ if (!parsed) {
50
+ return {
51
+ kind: "blocker-candidate",
52
+ blocker: {
53
+ blocker: buildProofInvalidBlocker({
54
+ detectedAt: input.observation.occurredAt,
55
+ scope: "scenario",
56
+ affected: toAffected(input.expectedTarget),
57
+ reason: "payload.proof is not a well-formed proof object",
58
+ }),
59
+ observationFingerprint: input.observation.fingerprint,
60
+ },
61
+ };
62
+ }
63
+
64
+ // Phase mismatch: reducer spec §proof obligations requires exact phase alignment.
65
+ if (parsed.phase !== input.expectedPhase) {
66
+ return {
67
+ kind: "blocker-candidate",
68
+ blocker: {
69
+ blocker: buildProofInvalidBlocker({
70
+ detectedAt: input.observation.occurredAt,
71
+ scope: "scenario",
72
+ affected: toAffected(input.expectedTarget),
73
+ reason: `expected ${input.expectedPhase}-phase proof, received ${parsed.phase}-phase proof`,
74
+ }),
75
+ observationFingerprint: input.observation.fingerprint,
76
+ },
77
+ };
78
+ }
79
+
80
+ // Target mismatch: observation.target must align with the expected target.
81
+ if (!observationTargetMatchesExpected(input.observation, input.expectedTarget)) {
82
+ return {
83
+ kind: "blocker-candidate",
84
+ blocker: {
85
+ blocker: buildProofInvalidBlocker({
86
+ detectedAt: input.observation.occurredAt,
87
+ scope: "scenario",
88
+ affected: toAffected(input.expectedTarget),
89
+ reason: "proof target does not match the expected attempt target",
90
+ }),
91
+ observationFingerprint: input.observation.fingerprint,
92
+ },
93
+ };
94
+ }
95
+
96
+ const candidate: UltraPlanProofCandidate = {
97
+ phase: parsed.phase,
98
+ type: parsed.type,
99
+ target: input.expectedTarget,
100
+ evidence: parsed.evidence,
101
+ artifactRef: parsed.artifactRef ?? null,
102
+ observationFingerprint: input.observation.fingerprint,
103
+ fingerprint: computeProofFingerprint({
104
+ observationFingerprint: input.observation.fingerprint,
105
+ phase: parsed.phase,
106
+ type: parsed.type,
107
+ target: input.expectedTarget,
108
+ evidence: parsed.evidence,
109
+ artifactRef: parsed.artifactRef ?? null,
110
+ }),
111
+ };
112
+ return { kind: "proof", proof: candidate };
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Review artifact validation (spec §proof obligations line 602)
117
+ // ---------------------------------------------------------------------------
118
+
119
+ export interface ValidateReviewArtifactInput {
120
+ reviewType: "domain" | "stack";
121
+ stack: UltraPlanStackId;
122
+ domainId: string | null;
123
+ expectedCanonicalPath: string;
124
+ observedArtifactRef: string;
125
+ artifact: unknown;
126
+ }
127
+
128
+ export type ValidateReviewArtifactResult =
129
+ | { ok: true }
130
+ | { ok: false; reason: string };
131
+
132
+ export function validateReviewArtifactProof(input: ValidateReviewArtifactInput): ValidateReviewArtifactResult {
133
+ if (input.observedArtifactRef !== input.expectedCanonicalPath) {
134
+ return {
135
+ ok: false,
136
+ reason: `review artifact must live at canonical path ${input.expectedCanonicalPath}, observed ${input.observedArtifactRef}`,
137
+ };
138
+ }
139
+
140
+ if (input.reviewType === "domain") {
141
+ if (!isUltraPlanDomainReview(input.artifact)) {
142
+ return { ok: false, reason: "domain review artifact failed schema validation" };
143
+ }
144
+ const artifact = input.artifact;
145
+ if (artifact.status !== "passed") {
146
+ return { ok: false, reason: `domain review artifact status is ${artifact.status}, expected passed` };
147
+ }
148
+ if (artifact.stack !== input.stack || artifact.domainId !== input.domainId) {
149
+ return { ok: false, reason: "domain review artifact stack/domainId does not match review target" };
150
+ }
151
+ return { ok: true };
152
+ }
153
+
154
+ // stack review
155
+ if (!isUltraPlanStackReview(input.artifact)) {
156
+ return { ok: false, reason: "stack review artifact failed schema validation" };
157
+ }
158
+ const artifact = input.artifact;
159
+ if (artifact.status !== "passed") {
160
+ return { ok: false, reason: `stack review artifact status is ${artifact.status}, expected passed` };
161
+ }
162
+ if (artifact.stack !== input.stack) {
163
+ return { ok: false, reason: "stack review artifact stack does not match review target" };
164
+ }
165
+ return { ok: true };
166
+ }
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // helpers
170
+ // ---------------------------------------------------------------------------
171
+
172
+ interface ParsedProofShape {
173
+ type: UltraPlanProofCandidate["type"];
174
+ phase: UltraPlanExecutionPhase;
175
+ evidence: { summary: string; command?: string; outputRef?: string; metadata?: Record<string, unknown> };
176
+ artifactRef?: string;
177
+ }
178
+
179
+ const VALID_PROOF_TYPES: readonly string[] = ["test", "command", "review", "artifact"];
180
+ const VALID_PHASES: readonly string[] = ["red", "green", "review", "waiting", "complete"];
181
+
182
+ function parseProofShape(raw: unknown): ParsedProofShape | null {
183
+ if (!raw || typeof raw !== "object") return null;
184
+ const obj = raw as Record<string, unknown>;
185
+ if (typeof obj.type !== "string" || !VALID_PROOF_TYPES.includes(obj.type)) return null;
186
+ if (typeof obj.phase !== "string" || !VALID_PHASES.includes(obj.phase)) return null;
187
+ if (!obj.evidence || typeof obj.evidence !== "object") return null;
188
+ const ev = obj.evidence as Record<string, unknown>;
189
+ if (typeof ev.summary !== "string" || ev.summary.length === 0) return null;
190
+
191
+ const parsed: ParsedProofShape = {
192
+ type: obj.type as UltraPlanProofCandidate["type"],
193
+ phase: obj.phase as UltraPlanExecutionPhase,
194
+ evidence: {
195
+ summary: ev.summary,
196
+ ...(typeof ev.command === "string" ? { command: ev.command } : {}),
197
+ ...(typeof ev.outputRef === "string" ? { outputRef: ev.outputRef } : {}),
198
+ ...(ev.metadata && typeof ev.metadata === "object" && !Array.isArray(ev.metadata)
199
+ ? { metadata: ev.metadata as Record<string, unknown> }
200
+ : {}),
201
+ },
202
+ };
203
+ if (typeof obj.artifactRef === "string" && obj.artifactRef.length > 0) {
204
+ parsed.artifactRef = obj.artifactRef;
205
+ }
206
+ return parsed;
207
+ }
208
+
209
+ function toAffected(target: UltraPlanProofCandidateTarget) {
210
+ return {
211
+ stack: target.stack,
212
+ domainId: target.domainId,
213
+ level: target.level,
214
+ scenarioId: target.scenarioId,
215
+ };
216
+ }
217
+
218
+ function observationTargetMatchesExpected(
219
+ observation: UltraPlanHookObservation,
220
+ expected: UltraPlanProofCandidateTarget,
221
+ ): boolean {
222
+ const obsTarget = observation.target;
223
+ if (!obsTarget) return false;
224
+ return obsTarget.targetType === expected.targetType
225
+ && obsTarget.stack === expected.stack
226
+ && obsTarget.domainId === expected.domainId
227
+ && obsTarget.level === expected.level
228
+ && obsTarget.scenarioId === expected.scenarioId;
229
+ }
230
+
231
+ function computeProofFingerprint(parts: {
232
+ observationFingerprint: string;
233
+ phase: UltraPlanExecutionPhase;
234
+ type: UltraPlanProofCandidate["type"];
235
+ target: UltraPlanProofCandidateTarget;
236
+ evidence: ParsedProofShape["evidence"];
237
+ artifactRef: string | null;
238
+ }): string {
239
+ const canonical = JSON.stringify({
240
+ observationFingerprint: parts.observationFingerprint,
241
+ phase: parts.phase,
242
+ type: parts.type,
243
+ target: canonicalize(parts.target),
244
+ evidence: canonicalize(parts.evidence),
245
+ artifactRef: parts.artifactRef,
246
+ });
247
+ return createHash("sha256").update(canonical).digest("hex");
248
+ }
249
+
250
+ function canonicalize(value: unknown): unknown {
251
+ if (Array.isArray(value)) return value.map(canonicalize);
252
+ if (value && typeof value === "object") {
253
+ const entries = Object.entries(value as Record<string, unknown>)
254
+ .filter(([, v]) => v !== undefined)
255
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
256
+ .map(([k, v]) => [k, canonicalize(v)] as const);
257
+ return Object.fromEntries(entries);
258
+ }
259
+ return value;
260
+ }