@hassangameryt/freeflow 0.7.3 → 0.9.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 (426) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +3 -3
  5. package/.cursor-plugin/plugin.json +1 -1
  6. package/NOTICE +14 -0
  7. package/README.md +101 -159
  8. package/capabilities/cognitive-routing/SKILL.md +47 -210
  9. package/capabilities/cognitive-routing/references/coordinator.md +163 -0
  10. package/capabilities/cognitive-routing/references/worker.md +38 -0
  11. package/capabilities/compaction/SKILL.md +49 -0
  12. package/capabilities/compaction/references/summary-format.md +250 -0
  13. package/capabilities/messaging/SKILL.md +49 -0
  14. package/capabilities/task-memory/SKILL.md +87 -0
  15. package/command-surface.json +8 -52
  16. package/gemini-extension.json +1 -1
  17. package/hooks/claude/hooks.json +13 -3
  18. package/hooks/codex/hooks.json +2 -1
  19. package/hooks/shared/runtime-context.mjs +73 -83
  20. package/licenses/Apache-2.0.txt +55 -0
  21. package/package.json +42 -12
  22. package/pi-extension/README.md +41 -0
  23. package/pi-extension/dist/ask-user/ask.js +108 -0
  24. package/pi-extension/dist/ask-user/form.js +250 -0
  25. package/pi-extension/dist/ask-user/questions.js +66 -0
  26. package/pi-extension/dist/ask-user/tool.js +124 -0
  27. package/pi-extension/dist/cognitive-routing/assembler.js +417 -0
  28. package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
  29. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +8 -6
  30. package/pi-extension/dist/cognitive-routing/economics.js +45 -0
  31. package/pi-extension/dist/cognitive-routing/event-schema.js +388 -0
  32. package/pi-extension/dist/{session-sources/events.js → cognitive-routing/event-store.js} +107 -41
  33. package/pi-extension/dist/cognitive-routing/gate.js +256 -0
  34. package/pi-extension/dist/cognitive-routing/handlers.js +901 -0
  35. package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
  36. package/pi-extension/dist/cognitive-routing/model-control.js +476 -0
  37. package/pi-extension/dist/cognitive-routing/projection.js +520 -0
  38. package/pi-extension/dist/cognitive-routing/provenance.js +83 -0
  39. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +15 -15
  40. package/pi-extension/dist/cognitive-routing/runtime.js +398 -0
  41. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/schemas.js +47 -11
  42. package/pi-extension/dist/cognitive-routing/session.js +223 -0
  43. package/pi-extension/dist/{session-sources → cognitive-routing}/sources.js +126 -87
  44. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/state.js +55 -386
  45. package/pi-extension/dist/cognitive-routing/status.js +81 -0
  46. package/pi-extension/dist/cognitive-routing/tools.js +74 -0
  47. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +14 -11
  48. package/pi-extension/dist/compaction/carry.js +205 -0
  49. package/pi-extension/dist/compaction/config.js +24 -0
  50. package/pi-extension/dist/compaction/controller.js +494 -0
  51. package/pi-extension/dist/compaction/harness.js +93 -0
  52. package/pi-extension/dist/compaction/results.js +159 -0
  53. package/pi-extension/dist/compaction/thresholds.js +35 -0
  54. package/pi-extension/dist/compaction/tool.js +57 -0
  55. package/pi-extension/dist/host/branch.js +13 -0
  56. package/pi-extension/dist/host/canonical.js +12 -0
  57. package/pi-extension/dist/host/catalog.js +62 -0
  58. package/pi-extension/dist/host/config-validation.js +233 -0
  59. package/pi-extension/dist/host/config.js +361 -0
  60. package/pi-extension/dist/host/inbound.js +142 -0
  61. package/pi-extension/dist/host/projection-tags.js +60 -0
  62. package/pi-extension/dist/host/prompts.js +93 -0
  63. package/pi-extension/dist/{session-sources → host}/read-only-session.js +117 -1
  64. package/pi-extension/dist/{runtime → host}/request-history.js +125 -39
  65. package/pi-extension/dist/host/rows.js +25 -0
  66. package/pi-extension/dist/host/runtime-state.js +204 -0
  67. package/pi-extension/dist/host/section-keeper.js +85 -0
  68. package/pi-extension/dist/host/settings/freeflow-command.js +2323 -0
  69. package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +205 -73
  70. package/pi-extension/dist/host/staging.js +28 -0
  71. package/pi-extension/dist/host/status.js +115 -0
  72. package/pi-extension/dist/index.js +1004 -188
  73. package/pi-extension/dist/messaging/config.js +27 -0
  74. package/pi-extension/dist/messaging/messaging.js +398 -0
  75. package/pi-extension/dist/messaging/registry.js +57 -0
  76. package/pi-extension/dist/messaging/render.js +90 -0
  77. package/pi-extension/dist/messaging/tools.js +109 -0
  78. package/pi-extension/dist/messaging/transport.js +101 -0
  79. package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
  80. package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
  81. package/pi-extension/dist/provider-support/cache/health.js +73 -0
  82. package/pi-extension/dist/provider-support/cache/index.js +58 -0
  83. package/pi-extension/dist/provider-support/cache/keepalive.js +222 -0
  84. package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
  85. package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
  86. package/pi-extension/dist/provider-support/effort.js +13 -0
  87. package/pi-extension/dist/provider-support/index.js +44 -3
  88. package/pi-extension/dist/provider-support/{astra → openai}/adapter.js +46 -29
  89. package/pi-extension/dist/provider-support/{astra → openai}/history.js +10 -8
  90. package/pi-extension/dist/provider-support/{astra → openai}/index.js +8 -4
  91. package/pi-extension/dist/provider-support/{astra → openai}/session-state.js +29 -38
  92. package/pi-extension/dist/provider-support/routes.js +17 -0
  93. package/pi-extension/dist/task-memory/config.js +49 -0
  94. package/pi-extension/dist/task-memory/registry.js +119 -0
  95. package/pi-extension/dist/task-memory/render.js +107 -0
  96. package/pi-extension/dist/task-memory/runtime.js +994 -0
  97. package/pi-extension/dist/task-memory/tool.js +233 -0
  98. package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
  99. package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
  100. package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
  101. package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
  102. package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
  103. package/pi-extension/dist/tool-execution/background.js +430 -0
  104. package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
  105. package/pi-extension/dist/tool-execution/config.js +33 -0
  106. package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
  107. package/pi-extension/dist/tool-execution/file-state.js +138 -0
  108. package/pi-extension/dist/tool-execution/file-tracking.js +163 -0
  109. package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
  110. package/pi-extension/dist/tool-execution/prompt.js +54 -0
  111. package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
  112. package/pi-extension/dist/tool-execution/tools.js +16 -0
  113. package/pi-extension/freeflow/README.md +9 -0
  114. package/runtime/prompts/cognitive-routing.md +4 -4
  115. package/runtime/prompts/core.md +29 -72
  116. package/runtime/prompts/interaction-contract.md +18 -8
  117. package/runtime/prompts/tool-execution.md +25 -0
  118. package/runtime/prompts/working-method.md +25 -0
  119. package/skills/deliver-work/SKILL.md +76 -0
  120. package/skills/deliver-work/references/branch-closeout.md +134 -0
  121. package/skills/deliver-work/references/commit.md +83 -0
  122. package/skills/deliver-work/references/launch.md +94 -0
  123. package/skills/deliver-work/references/release.md +85 -0
  124. package/skills/deliver-work/scripts/commit-state.mjs +139 -0
  125. package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
  126. package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
  127. package/skills/deliver-work/scripts/push-route.mjs +122 -0
  128. package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
  129. package/skills/discuss-work/SKILL.md +148 -0
  130. package/skills/{discuss → discuss-work}/references/discussion-continuity.md +13 -50
  131. package/skills/discuss-work/references/learning-action.md +36 -0
  132. package/skills/discuss-work/scripts/choices.mjs +101 -0
  133. package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
  134. package/skills/evaluate-behavior/SKILL.md +107 -0
  135. package/skills/evaluate-behavior/references/authoring.md +381 -0
  136. package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
  137. package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
  138. package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
  139. package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
  140. package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
  141. package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
  142. package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
  143. package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
  144. package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
  145. package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
  146. package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
  147. package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
  148. package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
  149. package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
  150. package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
  151. package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
  152. package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
  153. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
  154. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
  155. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
  156. package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
  157. package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
  158. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
  159. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
  160. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
  161. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
  162. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
  163. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
  164. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
  165. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
  166. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
  167. package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
  168. package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
  169. package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
  170. package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
  171. package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
  172. package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
  173. package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
  174. package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
  175. package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
  176. package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
  177. package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
  178. package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
  179. package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
  180. package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
  181. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
  182. package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
  183. package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
  184. package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
  185. package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
  186. package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
  187. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +121 -14
  188. package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
  189. package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
  190. package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
  191. package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
  192. package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
  193. package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
  194. package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +239 -262
  195. package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
  196. package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
  197. package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
  198. package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
  199. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +218 -11
  200. package/skills/evaluate-behavior/scripts/pi-command-tool.mjs +159 -0
  201. package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
  202. package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
  203. package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
  204. package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
  205. package/skills/execute-work/SKILL.md +96 -72
  206. package/skills/execute-work/references/code-practices.md +27 -49
  207. package/skills/execute-work/references/design.md +271 -0
  208. package/skills/execute-work/references/diagnose.md +117 -0
  209. package/skills/execute-work/references/migration.md +108 -0
  210. package/skills/execute-work/references/simplify.md +65 -0
  211. package/skills/execute-work/scripts/self-review.mjs +422 -0
  212. package/skills/execute-work/scripts/touch-map.mjs +290 -0
  213. package/skills/review-work/SKILL.md +94 -64
  214. package/skills/review-work/references/adjudication.md +94 -0
  215. package/skills/review-work/references/artifact.md +198 -0
  216. package/skills/review-work/references/evals-and-judges.md +66 -0
  217. package/skills/review-work/references/work.md +177 -0
  218. package/skills/review-work/scripts/check-brief.mjs +128 -0
  219. package/skills/review-work/scripts/review-packet.mjs +233 -0
  220. package/skills/setup-freeflow/SKILL.md +69 -16
  221. package/skills/track-work/SKILL.md +135 -151
  222. package/skills/track-work/references/handoff.md +190 -0
  223. package/skills/track-work/references/projects.md +51 -0
  224. package/skills/track-work/references/working-record-format.md +201 -344
  225. package/skills/track-work/scripts/lib/document.mjs +79 -19
  226. package/skills/track-work/scripts/lib/format.mjs +18 -1
  227. package/skills/track-work/scripts/lib/recover.mjs +254 -0
  228. package/skills/track-work/scripts/lib/registry.mjs +656 -0
  229. package/skills/track-work/scripts/lib/search.mjs +40 -0
  230. package/skills/track-work/scripts/lib/store.mjs +53 -21
  231. package/skills/track-work/scripts/lib/transitions.mjs +79 -4
  232. package/skills/track-work/scripts/lib/views.mjs +50 -1
  233. package/skills/track-work/scripts/working-record.mjs +263 -22
  234. package/skills/verify-work/SKILL.md +64 -47
  235. package/skills/verify-work/references/feature-map.md +120 -0
  236. package/skills/verify-work/references/runtime-evidence.md +203 -0
  237. package/skills/verify-work/references/test-design.md +27 -6
  238. package/skills/verify-work/scripts/test-changes.mjs +246 -0
  239. package/skills/write-docs/SKILL.md +171 -0
  240. package/skills/write-docs/references/documentation-shapes.md +184 -0
  241. package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
  242. package/skills/write-docs/scripts/artifact-check.mjs +148 -0
  243. package/skills/write-docs/scripts/docs-check.mjs +229 -0
  244. package/skills/write-docs/scripts/plan-lint.mjs +123 -0
  245. package/skills/write-skill/SKILL.md +165 -165
  246. package/skills/write-skill/references/activation-and-wording.md +143 -0
  247. package/skills/write-skill/references/placement-and-dependencies.md +154 -0
  248. package/skills/write-skill/scripts/lib/compare.mjs +701 -0
  249. package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
  250. package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
  251. package/skills/write-skill/scripts/skill-author.mjs +67 -8
  252. package/capabilities/context-virtualization/SKILL.md +0 -103
  253. package/capabilities/conversation-history/SKILL.md +0 -53
  254. package/hooks/tests/check-host-adapters.test.mjs +0 -82
  255. package/hooks/tests/check-runtime-context-hook.sh +0 -212
  256. package/pi-extension/dist/cognitive-routing-v2/projection.js +0 -330
  257. package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -2181
  258. package/pi-extension/dist/cognitive-routing-v2/tools.js +0 -60
  259. package/pi-extension/dist/context-virtualization/commands.js +0 -89
  260. package/pi-extension/dist/context-virtualization/projector.js +0 -42
  261. package/pi-extension/dist/context-virtualization/runtime.js +0 -378
  262. package/pi-extension/dist/context-virtualization/state.js +0 -57
  263. package/pi-extension/dist/context-virtualization/types.js +0 -10
  264. package/pi-extension/dist/conversation-history/passages.js +0 -66
  265. package/pi-extension/dist/conversation-history/ranking.js +0 -105
  266. package/pi-extension/dist/conversation-history/runtime.js +0 -226
  267. package/pi-extension/dist/conversation-history/search.js +0 -56
  268. package/pi-extension/dist/conversation-history/types.js +0 -1
  269. package/pi-extension/dist/freeflow-context/resolver.js +0 -120
  270. package/pi-extension/dist/freeflow-context/runtime.js +0 -80
  271. package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
  272. package/pi-extension/dist/freeflow-context/tool.js +0 -314
  273. package/pi-extension/dist/freeflow-context/types.js +0 -12
  274. package/pi-extension/dist/freeflow-context/visibility.js +0 -28
  275. package/pi-extension/dist/integrations/mcp-config.js +0 -29
  276. package/pi-extension/dist/runtime/runtime-context.js +0 -754
  277. package/pi-extension/dist/runtime/runtime-identity.js +0 -5
  278. package/pi-extension/dist/session-sources/provenance.js +0 -32
  279. package/pi-extension/dist/settings/settings-ui.js +0 -1360
  280. package/pi-extension/dist/ui/text-component.js +0 -57
  281. package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
  282. package/pi-extension/src/cognitive-routing-v2/config.ts +0 -173
  283. package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -375
  284. package/pi-extension/src/cognitive-routing-v2/render.ts +0 -277
  285. package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -2241
  286. package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -180
  287. package/pi-extension/src/cognitive-routing-v2/state.ts +0 -839
  288. package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -62
  289. package/pi-extension/src/cognitive-routing-v2/types.ts +0 -254
  290. package/pi-extension/src/context-virtualization/commands.ts +0 -111
  291. package/pi-extension/src/context-virtualization/projector.ts +0 -62
  292. package/pi-extension/src/context-virtualization/runtime.ts +0 -428
  293. package/pi-extension/src/context-virtualization/state.ts +0 -79
  294. package/pi-extension/src/context-virtualization/types.ts +0 -92
  295. package/pi-extension/src/conversation-history/passages.ts +0 -81
  296. package/pi-extension/src/conversation-history/ranking.ts +0 -155
  297. package/pi-extension/src/conversation-history/runtime.ts +0 -250
  298. package/pi-extension/src/conversation-history/search.ts +0 -79
  299. package/pi-extension/src/conversation-history/types.ts +0 -31
  300. package/pi-extension/src/freeflow-context/resolver.ts +0 -139
  301. package/pi-extension/src/freeflow-context/runtime.ts +0 -100
  302. package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
  303. package/pi-extension/src/freeflow-context/tool.ts +0 -366
  304. package/pi-extension/src/freeflow-context/types.ts +0 -70
  305. package/pi-extension/src/freeflow-context/visibility.ts +0 -53
  306. package/pi-extension/src/index.ts +0 -390
  307. package/pi-extension/src/integrations/mcp-config.ts +0 -32
  308. package/pi-extension/src/provider-support/astra/adapter.ts +0 -108
  309. package/pi-extension/src/provider-support/astra/history.ts +0 -145
  310. package/pi-extension/src/provider-support/astra/index.ts +0 -12
  311. package/pi-extension/src/provider-support/astra/session-state.ts +0 -113
  312. package/pi-extension/src/provider-support/index.ts +0 -5
  313. package/pi-extension/src/runtime/request-history.ts +0 -243
  314. package/pi-extension/src/runtime/runtime-context.ts +0 -860
  315. package/pi-extension/src/runtime/runtime-identity.ts +0 -8
  316. package/pi-extension/src/session-sources/events.ts +0 -230
  317. package/pi-extension/src/session-sources/provenance.ts +0 -39
  318. package/pi-extension/src/session-sources/read-only-session.ts +0 -170
  319. package/pi-extension/src/session-sources/sources.ts +0 -334
  320. package/pi-extension/src/settings/settings-tui.ts +0 -501
  321. package/pi-extension/src/settings/settings-ui.ts +0 -1652
  322. package/pi-extension/src/ui/text-component.ts +0 -66
  323. package/pi-extension/tests/cache-reuse/history.test.js +0 -117
  324. package/pi-extension/tests/cache-reuse/native.test.js +0 -91
  325. package/pi-extension/tests/cache-reuse/settings.test.js +0 -211
  326. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -577
  327. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
  328. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +0 -557
  329. package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -474
  330. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
  331. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -300
  332. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
  333. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -1034
  334. package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -244
  335. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -423
  336. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
  337. package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -586
  338. package/pi-extension/tests/context-virtualization/extension.test.js +0 -775
  339. package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
  340. package/pi-extension/tests/context-virtualization/runtime.test.js +0 -230
  341. package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
  342. package/pi-extension/tests/conversation-history/search.test.js +0 -97
  343. package/pi-extension/tests/fixtures/pi-host.js +0 -4
  344. package/pi-extension/tests/fixtures/routing-native.js +0 -191
  345. package/pi-extension/tests/integration/mode-free-core.test.js +0 -220
  346. package/pi-extension/tests/integration/pi-extension.test.js +0 -1447
  347. package/pi-extension/tests/integration/prompt-architecture.test.js +0 -447
  348. package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
  349. package/pi-extension/tests/provider-support/astra-boundaries.test.js +0 -75
  350. package/pi-extension/tests/provider-support/astra.test.js +0 -306
  351. package/pi-extension/tests/provider-support/cli.test.js +0 -92
  352. package/pi-extension/tests/provider-support/native.test.js +0 -183
  353. package/pi-extension/tests/settings/settings-tui.test.js +0 -243
  354. package/pi-extension/tsconfig.json +0 -16
  355. package/plugin.json +0 -33
  356. package/runtime/prompts/context-virtualization.md +0 -3
  357. package/runtime/prompts/conversation-history.md +0 -3
  358. package/skills/action-selection/SKILL.md +0 -100
  359. package/skills/action-selection/references/trajectory-stalls.md +0 -71
  360. package/skills/bypass/SKILL.md +0 -153
  361. package/skills/commit-work/SKILL.md +0 -145
  362. package/skills/commit-work/references/git-state-edges.md +0 -94
  363. package/skills/commit-work/references/simple-push.md +0 -92
  364. package/skills/commit-work/references/staging-decisions.md +0 -106
  365. package/skills/decision-gate/SKILL.md +0 -106
  366. package/skills/design-for-depth/SKILL.md +0 -117
  367. package/skills/design-for-depth/references/design-pressure-signals.md +0 -182
  368. package/skills/design-for-depth/references/implementation-decisions.md +0 -76
  369. package/skills/design-for-depth/references/interface-design-loop.md +0 -136
  370. package/skills/design-for-depth/references/module-and-dependency-design.md +0 -147
  371. package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
  372. package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -136
  373. package/skills/diagnose-failure/SKILL.md +0 -125
  374. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -48
  375. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
  376. package/skills/discuss/SKILL.md +0 -116
  377. package/skills/evaluate-skill/SKILL.md +0 -219
  378. package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -385
  379. package/skills/evaluate-skill/references/evaluation-design.md +0 -112
  380. package/skills/evaluate-skill/references/execution-and-evidence.md +0 -90
  381. package/skills/evaluate-skill/references/review-and-revision.md +0 -92
  382. package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -407
  383. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
  384. package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
  385. package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -368
  386. package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -164
  387. package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -102
  388. package/skills/execute-work/references/execute-work-edges.md +0 -68
  389. package/skills/finish-branch/SKILL.md +0 -117
  390. package/skills/finish-branch/references/integration-options.md +0 -80
  391. package/skills/handoff/SKILL.md +0 -82
  392. package/skills/handoff/references/handoff-templates.md +0 -150
  393. package/skills/launch-work/SKILL.md +0 -116
  394. package/skills/launch-work/references/launch-readiness.md +0 -85
  395. package/skills/migration-work/SKILL.md +0 -115
  396. package/skills/migration-work/references/migration-lifecycle.md +0 -82
  397. package/skills/release-work/SKILL.md +0 -118
  398. package/skills/release-work/references/release-evidence.md +0 -78
  399. package/skills/review-artifact/SKILL.md +0 -142
  400. package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
  401. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
  402. package/skills/review-work/references/adjudicate-work-review.md +0 -97
  403. package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
  404. package/skills/review-work/references/security-risk-lens.md +0 -69
  405. package/skills/setup-freeflow/references/activation-contract.md +0 -109
  406. package/skills/setup-freeflow/references/host-setup.md +0 -67
  407. package/skills/simplify-code/SKILL.md +0 -85
  408. package/skills/simplify-code/references/simplification-patterns.md +0 -70
  409. package/skills/verify-work/references/browser-runtime-evidence.md +0 -55
  410. package/skills/verify-work/references/integration-evidence.md +0 -47
  411. package/skills/verify-work/references/performance-evidence.md +0 -57
  412. package/skills/workflow/SKILL.md +0 -127
  413. package/skills/workflow/references/domain-skill-composition.md +0 -43
  414. package/skills/write-plan/SKILL.md +0 -92
  415. package/skills/write-plan/references/plan-shapes.md +0 -156
  416. package/skills/write-skill/references/activation-boundaries.md +0 -104
  417. package/skills/write-skill/references/agent-first-instructions.md +0 -121
  418. package/skills/write-skill/references/progressive-disclosure.md +0 -127
  419. package/skills/write-skill/references/skill-dependency-graphs.md +0 -190
  420. package/skills/write-spec/SKILL.md +0 -92
  421. package/skills/write-spec/references/artifact-standards.md +0 -86
  422. package/skills/write-spec/references/decision-records.md +0 -78
  423. package/skills/write-spec/references/spec-shapes.md +0 -143
  424. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
  425. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
  426. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
