@gajae-code/coding-agent 0.14.2 → 0.15.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 (345) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/dist/types/async/job-manager.d.ts +4 -1
  3. package/dist/types/autoresearch/dashboard.d.ts +29 -4
  4. package/dist/types/autoresearch/data-context.d.ts +25 -0
  5. package/dist/types/autoresearch/git.d.ts +65 -7
  6. package/dist/types/autoresearch/harness.d.ts +58 -0
  7. package/dist/types/autoresearch/prompts.d.ts +51 -0
  8. package/dist/types/autoresearch/runs.d.ts +173 -0
  9. package/dist/types/autoresearch/types.d.ts +9 -139
  10. package/dist/types/capability/types.d.ts +20 -0
  11. package/dist/types/cli/accounts-cli.d.ts +1 -0
  12. package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
  13. package/dist/types/config/autorouting-contract.d.ts +146 -0
  14. package/dist/types/config/autorouting-generator.d.ts +40 -0
  15. package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
  16. package/dist/types/config/autorouting.d.ts +35 -0
  17. package/dist/types/config/model-discovery-manager.d.ts +8 -1
  18. package/dist/types/config/model-registry.d.ts +30 -1
  19. package/dist/types/config/provider-selection-policy.d.ts +23 -0
  20. package/dist/types/config/settings-schema.d.ts +61 -3
  21. package/dist/types/config/settings.d.ts +3 -0
  22. package/dist/types/coordinator/contract.d.ts +1 -1
  23. package/dist/types/coordinator-mcp/durability.d.ts +29 -0
  24. package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
  26. package/dist/types/coordinator-mcp/server.d.ts +14 -0
  27. package/dist/types/crash/index-store.d.ts +3 -0
  28. package/dist/types/crash/record-loader.d.ts +2 -0
  29. package/dist/types/crash/upstream/relay.d.ts +5 -3
  30. package/dist/types/cursor.d.ts +12 -0
  31. package/dist/types/defaults/gjc-defaults.d.ts +35 -1
  32. package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
  33. package/dist/types/discovery/helpers.d.ts +1 -1
  34. package/dist/types/edit/index.d.ts +2 -1
  35. package/dist/types/eval/py/executor.d.ts +2 -0
  36. package/dist/types/extensibility/extensions/loader.d.ts +32 -0
  37. package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
  40. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  41. package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
  42. package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
  44. package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
  45. package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
  46. package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
  47. package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
  49. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
  50. package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
  51. package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
  52. package/dist/types/hooks/skill-keywords.d.ts +1 -1
  53. package/dist/types/main.d.ts +0 -10
  54. package/dist/types/modes/acp/acp-agent.d.ts +13 -6
  55. package/dist/types/modes/components/model-selector.d.ts +14 -0
  56. package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
  57. package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
  58. package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
  59. package/dist/types/modes/interactive-mode.d.ts +3 -0
  60. package/dist/types/modes/types.d.ts +9 -0
  61. package/dist/types/rlm/artifacts.d.ts +4 -4
  62. package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
  63. package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
  64. package/dist/types/runtime-mcp/config.d.ts +6 -0
  65. package/dist/types/runtime-mcp/manager.d.ts +10 -0
  66. package/dist/types/sdk/acp/adapter.d.ts +0 -4
  67. package/dist/types/sdk/broker/broker.d.ts +1 -1
  68. package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
  69. package/dist/types/sdk/broker/session-index.d.ts +15 -0
  70. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
  71. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
  72. package/dist/types/sdk/bus/index.d.ts +8 -0
  73. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
  74. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
  75. package/dist/types/sdk/client/client.d.ts +39 -4
  76. package/dist/types/sdk/host/host.d.ts +1 -0
  77. package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
  78. package/dist/types/sdk/host/model-pin.d.ts +29 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +2 -1
  80. package/dist/types/sdk/lifecycle-session.d.ts +13 -0
  81. package/dist/types/sdk/router/session-router.d.ts +27 -1
  82. package/dist/types/sdk/session.d.ts +11 -1
  83. package/dist/types/session/agent-session.d.ts +36 -6
  84. package/dist/types/session/artifacts.d.ts +16 -1
  85. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  86. package/dist/types/session/session-manager.d.ts +84 -2
  87. package/dist/types/session/session-staging-paths.d.ts +3 -0
  88. package/dist/types/skill-state/canonical-skills.d.ts +1 -1
  89. package/dist/types/skill-state/workflow-hud.d.ts +12 -21
  90. package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
  91. package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
  92. package/dist/types/system-prompt.d.ts +23 -0
  93. package/dist/types/task/executor.d.ts +35 -4
  94. package/dist/types/task/index.d.ts +17 -3
  95. package/dist/types/task/receipt.d.ts +3 -1
  96. package/dist/types/task/types.d.ts +144 -0
  97. package/dist/types/tools/atomic-file-write.d.ts +72 -0
  98. package/dist/types/tools/descriptors.d.ts +1 -0
  99. package/dist/types/tools/index.d.ts +36 -1
  100. package/dist/types/tools/move-session.d.ts +40 -0
  101. package/dist/types/tools/python.d.ts +12 -0
  102. package/dist/types/tools/resource-gc.d.ts +0 -3
  103. package/dist/types/tools/search.d.ts +8 -1
  104. package/dist/types/tools/skill.d.ts +9 -4
  105. package/dist/types/tools/subagent.d.ts +33 -3
  106. package/dist/types/utils/xml-escape.d.ts +2 -0
  107. package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
  108. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
  109. package/package.json +11 -12
  110. package/scripts/build-sdk-package-smoke.ts +7 -5
  111. package/scripts/check-autorouting-tier-map.ts +183 -0
  112. package/scripts/generate-sdk-operation-inventory.ts +12 -0
  113. package/scripts/generate-tool-catalog.ts +40 -5
  114. package/src/async/job-manager.ts +5 -2
  115. package/src/autoresearch/dashboard.ts +134 -314
  116. package/src/autoresearch/data-context.ts +35 -0
  117. package/src/autoresearch/git.ts +186 -26
  118. package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
  119. package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
  120. package/src/autoresearch/prompts/prompt.md +103 -0
  121. package/src/autoresearch/prompts.ts +126 -0
  122. package/src/autoresearch/runs.ts +708 -0
  123. package/src/autoresearch/types.ts +9 -150
  124. package/src/capability/index.ts +4 -4
  125. package/src/capability/types.ts +20 -0
  126. package/src/cli/accounts-cli.ts +21 -5
  127. package/src/cli/config-cli.ts +25 -1
  128. package/src/cli/fast-help.ts +1 -2
  129. package/src/cli/list-models.ts +5 -8
  130. package/src/cli/setup-cli.ts +8 -0
  131. package/src/cli.ts +2 -21
  132. package/src/commands/autoresearch.ts +33 -0
  133. package/src/commands/coordinator.ts +15 -0
  134. package/src/commands/launch.ts +2 -4
  135. package/src/commands/quick-lane.ts +1 -1
  136. package/src/commands/sdk.ts +24 -0
  137. package/src/commands/state.ts +1 -1
  138. package/src/config/autorouting-contract.ts +474 -0
  139. package/src/config/autorouting-generator.ts +238 -0
  140. package/src/config/autorouting-tier-map.ts +6108 -0
  141. package/src/config/autorouting.ts +120 -0
  142. package/src/config/model-discovery-manager.ts +42 -11
  143. package/src/config/model-registry.ts +348 -68
  144. package/src/config/provider-selection-policy.ts +60 -9
  145. package/src/config/settings-schema.ts +195 -6
  146. package/src/config/settings.ts +62 -1
  147. package/src/config.ts +2 -3
  148. package/src/coordinator/contract.ts +0 -1
  149. package/src/coordinator-mcp/codex-handoff.ts +68 -42
  150. package/src/coordinator-mcp/durability.ts +200 -0
  151. package/src/coordinator-mcp/event-webhook.ts +278 -0
  152. package/src/coordinator-mcp/question-state.ts +109 -46
  153. package/src/coordinator-mcp/server.ts +932 -504
  154. package/src/crash/index-store.ts +126 -22
  155. package/src/crash/record-loader.ts +7 -0
  156. package/src/crash/upstream/relay.ts +82 -34
  157. package/src/cursor.ts +84 -1
  158. package/src/debug/index.ts +26 -0
  159. package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
  160. package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
  161. package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
  162. package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
  163. package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
  164. package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
  165. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
  166. package/src/defaults/gjc-defaults.ts +89 -2
  167. package/src/defaults/gjc-skills.generated.ts +24 -17
  168. package/src/discovery/builtin.ts +18 -5
  169. package/src/discovery/helpers.ts +6 -6
  170. package/src/edit/index.ts +5 -2
  171. package/src/eval/py/executor.ts +8 -0
  172. package/src/extensibility/extensions/loader.ts +135 -13
  173. package/src/extensibility/extensions/wrapper.ts +1 -2
  174. package/src/extensibility/gjc-plugins/schema.ts +1 -1
  175. package/src/extensibility/runtime-skill-discovery.ts +2 -2
  176. package/src/extensibility/skill-management.ts +2 -3
  177. package/src/extensibility/skills.ts +16 -8
  178. package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
  179. package/src/gjc-runtime/gc-render.ts +0 -1
  180. package/src/gjc-runtime/gc-runtime.ts +1 -11
  181. package/src/gjc-runtime/launch-tmux.ts +3 -3
  182. package/src/gjc-runtime/psmux-detect.ts +2 -2
  183. package/src/gjc-runtime/python-transcript.ts +58 -0
  184. package/src/gjc-runtime/ralplan-runtime.ts +6 -18
  185. package/src/gjc-runtime/session-layout.ts +18 -7
  186. package/src/gjc-runtime/session-state-sidecar.ts +58 -9
  187. package/src/gjc-runtime/state-renderer.ts +0 -38
  188. package/src/gjc-runtime/state-runtime.ts +43 -22
  189. package/src/gjc-runtime/state-schema.ts +1 -1
  190. package/src/gjc-runtime/tmux-common.ts +7 -7
  191. package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
  192. package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
  193. package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
  194. package/src/gjc-runtime/workflow-command-ref.ts +15 -11
  195. package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
  196. package/src/gjc-runtime/workflow-manifest.ts +43 -83
  197. package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
  198. package/src/hooks/skill-keywords.ts +3 -3
  199. package/src/hooks/skill-state.ts +2 -2
  200. package/src/internal-urls/docs-index.generated.ts +26 -24
  201. package/src/lsp/index.ts +25 -5
  202. package/src/main.ts +2 -27
  203. package/src/modes/acp/acp-agent.ts +200 -75
  204. package/src/modes/components/model-selector.ts +216 -13
  205. package/src/modes/components/oauth-selector.ts +5 -5
  206. package/src/modes/components/skill-hud/render.ts +173 -35
  207. package/src/modes/components/smart-routing-panel.ts +531 -0
  208. package/src/modes/components/tool-status-header.ts +6 -3
  209. package/src/modes/controllers/command-controller.ts +17 -7
  210. package/src/modes/controllers/input-controller.ts +44 -9
  211. package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
  212. package/src/modes/controllers/selector-controller.ts +298 -65
  213. package/src/modes/interactive-mode.ts +8 -1
  214. package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
  215. package/src/modes/types.ts +9 -1
  216. package/src/prompts/agents/planner.md +1 -1
  217. package/src/prompts/system/system-prompt.md +1 -1
  218. package/src/prompts/system/volatile-project-context.md +2 -1
  219. package/src/prompts/tools/move-session.md +15 -0
  220. package/src/prompts/tools/python.md +12 -0
  221. package/src/prompts/tools/skill-discovery.md +1 -1
  222. package/src/prompts/tools/skill.md +2 -2
  223. package/src/prompts/tools/task-summary.md +1 -0
  224. package/src/prompts/tools/task.md +6 -0
  225. package/src/rlm/artifacts.ts +12 -10
  226. package/src/runtime/optional-runtime-services.ts +3 -2
  227. package/src/runtime/workspace-tree-service.ts +11 -3
  228. package/src/runtime-mcp/config.ts +8 -1
  229. package/src/runtime-mcp/manager.ts +33 -0
  230. package/src/sdk/acp/adapter.ts +48 -1
  231. package/src/sdk/broker/broker.ts +30 -2
  232. package/src/sdk/broker/discovery.ts +217 -2
  233. package/src/sdk/broker/lifecycle.ts +385 -59
  234. package/src/sdk/broker/session-index.ts +243 -48
  235. package/src/sdk/broker/transport.ts +1 -0
  236. package/src/sdk/bus/chat-daemon-control.ts +8 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
  238. package/src/sdk/bus/index.ts +208 -11
  239. package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
  240. package/src/sdk/bus/reconciliation-store.ts +27 -5
  241. package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
  242. package/src/sdk/client/client.ts +204 -22
  243. package/src/sdk/host/host.ts +16 -0
  244. package/src/sdk/host/internal-autorouting-state.ts +13 -0
  245. package/src/sdk/host/model-pin.ts +101 -0
  246. package/src/sdk/host/reverse-leases.ts +2 -1
  247. package/src/sdk/host/session-runtime.test.ts +224 -1
  248. package/src/sdk/host/session-runtime.ts +125 -37
  249. package/src/sdk/lifecycle/service.ts +3 -1
  250. package/src/sdk/lifecycle-session.ts +42 -1
  251. package/src/sdk/protocol/operation-inventory.generated.json +66 -0
  252. package/src/sdk/router/session-router.ts +375 -36
  253. package/src/sdk/session.ts +547 -47
  254. package/src/session/account-inventory.ts +1 -1
  255. package/src/session/agent-session.ts +765 -189
  256. package/src/session/artifacts.ts +234 -4
  257. package/src/session/internal/managed-session-storage.ts +91 -4
  258. package/src/session/provider-safety-stop-hint.ts +3 -2
  259. package/src/session/session-manager.ts +775 -16
  260. package/src/session/session-staging-paths.ts +8 -0
  261. package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
  262. package/src/setup/host-plugin-setup.ts +2 -2
  263. package/src/skill-state/active-state.ts +2 -2
  264. package/src/skill-state/canonical-skills.ts +1 -1
  265. package/src/skill-state/initial-phase.ts +1 -1
  266. package/src/skill-state/workflow-hud.ts +41 -41
  267. package/src/skill-state/workflow-mutation-guard.ts +26 -7
  268. package/src/slash-commands/builtin-registry.ts +77 -0
  269. package/src/slash-commands/helpers/autorouting-status.ts +55 -0
  270. package/src/system-prompt.ts +102 -5
  271. package/src/task/discovery.ts +2 -3
  272. package/src/task/executor.ts +617 -43
  273. package/src/task/index.ts +262 -15
  274. package/src/task/receipt.ts +30 -0
  275. package/src/task/render.ts +11 -1
  276. package/src/task/types.ts +316 -0
  277. package/src/tools/atomic-file-write.ts +594 -0
  278. package/src/tools/bisect.ts +3 -2
  279. package/src/tools/descriptors.test.ts +3 -0
  280. package/src/tools/descriptors.ts +24 -0
  281. package/src/tools/image-gen.ts +5 -1
  282. package/src/tools/index.ts +62 -5
  283. package/src/tools/move-session.ts +107 -0
  284. package/src/tools/python.ts +205 -0
  285. package/src/tools/read.ts +88 -15
  286. package/src/tools/renderers.ts +2 -0
  287. package/src/tools/resource-gc.ts +8 -177
  288. package/src/tools/search.ts +30 -6
  289. package/src/tools/skill-discovery.ts +1 -0
  290. package/src/tools/skill.ts +37 -7
  291. package/src/tools/subagent-render.ts +119 -13
  292. package/src/tools/subagent.ts +91 -74
  293. package/src/tools/tool-catalog.generated.ts +64 -2
  294. package/src/tools/write.ts +16 -12
  295. package/src/utils/xml-escape.ts +14 -0
  296. package/src/web/search/providers/codex.ts +2 -2
  297. package/src/workflow/workflow-intent-diff.ts +13 -12
  298. package/src/workflow/workflow-intent-report-consumer.ts +6 -6
  299. package/dist/types/autoresearch/helpers.d.ts +0 -24
  300. package/dist/types/autoresearch/index.d.ts +0 -2
  301. package/dist/types/autoresearch/state.d.ts +0 -17
  302. package/dist/types/autoresearch/storage.d.ts +0 -142
  303. package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
  304. package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
  305. package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
  306. package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
  307. package/dist/types/commands/team.d.ts +0 -24
  308. package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
  309. package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
  310. package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
  311. package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
  312. package/dist/types/gjc-runtime/team-store.d.ts +0 -168
  313. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
  314. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
  315. package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
  316. package/dist/types/rlm/index.d.ts +0 -47
  317. package/dist/types/rlm/preset.d.ts +0 -23
  318. package/dist/types/rlm/python-tool.d.ts +0 -16
  319. package/src/autoresearch/command-resume.md +0 -14
  320. package/src/autoresearch/index.ts +0 -536
  321. package/src/autoresearch/prompt.md +0 -103
  322. package/src/autoresearch/resume-message.md +0 -10
  323. package/src/autoresearch/state.ts +0 -273
  324. package/src/autoresearch/storage.ts +0 -699
  325. package/src/autoresearch/tools/init-experiment.ts +0 -272
  326. package/src/autoresearch/tools/log-experiment.ts +0 -524
  327. package/src/autoresearch/tools/run-experiment.ts +0 -475
  328. package/src/autoresearch/tools/update-notes.ts +0 -109
  329. package/src/commands/rlm.ts +0 -19
  330. package/src/commands/team.ts +0 -238
  331. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
  332. package/src/defaults/gjc/skills/team/SKILL.md +0 -467
  333. package/src/gjc-runtime/team-gc.ts +0 -49
  334. package/src/gjc-runtime/team-launch.ts +0 -404
  335. package/src/gjc-runtime/team-notify.ts +0 -326
  336. package/src/gjc-runtime/team-runtime.ts +0 -5923
  337. package/src/gjc-runtime/team-store.ts +0 -878
  338. package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
  339. package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
  340. package/src/gjc-runtime/team-workers.ts +0 -1123
  341. package/src/prompts/system/rlm-report-command.md +0 -1
  342. package/src/prompts/system/rlm-research.md +0 -23
  343. package/src/rlm/index.ts +0 -365
  344. package/src/rlm/preset.ts +0 -76
  345. package/src/rlm/python-tool.ts +0 -68
