@hassangameryt/freeflow 0.8.0 → 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 (290) 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/README.md +96 -149
  7. package/capabilities/cognitive-routing/SKILL.md +21 -129
  8. package/capabilities/cognitive-routing/references/coordinator.md +163 -0
  9. package/capabilities/cognitive-routing/references/worker.md +38 -0
  10. package/capabilities/compaction/SKILL.md +3 -3
  11. package/capabilities/compaction/references/summary-format.md +26 -15
  12. package/capabilities/messaging/SKILL.md +49 -0
  13. package/capabilities/task-memory/SKILL.md +87 -0
  14. package/command-surface.json +6 -54
  15. package/gemini-extension.json +1 -1
  16. package/hooks/claude/hooks.json +13 -3
  17. package/hooks/codex/hooks.json +2 -1
  18. package/hooks/shared/runtime-context.mjs +69 -77
  19. package/package.json +25 -8
  20. package/pi-extension/dist/ask-user/ask.js +108 -0
  21. package/pi-extension/dist/ask-user/form.js +250 -0
  22. package/pi-extension/dist/ask-user/questions.js +66 -0
  23. package/pi-extension/dist/ask-user/tool.js +124 -0
  24. package/pi-extension/dist/cognitive-routing/assembler.js +35 -11
  25. package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
  26. package/pi-extension/dist/cognitive-routing/config.js +3 -4
  27. package/pi-extension/dist/cognitive-routing/event-store.js +4 -1
  28. package/pi-extension/dist/cognitive-routing/gate.js +20 -5
  29. package/pi-extension/dist/cognitive-routing/handlers.js +15 -13
  30. package/pi-extension/dist/cognitive-routing/model-control.js +17 -8
  31. package/pi-extension/dist/cognitive-routing/projection.js +23 -6
  32. package/pi-extension/dist/cognitive-routing/provenance.js +3 -1
  33. package/pi-extension/dist/cognitive-routing/render.js +15 -15
  34. package/pi-extension/dist/cognitive-routing/runtime.js +4 -2
  35. package/pi-extension/dist/cognitive-routing/schemas.js +4 -4
  36. package/pi-extension/dist/cognitive-routing/session.js +3 -1
  37. package/pi-extension/dist/cognitive-routing/sources.js +29 -4
  38. package/pi-extension/dist/cognitive-routing/state.js +18 -8
  39. package/pi-extension/dist/cognitive-routing/status.js +4 -3
  40. package/pi-extension/dist/cognitive-routing/tools.js +29 -15
  41. package/pi-extension/dist/cognitive-routing/types.js +10 -1
  42. package/pi-extension/dist/compaction/carry.js +36 -22
  43. package/pi-extension/dist/compaction/config.js +4 -2
  44. package/pi-extension/dist/compaction/controller.js +52 -29
  45. package/pi-extension/dist/compaction/harness.js +19 -3
  46. package/pi-extension/dist/compaction/results.js +14 -13
  47. package/pi-extension/dist/compaction/tool.js +6 -6
  48. package/pi-extension/dist/host/catalog.js +20 -35
  49. package/pi-extension/dist/host/config-validation.js +233 -0
  50. package/pi-extension/dist/host/config.js +78 -91
  51. package/pi-extension/dist/host/inbound.js +142 -0
  52. package/pi-extension/dist/host/projection-tags.js +1 -1
  53. package/pi-extension/dist/host/prompts.js +24 -13
  54. package/pi-extension/dist/host/read-only-session.js +6 -1
  55. package/pi-extension/dist/host/request-history.js +7 -3
  56. package/pi-extension/dist/host/rows.js +25 -0
  57. package/pi-extension/dist/host/runtime-state.js +17 -2
  58. package/pi-extension/dist/host/section-keeper.js +85 -0
  59. package/pi-extension/dist/host/settings/freeflow-command.js +846 -93
  60. package/pi-extension/dist/host/settings/settings-view.js +27 -8
  61. package/pi-extension/dist/host/status.js +19 -5
  62. package/pi-extension/dist/index.js +638 -72
  63. package/pi-extension/dist/messaging/config.js +27 -0
  64. package/pi-extension/dist/messaging/messaging.js +398 -0
  65. package/pi-extension/dist/messaging/registry.js +57 -0
  66. package/pi-extension/dist/messaging/render.js +90 -0
  67. package/pi-extension/dist/messaging/tools.js +109 -0
  68. package/pi-extension/dist/messaging/transport.js +101 -0
  69. package/pi-extension/dist/provider-support/cache/keepalive.js +16 -4
  70. package/pi-extension/dist/task-memory/config.js +49 -0
  71. package/pi-extension/dist/task-memory/registry.js +119 -0
  72. package/pi-extension/dist/task-memory/render.js +107 -0
  73. package/pi-extension/dist/task-memory/runtime.js +994 -0
  74. package/pi-extension/dist/task-memory/tool.js +233 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/tool.js +1 -1
  76. package/pi-extension/dist/tool-execution/background.js +220 -28
  77. package/pi-extension/dist/tool-execution/config.js +14 -3
  78. package/pi-extension/dist/tool-execution/file-tracking.js +24 -4
  79. package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
  80. package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
  81. package/pi-extension/dist/tool-execution/tools.js +2 -2
  82. package/runtime/prompts/cognitive-routing.md +4 -4
  83. package/runtime/prompts/core.md +24 -14
  84. package/runtime/prompts/interaction-contract.md +7 -4
  85. package/runtime/prompts/tool-execution.md +3 -16
  86. package/runtime/prompts/working-method.md +16 -99
  87. package/skills/deliver-work/SKILL.md +76 -0
  88. package/skills/deliver-work/references/branch-closeout.md +134 -0
  89. package/skills/deliver-work/references/commit.md +83 -0
  90. package/skills/deliver-work/references/launch.md +94 -0
  91. package/skills/deliver-work/references/release.md +85 -0
  92. package/skills/deliver-work/scripts/commit-state.mjs +139 -0
  93. package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
  94. package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
  95. package/skills/deliver-work/scripts/push-route.mjs +122 -0
  96. package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
  97. package/skills/discuss-work/SKILL.md +148 -0
  98. package/skills/{discuss → discuss-work}/references/discussion-continuity.md +10 -27
  99. package/skills/discuss-work/references/learning-action.md +36 -0
  100. package/skills/discuss-work/scripts/choices.mjs +101 -0
  101. package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
  102. package/skills/evaluate-behavior/SKILL.md +107 -0
  103. package/skills/evaluate-behavior/references/authoring.md +381 -0
  104. package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
  105. package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
  106. package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
  107. package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
  108. package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
  109. package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
  110. package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
  111. package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
  112. package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
  113. package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
  114. package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
  115. package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
  116. package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
  117. package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
  118. package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
  119. package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
  120. package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
  121. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
  122. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
  123. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
  124. package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
  125. package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
  126. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
  127. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
  128. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
  129. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
  130. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
  131. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
  132. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
  133. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
  134. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
  135. package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
  136. package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
  137. package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
  138. package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
  139. package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
  140. package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
  141. package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
  142. package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
  143. package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
  144. package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
  145. package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
  146. package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
  147. package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
  148. package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
  149. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
  150. package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
  151. package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
  152. package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
  153. package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
  154. package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
  155. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +119 -14
  156. package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
  157. package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
  158. package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
  159. package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
  160. package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
  161. package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
  162. package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +235 -265
  163. package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
  164. package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
  165. package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
  166. package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
  167. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +159 -18
  168. package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-command-tool.mjs +21 -6
  169. package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
  170. package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
  171. package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
  172. package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
  173. package/skills/execute-work/SKILL.md +96 -104
  174. package/skills/execute-work/references/code-practices.md +27 -51
  175. package/skills/execute-work/references/design.md +271 -0
  176. package/skills/execute-work/references/diagnose.md +117 -0
  177. package/skills/execute-work/references/migration.md +108 -0
  178. package/skills/execute-work/references/simplify.md +65 -0
  179. package/skills/execute-work/scripts/self-review.mjs +422 -0
  180. package/skills/execute-work/scripts/touch-map.mjs +290 -0
  181. package/skills/review-work/SKILL.md +89 -65
  182. package/skills/review-work/references/adjudication.md +94 -0
  183. package/skills/review-work/references/artifact.md +198 -0
  184. package/skills/review-work/references/evals-and-judges.md +66 -0
  185. package/skills/review-work/references/work.md +177 -0
  186. package/skills/review-work/scripts/check-brief.mjs +128 -0
  187. package/skills/review-work/scripts/review-packet.mjs +233 -0
  188. package/skills/setup-freeflow/SKILL.md +68 -17
  189. package/skills/track-work/SKILL.md +52 -20
  190. package/skills/track-work/references/handoff.md +190 -0
  191. package/skills/track-work/references/projects.md +51 -0
  192. package/skills/track-work/references/working-record-format.md +36 -18
  193. package/skills/track-work/scripts/lib/document.mjs +17 -13
  194. package/skills/track-work/scripts/lib/format.mjs +2 -0
  195. package/skills/track-work/scripts/lib/recover.mjs +254 -0
  196. package/skills/track-work/scripts/lib/registry.mjs +656 -0
  197. package/skills/track-work/scripts/lib/search.mjs +40 -0
  198. package/skills/track-work/scripts/lib/store.mjs +46 -15
  199. package/skills/track-work/scripts/lib/views.mjs +50 -1
  200. package/skills/track-work/scripts/working-record.mjs +232 -13
  201. package/skills/verify-work/SKILL.md +45 -37
  202. package/skills/verify-work/references/feature-map.md +120 -0
  203. package/skills/verify-work/references/runtime-evidence.md +203 -0
  204. package/skills/verify-work/references/test-design.md +25 -6
  205. package/skills/verify-work/scripts/test-changes.mjs +246 -0
  206. package/skills/write-docs/SKILL.md +113 -25
  207. package/skills/write-docs/references/documentation-shapes.md +68 -3
  208. package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
  209. package/skills/write-docs/scripts/artifact-check.mjs +148 -0
  210. package/skills/write-docs/scripts/docs-check.mjs +96 -6
  211. package/skills/write-docs/scripts/plan-lint.mjs +123 -0
  212. package/skills/write-skill/SKILL.md +92 -52
  213. package/skills/write-skill/references/activation-and-wording.md +143 -0
  214. package/skills/write-skill/references/placement-and-dependencies.md +154 -0
  215. package/skills/write-skill/scripts/lib/compare.mjs +701 -0
  216. package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
  217. package/skills/write-skill/scripts/skill-author.mjs +51 -8
  218. package/capabilities/cognitive-routing/references/both-mode.md +0 -36
  219. package/capabilities/cognitive-routing/references/executor-mode.md +0 -48
  220. package/capabilities/cognitive-routing/references/helper-mode.md +0 -47
  221. package/plugin.json +0 -33
  222. package/skills/action-selection/SKILL.md +0 -92
  223. package/skills/action-selection/references/trajectory-stalls.md +0 -73
  224. package/skills/bypass/SKILL.md +0 -153
  225. package/skills/commit-work/SKILL.md +0 -93
  226. package/skills/commit-work/references/git-state-edges.md +0 -94
  227. package/skills/commit-work/references/simple-push.md +0 -94
  228. package/skills/commit-work/references/staging-decisions.md +0 -106
  229. package/skills/decision-gate/SKILL.md +0 -87
  230. package/skills/design-for-depth/SKILL.md +0 -108
  231. package/skills/design-for-depth/references/design-pressure-signals.md +0 -84
  232. package/skills/design-for-depth/references/implementation-decisions.md +0 -76
  233. package/skills/design-for-depth/references/interface-design-loop.md +0 -105
  234. package/skills/design-for-depth/references/module-and-dependency-design.md +0 -117
  235. package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -125
  236. package/skills/diagnose-failure/SKILL.md +0 -140
  237. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -50
  238. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
  239. package/skills/discuss/SKILL.md +0 -140
  240. package/skills/evaluate-skill/SKILL.md +0 -188
  241. package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -415
  242. package/skills/evaluate-skill/references/evaluation-design.md +0 -112
  243. package/skills/evaluate-skill/references/execution-and-evidence.md +0 -111
  244. package/skills/evaluate-skill/references/review-and-revision.md +0 -94
  245. package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -448
  246. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
  247. package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
  248. package/skills/evaluate-skill/scripts/lib/review.mjs +0 -196
  249. package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -405
  250. package/skills/evaluate-skill/scripts/lib/trials.mjs +0 -103
  251. package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -181
  252. package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -182
  253. package/skills/execute-work/references/execute-work-edges.md +0 -78
  254. package/skills/finish-branch/SKILL.md +0 -117
  255. package/skills/finish-branch/references/integration-options.md +0 -80
  256. package/skills/handoff/SKILL.md +0 -82
  257. package/skills/handoff/references/handoff-templates.md +0 -150
  258. package/skills/launch-work/SKILL.md +0 -116
  259. package/skills/launch-work/references/launch-readiness.md +0 -85
  260. package/skills/migration-work/SKILL.md +0 -115
  261. package/skills/migration-work/references/migration-lifecycle.md +0 -82
  262. package/skills/release-work/SKILL.md +0 -118
  263. package/skills/release-work/references/release-evidence.md +0 -78
  264. package/skills/review-artifact/SKILL.md +0 -143
  265. package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
  266. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
  267. package/skills/review-work/references/adjudicate-work-review.md +0 -97
  268. package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
  269. package/skills/review-work/references/security-risk-lens.md +0 -67
  270. package/skills/setup-freeflow/references/activation-contract.md +0 -103
  271. package/skills/setup-freeflow/references/host-setup.md +0 -67
  272. package/skills/simplify-code/SKILL.md +0 -85
  273. package/skills/simplify-code/references/simplification-patterns.md +0 -70
  274. package/skills/verify-work/references/browser-runtime-evidence.md +0 -51
  275. package/skills/verify-work/references/integration-evidence.md +0 -49
  276. package/skills/verify-work/references/performance-evidence.md +0 -59
  277. package/skills/workflow/SKILL.md +0 -171
  278. package/skills/workflow/references/domain-skill-composition.md +0 -45
  279. package/skills/write-plan/SKILL.md +0 -92
  280. package/skills/write-plan/references/plan-shapes.md +0 -156
  281. package/skills/write-skill/references/activation-boundaries.md +0 -106
  282. package/skills/write-skill/references/agent-first-instructions.md +0 -133
  283. package/skills/write-skill/references/progressive-disclosure.md +0 -129
  284. package/skills/write-skill/references/skill-dependency-graphs.md +0 -192
  285. package/skills/write-spec/SKILL.md +0 -85
  286. package/skills/write-spec/references/artifact-standards.md +0 -86
  287. package/skills/write-spec/references/decision-records.md +0 -80
  288. package/skills/write-spec/references/spec-shapes.md +0 -143
  289. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
  290. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