@@ -0,0 +1,124 @@
1
+ // What a task's copy lacks against the setting it was taken from. A fixture is copied into each workspace as plain
2
+ // files, so a subject never has the original's Git history, its ignored files (a `.env`, built output, installed
3
+ // packages), or tools and network the original had. `check-tasks` lists each such gap, without a model, so the author
4
+ // can judge whether the task is still fair before paying for runs. Gaps are reported, never refused.
5
+
6
+ import { execFile } from "node:child_process";
7
+ import { stat } from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { promisify } from "node:util";
10
+
11
+ import { armNames } from "./arms.mjs";
12
+ import { armSealing } from "./seal.mjs";
13
+
14
+ const run = promisify(execFile);
15
+ const GIT_WORDS = /\b(?:git|commit|commits|diff|branch|blame|stash|rebase|history|log)\b/i;
16
+ const RUN_WORDS = /\b(?:run|test|tests|build|install|npm|node|pytest|make|cargo|go test|execute)\b/i;
17
+ const NETWORK_WORDS = /\b(?:npm install|pip install|git clone|curl|wget|download|https?:\/\/\S+)/i;
18
+
19
+ /** Everything the subject is told: the prompt or turns, and each pipeline stage's own input. */
20
+ function taskText(group) {
21
+ const parts = [];
22
+ if (typeof group.input?.prompt === "string") parts.push(group.input.prompt);
23
+ for (const turn of group.input?.turns ?? []) if (typeof turn === "string") parts.push(turn);
24
+ for (const stage of group.participants?.stages ?? []) {
25
+ if (typeof stage.input === "string") parts.push(stage.input);
26
+ else if (typeof stage.input?.prompt === "string") parts.push(stage.input.prompt);
27
+ }
28
+ return parts.join("\n");
29
+ }
30
+
31
+ /** Words of the task that look like file or folder names: `src/a.js`, `.env`, `report.md`, `node_modules/`. */
32
+ function namedPaths(text) {
33
+ const names = new Set();
34
+ for (const raw of text.split(/[\s`'"(),;:]+/)) {
35
+ const word = raw.replace(/[.!?]+$/, "");
36
+ if (word === "" || /^[a-z]+:\/\//i.test(word) || word.startsWith("/") || word.includes("..")) continue;
37
+ if (word.includes("/") || /^\.[A-Za-z0-9_-]+/.test(word) || /^[\w-]+\.[A-Za-z0-9]{1,8}$/.test(word)) {
38
+ names.add(word.replace(/\/+$/, ""));
39
+ }
40
+ }
41
+ return [...names];
42
+ }
43
+
44
+ async function git(cwd, args) {
45
+ try {
46
+ const { stdout } = await run("git", args, { cwd, maxBuffer: 16 * 1024 * 1024 });
47
+ return { ok: true, stdout };
48
+ } catch (error) {
49
+ return { ok: false, stdout: String(error?.stdout ?? "") };
50
+ }
51
+ }
52
+
53
+ /**
54
+ * The gaps between one group's task copy and its original setting, with no model: `gaps` (each a line saying what
55
+ * the copy lacks), and the `tools` and `network` each arm's subject has.
56
+ *
57
+ * @param {any} group a loaded group
58
+ * @param {{ root: string }} options
59
+ */
60
+ export async function taskCopyGaps(group, { root }) {
61
+ const text = taskText(group);
62
+ const gaps = [];
63
+ const fixture = group.fixture === null ? null : path.resolve(root, group.fixture);
64
+
65
+ // Git history: a fixture is copied as plain files, so the workspace is never a repository.
66
+ const hasGit = fixture !== null && (await stat(path.join(fixture, ".git")).catch(() => null)) !== null;
67
+ if (!hasGit) {
68
+ const words = [...new Set((text.match(new RegExp(GIT_WORDS, "gi")) ?? []).map((word) => word.toLowerCase()))];
69
+ gaps.push(
70
+ `git history: the workspace is not a Git repository${words.length > 0 ? `, and the task mentions ${words.join(", ")}` : ""}`,
71
+ );
72
+ }
73
+
74
+ if (fixture !== null) {
75
+ // Ignored files the fixture holds here: a run copies them, but no clone has them, so the task changes by machine.
76
+ const ignored = await git(fixture, ["ls-files", "--others", "--ignored", "--exclude-standard", "--", "."]);
77
+ const present = ignored.ok ? ignored.stdout.split("\n").filter(Boolean) : [];
78
+ if (present.length > 0) {
79
+ gaps.push(
80
+ `ignored files: the fixture holds ${present.slice(0, 5).join(", ")}${present.length > 5 ? ` and ${present.length - 5} more` : ""}, which Git ignores, so a clone of the repository lacks them`,
81
+ );
82
+ }
83
+ // Ignored files the task names but the copy lacks: the original setting had them (a .env, built output).
84
+ const missing = [];
85
+ for (const name of namedPaths(text)) {
86
+ if ((await stat(path.join(fixture, name)).catch(() => null)) !== null) continue;
87
+ const checked = await git(fixture, ["check-ignore", "--no-index", "-q", "--", name]);
88
+ if (checked.ok) missing.push(name);
89
+ }
90
+ if (missing.length > 0) {
91
+ gaps.push(`ignored files the task names: ${missing.join(", ")}; Git ignores them here, and the copy lacks them`);
92
+ }
93
+ }
94
+
95
+ // Tools and network, per arm: what the subject can run, and what its code can reach.
96
+ const tools = {};
97
+ const network = {};
98
+ for (const arm of armNames(group)) {
99
+ const shell = group.variants?.[arm]?.shell;
100
+ const bash = !group.tools.includes("bash")
101
+ ? null
102
+ : shell?.container !== undefined
103
+ ? `bash in ${shell.container.image}`
104
+ : `bash in ${shell?.isolation ?? "an undeclared shell"}`;
105
+ const commands = (group.commands ?? []).map((command) => command.id);
106
+ tools[arm] = [
107
+ ...group.tools.filter((tool) => tool !== "bash" && tool !== "run_command"),
108
+ ...(bash === null ? [] : [bash]),
109
+ ...(commands.length > 0 ? [`run_command (${commands.join(", ")})`] : []),
110
+ ];
111
+ const sealing = armSealing(group, arm);
112
+ network[arm] = sealing.network;
113
+ // A description group only reads, so only a body group's tools and network can fall short of the task.
114
+ if (group.type !== "body") continue;
115
+ if (bash === null && commands.length === 0 && RUN_WORDS.test(text)) {
116
+ gaps.push(`tools: arm ${arm} has no bash or run_command, and the task asks to run something`);
117
+ }
118
+ const wants = NETWORK_WORDS.exec(text);
119
+ if (wants !== null && sealing.network === "none") {
120
+ gaps.push(`network: arm ${arm} has none, and the task refers to it (${wants[0]})`);
121
+ }
122
+ }
123
+ return { gaps: [...new Set(gaps)], tools, network };
124
+ }
@@ -0,0 +1,524 @@
1
+ import { mkdir, readFile, realpath } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { comparisonArms, referenceArm, storedArms } from "./arms.mjs";
5
+ import { budgetEvidence, createBudget } from "./budget.mjs";
6
+ import { decide, decisionClaim, undecidedCases } from "./decision.mjs";
7
+ import { loadDefinition, selectDefinition } from "./definitions.mjs";
8
+ import { createInvocationId, writeJson } from "./evidence.mjs";
9
+ import { aggregateSections, hasAggregators } from "./hooks.mjs";
10
+ import { createLimiter } from "./queue.mjs";
11
+ import { createRunReuse, refreshSelectedLogins, resultsDirectory, runEvaluation } from "./runner.mjs";
12
+ import { unclearedFlags } from "./scan.mjs";
13
+
14
+ const CHECK_STATES = ["pass", "fail", "unavailable"];
15
+
16
+ /**
17
+ * Runs the selection as independent complete invocations and records an aggregate beside them.
18
+ * Each trial remains an ordinary result; the aggregate only counts their deterministic grades.
19
+ *
20
+ * When the suite declares a `decision`, the aggregate records it and its claim is the decision's verdict. When
21
+ * the decision also declares `trials`, that schedule sets the trial count: `trials.initial` complete invocations,
22
+ * then rounds in which each case whose sign is not yet stable runs up to `trials.step` more trials, that group
23
+ * alone, until `trials.max`. A `trials` option that differs from `trials.initial` is refused; without a schedule,
24
+ * an omitted `trials` means one.
25
+ *
26
+ * Every invocation shares one subject limiter (`jobs` subjects at once), one budget and one reuse state, and
27
+ * logins are refreshed once before any of them. With one job, invocations run one after another as before.
28
+ * With `pilot`, the first `pilot` groups run once first; any problem stops the run before the batch (no
29
+ * aggregate is written), and otherwise the pilot, with one invocation of the remaining groups, is the first trial.
30
+ *
31
+ * @param {string} definitionFile
32
+ * @param {any} selectors
33
+ * @param {{root?: string, signal?: AbortSignal, trials?: number | null, maxCost?: number | null, onTrial?: (result: any) => void, dryRun?: boolean, evaluate?: typeof runEvaluation, jobs?: number, results?: string | null, reuse?: boolean, pilot?: number | null}} options
34
+ */
35
+ export async function runTrials(definitionFile, selectors, options = {}) {
36
+ const { root = process.cwd(), signal, trials = null, maxCost = null, onTrial, dryRun = false } = options;
37
+ const { evaluate = runEvaluation, jobs = 1, pilot = null } = options;
38
+ const { selection, rule } = await loadSelection(definitionFile, selectors, root);
39
+ const schedule = rule?.trials ?? null;
40
+ if (schedule !== null && trials !== null && trials !== schedule.initial) {
41
+ throw new Error(
42
+ `the suite's decision schedules ${schedule.initial} initial trials; omit --trials or set it to ${schedule.initial}`,
43
+ );
44
+ }
45
+ const initial = schedule?.initial ?? trials ?? 1;
46
+ if (!Number.isInteger(initial) || initial < 1) throw new Error("trials must be a positive integer");
47
+
48
+ const runsDirectory = resultsDirectory(root, options.results);
49
+ const budget = createBudget(maxCost);
50
+ const shared = {
51
+ root,
52
+ signal,
53
+ budget,
54
+ dryRun,
55
+ subjects: createLimiter(jobs),
56
+ results: options.results ?? null,
57
+ reuse: createRunReuse(runsDirectory, options.reuse === true),
58
+ loginsRefreshed: true,
59
+ skipReferenceCheck: options.skipReferenceCheck === true,
60
+ sources: options.sources ?? null,
61
+ };
62
+ await refreshSelectedLogins(selection);
63
+ const stopped = () => Boolean(signal?.aborted || budget?.exhausted);
64
+ const results = [];
65
+ const entries = [];
66
+ const invoke = async (runSelectors, entry, extra = {}) => {
67
+ const result = await evaluate(definitionFile, runSelectors, { ...shared, ...extra });
68
+ onTrial?.(result);
69
+ // Each trial's own spend; the aggregate's budget holds the run's total.
70
+ const cost = result.summary?.cost?.spent;
71
+ return { result, entry: { id: result.id, state: result.state, ...(cost === undefined ? {} : { cost }), ...entry } };
72
+ };
73
+ // Invocations in order with one job; otherwise all at once, sharing the subject slots. Recorded in plan order.
74
+ const runAll = async (tasks) => {
75
+ const settled = [];
76
+ if (jobs === 1) {
77
+ for (const task of tasks) {
78
+ if (stopped()) break;
79
+ settled.push(await task());
80
+ }
81
+ } else {
82
+ settled.push(...(await Promise.all(tasks.map((task) => (stopped() ? null : task())))));
83
+ }
84
+ for (const outcome of settled) {
85
+ if (outcome === null) continue;
86
+ results.push(outcome.result);
87
+ entries.push(outcome.entry);
88
+ }
89
+ };
90
+
91
+ let planned = 0;
92
+ const tasks = [];
93
+ let pilotRecord = null;
94
+ if (pilot !== null) {
95
+ const outcome = await invoke(selectors, { part: "pilot" }, { groupRange: [0, pilot] });
96
+ const problems = await pilotProblems(outcome.result);
97
+ pilotRecord = { id: outcome.result.id, groups: outcome.result.summary.groups.length, problems };
98
+ if (problems.length > 0)
99
+ return { id: null, path: null, state: "pilot-stopped", pilot: pilotRecord, aggregate: null };
100
+ results.push(outcome.result);
101
+ entries.push(outcome.entry);
102
+ planned += 1;
103
+ // The pilot is the first trial of its groups; the remaining groups run once to complete that trial.
104
+ if (pilot < selection.groups.length) {
105
+ tasks.push(() => invoke(selectors, { part: "rest" }, { groupRange: [pilot, selection.groups.length] }));
106
+ planned += 1;
107
+ }
108
+ }
109
+ const full = pilot === null ? initial : initial - 1;
110
+ for (let trial = 0; trial < full; trial += 1) tasks.push(() => invoke(selectors, {}));
111
+ planned += full;
112
+ await runAll(tasks);
113
+ let groups = await aggregateGroups(results);
114
+
115
+ // Adaptive trials: only the declared schedule adds trials, and only to cases whose sign could still change.
116
+ const rounds = [];
117
+ const maxRounds = schedule === null ? 0 : Math.ceil((schedule.max - schedule.initial) / schedule.step);
118
+ while (rounds.length < maxRounds && results.length === planned && !stopped()) {
119
+ const undecided = undecidedCases(rule, groups);
120
+ if (undecided.length === 0) break;
121
+ const extra = undecided.map(({ id, trials: done }) => ({ id, add: Math.min(schedule.step, schedule.max - done) }));
122
+ const scheduled = extra.reduce((sum, { add }) => sum + add, 0);
123
+ const round = { round: rounds.length + 1, groups: undecided.map(({ id }) => id), scheduled, trials: 0 };
124
+ rounds.push(round);
125
+ planned += scheduled;
126
+ // Interleaved by step, so a budget that runs out leaves the cases evenly extended.
127
+ const roundTasks = [];
128
+ for (let step = 0; step < schedule.step; step += 1) {
129
+ for (const { id, add } of extra) {
130
+ if (step < add) roundTasks.push(() => invoke({ ...selectors, group: id }, { round: round.round, group: id }));
131
+ }
132
+ }
133
+ const before = results.length;
134
+ await runAll(roundTasks);
135
+ round.trials = results.length - before;
136
+ groups = await aggregateGroups(results);
137
+ }
138
+
139
+ const id = createInvocationId();
140
+ const directory = path.join(runsDirectory, id);
141
+ await mkdir(directory, { recursive: true });
142
+ const aggregate = {
143
+ schema_version: 1,
144
+ kind: "aggregate",
145
+ id,
146
+ state: aggregateState(results, planned),
147
+ // The full invocations asked for; trials an adaptive round added are counted in `schedule.rounds`.
148
+ requestedTrials: initial,
149
+ trials: entries,
150
+ groups,
151
+ dryRun,
152
+ claim: null,
153
+ completedAt: new Date().toISOString(),
154
+ };
155
+ if (pilotRecord !== null) aggregate.pilot = pilotRecord;
156
+ const reused = reusedRuns(results);
157
+ if (reused.length > 0) aggregate.reused = reused;
158
+ const held = heldRuns(groups);
159
+ if (held.length > 0) aggregate.held = held;
160
+ if (schedule !== null) aggregate.schedule = { ...schedule, rounds };
161
+ if (rule !== null) aggregate.decision = decide(rule, groups);
162
+ aggregate.claim = claimFor(results, initial, dryRun, aggregate.decision, held.length);
163
+ if (budget !== null) aggregate.budget = budgetEvidence(budget);
164
+ await writeJson(path.join(directory, "aggregate.json"), aggregate);
165
+ return { id, path: directory, state: aggregate.state, aggregate };
166
+ }
167
+
168
+ /**
169
+ * What stops a pilot: a run that was cancelled, failed its infrastructure or was invalid, a grade that is
170
+ * unreadable or has errors, a group that recorded errors, or a comparison that no arm passed.
171
+ */
172
+ export async function pilotProblems(result) {
173
+ const problems = [];
174
+ for (const group of result.summary?.groups ?? []) {
175
+ for (const [arm, state] of Object.entries(group.variants ?? {})) {
176
+ if (["cancelled", "infrastructure-failed", "invalid"].includes(state)) {
177
+ const reason = state === "invalid" ? group.invalid?.[arm] : undefined;
178
+ problems.push(`${group.id}: arm ${arm} is ${state}${reason ? ` (${reason})` : ""}`);
179
+ }
180
+ }
181
+ for (const error of group.errors ?? []) problems.push(`${group.id}: ${error.message ?? error.kind}`);
182
+ for (const [arm, flags] of Object.entries(group.flagged ?? {})) {
183
+ problems.push(
184
+ `${group.id}: arm ${arm} has transcript flags no one has cleared (${flags.join(", ")}); read them, then clear them with skill-eval clear or fix the setup`,
185
+ );
186
+ }
187
+ const grade = await readJson(path.join(result.path, "groups", group.id, group.artifacts?.grade ?? ""));
188
+ if (grade === null) {
189
+ problems.push(`${group.id}: grade unreadable`);
190
+ continue;
191
+ }
192
+ for (const error of grade.errors ?? []) problems.push(`${group.id}: grade error: ${error.reason ?? error}`);
193
+ for (const comparison of grade.comparisons ?? []) {
194
+ const { arms } = comparisonArms(comparison);
195
+ if (!Object.values(arms ?? {}).some((outcome) => outcome?.state === "pass")) {
196
+ problems.push(`${group.id}: no arm passed ${comparison.id}`);
197
+ }
198
+ }
199
+ }
200
+ return problems;
201
+ }
202
+
203
+ /** Every run held out of the counts for transcript flags no one has cleared: the trial, group, arm and flags. */
204
+ function heldRuns(groups) {
205
+ return groups.flatMap((group) => (group.held ?? []).map((entry) => ({ group: group.id, ...entry })));
206
+ }
207
+
208
+ /** Every run a trial took from the index instead of running it: the trial, group and arm, and where it came from. */
209
+ function reusedRuns(results) {
210
+ const reused = [];
211
+ for (const result of results) {
212
+ for (const group of result.summary?.groups ?? []) {
213
+ for (const [arm, from] of Object.entries(group.reused ?? {})) {
214
+ reused.push({ trial: result.id, group: group.id, arm, from, reused: true });
215
+ }
216
+ }
217
+ }
218
+ return reused;
219
+ }
220
+
221
+ /**
222
+ * Rereads every trial an aggregate lists and rewrites its aggregate.json: groups, aggregator sections, decision
223
+ * and claim. Everything else (its ID, trials, schedule, budget, times) is kept, so rebuilding an unchanged
224
+ * aggregate reproduces it exactly. A trial whose summary cannot be read is refused rather than dropped. Trials
225
+ * are read from the directory that holds the aggregate, where they were stored beside it.
226
+ *
227
+ * @param {string} aggregatePath the aggregate's directory or its aggregate.json
228
+ * @param {{root?: string, results?: string | null}} [options]
229
+ */
230
+ export async function rebuildAggregate(aggregatePath, { root = process.cwd(), results = null } = {}) {
231
+ const file = aggregatePath.endsWith(".json") ? aggregatePath : path.join(aggregatePath, "aggregate.json");
232
+ const aggregate = await readJson(file);
233
+ if (aggregate === null || aggregate.kind !== "aggregate" || !Array.isArray(aggregate.trials)) {
234
+ throw new Error(`not a readable aggregate: ${file}`);
235
+ }
236
+ const runsDirectory =
237
+ results === null ? path.dirname(path.dirname(path.resolve(root, file))) : path.resolve(root, results);
238
+ const trialResults = [];
239
+ for (const trial of aggregate.trials) {
240
+ const directory = path.join(runsDirectory, trial.id);
241
+ const summary = await readJson(path.join(directory, "summary.json"));
242
+ if (summary === null) throw new Error(`trial ${trial.id} has no readable summary.json`);
243
+ trialResults.push({ id: trial.id, path: directory, state: summary.state, summary });
244
+ }
245
+ const groups = await aggregateGroups(trialResults);
246
+ const { held: _held, ...kept } = aggregate;
247
+ const rebuilt = { ...kept, groups };
248
+ const held = heldRuns(groups);
249
+ if (held.length > 0) rebuilt.held = held;
250
+ if (aggregate.decision !== undefined) rebuilt.decision = decide(aggregate.decision.rule, groups);
251
+ const k = aggregate.schedule?.initial ?? aggregate.requestedTrials;
252
+ rebuilt.claim = claimFor(trialResults, k, aggregate.dryRun === true, rebuilt.decision, held.length);
253
+ await writeJson(file, rebuilt);
254
+ return rebuilt;
255
+ }
256
+
257
+ /**
258
+ * The selection, and the suite's validated decision rule or null. A decision run must select both of its arms;
259
+ * loading the suite has already checked that every group has them and records the measure in both.
260
+ */
261
+ async function loadSelection(definitionFile, selectors, root) {
262
+ const definitionPath = await realpath(path.resolve(root, definitionFile));
263
+ const definition = await loadDefinition(definitionPath, { root });
264
+ const selection = selectDefinition(definition, selectors);
265
+ const rule = definition.kind === "suite" ? (definition.decision ?? null) : null;
266
+ if (rule === null) return { selection, rule };
267
+ if (selectors?.variant !== null && selectors?.variant !== undefined) {
268
+ throw new Error("the suite's decision compares two arms; --variant would run only one");
269
+ }
270
+ return { selection, rule };
271
+ }
272
+
273
+ /**
274
+ * The strongest claim the aggregate supports. Without a decision rule, counts over complete trials support a rate
275
+ * on this suite. With one, the claim is the rule's verdict ("beats", "worse" or "no-difference-shown") with its
276
+ * split, p and rule hash. A dry run read inputs no commit identifies, so it supports no claim either way.
277
+ */
278
+ function claimFor(results, k, dryRun, decision, held = 0) {
279
+ const complete = results.filter((result) => result.state === "complete").length;
280
+ // Runs held for uncleared transcript flags are left out of the counts and the decision; the claim says how many.
281
+ const heldNote = held > 0 ? { held } : {};
282
+ if (decision !== undefined) {
283
+ const reason = dryRun ? "dry run" : complete === 0 ? "no complete trial" : null;
284
+ return { ...decisionClaim(decision, { k, supported: reason === null, reason }), ...heldNote };
285
+ }
286
+ return {
287
+ class: complete > 0 && !dryRun ? "rate-on-suite" : "none",
288
+ ...(dryRun ? { reason: "dry run" } : {}),
289
+ k,
290
+ beats: "not supported: the suite declares no decision rule",
291
+ ...heldNote,
292
+ };
293
+ }
294
+
295
+ function aggregateState(results, requested) {
296
+ if (results.length < requested || results.some((result) => result.state === "cancelled")) return "cancelled";
297
+ if (results.every((result) => result.state === "complete")) return "complete";
298
+ return "partially-complete";
299
+ }
300
+
301
+ /**
302
+ * Counts each group's checks and comparisons over every trial that ran it. A trial that recorded no state for a
303
+ * check or comparison (its grade was unreadable, or that check or comparison could not be graded) counts as
304
+ * `unavailable` for it, so every count, and pass^k, covers all the group's trials. Comparisons are counted per
305
+ * arm, and each non-reference arm's transitions and pairs against the reference.
306
+ */
307
+ export async function aggregateGroups(results) {
308
+ /** @type {Map<string, any>} */
309
+ const groups = new Map();
310
+ const collectTrials = hasAggregators();
311
+ for (const result of results) {
312
+ for (const group of result.summary.groups) {
313
+ const groupDirectory = path.join(result.path, "groups", group.id);
314
+ let entry = groups.get(group.id);
315
+ if (entry === undefined) {
316
+ entry = {
317
+ id: group.id,
318
+ trials: 0,
319
+ unreadableGrades: 0,
320
+ gradeErrors: 0,
321
+ runStates: {},
322
+ comparisons: new Map(),
323
+ checks: new Map(),
324
+ definition: null,
325
+ perTrial: [],
326
+ held: [],
327
+ };
328
+ groups.set(group.id, entry);
329
+ }
330
+ entry.trials += 1;
331
+ if (entry.definition === null) {
332
+ entry.definition = await readJson(path.join(groupDirectory, "definition.json"));
333
+ seedChecks(entry);
334
+ }
335
+ const arms = storedArms(group, entry.definition);
336
+ for (const arm of arms) {
337
+ const states = (entry.runStates[arm] ??= {});
338
+ const state = group.variants?.[arm];
339
+ if (typeof state === "string") states[state] = (states[state] ?? 0) + 1;
340
+ }
341
+ const runs = Object.fromEntries(
342
+ await Promise.all(arms.map(async (arm) => [arm, await readJson(path.join(groupDirectory, arm, "run.json"))])),
343
+ );
344
+ // A run with transcript flags no one has cleared counts as unavailable and is named as held; read fresh, so a
345
+ // clearance made after the run takes effect when the aggregate is rebuilt.
346
+ const held = [];
347
+ for (const arm of arms) {
348
+ const flags = await unclearedFlags(runs[arm], path.join(groupDirectory, arm));
349
+ if (flags.length === 0) continue;
350
+ held.push(arm);
351
+ entry.held.push({ trial: result.id ?? null, arm, flags });
352
+ }
353
+ // One trial's missing or corrupt grade is counted, not allowed to discard every other trial's evidence.
354
+ const grade = await readJson(path.join(groupDirectory, group.artifacts?.grade ?? "deterministic-grade.json"));
355
+ if (grade === null) entry.unreadableGrades += 1;
356
+ else countGrade(entry, grade, new Set(held));
357
+ if (collectTrials) {
358
+ entry.perTrial.push({
359
+ id: result.id ?? null,
360
+ directory: groupDirectory,
361
+ state: group.state ?? null,
362
+ grade,
363
+ runs,
364
+ held,
365
+ });
366
+ }
367
+ }
368
+ }
369
+ const aggregated = [];
370
+ for (const entry of groups.values()) {
371
+ const output = {
372
+ id: entry.id,
373
+ trials: entry.trials,
374
+ unreadableGrades: entry.unreadableGrades,
375
+ gradeErrors: entry.gradeErrors,
376
+ runStates: entry.runStates,
377
+ comparisons: [...entry.comparisons.values()].map((counts) => comparisonOutput(counts, entry.trials)),
378
+ // pass^k: the check passed in every one of the group's k trials.
379
+ checks: [...entry.checks.values()].map((check) => {
380
+ const counted = check.pass + check.fail + check.unavailable;
381
+ const filled = { ...check, unavailable: check.unavailable + Math.max(0, entry.trials - counted) };
382
+ const graded = filled.pass + filled.fail + filled.unavailable;
383
+ return { ...filled, trials: graded, passAll: graded > 0 && filled.pass === graded };
384
+ }),
385
+ };
386
+ if (entry.held.length > 0) output.held = entry.held;
387
+ if (collectTrials) {
388
+ Object.assign(
389
+ output,
390
+ await aggregateSections({
391
+ id: entry.id,
392
+ arms: Object.keys(entry.runStates),
393
+ reference: referenceArm(entry.definition),
394
+ trials: entry.perTrial,
395
+ }),
396
+ );
397
+ }
398
+ aggregated.push(output);
399
+ }
400
+ return aggregated;
401
+ }
402
+
403
+ async function readJson(file) {
404
+ try {
405
+ const value = JSON.parse(await readFile(file, "utf8"));
406
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
407
+ } catch {
408
+ return null;
409
+ }
410
+ }
411
+
412
+ /** Declared expectations become checks before any grade is read, so one that never graded is still counted. */
413
+ function seedChecks(entry) {
414
+ const expectations = Array.isArray(entry.definition?.expectations) ? entry.definition.expectations : [];
415
+ for (const expectation of expectations) {
416
+ if (typeof expectation?.id !== "string" || entry.checks.has(expectation.id)) continue;
417
+ entry.checks.set(expectation.id, {
418
+ id: expectation.id,
419
+ variant: expectation.variant,
420
+ kind: expectation.kind,
421
+ ...zeroCounts(),
422
+ });
423
+ }
424
+ }
425
+
426
+ function countGrade(entry, grade, held = new Set()) {
427
+ // A check that could not be graded is counted, so it cannot silently leave every denominator.
428
+ entry.gradeErrors += Array.isArray(grade.errors) ? grade.errors.length : 0;
429
+ for (const check of grade.checks ?? []) {
430
+ const counts = entry.checks.get(check.id) ?? {
431
+ id: check.id,
432
+ variant: check.variant,
433
+ kind: check.kind,
434
+ ...zeroCounts(),
435
+ };
436
+ entry.checks.set(check.id, counts);
437
+ if (held.has(check.variant)) holdOne(counts);
438
+ else if (CHECK_STATES.includes(check.state)) counts[check.state] += 1;
439
+ }
440
+ for (const comparison of grade.comparisons ?? []) {
441
+ const { reference, arms, transitions } = comparisonArms(comparison);
442
+ let counts = entry.comparisons.get(comparison.id);
443
+ if (counts === undefined) {
444
+ counts = {
445
+ id: comparison.id,
446
+ kind: comparison.kind,
447
+ classic: comparison.arms === undefined,
448
+ reference,
449
+ arms: {},
450
+ outcomes: {},
451
+ recorded: 0,
452
+ };
453
+ entry.comparisons.set(comparison.id, counts);
454
+ }
455
+ counts.recorded += 1;
456
+ // A held run's outcome is unavailable here, so the pair it makes is incomplete.
457
+ const stateOf = (arm) => (held.has(arm) ? "held" : arms?.[arm]?.state);
458
+ for (const [arm, outcome] of Object.entries(arms ?? {})) {
459
+ const armCounts = (counts.arms[arm] ??= zeroCounts());
460
+ if (held.has(arm)) holdOne(armCounts);
461
+ else if (CHECK_STATES.includes(outcome?.state)) armCounts[outcome.state] += 1;
462
+ if (arm === reference) continue;
463
+ const versus = (counts.outcomes[arm] ??= { transitions: {}, pairs: zeroPairs(reference, arm) });
464
+ const transition = held.has(arm) || held.has(reference) ? "held" : transitions[arm];
465
+ versus.transitions[transition] = (versus.transitions[transition] ?? 0) + 1;
466
+ versus.pairs[pairOf(stateOf(reference), stateOf(arm), reference, arm)] += 1;
467
+ }
468
+ }
469
+ }
470
+
471
+ /**
472
+ * A comparison's counts with every trial accounted for. A baseline/candidate comparison keeps its original shape
473
+ * (`baseline`, `candidate`, `transitions`, `pairs`); any other has `reference`, per-arm `arms`, and `outcomes`
474
+ * holding each non-reference arm's transitions and pairs against the reference.
475
+ */
476
+ function comparisonOutput(counts, trials) {
477
+ const missing = Math.max(0, trials - counts.recorded);
478
+ for (const armCounts of Object.values(counts.arms)) {
479
+ armCounts.unavailable += Math.max(0, trials - (armCounts.pass + armCounts.fail + armCounts.unavailable));
480
+ }
481
+ for (const versus of Object.values(counts.outcomes)) {
482
+ if (missing > 0) versus.transitions.unavailable = (versus.transitions.unavailable ?? 0) + missing;
483
+ versus.pairs.incomplete += missing;
484
+ }
485
+ if (counts.classic) {
486
+ return {
487
+ id: counts.id,
488
+ kind: counts.kind,
489
+ baseline: counts.arms.baseline ?? zeroCounts(),
490
+ candidate: counts.arms.candidate ?? zeroCounts(),
491
+ transitions: counts.outcomes.candidate?.transitions ?? {},
492
+ pairs: counts.outcomes.candidate?.pairs ?? zeroPairs("baseline", "candidate"),
493
+ };
494
+ }
495
+ return {
496
+ id: counts.id,
497
+ kind: counts.kind,
498
+ reference: counts.reference,
499
+ arms: counts.arms,
500
+ outcomes: counts.outcomes,
501
+ };
502
+ }
503
+
504
+ /** One trial's pair of outcomes for an arm against the reference; anything but pass or fail on either side is incomplete. */
505
+ function pairOf(referenceState, armState, reference, arm) {
506
+ const graded = (state) => state === "pass" || state === "fail";
507
+ if (!graded(referenceState) || !graded(armState)) return "incomplete";
508
+ if (referenceState === "pass") return armState === "pass" ? "both-pass" : `${reference}-only`;
509
+ return armState === "pass" ? `${arm}-only` : "both-fail";
510
+ }
511
+
512
+ function zeroPairs(reference, arm) {
513
+ return { "both-pass": 0, "both-fail": 0, [`${reference}-only`]: 0, [`${arm}-only`]: 0, incomplete: 0 };
514
+ }
515
+
516
+ function zeroCounts() {
517
+ return { pass: 0, fail: 0, unavailable: 0 };
518
+ }
519
+
520
+ /** One held outcome: unavailable in every denominator and pass^k, and named in `held`. */
521
+ function holdOne(counts) {
522
+ counts.unavailable += 1;
523
+ counts.held = (counts.held ?? 0) + 1;
524
+ }