@iowarp/clio-coder 0.3.2 → 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 (259) hide show
  1. package/CHANGELOG.md +269 -458
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +3 -3
  4. package/dist/{acp-BIYHVZIM.js → acp-S5R4RR5B.js} +7 -6
  5. package/dist/{agents-YT6SSRIT.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-WMSVI4G2.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-WVO7V2QY.js → chunk-35MKKU5R.js} +4 -4
  13. package/dist/{chunk-LBNRH5WM.js → chunk-3HZ5RWN2.js} +5 -5
  14. package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
  15. package/dist/{chunk-MBS4V7ZP.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-77VKQEHF.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-J5HN4RYU.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-G2DE3C7R.js → chunk-EDRHSCIE.js} +4 -4
  29. package/dist/{chunk-4KLWL3UC.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-EPVUXGXG.js → chunk-HV5X7OR2.js} +14 -12
  33. package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
  34. package/dist/{chunk-IGLFWIYI.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-V4RXGQ5Q.js → chunk-KRPY7NTG.js} +10 -7
  38. package/dist/chunk-LL4KHSZI.js +22 -0
  39. package/dist/{chunk-KJ5LWLOE.js → chunk-MEQ45TQ4.js} +15 -9
  40. package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
  41. package/dist/{chunk-AO4RKG4M.js → chunk-N4CZJQRK.js} +5 -5
  42. package/dist/{chunk-ARBGF5F7.js → chunk-NILBFAPG.js} +14 -8
  43. package/dist/chunk-OZNBF4L3.js +23 -0
  44. package/dist/{verify-G6V4D2G7.js → chunk-PCZJO5TI.js} +127 -42
  45. package/dist/chunk-QQK64KLB.js +1360 -0
  46. package/dist/{chunk-6EJV5X2W.js → chunk-QQL5RT5M.js} +979 -1619
  47. package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
  48. package/dist/{chunk-2EHAIA3X.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-7EYHLWU7.js → chunk-UZHIZC5S.js} +7 -7
  55. package/dist/{chunk-QTYWRVRA.js → chunk-VAWWTKDP.js} +8 -8
  56. package/dist/{chunk-X75S7HFS.js → chunk-VEZEGCGW.js} +214 -20
  57. package/dist/{chunk-OHHN2SO4.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-3ZXDFGR5.js → chunk-X6COSD2O.js} +5 -5
  63. package/dist/chunk-ZGVHUX3M.js +66 -0
  64. package/dist/{chunk-MAW544W2.js → chunk-ZWMF7253.js} +4 -4
  65. package/dist/{chunk-MQSRRFWA.js → chunk-ZYKPLLNQ.js} +563 -546
  66. package/dist/cli/index.js +27 -23
  67. package/dist/{clio-4LY5K2AC.js → clio-J5JIOIDS.js} +7 -6
  68. package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
  69. package/dist/{config-GTLUW2PR.js → config-OEBMIN2U.js} +37 -27
  70. package/dist/{configure-R6A64DHX.js → configure-PUQOSIXQ.js} +16 -13
  71. package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
  72. package/dist/{context-RW5HC47S.js → context-MGSE4Z2T.js} +33 -23
  73. package/dist/{context-JFZEJ7W5.js → context-URSXPBCK.js} +17 -9
  74. package/dist/{context-clear-6ZHBAZZT.js → context-clear-KDAJRNUK.js} +33 -23
  75. package/dist/context-working-set-SBKMPPI2.js +1552 -0
  76. package/dist/{dispatch-runner-VKBRCWQC.js → dispatch-runner-MSWN72NK.js} +43 -29
  77. package/dist/{doctor-KI767GSN.js → doctor-7BSE27PJ.js} +10 -10
  78. package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
  79. package/dist/{evidence-UA6AWDQQ.js → evidence-SR7WXB5B.js} +51 -23
  80. package/dist/{evolve-QNTFGV6Z.js → evolve-K7VE2CBX.js} +30 -20
  81. package/dist/{fleet-Q7UOMUSG.js → fleet-7XMJNQNF.js} +48 -38
  82. package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
  83. package/dist/{init-WBB65ZHQ.js → init-JGNPAYXT.js} +41 -31
  84. package/dist/{memory-MD3O64RI.js → memory-4ALKDJ4Q.js} +32 -22
  85. package/dist/{models-BZU34YWD.js → models-ZMMLFJNN.js} +22 -19
  86. package/dist/{monitor-MEQA5C3I.js → monitor-2F3T5KHP.js} +55 -43
  87. package/dist/{orchestrator-CGFKEP27.js → orchestrator-ORHT43JB.js} +2507 -1896
  88. package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
  89. package/dist/{run-IV4Q6RLN.js → run-RF4WJGMT.js} +51 -41
  90. package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
  91. package/dist/{skills-LQEKRDTN.js → skills-PSACKC5Q.js} +2 -2
  92. package/dist/{skills-eval-3DC4HEWS.js → skills-eval-WJSI55RZ.js} +34 -24
  93. package/dist/{targets-C4SSGQOB.js → targets-PIIRAOYS.js} +23 -20
  94. package/dist/{terminal-lease-IT5JW2NR.js → terminal-lease-ULWXWNVY.js} +5 -4
  95. package/dist/{upgrade-7TT7SQ3G.js → upgrade-346TZ6AV.js} +18 -17
  96. package/dist/{usage-GV4PKT3M.js → usage-6KKXR32N.js} +34 -24
  97. package/dist/verifiers-4UUM6TEE.js +1214 -0
  98. package/dist/verify-X5HDROLA.js +25 -0
  99. package/dist/{wiki-generate-DQF6Z66B.js → wiki-generate-7STOCIFZ.js} +42 -31
  100. package/dist/worker/entry.js +33 -24
  101. package/docs/README.md +8 -7
  102. package/docs/acp.md +1 -1
  103. package/docs/alcf-provider.md +1 -1
  104. package/docs/architecture.md +2 -2
  105. package/docs/artifact-versions.md +1 -1
  106. package/docs/built-in-agents.md +1 -1
  107. package/docs/capacity-and-scheduling.md +1 -1
  108. package/docs/commands-and-modes.md +53 -21
  109. package/docs/config-knobs-audit.md +1 -2
  110. package/docs/configuration-and-targets.md +15 -1
  111. package/docs/context-engine.md +64 -12
  112. package/docs/context-working-set.md +194 -0
  113. package/docs/development-pipeline.md +1 -1
  114. package/docs/documentation-coverage.md +5 -5
  115. package/docs/documentation-guide.md +6 -5
  116. package/docs/environment-variables.md +2 -1
  117. package/docs/eval-runner.md +1 -1
  118. package/docs/evals-internal.md +14 -1
  119. package/docs/evidence-and-memory.md +74 -2
  120. package/docs/evolution.md +1 -1
  121. package/docs/exit-codes-and-output.md +1 -1
  122. package/docs/extensions-and-sharing.md +2 -2
  123. package/docs/fleet-dispatch.md +22 -7
  124. package/docs/glossary.md +21 -1
  125. package/docs/installation-and-lifecycle.md +6 -6
  126. package/docs/middleware-and-components.md +1 -1
  127. package/docs/model-catalog.md +7 -9
  128. package/docs/observability.md +4 -4
  129. package/docs/performance-methodology.md +2 -2
  130. package/docs/proactive-memory.md +1 -1
  131. package/docs/prompt-envelope-and-tools.md +4 -4
  132. package/docs/provider-adapter-cookbook.md +1 -1
  133. package/docs/release-cut-checklist.md +35 -35
  134. package/docs/safety-model.md +23 -4
  135. package/docs/scientific-validation.md +21 -3
  136. package/docs/session-lifecycle.md +3 -3
  137. package/docs/skills-marketplace.md +1 -1
  138. package/docs/tool-usage.md +79 -12
  139. package/docs/trace-store.md +1 -1
  140. package/docs/troubleshooting.md +1 -1
  141. package/docs/tui-design.md +2 -2
  142. package/docs/worker-dispatch-mechanics.md +11 -1
  143. package/package.json +8 -11
  144. package/skills/meta/clio-test/SKILL.md +20 -17
  145. package/skills/meta/clio-test/evals.md +3 -3
  146. package/skills/meta/clio-test/references/harness.md +35 -6
  147. package/skills/meta/clio-test/references/test-map.md +20 -10
  148. package/skills/registry.yaml +2 -2
  149. package/skills/skill-marketplace.json +1 -1
  150. package/src/cli/context-working-set.ts +513 -0
  151. package/src/cli/context.ts +8 -0
  152. package/src/cli/evidence.ts +20 -2
  153. package/src/cli/index.ts +4 -0
  154. package/src/cli/verifiers.ts +325 -0
  155. package/src/core/bash-exec.ts +39 -14
  156. package/src/core/bus-events.ts +19 -4
  157. package/src/core/config.ts +54 -0
  158. package/src/core/defaults.ts +50 -3
  159. package/src/core/git-commit-attribution.ts +46 -21
  160. package/src/core/verification-scripts.ts +6 -0
  161. package/src/domains/agents/builtins/verifier.md +3 -0
  162. package/src/domains/config/classify.ts +1 -0
  163. package/src/domains/config/keybindings.ts +3 -3
  164. package/src/domains/context/working-set/contract.ts +161 -0
  165. package/src/domains/context/working-set/defaults.ts +28 -0
  166. package/src/domains/context/working-set/engine.ts +203 -0
  167. package/src/domains/context/working-set/fold.ts +62 -0
  168. package/src/domains/context/working-set/horizon.ts +38 -0
  169. package/src/domains/context/working-set/marker.ts +103 -0
  170. package/src/domains/context/working-set/path-index.ts +436 -0
  171. package/src/domains/context/working-set/payload.ts +152 -0
  172. package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
  173. package/src/domains/context/working-set/policies/index.ts +21 -0
  174. package/src/domains/context/working-set/policies/structural.ts +160 -0
  175. package/src/domains/context/working-set/project.ts +132 -0
  176. package/src/domains/context/working-set/protect.ts +109 -0
  177. package/src/domains/context/working-set/recall.ts +177 -0
  178. package/src/domains/context/working-set/replay/controls.ts +112 -0
  179. package/src/domains/context/working-set/replay/load-clio.ts +199 -0
  180. package/src/domains/context/working-set/replay/metrics.ts +185 -0
  181. package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
  182. package/src/domains/context/working-set/replay/report.ts +139 -0
  183. package/src/domains/context/working-set/replay/runner.ts +325 -0
  184. package/src/domains/context/working-set/replay/synthetic.ts +422 -0
  185. package/src/domains/context/working-set/replay/trace.ts +21 -0
  186. package/src/domains/context/working-set/visible.ts +54 -0
  187. package/src/domains/evidence/build.ts +112 -45
  188. package/src/domains/evidence/eval.ts +24 -7
  189. package/src/domains/evidence/index.ts +53 -0
  190. package/src/domains/evidence/ordering.ts +12 -0
  191. package/src/domains/evidence/run-trust.ts +221 -0
  192. package/src/domains/evidence/store.ts +46 -6
  193. package/src/domains/evidence/trust-status.ts +854 -0
  194. package/src/domains/evidence/types.ts +26 -0
  195. package/src/domains/middleware/memory-intervention.ts +3 -0
  196. package/src/domains/middleware/stalled-turn.ts +165 -4
  197. package/src/domains/safety/autonomy.ts +1 -1
  198. package/src/domains/safety/default-path-policy.ts +8 -0
  199. package/src/domains/safety/finish-contract.ts +4 -3
  200. package/src/domains/safety/policy-engine.ts +48 -6
  201. package/src/domains/session/compaction/compact.ts +23 -1
  202. package/src/domains/session/compaction/cut-point.ts +2 -0
  203. package/src/domains/session/compaction/tokens.ts +16 -1
  204. package/src/domains/session/context-ledger.ts +2 -0
  205. package/src/domains/session/entries.ts +107 -1
  206. package/src/domains/session/manager.ts +9 -2
  207. package/src/domains/session/migrations/index.ts +22 -3
  208. package/src/engine/acp/server.ts +3 -0
  209. package/src/engine/agent.ts +18 -1
  210. package/src/engine/session.ts +9 -3
  211. package/src/entry/orchestrator.ts +16 -4
  212. package/src/interactive/chat-loop-messages.ts +18 -6
  213. package/src/interactive/chat-panel.ts +571 -244
  214. package/src/interactive/chat-renderer.ts +79 -39
  215. package/src/interactive/context-meter.ts +10 -0
  216. package/src/interactive/context-overlay.ts +81 -6
  217. package/src/interactive/context-recall-command.ts +110 -0
  218. package/src/interactive/editor-submit.ts +26 -1
  219. package/src/interactive/footer/widgets.ts +22 -20
  220. package/src/interactive/footer-panel.ts +6 -1
  221. package/src/interactive/interactive-application.ts +2 -0
  222. package/src/interactive/interactive-event-projection.ts +12 -0
  223. package/src/interactive/interactive-slash-runtime.ts +49 -8
  224. package/src/interactive/model-session-replay.ts +21 -0
  225. package/src/interactive/overlay-general-openers.ts +6 -0
  226. package/src/interactive/overlay-session-lifecycle.ts +8 -4
  227. package/src/interactive/overlays/ask-user.ts +146 -24
  228. package/src/interactive/renderers/tool-execution.ts +167 -56
  229. package/src/interactive/session-transcript.ts +2 -2
  230. package/src/interactive/slash-commands.ts +29 -2
  231. package/src/interactive/status/index.ts +12 -1
  232. package/src/interactive/status/reasoning.ts +87 -0
  233. package/src/interactive/status/summary.ts +13 -2
  234. package/src/interactive/transcript-detail.ts +120 -0
  235. package/src/interactive/turn-context.ts +238 -88
  236. package/src/interactive/turn-middleware.ts +6 -6
  237. package/src/tools/agent-tools.ts +11 -4
  238. package/src/tools/bash.ts +144 -82
  239. package/src/tools/builtin-tool-catalog.ts +18 -6
  240. package/src/tools/context/index.ts +105 -3
  241. package/src/tools/context/surface.ts +3 -2
  242. package/src/tools/core-bootstrap.ts +21 -0
  243. package/src/tools/dispatch-runner.ts +9 -7
  244. package/src/tools/monitor.ts +28 -20
  245. package/src/tools/presentation.ts +107 -0
  246. package/src/tools/registry.ts +65 -7
  247. package/src/tools/result-disposition.ts +550 -0
  248. package/src/tools/result-shaping.ts +262 -19
  249. package/src/tools/safe-exec.ts +2 -0
  250. package/src/tools/verify/authoring.ts +1119 -0
  251. package/src/tools/verify/catalog.ts +346 -0
  252. package/src/tools/verify/index.ts +13 -3
  253. package/src/tools/verify/scripts.ts +135 -37
  254. package/src/tools/verify/surface.ts +9 -5
  255. package/src/tools/worker-evidence.ts +35 -12
  256. package/dist/chunk-MNA4JGU4.js +0 -255
  257. package/dist/chunk-SRF2PJNW.js +0 -184
  258. package/dist/chunk-T6YILFSB.js +0 -80
  259. package/dist/chunk-VAKQQHWR.js +0 -434