@@ -1,273 +1,138 @@
1
- import { matchesKey, replaceTabs, Text, truncateToWidth, visibleWidth } from "@gajae-code/tui";
2
- import type { Theme } from "../modes/theme/theme";
3
- import { formatElapsed, formatNum, isBetter } from "./helpers";
4
- import { currentResults, findBaselineMetric, findBaselineRunNumber, findBaselineSecondary } from "./state";
5
- import type { AutoresearchRuntime, DashboardController, ExperimentResult, ExperimentState } from "./types";
1
+ /**
2
+ * Autoresearch TUI dashboard (ported from the deleted extension's
3
+ * `dashboard.ts`): run table plus collapsed/expanded rendering.
4
+ *
5
+ * The extension version rendered through TUI widget/overlay machinery with
6
+ * theme colors; this rebuild renders the same content as sanitized plain text
7
+ * lines (every string passes through `replaceTabs` and `truncateToWidth` like
8
+ * the other renderers) so it can be shown in any text surface.
9
+ */
10
+ import { replaceTabs, truncateToWidth } from "@gajae-code/tui";
11
+ import { formatElapsed, formatNum, isBetter } from "./harness";
12
+ import {
13
+ type AutoresearchExperimentResult,
14
+ type AutoresearchExperimentState,
15
+ findBaselineMetric,
16
+ findBaselineRunNumber,
17
+ findBaselineSecondary,
18
+ } from "./runs";
6
19
 
