@iowarp/clio-coder 0.3.3 → 0.3.4

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 (244) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +3 -3
  4. package/dist/{acp-P2AQILE2.js → acp-S5R4RR5B.js} +7 -6
  5. package/dist/{agents-72W3BI7I.js → agents-P6DMMVZY.js} +24 -21
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-5TWEIYDN.js → auth-2XCZLPKS.js} +12 -8
  8. package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
  9. package/dist/{chunk-2DJ2KNFG.js → chunk-2LZI5CAG.js} +133 -13
  10. package/dist/{chunk-OAO4GE4M.js → chunk-2TZWSW76.js} +2 -2
  11. package/dist/{chunk-OOJYHWRB.js → chunk-34475P3I.js} +2 -2
  12. package/dist/{chunk-4XUGQOHA.js → chunk-35MKKU5R.js} +4 -4
  13. package/dist/{chunk-LM5TQCJZ.js → chunk-3HZ5RWN2.js} +5 -5
  14. package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
  15. package/dist/{chunk-KZWTDYJF.js → chunk-4JUF2NNX.js} +7 -7
  16. package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
  17. package/dist/chunk-5M54SPOL.js +926 -0
  18. package/dist/{chunk-STBPMHSX.js → chunk-7RXG6QRZ.js} +51 -11
  19. package/dist/{chunk-X6IAEBZR.js → chunk-A2GZF7DC.js} +5 -5
  20. package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
  21. package/dist/chunk-AOCYTWAV.js +449 -0
  22. package/dist/chunk-BEY543CS.js +258 -0
  23. package/dist/{chunk-6N5PTWMY.js → chunk-BP4OYD6A.js} +32 -13
  24. package/dist/chunk-BPGS2WCQ.js +612 -0
  25. package/dist/{chunk-V6RTAOC2.js → chunk-BRXQQJFP.js} +8 -8
  26. package/dist/chunk-CFGTUFWB.js +67 -0
  27. package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
  28. package/dist/{chunk-DUYJ5IO6.js → chunk-EDRHSCIE.js} +4 -4
  29. package/dist/{chunk-XBXAASKX.js → chunk-EFADSJET.js} +2 -2
  30. package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
  31. package/dist/chunk-FYYLNIL5.js +313 -0
  32. package/dist/{chunk-FNTMWMX5.js → chunk-HV5X7OR2.js} +14 -12
  33. package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
  34. package/dist/{chunk-5UFT4SUX.js → chunk-K6WL7QZT.js} +3 -3
  35. package/dist/chunk-K7VKOLQQ.js +15 -0
  36. package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
  37. package/dist/{chunk-OC7FIQPC.js → chunk-KRPY7NTG.js} +10 -7
  38. package/dist/chunk-LL4KHSZI.js +22 -0
  39. package/dist/{chunk-DSELYM6W.js → chunk-MEQ45TQ4.js} +15 -9
  40. package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
  41. package/dist/{chunk-6SGHMWE3.js → chunk-N4CZJQRK.js} +5 -5
  42. package/dist/{chunk-TZK7PACC.js → chunk-NILBFAPG.js} +14 -8
  43. package/dist/chunk-OZNBF4L3.js +23 -0
  44. package/dist/{verify-375KUB3Y.js → chunk-PCZJO5TI.js} +127 -42
  45. package/dist/chunk-QQK64KLB.js +1360 -0
  46. package/dist/{chunk-SRDMMSEP.js → chunk-QQL5RT5M.js} +979 -1619
  47. package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
  48. package/dist/{chunk-2TLUCQVG.js → chunk-RD5U66HV.js} +3 -3
  49. package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
  50. package/dist/{chunk-OQ33BKR3.js → chunk-TTNYS3EA.js} +3 -60
  51. package/dist/chunk-TW3WDMVS.js +677 -0
  52. package/dist/chunk-TZSKNMZG.js +434 -0
  53. package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
  54. package/dist/{chunk-PIWWS5BL.js → chunk-UZHIZC5S.js} +7 -7
  55. package/dist/{chunk-UFIIWP2H.js → chunk-VAWWTKDP.js} +8 -8
  56. package/dist/{chunk-COU2UHX6.js → chunk-VEZEGCGW.js} +170 -2
  57. package/dist/{chunk-LW6DSM3M.js → chunk-VMNQ6OZA.js} +98 -202
  58. package/dist/chunk-VSNATDE6.js +122 -0
  59. package/dist/chunk-W6GROXXM.js +69 -0
  60. package/dist/chunk-WPQLXFOZ.js +375 -0
  61. package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
  62. package/dist/{chunk-PAJK6MAQ.js → chunk-X6COSD2O.js} +5 -5
  63. package/dist/chunk-ZGVHUX3M.js +66 -0
  64. package/dist/{chunk-LWLEKMDQ.js → chunk-ZYKPLLNQ.js} +510 -547
  65. package/dist/cli/index.js +27 -23
  66. package/dist/{clio-JOU4FXVA.js → clio-J5JIOIDS.js} +7 -6
  67. package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
  68. package/dist/{config-XCDVKR23.js → config-OEBMIN2U.js} +37 -27
  69. package/dist/{configure-4GAP54ZW.js → configure-PUQOSIXQ.js} +16 -13
  70. package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
  71. package/dist/{context-4UOGGLQ5.js → context-MGSE4Z2T.js} +33 -23
  72. package/dist/{context-77FM5DV5.js → context-URSXPBCK.js} +17 -9
  73. package/dist/{context-clear-XXJRLCJJ.js → context-clear-KDAJRNUK.js} +33 -23
  74. package/dist/context-working-set-SBKMPPI2.js +1552 -0
  75. package/dist/{dispatch-runner-QPRDDBDX.js → dispatch-runner-MSWN72NK.js} +43 -29
  76. package/dist/{doctor-HR46URBJ.js → doctor-7BSE27PJ.js} +10 -10
  77. package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
  78. package/dist/{evidence-6HG2PY2B.js → evidence-SR7WXB5B.js} +51 -23
  79. package/dist/{evolve-K7YU3NCY.js → evolve-K7VE2CBX.js} +30 -20
  80. package/dist/{fleet-VY3HHKN6.js → fleet-7XMJNQNF.js} +48 -38
  81. package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
  82. package/dist/{init-JYGXI3FK.js → init-JGNPAYXT.js} +41 -31
  83. package/dist/{memory-WFZMGYHX.js → memory-4ALKDJ4Q.js} +32 -22
  84. package/dist/{models-I5QWSEOM.js → models-ZMMLFJNN.js} +22 -19
  85. package/dist/{monitor-GE4ID3IA.js → monitor-2F3T5KHP.js} +55 -43
  86. package/dist/{orchestrator-EM5MC3HM.js → orchestrator-ORHT43JB.js} +572 -388
  87. package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
  88. package/dist/{run-ZU3QMZPZ.js → run-RF4WJGMT.js} +51 -41
  89. package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
  90. package/dist/{skills-X5VXCRNQ.js → skills-PSACKC5Q.js} +2 -2
  91. package/dist/{skills-eval-WKIHWTHR.js → skills-eval-WJSI55RZ.js} +34 -24
  92. package/dist/{targets-SNCPI2NR.js → targets-PIIRAOYS.js} +23 -20
  93. package/dist/{terminal-lease-BNAHVHBS.js → terminal-lease-ULWXWNVY.js} +4 -3
  94. package/dist/{upgrade-JQHHPQ4K.js → upgrade-346TZ6AV.js} +18 -17
  95. package/dist/{usage-OR4O5SMZ.js → usage-6KKXR32N.js} +34 -24
  96. package/dist/verifiers-4UUM6TEE.js +1214 -0
  97. package/dist/verify-X5HDROLA.js +25 -0
  98. package/dist/{wiki-generate-UEXP2ARI.js → wiki-generate-7STOCIFZ.js} +42 -31
  99. package/dist/worker/entry.js +33 -24
  100. package/docs/README.md +7 -6
  101. package/docs/acp.md +1 -1
  102. package/docs/alcf-provider.md +1 -1
  103. package/docs/architecture.md +2 -2
  104. package/docs/artifact-versions.md +1 -1
  105. package/docs/built-in-agents.md +1 -1
  106. package/docs/capacity-and-scheduling.md +1 -1
  107. package/docs/commands-and-modes.md +53 -21
  108. package/docs/config-knobs-audit.md +1 -2
  109. package/docs/configuration-and-targets.md +15 -1
  110. package/docs/context-engine.md +64 -12
  111. package/docs/context-working-set.md +194 -0
  112. package/docs/development-pipeline.md +1 -1
  113. package/docs/documentation-coverage.md +5 -5
  114. package/docs/documentation-guide.md +6 -5
  115. package/docs/environment-variables.md +2 -1
  116. package/docs/eval-runner.md +1 -1
  117. package/docs/evals-internal.md +14 -1
  118. package/docs/evidence-and-memory.md +74 -2
  119. package/docs/evolution.md +1 -1
  120. package/docs/exit-codes-and-output.md +1 -1
  121. package/docs/extensions-and-sharing.md +2 -2
  122. package/docs/fleet-dispatch.md +22 -7
  123. package/docs/glossary.md +21 -1
  124. package/docs/installation-and-lifecycle.md +2 -2
  125. package/docs/middleware-and-components.md +1 -1
  126. package/docs/model-catalog.md +7 -9
  127. package/docs/observability.md +4 -4
  128. package/docs/proactive-memory.md +1 -1
  129. package/docs/prompt-envelope-and-tools.md +4 -4
  130. package/docs/provider-adapter-cookbook.md +1 -1
  131. package/docs/release-cut-checklist.md +31 -31
  132. package/docs/safety-model.md +23 -4
  133. package/docs/scientific-validation.md +21 -3
  134. package/docs/session-lifecycle.md +3 -3
  135. package/docs/skills-marketplace.md +1 -1
  136. package/docs/tool-usage.md +79 -12
  137. package/docs/trace-store.md +1 -1
  138. package/docs/troubleshooting.md +1 -1
  139. package/docs/tui-design.md +1 -1
  140. package/docs/worker-dispatch-mechanics.md +11 -1
  141. package/package.json +8 -11
  142. package/skills/meta/clio-test/SKILL.md +20 -17
  143. package/skills/meta/clio-test/evals.md +3 -3
  144. package/skills/meta/clio-test/references/harness.md +35 -6
  145. package/skills/meta/clio-test/references/test-map.md +20 -10
  146. package/skills/registry.yaml +2 -2
  147. package/skills/skill-marketplace.json +1 -1
  148. package/src/cli/context-working-set.ts +513 -0
  149. package/src/cli/context.ts +8 -0
  150. package/src/cli/evidence.ts +20 -2
  151. package/src/cli/index.ts +4 -0
  152. package/src/cli/verifiers.ts +325 -0
  153. package/src/core/bash-exec.ts +39 -14
  154. package/src/core/bus-events.ts +19 -4
  155. package/src/core/config.ts +54 -0
  156. package/src/core/defaults.ts +50 -3
  157. package/src/core/verification-scripts.ts +6 -0
  158. package/src/domains/agents/builtins/verifier.md +3 -0
  159. package/src/domains/config/classify.ts +1 -0
  160. package/src/domains/context/working-set/contract.ts +161 -0
  161. package/src/domains/context/working-set/defaults.ts +28 -0
  162. package/src/domains/context/working-set/engine.ts +203 -0
  163. package/src/domains/context/working-set/fold.ts +62 -0
  164. package/src/domains/context/working-set/horizon.ts +38 -0
  165. package/src/domains/context/working-set/marker.ts +103 -0
  166. package/src/domains/context/working-set/path-index.ts +436 -0
  167. package/src/domains/context/working-set/payload.ts +152 -0
  168. package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
  169. package/src/domains/context/working-set/policies/index.ts +21 -0
  170. package/src/domains/context/working-set/policies/structural.ts +160 -0
  171. package/src/domains/context/working-set/project.ts +132 -0
  172. package/src/domains/context/working-set/protect.ts +109 -0
  173. package/src/domains/context/working-set/recall.ts +177 -0
  174. package/src/domains/context/working-set/replay/controls.ts +112 -0
  175. package/src/domains/context/working-set/replay/load-clio.ts +199 -0
  176. package/src/domains/context/working-set/replay/metrics.ts +185 -0
  177. package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
  178. package/src/domains/context/working-set/replay/report.ts +139 -0
  179. package/src/domains/context/working-set/replay/runner.ts +325 -0
  180. package/src/domains/context/working-set/replay/synthetic.ts +422 -0
  181. package/src/domains/context/working-set/replay/trace.ts +21 -0
  182. package/src/domains/context/working-set/visible.ts +54 -0
  183. package/src/domains/evidence/build.ts +112 -45
  184. package/src/domains/evidence/eval.ts +24 -7
  185. package/src/domains/evidence/index.ts +53 -0
  186. package/src/domains/evidence/ordering.ts +12 -0
  187. package/src/domains/evidence/run-trust.ts +221 -0
  188. package/src/domains/evidence/store.ts +46 -6
  189. package/src/domains/evidence/trust-status.ts +854 -0
  190. package/src/domains/evidence/types.ts +26 -0
  191. package/src/domains/middleware/memory-intervention.ts +3 -0
  192. package/src/domains/middleware/stalled-turn.ts +165 -4
  193. package/src/domains/safety/autonomy.ts +1 -1
  194. package/src/domains/safety/default-path-policy.ts +8 -0
  195. package/src/domains/safety/finish-contract.ts +4 -3
  196. package/src/domains/safety/policy-engine.ts +48 -6
  197. package/src/domains/session/compaction/compact.ts +23 -1
  198. package/src/domains/session/compaction/cut-point.ts +2 -0
  199. package/src/domains/session/compaction/tokens.ts +16 -1
  200. package/src/domains/session/context-ledger.ts +2 -0
  201. package/src/domains/session/entries.ts +107 -1
  202. package/src/domains/session/manager.ts +9 -2
  203. package/src/domains/session/migrations/index.ts +22 -3
  204. package/src/engine/acp/server.ts +3 -0
  205. package/src/engine/agent.ts +18 -1
  206. package/src/engine/session.ts +9 -3
  207. package/src/entry/orchestrator.ts +16 -4
  208. package/src/interactive/chat-loop-messages.ts +18 -6
  209. package/src/interactive/chat-panel.ts +17 -1
  210. package/src/interactive/chat-renderer.ts +30 -21
  211. package/src/interactive/context-meter.ts +10 -0
  212. package/src/interactive/context-overlay.ts +81 -6
  213. package/src/interactive/context-recall-command.ts +110 -0
  214. package/src/interactive/interactive-slash-runtime.ts +37 -1
  215. package/src/interactive/model-session-replay.ts +21 -0
  216. package/src/interactive/overlay-general-openers.ts +6 -0
  217. package/src/interactive/overlay-session-lifecycle.ts +8 -4
  218. package/src/interactive/renderers/tool-execution.ts +18 -2
  219. package/src/interactive/session-transcript.ts +2 -2
  220. package/src/interactive/slash-commands.ts +29 -2
  221. package/src/interactive/turn-context.ts +238 -88
  222. package/src/interactive/turn-middleware.ts +6 -6
  223. package/src/tools/agent-tools.ts +11 -4
  224. package/src/tools/bash.ts +144 -82
  225. package/src/tools/builtin-tool-catalog.ts +11 -5
  226. package/src/tools/context/index.ts +105 -3
  227. package/src/tools/context/surface.ts +3 -2
  228. package/src/tools/core-bootstrap.ts +21 -0
  229. package/src/tools/dispatch-runner.ts +9 -7
  230. package/src/tools/monitor.ts +28 -20
  231. package/src/tools/registry.ts +59 -7
  232. package/src/tools/result-disposition.ts +550 -0
  233. package/src/tools/result-shaping.ts +262 -19
  234. package/src/tools/safe-exec.ts +2 -0
  235. package/src/tools/verify/authoring.ts +1119 -0
  236. package/src/tools/verify/catalog.ts +346 -0
  237. package/src/tools/verify/index.ts +13 -3
  238. package/src/tools/verify/scripts.ts +135 -37
  239. package/src/tools/verify/surface.ts +9 -5
  240. package/src/tools/worker-evidence.ts +35 -12
  241. package/dist/chunk-J7CWMCQD.js +0 -255
  242. package/dist/chunk-T6YILFSB.js +0 -80
  243. package/dist/chunk-VAKQQHWR.js +0 -434
  244. package/dist/chunk-VPAYEGVX.js +0 -184
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Procedural long-horizon traces for the context engine.
3
+ *
4
+ * Real Clio ledgers on a developer machine are short, and the one long corpus
5
+ * the replay tables used to rest on was private Claude Code transcripts that
6
+ * nobody else could regenerate. This module replaces that corpus with traces
7
+ * any checkout can rebuild byte for byte from a seed: science-coding sessions
8
+ * of hundreds of turns that exercise every structural rung and every metric
9
+ * the replay measures.
10
+ *
11
+ * The grammar is deliberately small. A trace is a run of episodes, each opened
12
+ * by an operator message. An episode picks a focus set of files under a
13
+ * Zipf-shaped popularity so a few files are hot and most are cold, then plays
14
+ * one of four scripts: explore (listings and greps whose surfaced paths are
15
+ * then read), implement (read, edit, read back, so the first read goes stale
16
+ * and the read-back supersedes a range), validate (a test run that fails, an
17
+ * edit, the same command passing, so a failure is resolved), and analyze (a
18
+ * simulation run whose stdout is far over the result cap and is offloaded,
19
+ * then a read of the results file). Episodes return to files from earlier
20
+ * episodes with a tunable probability, which is what produces the long-range
21
+ * future references retention is measured on. Every assistant turn carries a
22
+ * thinking block so thinking eviction has something to remove.
23
+ *
24
+ * Pure over the spec and the seed: same inputs, same entries, same bytes. No
25
+ * clock, no filesystem, no `Math.random`.
26
+ */
27
+
28
+ import type { SessionEntry } from "../../../session/entries.js";
29
+ import type { ReplayLoadCascade } from "./load-clio.js";
30
+ import { countReplayTurns, type Trace } from "./trace.js";
31
+
32
+ export interface SyntheticCorpusSpec {
33
+ /** Stable id, also the trace id prefix and the JSON `corpus` field. */
34
+ id: string;
35
+ seed: number;
36
+ traces: number;
37
+ /** Operator turns per trace. */
38
+ turns: number;
39
+ /** Files in the synthetic repository. */
40
+ files: number;
41
+ /** Probability that an episode returns to a focus set from an earlier episode. */
42
+ returnProbability: number;
43
+ /** Exponent of the file-rank power law; 1 is the log-uniform Zipf case. */
44
+ zipfExponent: number;
45
+ /** Relative weight of each episode script. */
46
+ scripts: { explore: number; implement: number; validate: number; analyze: number };
47
+ /** Lines per file, uniform in this range. */
48
+ fileLines: [number, number];
49
+ /** Lines a simulation prints; anything over `offloadAfterLines` is offloaded. */
50
+ simulationLines: [number, number];
51
+ offloadAfterLines: number;
52
+ }
53
+
54
+ export const SYNTHETIC_CORPORA: ReadonlyArray<SyntheticCorpusSpec> = [
55
+ {
56
+ id: "science-long",
57
+ seed: 7,
58
+ traces: 8,
59
+ turns: 300,
60
+ files: 48,
61
+ returnProbability: 0.3,
62
+ zipfExponent: 1,
63
+ scripts: { explore: 1, implement: 3, validate: 2, analyze: 2 },
64
+ fileLines: [12, 96],
65
+ simulationLines: [100, 2400],
66
+ offloadAfterLines: 200,
67
+ },
68
+ {
69
+ id: "refactor",
70
+ seed: 11,
71
+ traces: 8,
72
+ turns: 200,
73
+ files: 64,
74
+ returnProbability: 0.4,
75
+ zipfExponent: 1,
76
+ scripts: { explore: 1, implement: 5, validate: 2, analyze: 0 },
77
+ fileLines: [16, 110],
78
+ simulationLines: [100, 400],
79
+ offloadAfterLines: 200,
80
+ },
81
+ {
82
+ id: "exploration",
83
+ seed: 13,
84
+ traces: 8,
85
+ turns: 200,
86
+ files: 120,
87
+ returnProbability: 0.15,
88
+ zipfExponent: 1,
89
+ scripts: { explore: 4, implement: 1, validate: 1, analyze: 1 },
90
+ fileLines: [10, 64],
91
+ simulationLines: [100, 800],
92
+ offloadAfterLines: 200,
93
+ },
94
+ ];
95
+
96
+ export function syntheticCorpus(id: string): SyntheticCorpusSpec | undefined {
97
+ return SYNTHETIC_CORPORA.find((spec) => spec.id === id);
98
+ }
99
+
100
+ function mulberry32(seed: number): () => number {
101
+ let value = seed >>> 0;
102
+ return () => {
103
+ value = (value + 0x6d2b79f5) >>> 0;
104
+ let next = value;
105
+ next = Math.imul(next ^ (next >>> 15), next | 1);
106
+ next ^= next + Math.imul(next ^ (next >>> 7), next | 61);
107
+ return ((next ^ (next >>> 14)) >>> 0) / 4_294_967_296;
108
+ };
109
+ }
110
+
111
+ /** Sample zero-based rank from a bounded continuous power law. */
112
+ function powerLawRank(size: number, exponent: number, draw: number): number {
113
+ const upper = size + 1;
114
+ const sample = exponent === 1 ? upper ** draw : (1 + draw * (upper ** (1 - exponent) - 1)) ** (1 / (1 - exponent));
115
+ return Math.min(size - 1, Math.max(0, Math.floor(sample) - 1));
116
+ }
117
+
118
+ const DIRECTORIES = ["src/sim", "src/io", "src/analysis", "src/mesh", "tests", "scripts", "configs"];
119
+ const STEMS = ["solver", "reader", "writer", "grid", "field", "boundary", "stepper", "metrics", "plot", "checkpoint"];
120
+ const EXTENSIONS = ["py", "py", "py", "c", "h", "yaml", "toml"];
121
+
122
+ // A consumed listing must remain plausible under Clio's shipped per-turn
123
+ // ceilings. At most 48 paths are sampled with 20-line reads, so the listing,
124
+ // grep, and reads stay below the 192 KiB observation pool and the 60-call
125
+ // backstop. Broad unconsumed listings still surface the full repository.
126
+ const CONSUMED_LISTING_READ_LIMIT = 48;
127
+ const CONSUMED_LISTING_SAMPLE_LINES = 20;
128
+
129
+ interface RepoFile {
130
+ path: string;
131
+ lines: number;
132
+ version: number;
133
+ }
134
+
135
+ class Ledger {
136
+ readonly entries: SessionEntry[] = [];
137
+ private parent: string | null = null;
138
+ private sequence = 0;
139
+ private calls = 0;
140
+
141
+ constructor(private readonly cwd: string) {}
142
+
143
+ private next(kind: string): { turnId: string; parentTurnId: string | null; timestamp: string } {
144
+ this.sequence += 1;
145
+ const turnId = `${kind}-${String(this.sequence).padStart(5, "0")}`;
146
+ const stamp = {
147
+ turnId,
148
+ parentTurnId: this.parent,
149
+ timestamp: new Date(1_787_000_000_000 + this.sequence * 1000).toISOString(),
150
+ };
151
+ this.parent = turnId;
152
+ return stamp;
153
+ }
154
+
155
+ user(text: string): void {
156
+ this.entries.push({ kind: "message", ...this.next("user"), role: "user", payload: { text } });
157
+ }
158
+
159
+ assistant(thinking: string, text: string): void {
160
+ this.entries.push({
161
+ kind: "message",
162
+ ...this.next("assistant"),
163
+ role: "assistant",
164
+ payload: {
165
+ content: [
166
+ { type: "thinking", thinking },
167
+ { type: "text", text },
168
+ ],
169
+ },
170
+ });
171
+ }
172
+
173
+ call(
174
+ name: string,
175
+ args: Record<string, unknown>,
176
+ text: string,
177
+ options: { isError?: boolean; offload?: boolean } = {},
178
+ ): void {
179
+ this.calls += 1;
180
+ const toolCallId = `call-${String(this.calls).padStart(5, "0")}`;
181
+ this.entries.push({ kind: "message", ...this.next("call"), role: "tool_call", payload: { toolCallId, name, args } });
182
+ const details =
183
+ options.offload === true
184
+ ? { resultDisposition: { offloadPath: `${this.cwd}/.clio-state/scratch/${toolCallId}.txt` } }
185
+ : undefined;
186
+ this.entries.push({
187
+ kind: "message",
188
+ ...this.next("result"),
189
+ role: "tool_result",
190
+ payload: {
191
+ toolCallId,
192
+ toolName: name,
193
+ result: { content: [{ type: "text", text }], ...(details === undefined ? {} : { details }) },
194
+ isError: options.isError === true,
195
+ },
196
+ });
197
+ }
198
+ }
199
+
200
+ function fileBody(file: RepoFile, offset = 0, limit: number | null = null): string {
201
+ const end = limit === null ? file.lines : Math.min(file.lines, offset + limit);
202
+ const lines: string[] = [];
203
+ for (let line = offset + 1; line <= end; line += 1) {
204
+ lines.push(`${String(line).padStart(4, " ")} ${file.path}@v${file.version}: x[${line}] = f(x[${line - 1}]) + eps`);
205
+ }
206
+ return lines.join("\n");
207
+ }
208
+
209
+ function simulationOutput(lines: number, seed: number): string {
210
+ const rnd = mulberry32(seed);
211
+ const out: string[] = [];
212
+ for (let step = 0; step < lines; step += 1) {
213
+ out.push(
214
+ `step ${String(step).padStart(5, "0")} t=${(step * 0.01).toFixed(2)} e=${(1 - rnd() * 1e-3).toFixed(6)} r=${(rnd() * 1e-6).toExponential(2)}`,
215
+ );
216
+ }
217
+ return out.join("\n");
218
+ }
219
+
220
+ function testOutput(files: ReadonlyArray<RepoFile>, failing: RepoFile | null): string {
221
+ const out = [
222
+ "============================= test session starts =============================",
223
+ `collected ${files.length * 4} items`,
224
+ "",
225
+ ];
226
+ for (const file of files) {
227
+ for (let case_ = 1; case_ <= 4; case_ += 1) {
228
+ out.push(
229
+ `${file.path}::test_${file.path.replace(/\W/g, "_")}_${case_} ${file === failing && case_ === 3 ? "FAILED" : "PASSED"} [${String(Math.round((100 * case_) / 4)).padStart(3, " ")}%]`,
230
+ );
231
+ }
232
+ }
233
+ if (failing !== null) {
234
+ const line = Math.max(1, Math.floor(failing.lines / 2));
235
+ out.push(
236
+ "",
237
+ "=================================== FAILURES ===================================",
238
+ `____________________ test_${failing.path.replace(/\W/g, "_")}_3 ____________________`,
239
+ "",
240
+ );
241
+ for (let frame = 0; frame < 12; frame += 1) {
242
+ out.push(
243
+ ` File "${failing.path}", line ${line + frame}, in step_${frame}`,
244
+ ` residual = advance(state, dt) # frame ${frame}`,
245
+ );
246
+ }
247
+ out.push(
248
+ "",
249
+ "AssertionError: residual 3.2e-4 exceeds tolerance 1e-6",
250
+ `FAILED ${failing.path}::test_${failing.path.replace(/\W/g, "_")}_3`,
251
+ );
252
+ }
253
+ out.push(
254
+ "",
255
+ `${failing === null ? files.length * 4 : files.length * 4 - 1} passed, ${failing === null ? 0 : 1} failed`,
256
+ );
257
+ return out.join("\n");
258
+ }
259
+
260
+ /** Generate one trace of the corpus. `index` selects the trace; the spec's seed and index fix every byte. */
261
+ export function generateSyntheticTrace(spec: SyntheticCorpusSpec, index: number): Trace {
262
+ const rnd = mulberry32(spec.seed * 1000 + index);
263
+ const between = (low: number, high: number): number => low + Math.floor(rnd() * (high - low + 1));
264
+ const pick = <T>(items: ReadonlyArray<T>): T => items[Math.floor(rnd() * items.length)] as T;
265
+ const cwd = `/synthetic/${spec.id}-${index}`;
266
+ const ledger = new Ledger(cwd);
267
+
268
+ const repo: RepoFile[] = [];
269
+ for (let file = 0; file < spec.files; file += 1) {
270
+ const dir = DIRECTORIES[file % DIRECTORIES.length] as string;
271
+ const stem = STEMS[Math.floor(file / DIRECTORIES.length) % STEMS.length] as string;
272
+ const ext = EXTENSIONS[file % EXTENSIONS.length] as string;
273
+ repo.push({
274
+ path: `${dir}/${stem}${Math.floor(file / (DIRECTORIES.length * STEMS.length)) || ""}.${ext}`,
275
+ lines: between(spec.fileLines[0], spec.fileLines[1]),
276
+ version: 1,
277
+ });
278
+ }
279
+ /** Zipf over file rank: rank 0 is hot, and every tail rank is reachable. */
280
+ const zipf = (): RepoFile => repo[powerLawRank(repo.length, spec.zipfExponent, rnd())] as RepoFile;
281
+ const scriptNames = (Object.keys(spec.scripts) as Array<keyof typeof spec.scripts>).flatMap((name) =>
282
+ Array.from({ length: spec.scripts[name] }, () => name),
283
+ );
284
+ const history: RepoFile[][] = [];
285
+ let treeListings = 0;
286
+
287
+ const readWhole = (file: RepoFile): void => ledger.call("read", { path: file.path }, fileBody(file));
288
+ const readListingPath = (file: RepoFile): void =>
289
+ ledger.call(
290
+ "read",
291
+ { path: file.path, offset: 1, limit: CONSUMED_LISTING_SAMPLE_LINES },
292
+ fileBody(file, 0, CONSUMED_LISTING_SAMPLE_LINES),
293
+ );
294
+ const readRange = (file: RepoFile): void => {
295
+ const limit = between(20, 80);
296
+ const offset = between(1, Math.max(1, file.lines - limit));
297
+ ledger.call("read", { path: file.path, offset, limit }, fileBody(file, offset - 1, limit));
298
+ };
299
+ const edit = (file: RepoFile): void => {
300
+ file.version += 1;
301
+ ledger.call(
302
+ "edit",
303
+ { path: file.path, edits: [{ oldText: `v${file.version - 1}`, newText: `v${file.version}` }] },
304
+ `Applied 1 edit to ${file.path}`,
305
+ );
306
+ };
307
+ const runTests = (focus: RepoFile[], failing: RepoFile | null): void =>
308
+ ledger.call(
309
+ "bash",
310
+ { command: `pytest ${focus.map((file) => file.path).join(" ")} -q` },
311
+ testOutput(focus, failing),
312
+ { isError: failing !== null },
313
+ );
314
+
315
+ for (let turn = 0; turn < spec.turns; turn += 1) {
316
+ const script = pick(scriptNames);
317
+ const returning = history.length > 0 && rnd() < spec.returnProbability;
318
+ const focus = returning ? (pick(history) as RepoFile[]) : Array.from({ length: between(1, 3) }, zipf);
319
+ if (!returning) history.push(focus);
320
+ const names = focus.map((file) => file.path).join(", ");
321
+ ledger.user(`Episode ${turn + 1}: ${script} ${names}`);
322
+ ledger.assistant(`Plan for ${script}: start from ${names} and keep the residual bounded.`, `Working on ${names}.`);
323
+ switch (script) {
324
+ case "explore": {
325
+ // A listing walks a subtree; relative names resolve against the listed
326
+ // root, exactly as the path index reads a real find/ls result.
327
+ const dir = pick(DIRECTORIES);
328
+ const tree = rnd() < 0.6;
329
+ const discovered = tree
330
+ ? repo.slice(0, between(24, repo.length))
331
+ : repo.filter((file) => file.path.startsWith(`${dir}/`));
332
+ // Every seventh tree listing is a bounded, fully consumed sample. The
333
+ // fixed cadence guarantees an evictable listing in every seed; short
334
+ // directory listings remain below the low-yield eviction floor.
335
+ const consumeListing = tree && treeListings % 7 === 0;
336
+ if (tree) treeListings += 1;
337
+ const listed = consumeListing ? repo.slice(0, CONSUMED_LISTING_READ_LIMIT) : discovered;
338
+ const root = tree ? "." : dir;
339
+ ledger.call(
340
+ rnd() < 0.5 ? "find" : "ls",
341
+ { path: root },
342
+ listed.map((file) => (tree ? file.path : file.path.slice(dir.length + 1))).join("\n"),
343
+ );
344
+ const term = pick(["residual", "checkpoint", "boundary", "tolerance"]);
345
+ const matches = repo.filter(() => rnd() < 0.15).slice(0, 20);
346
+ ledger.call(
347
+ "grep",
348
+ { pattern: term, path: "src" },
349
+ matches
350
+ .map(
351
+ (file) =>
352
+ `${file.path}:${between(1, file.lines)}: ${term} = compute_${term}(state, dt, tol=1e-6) # ${file.path}`,
353
+ )
354
+ .join("\n"),
355
+ );
356
+ // A few listings are walked in full, which is what rung 4 and the
357
+ // discovery edges measure; the rest leave unread paths behind.
358
+ const walk = consumeListing ? listed : listed.slice(0, between(2, 4));
359
+ for (const file of walk) (consumeListing ? readListingPath : readWhole)(file);
360
+ for (const file of matches.slice(0, between(0, 2))) readWhole(file);
361
+ break;
362
+ }
363
+ case "implement": {
364
+ for (const file of focus) {
365
+ readWhole(file);
366
+ edit(file);
367
+ if (rnd() < 0.7) readRange(file);
368
+ }
369
+ break;
370
+ }
371
+ case "validate": {
372
+ const culprit = pick(focus);
373
+ readWhole(culprit);
374
+ runTests(focus, culprit);
375
+ edit(culprit);
376
+ runTests(focus, null);
377
+ break;
378
+ }
379
+ case "analyze": {
380
+ const lines = between(spec.simulationLines[0], spec.simulationLines[1]);
381
+ const text = simulationOutput(lines, spec.seed + turn);
382
+ const offload = lines > spec.offloadAfterLines;
383
+ ledger.call(
384
+ "bash",
385
+ { command: `python scripts/run_sim.py --config configs/run.yaml --steps ${lines}` },
386
+ offload ? `${text.split("\n").slice(0, spec.offloadAfterLines).join("\n")}\n[tool result truncated]` : text,
387
+ { offload },
388
+ );
389
+ const results = pick(focus);
390
+ readRange(results);
391
+ if (rnd() < 0.5)
392
+ ledger.call(
393
+ "grep",
394
+ { pattern: "energy", path: results.path },
395
+ `${results.path}:${between(1, results.lines)}: energy = 0.999`,
396
+ );
397
+ break;
398
+ }
399
+ }
400
+ ledger.assistant(`Checked ${names}; residual acceptable for this episode.`, `Done with episode ${turn + 1}.`);
401
+ }
402
+
403
+ const entries = ledger.entries;
404
+ return {
405
+ id: `${spec.id}-${String(index).padStart(2, "0")}`,
406
+ source: `synthetic:${spec.id}#${index}`,
407
+ cwd,
408
+ entries,
409
+ turnCount: countReplayTurns(entries),
410
+ };
411
+ }
412
+
413
+ /** Every trace of the named corpora, with a cascade shaped like the ledger loader's. */
414
+ export function generateSyntheticCorpora(ids: ReadonlyArray<string>): { traces: Trace[]; cascade: ReplayLoadCascade } {
415
+ const traces: Trace[] = [];
416
+ for (const id of ids) {
417
+ const spec = syntheticCorpus(id);
418
+ if (spec === undefined) throw new Error(`unknown synthetic corpus: ${id}`);
419
+ for (let index = 0; index < spec.traces; index += 1) traces.push(generateSyntheticTrace(spec, index));
420
+ }
421
+ return { traces, cascade: { found: traces.length, unreadable: 0, filtered: {}, kept: traces.length } };
422
+ }
@@ -0,0 +1,21 @@
1
+ import type { SessionEntry } from "../../../session/entries.js";
2
+ import { isTurnStart } from "../horizon.js";
3
+
4
+ /** One active-path Clio ledger prepared for deterministic replay. */
5
+ export interface Trace {
6
+ id: string;
7
+ source: string;
8
+ /** Session working directory the ledger's relative paths resolve against; null when the source did not record one. */
9
+ cwd: string | null;
10
+ entries: ReadonlyArray<SessionEntry>;
11
+ turnCount: number;
12
+ }
13
+
14
+ /** Turns in the sense the protection horizon counts them (`isTurnStart` in horizon.ts). */
15
+ export function countReplayTurns(entries: ReadonlyArray<SessionEntry>): number {
16
+ let count = 0;
17
+ for (const entry of entries) {
18
+ if (isTurnStart(entry)) count += 1;
19
+ }
20
+ return count;
21
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The entries the model can currently see.
3
+ *
4
+ * A policy that is shown the whole active path will happily "evict" results
5
+ * that a compaction summary already removed from the replay. Those items price
6
+ * as real savings, so the event records tokens it never freed, the structural
7
+ * age rung stops early believing it reached target, and the summary stage runs
8
+ * again for nothing. So the policy input and the replay builder share one cut:
9
+ * `compactionCut` is the single definition of "after the latest compaction",
10
+ * and `selectReplayEntries` in chat-renderer.ts builds on it rather than
11
+ * keeping a second copy that has to agree.
12
+ *
13
+ * The fold (`WorkingSetView`) deliberately keeps running over the full active
14
+ * path so refs evicted before a later compaction stay known as evicted.
15
+ */
16
+
17
+ import type { SessionEntry } from "../../session/entries.js";
18
+ import { filterEntriesToActivePath } from "../../session/tree/active-path.js";
19
+
20
+ export interface CompactionCut {
21
+ /** Index of the latest `compactionSummary` in the slice; -1 when there is none. */
22
+ compactionIndex: number;
23
+ /**
24
+ * What the model sees after the cut, without the compaction entry itself:
25
+ * the kept tail from `firstKeptTurnId` up to the summary, then everything
26
+ * after it. The whole slice when there is no compaction.
27
+ */
28
+ visible: SessionEntry[];
29
+ }
30
+
31
+ /** Apply the latest compaction's cut to a slice that is already on one path, in ledger order. */
32
+ export function compactionCut(entries: ReadonlyArray<SessionEntry>): CompactionCut {
33
+ let compactionIndex = -1;
34
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
35
+ if (entries[i]?.kind === "compactionSummary") {
36
+ compactionIndex = i;
37
+ break;
38
+ }
39
+ }
40
+ const compaction = entries[compactionIndex];
41
+ if (compaction?.kind !== "compactionSummary") return { compactionIndex: -1, visible: [...entries] };
42
+ const firstKeptIndex =
43
+ compaction.firstKeptTurnId.length > 0
44
+ ? entries.findIndex((entry) => entry.turnId === compaction.firstKeptTurnId)
45
+ : -1;
46
+ const kept =
47
+ firstKeptIndex >= 0 && firstKeptIndex < compactionIndex ? entries.slice(firstKeptIndex, compactionIndex) : [];
48
+ return { compactionIndex, visible: [...kept, ...entries.slice(compactionIndex + 1)] };
49
+ }
50
+
51
+ /** Active path, then the compaction cut: the slice a policy may select from. */
52
+ export function selectVisibleEntries(entries: ReadonlyArray<SessionEntry>, activeLeafTurnId?: string): SessionEntry[] {
53
+ return compactionCut(filterEntriesToActivePath(entries, activeLeafTurnId)).visible;
54
+ }