@@ -1,6 +1,6 @@
1
1
  import { InvalidIdError } from "../core/safe-id.js";
2
2
  import { clioDataDir, clioStateDir } from "../core/xdg.js";
3
- import type { EvidenceOverview, EvidenceRunProvenance } from "../domains/evidence/index.js";
3
+ import type { CanonicalTrustStatus, EvidenceOverview, EvidenceRunProvenance } from "../domains/evidence/index.js";
4
4
  import {
5
5
  buildEvalEvidence,
6
6
  buildEvidence,
@@ -178,7 +178,7 @@ function renderEvidence(
178
178
  input: Awaited<ReturnType<typeof inspectEvidence>>,
179
179
  provenance: ReadonlyArray<EvidenceRunProvenance> = [],
180
180
  ): void {
181
- const { overview, findings } = input;
181
+ const { overview, findings, trustStatus } = input;
182
182
  process.stdout.write(`evidence: ${overview.evidenceId}\n`);
183
183
  process.stdout.write(`source: ${formatSource(overview)}\n`);
184
184
  process.stdout.write(`generated: ${overview.generatedAt}\n`);
@@ -188,6 +188,13 @@ function renderEvidence(
188
188
  process.stdout.write(`blocked tools: ${overview.totals.blockedToolCalls}\n`);
189
189
  process.stdout.write(`tags: ${formatList(overview.tags)}\n`);
190
190
  process.stdout.write(`findings: ${findings.length}\n`);
191
+ if (trustStatus.projection === "historical_format") {
192
+ process.stdout.write("trust status: unavailable (historical bundle without canonical projection)\n");
193
+ } else {
194
+ for (const run of trustStatus.runs) {
195
+ process.stdout.write(`trust ${run.runId}: ${formatTrustStatus(run.status)}\n`);
196
+ }
197
+ }
191
198
  // Provenance is printed only for runs whose receipts carry it, so a legacy
192
199
  // bundle prints nothing new here.
193
200
  for (const { runId, view } of provenance) {
@@ -197,6 +204,17 @@ function renderEvidence(
197
204
  process.stdout.write(`files: ${formatList(overview.files)}\n`);
198
205
  }
199
206
 
207
+ function formatTrustStatus(status: CanonicalTrustStatus): string {
208
+ return [
209
+ `artifactIntegrity=${status.artifactIntegrity.state}`,
210
+ `validationGrounding=${status.validationGrounding.state}`,
211
+ `independentReview=${status.independentReview.state}`,
212
+ `contextProvenance=${status.contextProvenance.state}`,
213
+ `autonomyEnforcement=${status.autonomyEnforcement.state}`,
214
+ `completionEvidence=${status.completionEvidence.state}`,
215
+ ].join(" ");
216
+ }
217
+
200
218
  function renderEvidenceList(overviews: ReadonlyArray<EvidenceOverview>): void {
201
219
  process.stdout.write(`${overviews.length} evidence artifacts\n`);
202
220
  if (overviews.length === 0) return;
package/src/cli/index.ts CHANGED
@@ -52,6 +52,8 @@ Usage:
52
52
  clio-coder context wiki [--update] [--status] [--depth auto|simple|medium|detailed] [--target <id>] [--model <id>]
53
53
  clio-coder context reset [--all] [--yes] clear accumulated project context artifacts
54
54
  clio-coder context index [--json] build the codewiki index without model calls
55
+ clio-coder context replay --sessions <path>... compare working-set policies over Clio ledgers
56
+ clio-coder context working-set --session <id|path> inspect one session's working-set state
55
57
  clio-coder uninstall remove all Clio Coder state; --remove-binary also unlinks the launcher
56
58
  clio-coder upgrade upgrade Clio Coder and run pending migrations
57
59
  clio-coder agents list discovered agent recipes
@@ -63,6 +65,7 @@ Usage:
63
65
  clio-coder trace query or view the durable dispatch trace mirror
64
66
  clio-coder extensions install, list, enable, disable, or remove extension packages
65
67
  clio-coder skills list, inspect, validate, or install skills
68
+ clio-coder verifiers discover, author, validate, edit, or dry-run project checks
66
69
  clio-coder docs [topic] serve the HTML docs of a source checkout on 127.0.0.1 (--no-open to skip browser)
67
70
  clio-coder dev <command> harness instruments; run 'clio-coder dev' for the list
68
71
  clio-coder --help, -h this message
@@ -243,6 +246,7 @@ const COMMAND_HANDLERS = new Map<string, CommandHandler>([
243
246
  ["ext", extensionsCommand],
244
247
  ["fleet", async (subArgs) => (await import("./fleet.js")).runFleetCommand(subArgs)],
245
248
  ["skills", async (subArgs) => (await import("./skills.js")).runSkillsCommand(subArgs)],
249
+ ["verifiers", async (subArgs) => (await import("./verifiers.js")).runVerifiersCommand(subArgs)],
246
250
  ["docs", async (subArgs) => (await import("./docs.js")).runDocsCommand(subArgs)],
247
251
  ["share", async (subArgs) => (await import("./share.js")).runShareCommand(subArgs)],
248
252
  ["export", async (subArgs) => (await import("./share.js")).runExportCommand(subArgs)],
@@ -0,0 +1,325 @@
1
+ import { SAFE_EXEC_DEFAULT_TIMEOUT_MS } from "../core/safe-exec.js";
2
+ import {
3
+ createVerifierDraft,
4
+ discoverVerifierAuthoring,
5
+ previewVerifierDraft,
6
+ runVerifierAuthoringWorkflow,
7
+ type VerifierRevision,
8
+ } from "../tools/verify/authoring.js";
9
+ import { loadProjectVerifierCatalog, PROJECT_VERIFIER_CATALOG_RELATIVE_PATH } from "../tools/verify/catalog.js";
10
+ import { verifyTool } from "../tools/verify/index.js";
11
+ import { printError, printOk } from "./argv.js";
12
+
13
+ const HELP = `Project verifier authoring
14
+
15
+ Usage:
16
+ clio-coder verifiers discover
17
+ clio-coder verifiers author [--exclude <id>] [--rename <old>=<new>] [--dry-run <id>] [--yes]
18
+ clio-coder verifiers validate
19
+ clio-coder verifiers dry-run <id>
20
+ clio-coder verifiers add --id <id> --description <text> --command <json-argv> [fields] [--yes]
21
+ clio-coder verifiers edit <id> [--description <text>] [--command <json-argv>] [fields] [--yes]
22
+ clio-coder verifiers rename <old> <new> [--yes]
23
+ clio-coder verifiers remove <id> [--yes]
24
+
25
+ Fields:
26
+ --cwd <path> repository-relative working directory (default .)
27
+ --timeout-ms <number> bounded timeout in milliseconds (default 120000)
28
+ --tags <a,b> comma-separated catalog tags
29
+
30
+ Discovery and every preview are read-only. Mutating commands write only with
31
+ --yes after printing the exact argv, cwd, timeout, tags, source provenance, and
32
+ effective execution authority. dry-run invokes the production verify path.
33
+ `;
34
+
35
+ const VALUE_OPTIONS = new Set([
36
+ "--id",
37
+ "--description",
38
+ "--command",
39
+ "--cwd",
40
+ "--timeout-ms",
41
+ "--tags",
42
+ "--exclude",
43
+ "--rename",
44
+ "--dry-run",
45
+ ]);
46
+
47
+ interface ParsedAuthoringArgs {
48
+ yes: boolean;
49
+ help: boolean;
50
+ positional: string[];
51
+ values: Map<string, string[]>;
52
+ error?: string;
53
+ }
54
+
55
+ function parseAuthoringArgs(argv: ReadonlyArray<string>): ParsedAuthoringArgs {
56
+ const positional: string[] = [];
57
+ const values = new Map<string, string[]>();
58
+ let yes = false;
59
+ let help = false;
60
+ for (let index = 0; index < argv.length; index += 1) {
61
+ const arg = argv[index];
62
+ if (arg === "--yes") {
63
+ yes = true;
64
+ continue;
65
+ }
66
+ if (arg === "--help" || arg === "-h") {
67
+ help = true;
68
+ continue;
69
+ }
70
+ if (arg?.startsWith("--") === true) {
71
+ if (!VALUE_OPTIONS.has(arg)) return { yes, help, positional, values, error: `unknown option: ${arg}` };
72
+ const value = argv[index + 1];
73
+ if (value === undefined || value.startsWith("--")) {
74
+ return { yes, help, positional, values, error: `${arg} requires a value` };
75
+ }
76
+ const prior = values.get(arg) ?? [];
77
+ prior.push(value);
78
+ values.set(arg, prior);
79
+ index += 1;
80
+ continue;
81
+ }
82
+ if (arg !== undefined) positional.push(arg);
83
+ }
84
+ return { yes, help, positional, values };
85
+ }
86
+
87
+ function oneValue(parsed: ParsedAuthoringArgs, option: string): string | undefined {
88
+ return parsed.values.get(option)?.at(-1);
89
+ }
90
+
91
+ function parseCommand(value: string | undefined): string[] | Error {
92
+ if (value === undefined) return new Error("--command is required and must be a JSON argv array");
93
+ let parsed: unknown;
94
+ try {
95
+ parsed = JSON.parse(value) as unknown;
96
+ } catch (error) {
97
+ return new Error(`--command must be valid JSON: ${error instanceof Error ? error.message : String(error)}`);
98
+ }
99
+ if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some((entry) => typeof entry !== "string")) {
100
+ return new Error("--command must be a non-empty JSON string array");
101
+ }
102
+ return parsed as string[];
103
+ }
104
+
105
+ function parseTimeout(value: string | undefined): number | Error {
106
+ if (value === undefined) return SAFE_EXEC_DEFAULT_TIMEOUT_MS;
107
+ const parsed = Number(value);
108
+ if (!Number.isInteger(parsed) || parsed <= 0) return new Error("--timeout-ms must be a positive integer");
109
+ return parsed;
110
+ }
111
+
112
+ function tags(value: string | undefined): string[] {
113
+ return value === undefined
114
+ ? []
115
+ : value
116
+ .split(",")
117
+ .map((tag) => tag.trim())
118
+ .filter((tag) => tag.length > 0);
119
+ }
120
+
121
+ function requiredValue(parsed: ParsedAuthoringArgs, option: string): string | Error {
122
+ const value = oneValue(parsed, option);
123
+ return value === undefined || value.length === 0 ? new Error(`${option} is required`) : value;
124
+ }
125
+
126
+ function authorRevisions(parsed: ParsedAuthoringArgs): VerifierRevision[] | Error {
127
+ const revisions: VerifierRevision[] = [];
128
+ for (const id of parsed.values.get("--exclude") ?? []) revisions.push({ kind: "remove", id });
129
+ for (const rename of parsed.values.get("--rename") ?? []) {
130
+ const separator = rename.indexOf("=");
131
+ if (separator <= 0 || separator === rename.length - 1) {
132
+ return new Error("--rename must use <old>=<new>");
133
+ }
134
+ revisions.push({ kind: "rename", id: rename.slice(0, separator), newId: rename.slice(separator + 1) });
135
+ }
136
+ return revisions;
137
+ }
138
+
139
+ function addRevision(parsed: ParsedAuthoringArgs): VerifierRevision | Error {
140
+ const id = requiredValue(parsed, "--id");
141
+ if (id instanceof Error) return id;
142
+ const description = requiredValue(parsed, "--description");
143
+ if (description instanceof Error) return description;
144
+ const command = parseCommand(oneValue(parsed, "--command"));
145
+ if (command instanceof Error) return command;
146
+ const timeoutMs = parseTimeout(oneValue(parsed, "--timeout-ms"));
147
+ if (timeoutMs instanceof Error) return timeoutMs;
148
+ return {
149
+ kind: "add",
150
+ check: {
151
+ id,
152
+ description,
153
+ command,
154
+ cwd: oneValue(parsed, "--cwd") ?? ".",
155
+ timeoutMs,
156
+ tags: tags(oneValue(parsed, "--tags")),
157
+ },
158
+ };
159
+ }
160
+
161
+ function editRevision(parsed: ParsedAuthoringArgs): VerifierRevision | Error {
162
+ const id = parsed.positional[1];
163
+ if (id === undefined) return new Error("edit requires a check ID");
164
+ const changes: Extract<VerifierRevision, { kind: "edit" }>["changes"] = {};
165
+ const description = oneValue(parsed, "--description");
166
+ if (description !== undefined) changes.description = description;
167
+ const commandValue = oneValue(parsed, "--command");
168
+ if (commandValue !== undefined) {
169
+ const command = parseCommand(commandValue);
170
+ if (command instanceof Error) return command;
171
+ changes.command = command;
172
+ }
173
+ const cwd = oneValue(parsed, "--cwd");
174
+ if (cwd !== undefined) changes.cwd = cwd;
175
+ const timeoutValue = oneValue(parsed, "--timeout-ms");
176
+ if (timeoutValue !== undefined) {
177
+ const timeoutMs = parseTimeout(timeoutValue);
178
+ if (timeoutMs instanceof Error) return timeoutMs;
179
+ changes.timeoutMs = timeoutMs;
180
+ }
181
+ const tagValue = oneValue(parsed, "--tags");
182
+ if (tagValue !== undefined) changes.tags = tags(tagValue);
183
+ if (Object.keys(changes).length === 0) return new Error("edit requires at least one changed field");
184
+ return { kind: "edit", id, changes };
185
+ }
186
+
187
+ function mutationRevision(parsed: ParsedAuthoringArgs): VerifierRevision | Error {
188
+ const command = parsed.positional[0];
189
+ if (command === "add") return addRevision(parsed);
190
+ if (command === "edit") return editRevision(parsed);
191
+ if (command === "rename") {
192
+ const id = parsed.positional[1];
193
+ const newId = parsed.positional[2];
194
+ if (id === undefined || newId === undefined) return new Error("rename requires <old> and <new> IDs");
195
+ return { kind: "rename", id, newId };
196
+ }
197
+ if (command === "remove") {
198
+ const id = parsed.positional[1];
199
+ if (id === undefined) return new Error("remove requires a check ID");
200
+ return { kind: "remove", id };
201
+ }
202
+ return new Error(`unknown verifiers command: ${command ?? "(none)"}`);
203
+ }
204
+
205
+ function printDryRun(id: string, result: Awaited<ReturnType<typeof verifyTool.run>>): number {
206
+ if (result.kind === "error") {
207
+ printError(`dry-run '${id}' failed: ${result.message}`);
208
+ return 1;
209
+ }
210
+ process.stdout.write(`Dry run '${id}':\n${result.output}${result.output.endsWith("\n") ? "" : "\n"}`);
211
+ return 0;
212
+ }
213
+
214
+ async function executeWorkflow(
215
+ parsed: ParsedAuthoringArgs,
216
+ options: { includeProposals: boolean; revisions: VerifierRevision[] },
217
+ ): Promise<number> {
218
+ let printedPreview = false;
219
+ const dryRunCheckIds = parsed.values.get("--dry-run") ?? [];
220
+ const result = await runVerifierAuthoringWorkflow({
221
+ includeProposals: options.includeProposals,
222
+ initialRevisions: options.revisions,
223
+ confirmed: parsed.yes,
224
+ decide(context) {
225
+ process.stdout.write(`${context.preview}\n`);
226
+ printedPreview = true;
227
+ if (!parsed.yes || (context.draft.checks.length === 0 && options.revisions.length === 0)) {
228
+ return { kind: "reject" };
229
+ }
230
+ return { kind: "confirm", dryRunCheckIds };
231
+ },
232
+ });
233
+ if (result.status === "invalid") {
234
+ if (!printedPreview && result.preview !== undefined) process.stdout.write(`${result.preview}\n`);
235
+ printError(result.reason);
236
+ if (result.wrote && result.path !== undefined) {
237
+ process.stderr.write(` ${result.path} was written; repair or remove it before running a check.\n`);
238
+ }
239
+ return 1;
240
+ }
241
+ if (result.status === "rejected") {
242
+ if (!parsed.yes) {
243
+ process.stdout.write("Review complete. Nothing changed; rerun the same command with --yes to write this preview.\n");
244
+ } else {
245
+ process.stdout.write(
246
+ "Nothing changed because the preview contains no catalog checks. Package checks remain active; unsupported commands can be added with `clio-coder verifiers add`.\n",
247
+ );
248
+ }
249
+ return 0;
250
+ }
251
+ printOk(`wrote ${result.path}; production parser accepted the catalog`);
252
+ let code = 0;
253
+ for (const dryRun of result.dryRuns) code = Math.max(code, printDryRun(dryRun.id, dryRun.result));
254
+ return code;
255
+ }
256
+
257
+ async function discoverCommand(): Promise<number> {
258
+ const discovery = discoverVerifierAuthoring();
259
+ if (!discovery.ok) {
260
+ printError(discovery.reason);
261
+ process.stderr.write(`${discovery.manualEntry}\n`);
262
+ return 1;
263
+ }
264
+ process.stdout.write(`${previewVerifierDraft(createVerifierDraft(discovery))}\n`);
265
+ return 0;
266
+ }
267
+
268
+ function validateCommand(): number {
269
+ const loaded = loadProjectVerifierCatalog(process.cwd());
270
+ if (!loaded.ok) {
271
+ printError(`production catalog parser rejected ${loaded.reason}`);
272
+ return 1;
273
+ }
274
+ if (loaded.source === null) {
275
+ process.stdout.write(`No ${PROJECT_VERIFIER_CATALOG_RELATIVE_PATH} exists. Run \`clio-coder verifiers author\`.\n`);
276
+ return 0;
277
+ }
278
+ printOk(
279
+ `production catalog parser accepted ${PROJECT_VERIFIER_CATALOG_RELATIVE_PATH} (${loaded.source.checks.length} check${loaded.source.checks.length === 1 ? "" : "s"})`,
280
+ );
281
+ return 0;
282
+ }
283
+
284
+ export async function runVerifiersCommand(argv: string[]): Promise<number> {
285
+ const parsed = parseAuthoringArgs(argv);
286
+ if (parsed.error !== undefined) {
287
+ printError(parsed.error);
288
+ process.stderr.write(HELP);
289
+ return 2;
290
+ }
291
+ if (parsed.help || parsed.positional.length === 0) {
292
+ process.stdout.write(HELP);
293
+ return 0;
294
+ }
295
+ const command = parsed.positional[0];
296
+ if (command === "discover") return discoverCommand();
297
+ if (command === "validate") return validateCommand();
298
+ if (command === "dry-run") {
299
+ const id = parsed.positional[1];
300
+ if (id === undefined) {
301
+ printError("dry-run requires a check ID");
302
+ return 2;
303
+ }
304
+ return printDryRun(id, await verifyTool.run({ check: id }));
305
+ }
306
+ if (command === "author") {
307
+ const revisions = authorRevisions(parsed);
308
+ if (revisions instanceof Error) {
309
+ printError(revisions.message);
310
+ return 2;
311
+ }
312
+ return executeWorkflow(parsed, { includeProposals: true, revisions });
313
+ }
314
+ if (["add", "edit", "rename", "remove"].includes(command ?? "")) {
315
+ const revision = mutationRevision(parsed);
316
+ if (revision instanceof Error) {
317
+ printError(revision.message);
318
+ return 2;
319
+ }
320
+ return executeWorkflow(parsed, { includeProposals: false, revisions: [revision] });
321
+ }
322
+ printError(`unknown verifiers command: ${command}`);
323
+ process.stderr.write(HELP);
324
+ return 2;
325
+ }
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { StringDecoder } from "node:string_decoder";
2
3
  import { AI_AGENT_NAME } from "./agent-environment.js";
3
4
  import {
4
5
  gitCommitAttributionEnabled,
@@ -28,6 +29,8 @@ export interface BashCommandResult {
28
29
  aborted: boolean;
29
30
  timedOut: boolean;
30
31
  outputCapped: boolean;
32
+ /** Exact bytes retained from stdout and stderr, never above the hard cap. */
33
+ outputBytes: number;
31
34
  }
32
35
 
33
36
  export interface BashCommandProgress {
@@ -151,6 +154,9 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
151
154
  let killSent = false;
152
155
  let stdout = "";
153
156
  let stderr = "";
157
+ const stdoutDecoder = new StringDecoder("utf8");
158
+ const stderrDecoder = new StringDecoder("utf8");
159
+ let decodersFinished = false;
154
160
  let outputBytes = 0;
155
161
  let outputCapped = false;
156
162
  let updateTimer: ReturnType<typeof setTimeout> | null = null;
@@ -164,6 +170,16 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
164
170
  stdio: ["ignore", "pipe", "pipe"],
165
171
  });
166
172
 
173
+ const finishDecoders = (): void => {
174
+ if (decodersFinished) return;
175
+ decodersFinished = true;
176
+ // A hard byte cap may bisect a multibyte code point. Discard the
177
+ // decoder's incomplete suffix instead of manufacturing U+FFFD.
178
+ if (outputCapped) return;
179
+ stdout += stdoutDecoder.end();
180
+ stderr += stderrDecoder.end();
181
+ };
182
+
167
183
  const emitUpdate = (): void => {
168
184
  if (options.onUpdate === undefined || !updateDirty) return;
169
185
  updateDirty = false;
@@ -250,24 +266,20 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
250
266
 
251
267
  const appendChunk = (target: "stdout" | "stderr", chunk: Buffer): void => {
252
268
  if (outputCapped) return;
253
- outputBytes += chunk.byteLength;
254
- if (outputBytes > BASH_HARD_CAP_BYTES) {
255
- // Keep the bytes seen so far (including this chunk's prefix would
256
- // overshoot memory, so append the remaining budget) and stop the
257
- // child. Truncation for display is tail-biased downstream.
258
- const remaining = BASH_HARD_CAP_BYTES - (outputBytes - chunk.byteLength);
259
- if (remaining > 0) {
260
- const slice = chunk.subarray(0, remaining).toString("utf8");
261
- if (target === "stdout") stdout += slice;
262
- else stderr += slice;
263
- }
269
+ const remaining = BASH_HARD_CAP_BYTES - outputBytes;
270
+ const accepted = chunk.byteLength <= remaining ? chunk : chunk.subarray(0, Math.max(0, remaining));
271
+ outputBytes += accepted.byteLength;
272
+ if (accepted.byteLength > 0) {
273
+ const decoded = target === "stdout" ? stdoutDecoder.write(accepted) : stderrDecoder.write(accepted);
274
+ if (target === "stdout") stdout += decoded;
275
+ else stderr += decoded;
276
+ }
277
+ if (accepted.byteLength < chunk.byteLength) {
264
278
  outputCapped = true;
265
279
  scheduleUpdate();
266
280
  killChild();
267
281
  return;
268
282
  }
269
- if (target === "stdout") stdout += chunk.toString("utf8");
270
- else stderr += chunk.toString("utf8");
271
283
  scheduleUpdate();
272
284
  };
273
285
 
@@ -278,6 +290,7 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
278
290
  settled = true;
279
291
  if (timeoutId) clearTimeout(timeoutId);
280
292
  clearKillGraceTimer();
293
+ finishDecoders();
281
294
  finishUpdates();
282
295
  options.signal?.removeEventListener("abort", onAbort);
283
296
  resolve({
@@ -289,6 +302,7 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
289
302
  aborted,
290
303
  timedOut,
291
304
  outputCapped,
305
+ outputBytes,
292
306
  });
293
307
  });
294
308
  child.on("close", (code, signalName) => {
@@ -296,6 +310,7 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
296
310
  settled = true;
297
311
  if (timeoutId) clearTimeout(timeoutId);
298
312
  clearKillGraceTimer();
313
+ finishDecoders();
299
314
  finishUpdates();
300
315
  options.signal?.removeEventListener("abort", onAbort);
301
316
  const error =
@@ -307,7 +322,17 @@ export async function runBashCommand(command: string, options: RunBashCommandOpt
307
322
  code: code ?? undefined,
308
323
  signal: signalName ?? undefined,
309
324
  } as NodeJS.ErrnoException);
310
- resolve({ error, stdout, stderr, exitCode: code, signal: signalName, aborted, timedOut, outputCapped });
325
+ resolve({
326
+ error,
327
+ stdout,
328
+ stderr,
329
+ exitCode: code,
330
+ signal: signalName,
331
+ aborted,
332
+ timedOut,
333
+ outputCapped,
334
+ outputBytes,
335
+ });
311
336
  });
312
337
  });
313
338
  }
@@ -61,6 +61,7 @@ export const BusChannels = {
61
61
  ContextActivity: "context.activity",
62
62
  ContextWarning: "context.warning",
63
63
  ContextPruned: "context.pruned",
64
+ ContextRecalled: "context.recalled",
64
65
  AgentStatusChanged: "agent.status.changed",
65
66
  RunAborted: "run.aborted",
66
67
  BudgetAlert: "budget.alert",
@@ -253,21 +254,34 @@ export function isRunAbortedPayload(value: unknown): value is RunAbortedPayload
253
254
  return true;
254
255
  }
255
256
 
256
- /** Payload published on {@link BusChannels.ContextPruned} after compaction reclaims tokens. */
257
+ /** Payload published on {@link BusChannels.ContextPruned} after projected or summarized context shrinks. */
257
258
  export interface ContextPrunedPayload {
258
- stage: "mask_observations" | "llm_summary";
259
+ stage: "mask_observations" | "working_set" | "llm_summary";
259
260
  tokensBefore: number;
260
261
  tokensAfter: number;
261
262
  trigger: string;
262
263
  snapshotIdBefore: string | null;
263
264
  snapshotIdAfter: string;
264
265
  at: number;
265
- /** Used/window ratio at trigger time; mask stage only. */
266
+ /** Used/window ratio at trigger time; working-set and legacy-mask stages only. */
266
267
  pressure?: number | null;
268
+ /** Legacy destructive-mask count. */
267
269
  maskedObservations?: number;
268
- /** Thinking blocks stripped from stale assistant messages; mask stage only. */
270
+ /** Thinking blocks stripped from stale assistant messages; legacy mask only. */
269
271
  maskedThinkingBlocks?: number;
270
272
  maskedThinkingChars?: number;
273
+ /** Working-set policy that selected an applied eviction event. */
274
+ policyId?: string;
275
+ /** Number of working-set units evicted by the event. */
276
+ evictedItems?: number;
277
+ }
278
+
279
+ /** Payload published on {@link BusChannels.ContextRecalled} after an exact working-set recall. */
280
+ export interface ContextRecalledPayload {
281
+ ref: string;
282
+ trigger: "tool" | "operator";
283
+ tokensReadmitted: number;
284
+ at: number;
271
285
  }
272
286
 
273
287
  // ---------------------------------------------------------------------------
@@ -727,6 +741,7 @@ export type BusPayloadMap = {
727
741
  [BusChannels.ContextActivity]: ContextActivityPayload;
728
742
  [BusChannels.ContextWarning]: ContextWarningPayload;
729
743
  [BusChannels.ContextPruned]: ContextPrunedPayload;
744
+ [BusChannels.ContextRecalled]: ContextRecalledPayload;
730
745
  [BusChannels.AgentStatusChanged]: AgentStatusChangedPayload;
731
746
  [BusChannels.RunAborted]: RunAbortedPayload;
732
747
  [BusChannels.BudgetAlert]: BudgetAlertPayload;
@@ -968,6 +968,7 @@ const TOP_LEVEL_KEYS = [
968
968
  "delegation",
969
969
  "keybindings",
970
970
  "compaction",
971
+ "context",
971
972
  "retry",
972
973
  "guardrails",
973
974
  ] as const;
@@ -1405,6 +1406,59 @@ export function validateSettings(raw: unknown): SettingsValidationResult {
1405
1406
  }
1406
1407
  }
1407
1408
 
1409
+ if ("context" in raw) {
1410
+ if (!isPlainObject(raw.context)) {
1411
+ issues.add("context", `expected a map, got ${describe(raw.context)}`);
1412
+ } else {
1413
+ issues.unknownKeys("context", raw.context, ["workingSet"]);
1414
+ if ("workingSet" in raw.context) {
1415
+ if (!isPlainObject(raw.context.workingSet)) {
1416
+ issues.add("context.workingSet", `expected a map, got ${describe(raw.context.workingSet)}`);
1417
+ } else {
1418
+ const workingSet = raw.context.workingSet;
1419
+ issues.unknownKeys("context.workingSet", workingSet, [
1420
+ "enabled",
1421
+ "policy",
1422
+ "target",
1423
+ "protectLastTurns",
1424
+ "minEvictableTokens",
1425
+ ]);
1426
+ if ("enabled" in workingSet) {
1427
+ const v = expectBoolean(issues, "context.workingSet.enabled", workingSet.enabled);
1428
+ if (v !== undefined) settings.context.workingSet.enabled = v;
1429
+ }
1430
+ if ("policy" in workingSet) {
1431
+ const v = expectEnum(issues, "context.workingSet.policy", workingSet.policy, [
1432
+ "age-horizon",
1433
+ "structural-v1",
1434
+ ] as const);
1435
+ if (v !== undefined) settings.context.workingSet.policy = v;
1436
+ }
1437
+ if ("target" in workingSet) {
1438
+ const v = expectNumber(issues, "context.workingSet.target", workingSet.target);
1439
+ if (v !== undefined && (v <= 0 || v >= 1)) {
1440
+ issues.add("context.workingSet.target", `expected a number > 0 and < 1, got ${v}`);
1441
+ } else if (v !== undefined) {
1442
+ settings.context.workingSet.target = v;
1443
+ }
1444
+ }
1445
+ if ("protectLastTurns" in workingSet) {
1446
+ const v = expectInteger(issues, "context.workingSet.protectLastTurns", workingSet.protectLastTurns, {
1447
+ min: 1,
1448
+ });
1449
+ if (v !== undefined) settings.context.workingSet.protectLastTurns = v;
1450
+ }
1451
+ if ("minEvictableTokens" in workingSet) {
1452
+ const v = expectInteger(issues, "context.workingSet.minEvictableTokens", workingSet.minEvictableTokens, {
1453
+ min: 0,
1454
+ });
1455
+ if (v !== undefined) settings.context.workingSet.minEvictableTokens = v;
1456
+ }
1457
+ }
1458
+ }
1459
+ }
1460
+ }
1461
+
1408
1462
  if ("retry" in raw) {
1409
1463
  if (!isPlainObject(raw.retry)) {
1410
1464
  issues.add("retry", `expected a map, got ${describe(raw.retry)}`);