7
- export function createDashboardController(): DashboardController {
8
- let overlayTui: { requestLayoutRender(source?: string): void } | null = null;
9
- let spinnerTimer: NodeJS.Timeout | undefined;
10
- let spinnerFrame = 0;
11
-
12
- const requestRender = (): void => {
13
- overlayTui?.requestLayoutRender("autoresearch-overlay");
14
- };
15
-
16
- const stopSpinner = (): void => {
17
- if (!spinnerTimer) return;
18
- clearInterval(spinnerTimer);
19
- spinnerTimer = undefined;
20
- };
21
-
22
- const syncSpinner = (runtime: AutoresearchRuntime): void => {
23
- if (!overlayTui || !runtime.runningExperiment) {
24
- stopSpinner();
25
- return;
26
- }
27
- if (spinnerTimer) return;
28
- spinnerTimer = setInterval(() => {
29
- if (!runtime.runningExperiment) {
30
- stopSpinner();
31
- return;
32
- }
33
- spinnerFrame += 1;
34
- requestRender();
35
- }, 80);
36
- };
37
-
38
- const clear = (): void => {
39
- overlayTui = null;
40
- stopSpinner();
41
- };
42
-
43
- return {
44
- clear(ctx): void {
45
- clear();
46
- if (ctx.hasUI) {
47
- ctx.ui.setWidget("autoresearch", undefined);
48
- }
49
- },
50
- requestRender,
51
- updateWidget(ctx, runtime): void {
52
- if (!ctx.hasUI) return;
53
- syncSpinner(runtime);
54
- const state = runtime.state;
55
- if (!shouldShowDashboard(runtime, state)) {
56
- ctx.ui.setWidget("autoresearch", undefined);
57
- return;
58
- }
59
-
60
- ctx.ui.setWidget("autoresearch", (_tui, theme) => {
61
- if (state.results.length === 0 && runtime.runningExperiment) {
62
- return new Text(renderRunningOnly(runtime, state, theme), 0, 0);
63
- }
64
- if (runtime.dashboardExpanded) {
65
- const width = process.stdout.columns ?? 120;
66
- const lines = [
67
- renderExpandedHeader(runtime, width, theme),
68
- ...renderDashboardLines(runtime, width, theme, 8),
69
- ];
70
- return new Text(lines.join("\n"), 0, 0);
71
- }
72
- return new Text(renderCollapsedLine(runtime, state, theme), 0, 0);
73
- });
74
- },
75
- async showOverlay(ctx, runtime): Promise<void> {
76
- if (!ctx.hasUI || !shouldShowDashboard(runtime, runtime.state)) return;
77
- await ctx.ui.custom<void>(
78
- (tui, theme, _keybindings, done) => {
79
- overlayTui = tui;
80
- syncSpinner(runtime);
81
-
82
- let scrollOffset = 0;
83
- return {
84
- render(width: number): string[] {
85
- const terminalRows = process.stdout.rows ?? 40;
86
- const header = renderExpandedHeader(runtime, width, theme);
87
- const body = renderDashboardLines(runtime, width, theme, 0);
88
- if (runtime.runningExperiment) {
89
- body.push(renderOverlayRunningLine(runtime, theme, width, spinnerFrame));
90
- }
91
- const viewportRows = Math.max(4, terminalRows - 4);
92
- const maxScroll = Math.max(0, body.length - viewportRows);
93
- if (scrollOffset > maxScroll) scrollOffset = maxScroll;
94
- const visible = body.slice(scrollOffset, scrollOffset + viewportRows);
95
- const footer = renderOverlayFooter(width, scrollOffset, viewportRows, body.length, theme);
96
- return [
97
- header,
98
- ...visible,
99
- ...Array.from({ length: Math.max(0, viewportRows - visible.length) }, () => ""),
100
- footer,
101
- ];
102
- },
103
- handleInput(data: string): void {
104
- const totalRows =
105
- renderDashboardLines(runtime, process.stdout.columns ?? 120, theme, 0).length +
106
- (runtime.runningExperiment ? 1 : 0);
107
- const viewportRows = Math.max(4, (process.stdout.rows ?? 40) - 4);
108
- const maxScroll = Math.max(0, totalRows - viewportRows);
109
- if (matchesKey(data, "escape") || matchesKey(data, "esc") || data === "q") {
110
- done(undefined);
111
- return;
112
- }
113
- if (matchesKey(data, "up") || data === "k") {
114
- scrollOffset = Math.max(0, scrollOffset - 1);
115
- } else if (matchesKey(data, "down") || data === "j") {
116
- scrollOffset = Math.min(maxScroll, scrollOffset + 1);
117
- } else if (matchesKey(data, "pageUp")) {
118
- scrollOffset = Math.max(0, scrollOffset - viewportRows);
119
- } else if (matchesKey(data, "pageDown")) {
120
- scrollOffset = Math.min(maxScroll, scrollOffset + viewportRows);
121
- } else if (data === "g") {
122
- scrollOffset = 0;
123
- } else if (data === "G") {
124
- scrollOffset = maxScroll;
125
- }
126
- tui.requestLayoutRender("autoresearch-overlay-input");
127
- },
128
- invalidate(): void {},
129
- dispose(): void {
130
- clear();
131
- },
132
- };
133
- },
134
- { overlay: true },
135
- );
136
- },
137
- };
20
+ export interface AutoresearchDashboardRuntime {
21
+ modeOn: boolean;
22
+ running?: { command: string; startedAt: number } | null;
23
+ pendingRun?: {
24
+ runNumber: number;
25
+ passed: boolean;
26
+ parsedPrimary: number | null;
27
+ } | null;
138
28
  }
139
29
 
140
- function renderRunningOnly(runtime: AutoresearchRuntime, state: ExperimentState, theme: Theme): string {
141
- const parts = [theme.fg("accent", "autoresearch"), theme.fg("warning", " running...")];
142
- if (state.name) {
143
- parts.push(theme.fg("dim", ` | ${replaceTabs(state.name)}`));
144
- }
145
- if (runtime.runningExperiment) {
146
- parts.push(theme.fg("dim", ` | ${replaceTabs(runtime.runningExperiment.command)}`));
147
- }
148
- return parts.join("");
149
- }
150
-
151
- function shouldShowDashboard(runtime: AutoresearchRuntime, state: ExperimentState): boolean {
152
- return (
153
- runtime.autoresearchMode ||
154
- state.results.length > 0 ||
155
- runtime.runningExperiment !== null ||
156
- runtime.lastRunSummary !== null
157
- );
30
+ export interface AutoresearchDashboardInput {
31
+ state: AutoresearchExperimentState;
32
+ runtime?: AutoresearchDashboardRuntime;
158
33
  }
159
34
 
160
- function renderExpandedHeader(runtime: AutoresearchRuntime, width: number, theme: Theme): string {
161
- const state = runtime.state;
162
- const status = renderModeStatus(runtime, state);
163
- const label = state.name ? ` autoresearch: ${replaceTabs(state.name)} ` : " autoresearch ";
164
- const hint = theme.fg("dim", ` ctrl+x collapse ctrl+shift+x overlay${status ? ` ${status}` : ""} `);
165
- const fillWidth = Math.max(0, width - visibleWidth(label) - visibleWidth(hint));
166
- return truncateToWidth(theme.fg("accent", label) + theme.fg("borderMuted", "-".repeat(fillWidth)) + hint, width);
167
- }
168
-
169
- function renderCollapsedLine(runtime: AutoresearchRuntime, state: ExperimentState, theme: Theme): string {
170
- if (runtime.lastRunSummary) {
35
+ /** Collapsed one-line summary. */
36
+ export function renderCollapsedLine(input: AutoresearchDashboardInput, width = 120): string {
37
+ const state = input.state;
38
+ const runtime = input.runtime ?? { modeOn: true };
39
+ if (runtime.pendingRun) {
171
40
  const parts = [
172
- theme.fg("accent", "autoresearch"),
173
- theme.fg("warning", ` pending run #${runtime.lastRunSummary.runNumber}`),
174
- theme.fg("dim", runtime.lastRunSummary.passed ? " pass" : " fail"),
41
+ "autoresearch",
42
+ ` pending run #${runtime.pendingRun.runNumber}`,
43
+ runtime.pendingRun.passed ? " pass" : " fail",
175
44
  ];
176
- if (runtime.lastRunSummary.parsedPrimary !== null) {
177
- parts.push(
178
- theme.fg(
179
- "muted",
180
- ` | ${state.metricName}=${formatNum(runtime.lastRunSummary.parsedPrimary, state.metricUnit)}`,
181
- ),
182
- );
45
+ if (runtime.pendingRun.parsedPrimary !== null) {
46
+ parts.push(` | ${state.metricName}=${formatNum(runtime.pendingRun.parsedPrimary, state.metricUnit)}`);
183
47
  }
184
- parts.push(theme.fg("warning", " | log_experiment required"));
185
- if (!runtime.autoresearchMode) {
186
- parts.push(theme.fg("dim", " | mode off"));
187
- }
188
- return parts.join("");
48
+ parts.push(" | log required");
49
+ if (!runtime.modeOn) parts.push(" | mode off");
50
+ return truncateToWidth(replaceTabs(parts.join("")), width);
189
51
  }
190
52
  if (state.results.length === 0) {
191
- const modeStatus = runtime.autoresearchMode ? "baseline pending" : "mode off";
192
- const parts = [theme.fg("accent", "autoresearch"), theme.fg("warning", ` ${modeStatus}`)];
193
- if (state.name) {
194
- parts.push(theme.fg("dim", ` | ${replaceTabs(state.name)}`));
195
- }
196
- if (runtime.autoresearchMode) {
197
- parts.push(theme.fg("dim", " | run the baseline"));
198
- }
199
- return parts.join("");
53
+ const modeStatus = runtime.modeOn ? "baseline pending" : "mode off";
54
+ const parts = ["autoresearch", ` ${modeStatus}`];
55
+ if (state.name) parts.push(` | ${replaceTabs(state.name)}`);
56
+ if (runtime.modeOn) parts.push(" | run the baseline");
57
+ return truncateToWidth(replaceTabs(parts.join("")), width);
200
58
  }
201
- const current = currentResults(state.results, state.currentSegment);
59
+ const current = state.results.filter(result => result.segment === state.currentSegment);
202
60
  const kept = current.filter(result => result.status === "keep").length;
203
61
  const crashed = current.filter(result => result.status === "crash").length;
204
62
  const checksFailed = current.filter(result => result.status === "checks_failed").length;
205
63
  const best = findBestResult(state);
206
64
  const archivedRuns = Math.max(0, state.results.length - current.length);
207
- const parts = [
208
- theme.fg("accent", "autoresearch"),
209
- theme.fg("muted", ` ${current.length} runs`),
210
- theme.fg("success", ` ${kept} kept`),
211
- ];
212
- if (archivedRuns > 0) parts.push(theme.fg("dim", ` +${archivedRuns} archived`));
213
- if (crashed > 0) parts.push(theme.fg("error", ` ${crashed} crash`));
214
- if (checksFailed > 0) parts.push(theme.fg("error", ` ${checksFailed} checks_failed`));
215
- parts.push(theme.fg("dim", " | "));
216
- if (best && state.bestMetric !== null && best.result.metric !== state.bestMetric) {
217
- parts.push(theme.fg("warning", `best ${formatNum(best.result.metric, state.metricUnit)}`));
218
- parts.push(theme.fg("dim", ` baseline ${formatNum(state.bestMetric, state.metricUnit)}`));
65
+ const parts = ["autoresearch", ` ${current.length} runs`, ` ${kept} kept`];
66
+ if (archivedRuns > 0) parts.push(` +${archivedRuns} archived`);
67
+ if (crashed > 0) parts.push(` ${crashed} crash`);
68
+ if (checksFailed > 0) parts.push(` ${checksFailed} checks_failed`);
69
+ parts.push(" | ");
70
+ if (best && state.bestMetric !== null && best.metric !== state.bestMetric) {
71
+ parts.push(`best ${formatNum(best.metric, state.metricUnit)}`);
72
+ parts.push(` baseline ${formatNum(state.bestMetric, state.metricUnit)}`);
219
73
  } else if (state.bestMetric !== null) {
220
- parts.push(theme.fg("warning", `baseline ${formatNum(state.bestMetric, state.metricUnit)}`));
74
+ parts.push(`baseline ${formatNum(state.bestMetric, state.metricUnit)}`);
221
75
  } else {
222
- parts.push(theme.fg("warning", `no kept runs yet`));
76
+ parts.push("no kept runs yet");
223
77
  }
224
78
  if (state.confidence !== null) {
225
- const confidenceColor = state.confidence >= 2 ? "success" : state.confidence >= 1 ? "warning" : "error";
226
- parts.push(theme.fg("dim", " | "));
227
- parts.push(theme.fg(confidenceColor, `conf ${state.confidence.toFixed(1)}x`));
79
+ parts.push(" | ");
80
+ parts.push(`conf ${state.confidence.toFixed(1)}x`);
228
81
  }
229
- if (runtime.runningExperiment) {
230
- parts.push(theme.fg("dim", ` | running ${formatElapsed(Date.now() - runtime.runningExperiment.startedAt)}`));
231
- } else if (!runtime.autoresearchMode) {
232
- parts.push(theme.fg("dim", ` | ${renderModeStatus(runtime, state)}`));
82
+ if (runtime.running) {
83
+ parts.push(` | running ${formatElapsed(Date.now() - runtime.running.startedAt)}`);
84
+ } else if (!runtime.modeOn) {
85
+ parts.push(" | mode off");
233
86
  }
234
- parts.push(theme.fg("dim", " | ctrl+x expand"));
235
- return parts.join("");
87
+ parts.push(" | ctrl+x expand");
88
+ return truncateToWidth(replaceTabs(parts.join("")), width);
89
+ }
90
+
91
+ /** Expanded header line. */
92
+ export function renderExpandedHeader(input: AutoresearchDashboardInput, width = 120): string {
93
+ const state = input.state;
94
+ const label = state.name ? ` autoresearch: ${replaceTabs(state.name)} ` : " autoresearch ";
95
+ const hint = " ctrl+x collapse ctrl+shift+x overlay ";
96
+ const fillWidth = Math.max(0, width - label.length - hint.length);
97
+ return truncateToWidth(`${label}${"-".repeat(fillWidth)}${hint}`, width);
236
98
  }
237
99
 
238
- export function renderDashboardLines(
239
- runtime: AutoresearchRuntime,
240
- width: number,
241
- theme: Theme,
242
- maxRows: number,
243
- ): string[] {
244
- const state = runtime.state;
100
+ /**
101
+ * Expanded dashboard body: segment summary, baseline/best lines, then the run
102
+ * table. `maxRows > 0` keeps only the last `maxRows` rows of the current
103
+ * segment (with an ellipsis for hidden earlier runs); `0` shows all.
104
+ */
105
+ export function renderDashboardLines(input: AutoresearchDashboardInput, width = 120, maxRows = 0): string[] {
106
+ const state = input.state;
107
+ const runtime = input.runtime ?? { modeOn: true };
245
108
  if (state.results.length === 0) {
246
- if (runtime.lastRunSummary) {
109
+ if (runtime.pendingRun) {
247
110
  const lines = [
248
- truncateToWidth(`Pending run: #${runtime.lastRunSummary.runNumber}`, width),
111
+ truncateToWidth(`Pending run: #${runtime.pendingRun.runNumber}`, width),
249
112
  truncateToWidth(
250
- `Result: ${runtime.lastRunSummary.passed ? "passed" : "failed"}${runtime.lastRunSummary.parsedPrimary !== null ? ` ${state.metricName} ${formatNum(runtime.lastRunSummary.parsedPrimary, state.metricUnit)}` : ""}`,
113
+ `Result: ${runtime.pendingRun.passed ? "passed" : "failed"}${
114
+ runtime.pendingRun.parsedPrimary !== null
115
+ ? ` ${state.metricName} ${formatNum(runtime.pendingRun.parsedPrimary, state.metricUnit)}`
116
+ : ""
117
+ }`,
251
118
  width,
252
119
  ),
253
- truncateToWidth("Next action: finish log_experiment before starting another run.", width),
120
+ truncateToWidth("Next action: log the run before starting another benchmark.", width),
254
121
  ];
255
- if (!runtime.autoresearchMode) {
256
- lines.push(truncateToWidth("Mode: off", width));
257
- }
122
+ if (!runtime.modeOn) lines.push(truncateToWidth("Mode: off", width));
258
123
  return lines;
259
124
  }
260
- if (runtime.autoresearchMode) {
125
+ if (runtime.modeOn) {
261
126
  return [
262
127
  truncateToWidth("Current segment: 0 runs", width),
263
128
  truncateToWidth("Baseline: pending", width),
264
129
  truncateToWidth("Next action: run and log the baseline experiment.", width),
265
130
  ];
266
131
  }
267
- return [theme.fg("dim", "No experiments logged yet.")];
132
+ return ["No experiments logged yet."];
268
133
  }
269
134
 
270
- const current = currentResults(state.results, state.currentSegment);
135
+ const current = state.results.filter(result => result.segment === state.currentSegment);
271
136
  const kept = current.filter(result => result.status === "keep").length;
272
137
  const discarded = current.filter(result => result.status === "discard").length;
273
138
  const crashed = current.filter(result => result.status === "crash").length;
@@ -291,22 +156,21 @@ export function renderDashboardLines(
291
156
  truncateToWidth(`Archived from earlier segments: ${state.results.length - current.length} runs`, width),
292
157
  );
293
158
  }
294
- if (runtime.lastRunSummary) {
159
+ if (runtime.pendingRun) {
295
160
  lines.push(
296
161
  truncateToWidth(
297
- `Pending run: #${runtime.lastRunSummary.runNumber} (${runtime.lastRunSummary.passed ? "passed" : "failed"}) — log_experiment required`,
162
+ `Pending run: #${runtime.pendingRun.runNumber} (${runtime.pendingRun.passed ? "passed" : "failed"}) — log required`,
298
163
  width,
299
164
  ),
300
165
  );
301
166
  }
302
- if (!runtime.autoresearchMode) {
303
- lines.push(truncateToWidth(`Mode: ${renderModeStatus(runtime, state)}`, width));
167
+ if (!runtime.modeOn) {
168
+ lines.push(truncateToWidth("Mode: off", width));
304
169
  }
305
170
  if (best) {
306
- const bestRunNumber = best.result.runNumber ?? best.index + 1;
307
- let progress = `Best: ${formatNum(best.result.metric, state.metricUnit)} (#${bestRunNumber})`;
308
- if (baseline !== null && baseline !== 0 && best.result.metric !== baseline) {
309
- const delta = ((best.result.metric - baseline) / baseline) * 100;
171
+ let progress = `Best: ${formatNum(best.metric, state.metricUnit)} (#${best.runNumber})`;
172
+ if (baseline !== null && baseline !== 0 && best.metric !== null && best.metric !== baseline) {
173
+ const delta = ((best.metric - baseline) / baseline) * 100;
310
174
  const sign = delta > 0 ? "+" : "";
311
175
  progress += ` ${sign}${delta.toFixed(1)}%`;
312
176
  }
@@ -319,7 +183,7 @@ export function renderDashboardLines(
319
183
  .map(metric =>
320
184
  renderSecondarySummary(
321
185
  metric.name,
322
- best.result.metrics[metric.name],
186
+ best.metrics[metric.name],
323
187
  baselineSecondary[metric.name],
324
188
  metric.unit,
325
189
  ),
@@ -331,35 +195,36 @@ export function renderDashboardLines(
331
195
  }
332
196
  }
333
197
  lines.push("");
334
- lines.push(renderTableHeader(state, width, theme));
335
- lines.push(theme.fg("borderMuted", "-".repeat(Math.max(0, width - 1))));
198
+ lines.push(truncateToWidth(renderTableHeader(state), width));
199
+ lines.push(truncateToWidth("-".repeat(Math.max(0, width - 1)), width));
336
200
 
337
201
  const visible = maxRows > 0 ? current.slice(-maxRows) : current;
338
202
  if (visible.length < current.length) {
339
- lines.push(theme.fg("dim", `... ${current.length - visible.length} earlier runs hidden ...`));
203
+ lines.push(`... ${current.length - visible.length} earlier runs hidden ...`);
340
204
  }
341
205
  for (const result of visible) {
342
- lines.push(renderResultRow(result, state, baselineSecondary, width, theme));
206
+ lines.push(truncateToWidth(renderResultRow(result, state, baselineSecondary), width));
343
207
  }
344
- return lines;
208
+ return lines.map(line => replaceTabs(line));
345
209
  }
346
210
 
347
- function renderTableHeader(state: ExperimentState, width: number, theme: Theme): string {
211
+ /** The full expanded dashboard (header + body). */
212
+ export function renderExpandedDashboard(input: AutoresearchDashboardInput, width = 120, maxRows = 0): string {
213
+ return [renderExpandedHeader(input, width), ...renderDashboardLines(input, width, maxRows)].join("\n");
214
+ }
215
+
216
+ function renderTableHeader(state: AutoresearchExperimentState): string {
348
217
  const secondaryHeader = state.secondaryMetrics.map(metric => truncateToWidth(metric.name, 10)).join(" ");
349
- return truncateToWidth(
350
- `${theme.fg("muted", "#".padEnd(4))}${theme.fg("muted", "commit".padEnd(10))}${theme.fg("warning", state.metricName.padEnd(12))}${secondaryHeader ? `${theme.fg("muted", secondaryHeader)} ` : ""}${theme.fg("muted", "status".padEnd(14))}${theme.fg("muted", "description")}`,
351
- width,
352
- );
218
+ return `${"#".padEnd(4)}${"commit".padEnd(10)}${state.metricName.padEnd(12)}${
219
+ secondaryHeader ? `${secondaryHeader} ` : ""
220
+ }${"status".padEnd(14)}description`;
353
221
  }
354
222
 
355
223
  function renderResultRow(
356
- result: ExperimentResult,
357
- state: ExperimentState,
224
+ result: AutoresearchExperimentResult,
225
+ state: AutoresearchExperimentState,
358
226
  baselineSecondary: { [key: string]: number },
359
- width: number,
360
- theme: Theme,
361
227
  ): string {
362
- const runNumber = result.runNumber ?? state.results.indexOf(result) + 1;
363
228
  const secondary = state.secondaryMetrics
364
229
  .map(metric =>
365
230
  truncateToWidth(
@@ -368,15 +233,14 @@ function renderResultRow(
368
233
  ).padEnd(11),
369
234
  )
370
235
  .join("");
371
- const statusColor = result.status === "keep" ? "success" : result.status === "discard" ? "warning" : "error";
372
- const line =
373
- `${theme.fg("dim", String(runNumber).padEnd(4))}` +
374
- `${theme.fg("accent", (result.commit || "-").padEnd(10))}` +
375
- `${theme.fg(statusColor, formatNum(result.metric, state.metricUnit).padEnd(12))}` +
236
+ return (
237
+ `${String(result.runNumber).padEnd(4)}` +
238
+ `${(result.commit || "-").padEnd(10)}` +
239
+ `${formatNum(result.metric, state.metricUnit).padEnd(12)}` +
376
240
  `${secondary}` +
377
- `${theme.fg(statusColor, result.status.padEnd(14))}` +
378
- `${theme.fg("muted", replaceTabs(result.description))}`;
379
- return truncateToWidth(line, width);
241
+ `${result.status.padEnd(14)}` +
242
+ `${replaceTabs(result.description)}`
243
+ );
380
244
  }
381
245
 
382
246
  function renderSecondaryCell(value: number | undefined, unit: string, baseline: number | undefined): string {
@@ -403,58 +267,14 @@ function renderSecondarySummary(
403
267
  return `${name} ${formatNum(value, unit)} ${sign}${delta.toFixed(1)}%`;
404
268
  }
405
269
 
406
- function renderOverlayRunningLine(
407
- runtime: AutoresearchRuntime,
408
- theme: Theme,
409
- width: number,
410
- spinnerFrame: number,
411
- ): string {
412
- const spinner = theme.spinnerFrames[spinnerFrame % theme.spinnerFrames.length] ?? "*";
413
- return truncateToWidth(
414
- theme.fg(
415
- "warning",
416
- `${spinner} running ${formatElapsed(Date.now() - (runtime.runningExperiment?.startedAt ?? Date.now()))} ${replaceTabs(
417
- runtime.runningExperiment?.command ?? "",
418
- )}`,
419
- ),
420
- width,
421
- );
422
- }
423
-
424
- function renderOverlayFooter(
425
- width: number,
426
- scrollOffset: number,
427
- viewportRows: number,
428
- totalRows: number,
429
- theme: Theme,
430
- ): string {
431
- const position =
432
- totalRows > viewportRows
433
- ? ` ${scrollOffset + 1}-${Math.min(totalRows, scrollOffset + viewportRows)}/${totalRows}`
434
- : "";
435
- const hint = theme.fg("dim", ` up/down j/k pageup pagedown g G esc${position} `);
436
- const fill = Math.max(0, width - visibleWidth(hint));
437
- return theme.fg("borderMuted", "-".repeat(fill)) + hint;
438
- }
439
-
440
- function renderModeStatus(runtime: AutoresearchRuntime, state: ExperimentState): string {
441
- if (runtime.autoresearchMode) {
442
- return state.results.length === 0 ? "baseline pending" : "mode on";
443
- }
444
- const current = currentResults(state.results, state.currentSegment);
445
- if (state.maxExperiments !== null && current.length >= state.maxExperiments) {
446
- return "segment complete";
447
- }
448
- return "mode off";
449
- }
450
-
451
- function findBestResult(state: ExperimentState): { index: number; result: ExperimentResult } | null {
452
- let best: { index: number; result: ExperimentResult } | null = null;
453
- for (let index = 0; index < state.results.length; index += 1) {
454
- const result = state.results[index];
455
- if (result.segment !== state.currentSegment || result.status !== "keep" || result.metric <= 0) continue;
456
- if (!best || isBetter(result.metric, best.result.metric, state.bestDirection)) {
457
- best = { index, result };
270
+ /** Best kept, unflagged result in the current segment (direction-aware). */
271
+ function findBestResult(state: AutoresearchExperimentState): AutoresearchExperimentResult | null {
272
+ let best: AutoresearchExperimentResult | null = null;
273
+ for (const result of state.results) {
274
+ if (result.segment !== state.currentSegment || result.status !== "keep" || result.flagged) continue;
275
+ if (result.metric === null || result.metric <= 0) continue;
276
+ if (!best || best.metric === null || isBetter(result.metric, best.metric, state.bestDirection)) {
277
+ best = result;
458
278
  }
459
279
  }
460
280
  return best;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Autoresearch data context: RLM data-description loading, GATED to explicit
3
+ * `data` or `mixed` mission mode only.
4
+ *
5
+ * This is the AC-16 no-inference rule at the consumption end: a mission mode is
6
+ * never inferred from the presence of a data file, so in `web` mode a data
7
+ * description must NOT be loaded — not an explicit `--data` flag, and not a
8
+ * project-root `DATA.md` auto-load. `data` and `mixed` modes may load one.
9
+ */
10
+ import { loadRlmDataContext, type RlmDataContext } from "../rlm/data-context";
11
+
12
+ export type { RlmDataContext };
13
+
14
+ export type AutoresearchDataContextMode = "web" | "mixed" | "data";
15
+
16
+ export interface AutoresearchDataContextInput {
17
+ cwd: string;
18
+ mode: AutoresearchDataContextMode;
19
+ /** Explicit data path (resolved against `cwd`). Optional. */
20
+ dataFlag?: string;
21
+ }
22
+
23
+ /**
24
+ * Load the data description for a mission, or return null when the mode does
25
+ * not permit one. `web` mode always returns null without touching the
26
+ * filesystem; `data`/`mixed` delegate to the RLM loader (explicit `--data`
27
+ * path wins over project-root `DATA.md`, which auto-loads when present).
28
+ */
29
+ export async function loadAutoresearchDataContext(input: AutoresearchDataContextInput): Promise<RlmDataContext | null> {
30
+ if (input.mode === "web") {
31
+ // AC-16 consumption gate: never infer or attach data context in web mode.
32
+ return null;
33
+ }
34
+ return loadRlmDataContext(input.cwd, input.dataFlag);
35
+ }