@@ -0,0 +1,332 @@
1
+ // Tracked metrics: what each run cost and how it went, derived only from evidence the run already records.
2
+ // Importing this module registers the run-metrics hook and the `metrics` aggregator; the CLI imports it once.
3
+ // A metric whose evidence is missing is null, never 0, so an absent measurement cannot read as a free one.
4
+
5
+ import { realpathSync } from "node:fs";
6
+ import path from "node:path";
7
+
8
+ import { measuredCost } from "./budget.mjs";
9
+ import { METRIC_NAMES } from "./decision.mjs";
10
+ import { registerAggregator, registerRunMetricsHook } from "./hooks.mjs";
11
+
12
+ // The names are kept with the decision rule, which checks them when a suite loads, so loading a definition
13
+ // registers no metrics hook.
14
+ export { METRIC_NAMES };
15
+
16
+ // No tokenizer ships with the evaluator, so skill-read tokens are estimated from the bytes that entered the context.
17
+ export const BYTES_PER_TOKEN = 4;
18
+ // The directories of a Freeflow package that hold model-facing instructions: skills, capability skills and prompts.
19
+ const FREEFLOW_INSTRUCTION_DIRECTORIES = ["skills", "capabilities", "runtime"];
20
+
21
+ /**
22
+ * One run's metrics. RPC observations record turns, each with its tool activity; a one-shot observation records
23
+ * one turn's tool activity at top level. Usage is the run's merged usage.
24
+ *
25
+ * `peakContext` and the skill reads come from the run's transcript (see `peakContext` and `skillReads`), so they are
26
+ * measured on Pi only; on another host, or without a transcript, they are null.
27
+ *
28
+ * @param {{ observation?: any, run: any, subject?: any }} context
29
+ */
30
+ export function runMetricsFor({ observation, run, subject }) {
31
+ const turns = Array.isArray(observation?.turns) ? observation.turns : Array.isArray(run?.turns) ? run.turns : null;
32
+ const activity = turns !== null ? turnActivity(turns) : topLevelActivity(observation, run);
33
+ const usage = isObject(run?.usage) ? run.usage : isObject(observation?.usage) ? observation.usage : null;
34
+ const tokens = {
35
+ input: count(usage?.input),
36
+ output: count(usage?.output),
37
+ cacheRead: count(usage?.cacheRead),
38
+ cacheWrite: count(usage?.cacheWrite),
39
+ };
40
+ const prompted =
41
+ tokens.input === null || tokens.cacheRead === null || tokens.cacheWrite === null
42
+ ? null
43
+ : tokens.input + tokens.cacheRead + tokens.cacheWrite;
44
+ return {
45
+ durationMs: duration(run?.startedAt, run?.completedAt),
46
+ turns: turns === null ? 1 : turns.length,
47
+ toolCalls: activity === null ? null : activity.length,
48
+ toolErrors: activity === null ? null : activity.filter((entry) => entry?.isError === true).length,
49
+ tokens,
50
+ // Null when unmeasured: a zero cost for a run that used tokens is a missing price, not a free run.
51
+ cost: measuredCost(usage),
52
+ // The share of prompt tokens served from the provider's cache.
53
+ cacheHitRate: prompted === null || prompted === 0 ? null : tokens.cacheRead / prompted,
54
+ ...transcriptMetrics({ run, subject }),
55
+ };
56
+ }
57
+
58
+ /** The transcript-derived metrics, null unless the run is a Pi run with a transcript. */
59
+ function transcriptMetrics({ run, subject }) {
60
+ const host = subject?.host?.name ?? run?.process?.host ?? null;
61
+ const transcript = Array.isArray(run?.transcript) ? run.transcript : null;
62
+ if (host !== "pi" || transcript === null) return { peakContext: null, skillReads: null, skillReadTokens: null };
63
+ const reads = skillReads(transcript, { roots: skillRoots(subject), cwd: run?.workspace ?? subject?.workspace });
64
+ return { peakContext: peakContext(transcript), skillReads: reads.reads, skillReadTokens: reads.tokens };
65
+ }
66
+
67
+ /**
68
+ * The largest single request a run sent: for each assistant message (one model request) of a Pi transcript, its
69
+ * input tokens plus cache reads plus cache writes, which together are the whole prompt that request carried. Null when
70
+ * no assistant message records all three.
71
+ *
72
+ * @param {any[]} transcript
73
+ */
74
+ export function peakContext(transcript) {
75
+ let peak = null;
76
+ for (const message of transcript) {
77
+ if (message?.role !== "assistant" || !isObject(message.usage)) continue;
78
+ const parts = [message.usage.input, message.usage.cacheRead, message.usage.cacheWrite].map(count);
79
+ if (parts.includes(null)) continue;
80
+ const size = parts[0] + parts[1] + parts[2];
81
+ if (peak === null || size > peak) peak = size;
82
+ }
83
+ return peak;
84
+ }
85
+
86
+ /**
87
+ * Directories whose files count as skill reads for a subject: each declared skill, and the instruction directories
88
+ * (`skills`, `capabilities`, `runtime`) of the Freeflow package the arm loads. Each is listed as given and by its
89
+ * real path, since a path in the transcript may use either.
90
+ */
91
+ export function skillRoots(subject) {
92
+ const roots = [
93
+ ...(subject?.environment?.skills ?? []).map((skill) => skill?.path),
94
+ ...(typeof subject?.freeflowRoot === "string"
95
+ ? FREEFLOW_INSTRUCTION_DIRECTORIES.map((directory) => path.join(subject.freeflowRoot, directory))
96
+ : []),
97
+ ].filter((root) => typeof root === "string" && path.isAbsolute(root));
98
+ return [...new Set(roots.flatMap((root) => [path.resolve(root), realOr(root)]))];
99
+ }
100
+
101
+ /**
102
+ * Every successful read of a skill file in a Pi transcript, re-reads included: a `read` call whose path lies inside
103
+ * one of `roots`, and a `bash` call whose command names a path inside one, such as `cat <skill>/SKILL.md`. Each
104
+ * counts the text its result put into the context, so a partial read counts what it returned and a bash command
105
+ * counts its whole output. Tokens are estimated as UTF-8 bytes over `BYTES_PER_TOKEN`, rounded up per read.
106
+ * Returns `{ reads, tokens, bytes }`; zero when the arm has no skills or read none.
107
+ *
108
+ * @param {any[]} transcript
109
+ * @param {{ roots: string[], cwd?: string }} options
110
+ */
111
+ export function skillReads(transcript, { roots, cwd }) {
112
+ const calls = new Map();
113
+ let reads = 0;
114
+ let tokens = 0;
115
+ let bytes = 0;
116
+ if (roots.length === 0) return { reads, tokens, bytes };
117
+ for (const message of transcript) {
118
+ if (message?.role === "assistant" && Array.isArray(message.content)) {
119
+ for (const block of message.content) {
120
+ if (block?.type === "toolCall" && typeof block.id === "string") calls.set(block.id, block);
121
+ }
122
+ continue;
123
+ }
124
+ if (message?.role !== "toolResult" || message.isError === true) continue;
125
+ const call = calls.get(message.toolCallId);
126
+ if (call === undefined || !readsSkill(call, roots, cwd)) continue;
127
+ const size = Buffer.byteLength(resultText(message), "utf8");
128
+ reads += 1;
129
+ bytes += size;
130
+ tokens += Math.ceil(size / BYTES_PER_TOKEN);
131
+ }
132
+ return { reads, tokens, bytes };
133
+ }
134
+
135
+ function readsSkill(call, roots, cwd) {
136
+ const args = isObject(call.arguments) ? call.arguments : {};
137
+ if (call.name === "read" && typeof args.path === "string") {
138
+ const target = resolveReadPath(args.path, cwd);
139
+ return roots.some((root) => target === root || target.startsWith(`${root}${path.sep}`));
140
+ }
141
+ if (call.name === "bash" && typeof args.command === "string") {
142
+ return roots.some((root) => new RegExp(`${escapeRegExp(root)}(?:/|$|["'\\s])`).test(args.command));
143
+ }
144
+ return false;
145
+ }
146
+
147
+ function escapeRegExp(text) {
148
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
149
+ }
150
+
151
+ function resolveReadPath(value, cwd) {
152
+ const written = value.startsWith("@") ? value.slice(1) : value;
153
+ return path.resolve(typeof cwd === "string" ? cwd : "/", written);
154
+ }
155
+
156
+ function resultText(message) {
157
+ if (typeof message.content === "string") return message.content;
158
+ if (!Array.isArray(message.content)) return "";
159
+ return message.content
160
+ .filter((part) => part?.type === "text" && typeof part.text === "string")
161
+ .map((part) => part.text)
162
+ .join("");
163
+ }
164
+
165
+ function realOr(target) {
166
+ try {
167
+ return realpathSync(target);
168
+ } catch {
169
+ return path.resolve(target);
170
+ }
171
+ }
172
+
173
+ /** A run's metrics as flat `{ name: number | null }` values; a run without recorded metrics has every value null. */
174
+ export function metricValues(run) {
175
+ const metrics = isObject(run?.metrics) ? run.metrics : {};
176
+ return Object.fromEntries(
177
+ METRIC_NAMES.map((name) => [name, number(name.split(".").reduce((value, key) => value?.[key], metrics))]),
178
+ );
179
+ }
180
+
181
+ /**
182
+ * The `metrics` aggregate section for one group: per arm, each metric's count, median, mean, min and max over the
183
+ * trials that measured it; per non-reference arm, the paired difference (arm minus reference, within each trial
184
+ * where both were measured); and every trial's values per the shared measures contract.
185
+ *
186
+ * @param {{ arms: string[], reference: string, trials: Array<{ id: string | null, runs: Record<string, any> }> }} input
187
+ */
188
+ export function aggregateMetrics({ arms, reference, trials }) {
189
+ const perTrial = [];
190
+ for (const trial of trials) {
191
+ // Only a complete run's metrics count: an invalid, failed or cancelled run's cost or turns are no measure of
192
+ // the arm, and must never reach a decision; nor do a run's held for transcript flags no one has cleared.
193
+ for (const arm of arms) {
194
+ const run = trial.runs?.[arm];
195
+ const counted = run?.state === "complete" && !trial.held?.includes(arm);
196
+ perTrial.push({ trial: trial.id, arm, values: metricValues(counted ? run : null) });
197
+ }
198
+ }
199
+ const valuesOf = (arm, name) => perTrial.filter((entry) => entry.arm === arm).map((entry) => entry.values[name]);
200
+ const summaries = {};
201
+ for (const arm of arms) {
202
+ summaries[arm] = Object.fromEntries(METRIC_NAMES.map((name) => [name, summarize(valuesOf(arm, name))]));
203
+ }
204
+ const differences = {};
205
+ for (const arm of arms) {
206
+ if (arm === reference || !arms.includes(reference)) continue;
207
+ differences[arm] = Object.fromEntries(
208
+ METRIC_NAMES.map((name) => {
209
+ const referenceValues = valuesOf(reference, name);
210
+ const paired = valuesOf(arm, name)
211
+ .map((value, index) =>
212
+ value === null || referenceValues[index] === null ? null : value - referenceValues[index],
213
+ )
214
+ .filter((value) => value !== null);
215
+ const { count: pairs, mean, median } = summarize(paired);
216
+ return [name, { pairs, mean, median }];
217
+ }),
218
+ );
219
+ }
220
+ return { reference, arms: summaries, differences, perTrial };
221
+ }
222
+
223
+ /** View rows for one run's metrics; tokens are already on the usage row. */
224
+ export function runMetricsLines(run) {
225
+ if (!isObject(run?.metrics)) return [];
226
+ const values = metricValues(run);
227
+ return [
228
+ row(" metrics", [
229
+ `duration-ms=${format(values.durationMs)}`,
230
+ `turns=${format(values.turns)}`,
231
+ `tool-calls=${format(values.toolCalls)}`,
232
+ `tool-errors=${format(values.toolErrors)}`,
233
+ `cache-hit-rate=${format(values.cacheHitRate)}`,
234
+ `cost=${format(values.cost)}`,
235
+ ]),
236
+ ...(values.peakContext === null && values.skillReads === null
237
+ ? []
238
+ : [
239
+ row(" context", [
240
+ `peak-context=${format(values.peakContext)}`,
241
+ `skill-reads=${format(values.skillReads)}`,
242
+ `skill-read-tokens~=${format(values.skillReadTokens)}`,
243
+ ]),
244
+ ]),
245
+ ];
246
+ }
247
+
248
+ /** View rows for an aggregate group's metrics section: one row per metric any arm measured. */
249
+ export function aggregateMetricsLines(section) {
250
+ if (!isObject(section)) return [];
251
+ if (typeof section.error === "string") return [row("metrics", ["error", section.error])];
252
+ const lines = [];
253
+ for (const name of METRIC_NAMES) {
254
+ const arms = Object.entries(section.arms ?? {});
255
+ if (arms.every(([, metrics]) => !metrics?.[name]?.count)) continue;
256
+ lines.push(
257
+ row("metric", [
258
+ name,
259
+ ...arms.map(([arm, metrics]) => {
260
+ const summary = metrics?.[name];
261
+ return `${arm}=median:${format(summary?.median)} mean:${format(summary?.mean)} n=${summary?.count ?? 0}`;
262
+ }),
263
+ ...Object.entries(section.differences ?? {}).map(([arm, metrics]) => {
264
+ const difference = metrics?.[name];
265
+ return `${arm}-${section.reference}=mean:${format(difference?.mean, true)} pairs=${difference?.pairs ?? 0}`;
266
+ }),
267
+ ]),
268
+ );
269
+ }
270
+ return lines;
271
+ }
272
+
273
+ function summarize(values) {
274
+ const measured = values.filter((value) => value !== null).sort((left, right) => left - right);
275
+ const n = measured.length;
276
+ if (n === 0) return { count: 0, median: null, mean: null, min: null, max: null };
277
+ const middle = Math.floor(n / 2);
278
+ return {
279
+ count: n,
280
+ median: n % 2 === 1 ? measured[middle] : (measured[middle - 1] + measured[middle]) / 2,
281
+ mean: measured.reduce((total, value) => total + value, 0) / n,
282
+ min: measured[0],
283
+ max: measured[n - 1],
284
+ };
285
+ }
286
+
287
+ function turnActivity(turns) {
288
+ if (!turns.every((turn) => Array.isArray(turn?.toolActivity))) return null;
289
+ return turns.flatMap((turn) => turn.toolActivity);
290
+ }
291
+
292
+ function topLevelActivity(observation, run) {
293
+ if (Array.isArray(observation?.toolActivity)) return observation.toolActivity;
294
+ if (Array.isArray(run?.toolActivity)) return run.toolActivity;
295
+ return null;
296
+ }
297
+
298
+ function duration(startedAt, completedAt) {
299
+ const start = typeof startedAt === "string" ? Date.parse(startedAt) : Number.NaN;
300
+ const end = typeof completedAt === "string" ? Date.parse(completedAt) : Number.NaN;
301
+ return Number.isFinite(start) && Number.isFinite(end) ? end - start : null;
302
+ }
303
+
304
+ function count(value) {
305
+ return Number.isInteger(value) && value >= 0 ? value : null;
306
+ }
307
+
308
+ function number(value) {
309
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
310
+ }
311
+
312
+ function isObject(value) {
313
+ return value !== null && typeof value === "object" && !Array.isArray(value);
314
+ }
315
+
316
+ function format(value, signed = false) {
317
+ if (typeof value !== "number" || !Number.isFinite(value)) return "unavailable";
318
+ const text = Number.isInteger(value) ? String(value) : String(Number(value.toFixed(6)));
319
+ return signed && value > 0 ? `+${text}` : text;
320
+ }
321
+
322
+ // Same cell escaping as view.mjs, so a metrics row reads like every other view row.
323
+ function row(kind, values) {
324
+ return [kind, ...values]
325
+ .map((value) =>
326
+ String(value).replace(/\\/g, "\\\\").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t"),
327
+ )
328
+ .join("\t");
329
+ }
330
+
331
+ registerRunMetricsHook("tracked", runMetricsFor);
332
+ registerAggregator("metrics", aggregateMetrics);
@@ -0,0 +1,17 @@
1
+ // One model call that is not a subject: a judge, a reviewer, or a simulated user. It runs on a host (Pi unless the
2
+ // setup names another) without tools, skills, context files or session, in its own isolated directories; the host
3
+ // adapter in hosts/ owns how.
4
+
5
+ import { hostAdapter } from "./hosts/index.mjs";
6
+
7
+ /**
8
+ * Starts one model call on a prompt in its own directory, which keeps the prompt, events, final response and stderr.
9
+ *
10
+ * @param {string} directory replaced on every call
11
+ * @param {string} prompt
12
+ * @param {{host?: string, model: string, thinking: string | null, extensions: Array<{path: string}>, settings: object, signal?: AbortSignal, role?: string}} options
13
+ * @returns {Promise<{response: string | null, errors: string[], usage: any, isolation: any}>}
14
+ */
15
+ export async function launchModel(directory, prompt, options) {
16
+ return hostAdapter(options.host ?? "pi").modelCall(directory, prompt, options);
17
+ }