@gajae-code/coding-agent 0.11.11 → 0.12.1

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 (344) hide show
  1. package/CHANGELOG.md +131 -4
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/cli/read-cli.d.ts +1 -0
  4. package/dist/types/commands/deep-interview.d.ts +9 -0
  5. package/dist/types/commands/harness.d.ts +22 -0
  6. package/dist/types/commands/read.d.ts +6 -0
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/provider-auth-health.d.ts +14 -0
  9. package/dist/types/config/provider-ranking.d.ts +59 -0
  10. package/dist/types/config/settings-schema.d.ts +83 -1
  11. package/dist/types/config/settings.d.ts +2 -0
  12. package/dist/types/exa/mcp-client.d.ts +10 -1
  13. package/dist/types/exec/bash-executor.d.ts +45 -12
  14. package/dist/types/extensibility/extensions/types.d.ts +38 -4
  15. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  16. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  17. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  18. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  19. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  20. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  21. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  22. package/dist/types/extensibility/shared-events.d.ts +6 -0
  23. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  24. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  25. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  26. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  27. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  28. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  29. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  30. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +70 -1
  31. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  32. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  33. package/dist/types/gjc-runtime/team-runtime.d.ts +27 -1
  34. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  35. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  36. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  37. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +17 -0
  38. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  39. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  40. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  41. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  42. package/dist/types/main.d.ts +1 -1
  43. package/dist/types/memories/index.d.ts +2 -0
  44. package/dist/types/modes/acp/acp-agent.d.ts +10 -19
  45. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  46. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  47. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  48. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  49. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  50. package/dist/types/modes/components/session-observer-overlay.d.ts +22 -0
  51. package/dist/types/modes/components/settings-selector.d.ts +11 -0
  52. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  53. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  54. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  55. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  56. package/dist/types/modes/interactive-mode.d.ts +2 -0
  57. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  58. package/dist/types/modes/types.d.ts +2 -0
  59. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  60. package/dist/types/reminders/star-reminder.d.ts +3 -13
  61. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  62. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  63. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  64. package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
  65. package/dist/types/sdk/broker/transport.d.ts +2 -0
  66. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  68. package/dist/types/sdk/bus/config.d.ts +6 -2
  69. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  70. package/dist/types/sdk/bus/index.d.ts +44 -1
  71. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +44 -0
  72. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  73. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  74. package/dist/types/sdk/bus/reconciliation-store.d.ts +69 -0
  75. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  76. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  77. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  78. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  79. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  80. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  81. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  82. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  83. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  84. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  85. package/dist/types/sdk/host/host.d.ts +2 -0
  86. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  87. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  88. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  89. package/dist/types/sdk/prompt-status.d.ts +23 -3
  90. package/dist/types/sdk/session.d.ts +6 -0
  91. package/dist/types/session/agent-session.d.ts +82 -12
  92. package/dist/types/session/artifacts.d.ts +8 -0
  93. package/dist/types/session/blob-store.d.ts +57 -4
  94. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  95. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  96. package/dist/types/session/session-manager.d.ts +48 -19
  97. package/dist/types/session/session-storage.d.ts +4 -0
  98. package/dist/types/session/streaming-output.d.ts +96 -12
  99. package/dist/types/setup/credential-import.d.ts +12 -0
  100. package/dist/types/skill-state/active-state.d.ts +12 -4
  101. package/dist/types/skill-state/workflow-hud.d.ts +9 -0
  102. package/dist/types/task/executor.d.ts +3 -2
  103. package/dist/types/task/index.d.ts +3 -1
  104. package/dist/types/task/provider-retry-status.d.ts +8 -0
  105. package/dist/types/task/receipt.d.ts +7 -1
  106. package/dist/types/task/types.d.ts +23 -0
  107. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  108. package/dist/types/tools/bash-interactive.d.ts +4 -1
  109. package/dist/types/tools/bash.d.ts +3 -1
  110. package/dist/types/tools/browser/launch.d.ts +7 -0
  111. package/dist/types/tools/fetch.d.ts +17 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -0
  113. package/dist/types/tools/index.d.ts +4 -0
  114. package/dist/types/tools/output-meta.d.ts +44 -3
  115. package/dist/types/tools/path-utils.d.ts +1 -0
  116. package/dist/types/tools/read-internals.d.ts +12 -0
  117. package/dist/types/tools/read.d.ts +45 -7
  118. package/dist/types/tools/resource-gc.d.ts +4 -0
  119. package/dist/types/tools/review.d.ts +5 -7
  120. package/dist/types/tools/subagent.d.ts +2 -0
  121. package/dist/types/tools/tool-result.d.ts +7 -1
  122. package/dist/types/utils/gh.d.ts +13 -0
  123. package/dist/types/utils/image-resize.d.ts +1 -0
  124. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  125. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  126. package/dist/types/web/search/providers/xai.d.ts +2 -0
  127. package/package.json +11 -13
  128. package/scripts/acp-conformance-agent.ts +218 -0
  129. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  130. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  131. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  132. package/scripts/generate-sdk-operation-inventory.ts +5 -0
  133. package/scripts/resident-memory-bench.ts +1341 -0
  134. package/scripts/run-acp-conformance.ts +285 -0
  135. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  136. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  137. package/src/async/job-manager.ts +78 -20
  138. package/src/capability/index.ts +8 -0
  139. package/src/cli/fast-help.ts +1 -1
  140. package/src/cli/plugin-cli.ts +176 -18
  141. package/src/cli/read-cli.ts +2 -1
  142. package/src/cli.ts +1 -0
  143. package/src/commands/deep-interview.ts +29 -9
  144. package/src/commands/harness.ts +35 -12
  145. package/src/commands/read.ts +11 -2
  146. package/src/commands/team.ts +20 -2
  147. package/src/config/file-lock.ts +11 -2
  148. package/src/config/model-registry.ts +21 -2
  149. package/src/config/provider-auth-health.ts +42 -0
  150. package/src/config/provider-ranking.ts +119 -0
  151. package/src/config/settings-schema.ts +74 -1
  152. package/src/config/settings.ts +246 -108
  153. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  154. package/src/coordinator-mcp/server.ts +5 -12
  155. package/src/defaults/gjc/skills/deep-interview/SKILL.md +230 -82
  156. package/src/defaults/gjc/skills/ralplan/SKILL.md +77 -41
  157. package/src/defaults/gjc/skills/team/SKILL.md +3 -1
  158. package/src/defaults/gjc/skills/ultragoal/SKILL.md +62 -39
  159. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  160. package/src/defaults/gjc-defaults.ts +4 -4
  161. package/src/eval/py/executor.ts +7 -2
  162. package/src/exa/mcp-client.ts +12 -3
  163. package/src/exec/bash-executor.ts +144 -27
  164. package/src/extensibility/extensions/runner.ts +15 -0
  165. package/src/extensibility/extensions/types.ts +46 -5
  166. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  167. package/src/extensibility/gjc-plugins/index.ts +19 -2
  168. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  169. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  170. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  171. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  172. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  173. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  174. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  175. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  176. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  177. package/src/extensibility/gjc-plugins/types.ts +141 -0
  178. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  179. package/src/extensibility/shared-events.ts +5 -0
  180. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
  181. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  182. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  183. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  184. package/src/gjc-runtime/deep-interview-state.ts +12 -943
  185. package/src/gjc-runtime/gc-render.ts +1 -0
  186. package/src/gjc-runtime/gc-runtime.ts +11 -1
  187. package/src/gjc-runtime/launch-tmux.ts +25 -6
  188. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  189. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  190. package/src/gjc-runtime/ralplan-runtime.ts +1149 -98
  191. package/src/gjc-runtime/state-runtime.ts +2 -1
  192. package/src/gjc-runtime/state-writer.ts +70 -222
  193. package/src/gjc-runtime/team-launch.ts +42 -5
  194. package/src/gjc-runtime/team-runtime.ts +1085 -40
  195. package/src/gjc-runtime/team-store.ts +32 -0
  196. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  197. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  198. package/src/gjc-runtime/team-workers.ts +1 -0
  199. package/src/gjc-runtime/tmux-owner-isolation.ts +28 -0
  200. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  201. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  202. package/src/gjc-runtime/ultragoal-runtime.ts +414 -618
  203. package/src/gjc-runtime/workflow-manifest.generated.json +105 -449
  204. package/src/gjc-runtime/workflow-manifest.ts +38 -247
  205. package/src/hooks/native-skill-hook.ts +17 -4
  206. package/src/hooks/skill-state.ts +1 -23
  207. package/src/internal-urls/docs-index.generated.ts +21 -22
  208. package/src/internal-urls/local-root-gc.ts +127 -0
  209. package/src/main.ts +9 -5
  210. package/src/memories/index.ts +10 -0
  211. package/src/modes/DESIGN.md +98 -0
  212. package/src/modes/acp/acp-agent.ts +339 -191
  213. package/src/modes/acp/acp-event-mapper.ts +14 -7
  214. package/src/modes/acp/acp-mode.ts +39 -3
  215. package/src/modes/components/assistant-message.ts +123 -22
  216. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  217. package/src/modes/components/irc-sidebar.ts +176 -62
  218. package/src/modes/components/model-selector.ts +61 -4
  219. package/src/modes/components/notifications-settings-editor.ts +12 -1
  220. package/src/modes/components/oauth-selector.ts +96 -28
  221. package/src/modes/components/read-tool-group.ts +82 -14
  222. package/src/modes/components/session-observer-overlay.ts +457 -83
  223. package/src/modes/components/settings-selector.ts +116 -23
  224. package/src/modes/components/status-line/gh.ts +16 -0
  225. package/src/modes/components/tool-execution.ts +138 -27
  226. package/src/modes/components/tool-status-header.ts +3 -14
  227. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  228. package/src/modes/controllers/command-controller.ts +4 -0
  229. package/src/modes/controllers/event-controller.ts +109 -27
  230. package/src/modes/controllers/extension-ui-controller.ts +26 -2
  231. package/src/modes/controllers/input-controller.ts +18 -3
  232. package/src/modes/controllers/selector-controller.ts +127 -36
  233. package/src/modes/interactive-mode.ts +62 -3
  234. package/src/modes/irc-observation-ledger.ts +71 -15
  235. package/src/modes/runtime-init.ts +22 -2
  236. package/src/modes/types.ts +2 -0
  237. package/src/modes/utils/ui-helpers.ts +94 -1
  238. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  239. package/src/prompts/agents/architect.md +9 -1
  240. package/src/prompts/agents/critic.md +9 -0
  241. package/src/prompts/system/eager-todo.md +8 -4
  242. package/src/prompts/system/system-prompt.md +11 -5
  243. package/src/prompts/tools/bash.md +5 -5
  244. package/src/prompts/tools/read.md +4 -2
  245. package/src/prompts/tools/search-tool-bm25.md +5 -0
  246. package/src/prompts/tools/task.md +1 -1
  247. package/src/reminders/star-reminder.ts +4 -42
  248. package/src/runtime-mcp/manager.ts +19 -4
  249. package/src/runtime-mcp/smithery-auth.ts +17 -2
  250. package/src/runtime-mcp/smithery-connect.ts +11 -1
  251. package/src/sdk/acp/adapter.ts +22 -6
  252. package/src/sdk/broker/lifecycle.ts +25 -6
  253. package/src/sdk/broker/transport.ts +35 -3
  254. package/src/sdk/bus/attachment-registry.ts +5 -1
  255. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  256. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  257. package/src/sdk/bus/config.ts +18 -2
  258. package/src/sdk/bus/control-drain-lease.ts +104 -0
  259. package/src/sdk/bus/index.ts +984 -146
  260. package/src/sdk/bus/kind-aware-reconciliation.ts +317 -0
  261. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  262. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  263. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  264. package/src/sdk/bus/reconciliation-store.ts +303 -0
  265. package/src/sdk/bus/rich-draft.ts +1 -1
  266. package/src/sdk/bus/rich-render.ts +2 -2
  267. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  268. package/src/sdk/bus/telegram-cli.ts +33 -8
  269. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  270. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  271. package/src/sdk/bus/telegram-daemon.ts +916 -23
  272. package/src/sdk/bus/telegram-reference.ts +40 -3
  273. package/src/sdk/bus/threaded-render.ts +48 -3
  274. package/src/sdk/bus/topic-registry.ts +20 -0
  275. package/src/sdk/host/control/dispatch.ts +5 -1
  276. package/src/sdk/host/control/operations.ts +1 -1
  277. package/src/sdk/host/host.ts +23 -1
  278. package/src/sdk/host/query/handlers.ts +55 -0
  279. package/src/sdk/host/query/revision-store.ts +36 -2
  280. package/src/sdk/host/reverse-leases.ts +44 -7
  281. package/src/sdk/mcp/server.ts +46 -18
  282. package/src/sdk/prompt-status.ts +16 -3
  283. package/src/sdk/protocol/operation-inventory.generated.json +51 -0
  284. package/src/sdk/protocol/operation-registry.ts +7 -2
  285. package/src/sdk/session.ts +101 -10
  286. package/src/session/agent-session.ts +1286 -220
  287. package/src/session/artifacts.ts +22 -0
  288. package/src/session/auth-broker-config.ts +10 -3
  289. package/src/session/blob-store.ts +915 -24
  290. package/src/session/internal/managed-session-scope.ts +44 -6
  291. package/src/session/internal/managed-session-storage.ts +48 -6
  292. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  293. package/src/session/session-manager.ts +2364 -438
  294. package/src/session/session-storage.ts +9 -1
  295. package/src/session/streaming-output.ts +500 -111
  296. package/src/setup/credential-import.ts +75 -13
  297. package/src/setup/provider-onboarding.ts +19 -4
  298. package/src/skill-state/active-state.ts +32 -10
  299. package/src/skill-state/workflow-hud.ts +42 -2
  300. package/src/skill-state/workflow-mutation-guard.ts +560 -8
  301. package/src/task/executor.ts +122 -39
  302. package/src/task/index.ts +23 -14
  303. package/src/task/provider-retry-status.ts +16 -0
  304. package/src/task/receipt.ts +23 -7
  305. package/src/task/render.ts +6 -2
  306. package/src/task/types.ts +108 -0
  307. package/src/tools/ask-answer-registry.ts +12 -6
  308. package/src/tools/ask.ts +169 -118
  309. package/src/tools/ast-edit.ts +1 -0
  310. package/src/tools/ast-grep.ts +1 -0
  311. package/src/tools/bash-interactive.ts +8 -3
  312. package/src/tools/bash.ts +503 -60
  313. package/src/tools/browser/launch.ts +41 -6
  314. package/src/tools/fetch.ts +114 -23
  315. package/src/tools/find.ts +1 -0
  316. package/src/tools/image-gen.ts +29 -5
  317. package/src/tools/index.ts +4 -0
  318. package/src/tools/output-meta.ts +281 -20
  319. package/src/tools/path-utils.ts +8 -3
  320. package/src/tools/read-internals.ts +44 -0
  321. package/src/tools/read.ts +1560 -310
  322. package/src/tools/resource-gc.ts +182 -18
  323. package/src/tools/review.ts +5 -7
  324. package/src/tools/search.ts +1 -0
  325. package/src/tools/subagent-render.ts +6 -2
  326. package/src/tools/subagent.ts +6 -0
  327. package/src/tools/telegram-send.ts +6 -3
  328. package/src/tools/tool-result.ts +14 -1
  329. package/src/utils/gh.ts +39 -0
  330. package/src/utils/image-resize.ts +3 -0
  331. package/src/web/search/providers/anthropic.ts +4 -4
  332. package/src/web/search/providers/codex.ts +1 -1
  333. package/src/web/search/providers/kimi.ts +15 -5
  334. package/src/web/search/providers/searxng.ts +47 -4
  335. package/src/web/search/providers/xai.ts +7 -2
  336. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  337. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  338. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  339. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  340. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  341. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  342. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  343. package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
  344. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
@@ -0,0 +1,239 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { Container, renderMetrics, Text, TUI } from "@gajae-code/tui";
4
+ import { VirtualTerminal } from "../../tui/test/virtual-terminal";
5
+ import {
6
+ __ircSidebarPerfCounters,
7
+ type IrcSidebarTheme,
8
+ IrcSplitViewComponent,
9
+ } from "../src/modes/components/irc-sidebar";
10
+ import { __ircLedgerPerfCounters, IrcObservationLedger } from "../src/modes/irc-observation-ledger";
11
+
12
+ const SCHEMA_VERSION = 1;
13
+ const WIDTH = 80;
14
+ const HEIGHTS = [1, 3, 10] as const;
15
+ const TRANSCRIPT_ROWS = [10_000, 100_000] as const;
16
+ const STICKY_SUFFIX_WORKLOAD_NAMES = [
17
+ "sticky-suffix-10000-rows-height-1",
18
+ "sticky-suffix-10000-rows-height-3",
19
+ "sticky-suffix-10000-rows-height-10",
20
+ "sticky-suffix-100000-rows-height-1",
21
+ "sticky-suffix-100000-rows-height-3",
22
+ "sticky-suffix-100000-rows-height-10",
23
+ ] as const;
24
+
25
+ const stickySuffixMatrix = TRANSCRIPT_ROWS.flatMap(transcriptRows =>
26
+ HEIGHTS.map(height => ({
27
+ transcriptRows,
28
+ height,
29
+ name: `sticky-suffix-${transcriptRows}-rows-height-${height}`,
30
+ })),
31
+ );
32
+
33
+ function assertStickySuffixMatrix(): void {
34
+ const names = stickySuffixMatrix.map(workload => workload.name);
35
+ if (JSON.stringify(names) !== JSON.stringify(STICKY_SUFFIX_WORKLOAD_NAMES))
36
+ throw new Error("sticky suffix workload matrix changed");
37
+ }
38
+
39
+ const sidebarTheme = {
40
+ fg: (_color: "dim" | "accent", text: string) => text,
41
+ bold: (text: string) => text,
42
+ boxSharp: { vertical: "|" },
43
+ } satisfies IrcSidebarTheme;
44
+
45
+ type HardCounters = Readonly<Record<string, number | boolean>>;
46
+ type WorkloadResult = Readonly<{ name: string; hard: HardCounters; advisory: Record<string, number> }>;
47
+ export type StickyViewportPr1Benchmark = Readonly<{
48
+ schemaVersion: number;
49
+ workloads: readonly WorkloadResult[];
50
+ advisory: {
51
+ readonly timingAndMemoryOnly: true;
52
+ readonly wallMs: number;
53
+ readonly cpuUserMicros: number;
54
+ readonly cpuSystemMicros: number;
55
+ readonly rssBytes: number;
56
+ };
57
+ }>;
58
+
59
+ function rows(count: number, prefix: string): Text[] {
60
+ return Array.from({ length: count }, (_, index) => new Text(`${prefix}-${index}`, 0, 0));
61
+ }
62
+
63
+ async function settle(terminal: VirtualTerminal): Promise<void> {
64
+ await terminal.waitForRender();
65
+ await Promise.resolve();
66
+ }
67
+
68
+ async function stickySuffixWorkload(transcriptRows: number, height: number): Promise<WorkloadResult> {
69
+ renderMetrics.reset();
70
+ const terminal = new VirtualTerminal(WIDTH, height, { isProcessTerminal: true });
71
+ const tui = new TUI(terminal);
72
+ const transcript = new Container();
73
+ for (const row of rows(transcriptRows, "transcript")) transcript.addChild(row);
74
+ const status = new Text("status: pinned", 0, 0);
75
+ const suffix = Array.from({ length: height + 3 }, (_value, index) => new Text(`suffix-${index}`, 0, 0));
76
+ tui.addChild(transcript);
77
+ tui.addChild(status);
78
+ for (const row of suffix) tui.addChild(row);
79
+ tui.setBottomPinnedComponent(status);
80
+ const flatFrameRows = transcriptRows + suffix.length + 1;
81
+ const originalSlice = Array.prototype.slice;
82
+ let flatFrameSlices = 0;
83
+ let negativeControlSlices = 0;
84
+ const isLargeFlatFrame = (receiver: unknown[]): boolean =>
85
+ receiver.length === flatFrameRows &&
86
+ receiver[0] === "transcript-0" &&
87
+ receiver[transcriptRows - 1] === `transcript-${transcriptRows - 1}`;
88
+ try {
89
+ Array.prototype.slice = function <T>(this: T[], start?: number, end?: number): T[] {
90
+ if (isLargeFlatFrame(this)) flatFrameSlices++;
91
+ return originalSlice.call(this, start, end);
92
+ };
93
+ const negativeControl = Array.from({ length: flatFrameRows }, (_value, index) =>
94
+ index < transcriptRows ? `transcript-${index}` : "suffix",
95
+ );
96
+ negativeControl.slice(0, 1);
97
+ negativeControlSlices = flatFrameSlices;
98
+ flatFrameSlices = 0;
99
+ tui.start();
100
+ await settle(terminal);
101
+ const structural = renderMetrics.snapshot().structuralCounters;
102
+ const selected = structural.pinnedSuffixSelectedRows ?? 0;
103
+ const overflowFrames = structural.pinnedSuffixOverflowFrames ?? 0;
104
+ if (flatFrameSlices !== 0) throw new Error("pinned suffix sliced the large transcript frame");
105
+ if (negativeControlSlices !== 1) throw new Error("large-frame slice detector did not count its negative control");
106
+ if (selected > height) throw new Error("pinned suffix selected rows exceed terminal height");
107
+ if (overflowFrames !== 1 || selected === 0) throw new Error("pinned suffix overflow workload did not execute");
108
+ return {
109
+ name: `sticky-suffix-${transcriptRows}-rows-height-${height}`,
110
+ hard: {
111
+ largeFlatFrameSliceCalls: flatFrameSlices,
112
+ pinnedSuffixOverflowFrames: overflowFrames,
113
+ pinnedSuffixSelectedRows: selected,
114
+ },
115
+ advisory: { renderCount: renderMetrics.snapshot().renderCount },
116
+ };
117
+ } finally {
118
+ Array.prototype.slice = originalSlice;
119
+ tui.stop();
120
+ }
121
+ }
122
+
123
+ async function equalOutputSourceWorkload(): Promise<WorkloadResult> {
124
+ renderMetrics.reset();
125
+ const terminal = new VirtualTerminal(WIDTH, 10);
126
+ const tui = new TUI(terminal);
127
+ try {
128
+ tui.addChild(new Text("output", 0, 0));
129
+ tui.setViewportOutputSource({ identity: "pr1-equal", revision: 1n });
130
+ tui.start();
131
+ await settle(terminal);
132
+ const before = renderMetrics.snapshot().renderCount;
133
+ for (let index = 0; index < 1_000; index++) tui.setViewportOutputSource({ identity: "pr1-equal", revision: 1n });
134
+ await Promise.resolve();
135
+ const snapshot = renderMetrics.snapshot();
136
+ const equalNoops = snapshot.structuralCounters.viewportOutputSourceEqualNoops ?? 0;
137
+ if (snapshot.renderCount !== before || equalNoops !== 1_000)
138
+ throw new Error("equal output source requested a render");
139
+ return {
140
+ name: "equal-output-source-1000",
141
+ hard: { equalNoops, renderRequests: snapshot.renderCount - before },
142
+ advisory: {},
143
+ };
144
+ } finally {
145
+ tui.stop();
146
+ }
147
+ }
148
+
149
+ function addObservation(ledger: IrcObservationLedger, id: string): void {
150
+ ledger.observe(
151
+ { observationId: id, kind: "incoming", from: "peer", to: "you", text: `observation ${id}`, timestamp: 0 },
152
+ false,
153
+ );
154
+ }
155
+
156
+ function sidebarCacheWorkload(): WorkloadResult {
157
+ __ircSidebarPerfCounters.reset();
158
+ __ircSidebarPerfCounters.enable();
159
+ __ircLedgerPerfCounters.reset();
160
+ __ircLedgerPerfCounters.enable();
161
+ const ledger = new IrcObservationLedger();
162
+ for (let index = 0; index < 9_999; index++) addObservation(ledger, `history-${index}`);
163
+ const split = new IrcSplitViewComponent(new Text("left pane", 0, 0), ledger, sidebarTheme);
164
+ split.setVisible(true);
165
+ for (let index = 0; index < 100; index++) split.render(WIDTH);
166
+ const stable = __ircSidebarPerfCounters.snapshot();
167
+ addObservation(ledger, "mutation");
168
+ split.render(WIDTH);
169
+ const final = __ircSidebarPerfCounters.snapshot();
170
+ const ledgerCounters = __ircLedgerPerfCounters.snapshot();
171
+ if (stable.projectionMemoMisses !== 1 || stable.styledCacheMisses !== 1 || stable.wrapCalls === 0)
172
+ throw new Error("unchanged sidebar did not reuse caches");
173
+ if (stable.projectionMemoHits !== 99 || stable.styledCacheHits !== 99)
174
+ throw new Error("unchanged sidebar cache counts changed");
175
+ if (final.wrapCalls <= stable.wrapCalls || final.projectionMemoMisses !== 2 || final.styledCacheMisses !== 2)
176
+ throw new Error("sidebar mutation did not reproject once");
177
+ return {
178
+ name: "irc-sidebar-near-cap-cache",
179
+ hard: {
180
+ ledgerEpochAdvances: ledgerCounters.epochAdvances,
181
+ unchangedProjectionMisses: stable.projectionMemoMisses,
182
+ unchangedProjectionHits: stable.projectionMemoHits,
183
+ unchangedStyledMisses: stable.styledCacheMisses,
184
+ unchangedStyledHits: stable.styledCacheHits,
185
+ unchangedWrapCalls: stable.wrapCalls,
186
+ mutationProjectionMisses: final.projectionMemoMisses,
187
+ mutationStyledMisses: final.styledCacheMisses,
188
+ mutationWrapCalls: final.wrapCalls,
189
+ },
190
+ advisory: {},
191
+ };
192
+ }
193
+
194
+ export async function runStickyViewportPr1Benchmark(): Promise<StickyViewportPr1Benchmark> {
195
+ const cpu = process.cpuUsage();
196
+ const started = performance.now();
197
+ const wasEnabled = renderMetrics.enabled;
198
+ renderMetrics.enable();
199
+ try {
200
+ assertStickySuffixMatrix();
201
+ const workloads: WorkloadResult[] = [];
202
+ for (const workload of stickySuffixMatrix)
203
+ workloads.push(await stickySuffixWorkload(workload.transcriptRows, workload.height));
204
+ workloads.push(await equalOutputSourceWorkload(), sidebarCacheWorkload());
205
+ const usage = process.cpuUsage(cpu);
206
+ return {
207
+ schemaVersion: SCHEMA_VERSION,
208
+ workloads,
209
+ advisory: {
210
+ timingAndMemoryOnly: true,
211
+ wallMs: performance.now() - started,
212
+ cpuUserMicros: usage.user,
213
+ cpuSystemMicros: usage.system,
214
+ rssBytes: process.memoryUsage().rss,
215
+ },
216
+ };
217
+ } finally {
218
+ renderMetrics.reset();
219
+ __ircSidebarPerfCounters.disable();
220
+ __ircLedgerPerfCounters.disable();
221
+ if (!wasEnabled) renderMetrics.disable();
222
+ }
223
+ }
224
+
225
+ function outputPath(args: readonly string[]): string | undefined {
226
+ const index = args.indexOf("--out");
227
+ if (index < 0 || !args[index + 1]) return undefined;
228
+ return args[index + 1];
229
+ }
230
+
231
+ if (import.meta.main) {
232
+ const benchmark = await runStickyViewportPr1Benchmark();
233
+ const output = `${JSON.stringify(benchmark, null, 2)}\n`;
234
+ const destination = outputPath(process.argv.slice(2));
235
+ if (destination) {
236
+ await fs.mkdir(path.dirname(destination), { recursive: true });
237
+ await Bun.write(destination, output);
238
+ } else process.stdout.write(output);
239
+ }
@@ -0,0 +1,306 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import type { GjcLifecycleContext } from "../src/extensibility/gjc-plugins/lifecycle";
4
+ import type { GjcRuntimeSnapshotProvider } from "../src/extensibility/gjc-plugins/runtime-quarantine";
5
+ import type {
6
+ GjcBundleIdentity,
7
+ GjcBundleSummary,
8
+ GjcLifecycleResult,
9
+ GjcToggleResult,
10
+ GjcUpdateApplyResult,
11
+ GjcUpdatePreview,
12
+ } from "../src/extensibility/gjc-plugins/types";
13
+ import { type GjcBundleLifecyclePort, GjcBundleSettingsComponent } from "../src/modes/components/gjc-bundle-settings";
14
+ import { setTheme } from "../src/modes/theme/theme";
15
+ import {
16
+ GJC_BUNDLE_SETTINGS_ENTRIES,
17
+ GJC_BUNDLE_SETTINGS_STATES,
18
+ GJC_BUNDLE_SETTINGS_VIEWPORTS,
19
+ type GjcBundleSettingsFixture,
20
+ } from "../test/fixtures/gjc-bundles-settings-cases";
21
+
22
+ export const GJC_BUNDLE_SETTINGS_CAPTURE_FILES = [
23
+ "terminal.txt",
24
+ "terminal-ansi.txt",
25
+ "terminal.html",
26
+ "metadata.json",
27
+ ] as const;
28
+
29
+ type CaptureFileName = (typeof GJC_BUNDLE_SETTINGS_CAPTURE_FILES)[number];
30
+
31
+ type FixtureEntry = (typeof GJC_BUNDLE_SETTINGS_ENTRIES)[number];
32
+
33
+ type CapturePlanItem = {
34
+ entryId: string;
35
+ fileName: CaptureFileName;
36
+ };
37
+
38
+ type CaptureMetadata = {
39
+ entryId: string;
40
+ stateId: string;
41
+ viewport: { id: string; cols: number; rows: number };
42
+ variant: { renderMode: string };
43
+ sha256: Record<Exclude<CaptureFileName, "metadata.json">, string>;
44
+ };
45
+
46
+ /**
47
+ * Locator-shaped content that must never reach a capture. Applied to the
48
+ * rendered terminal text, which is the only surface that can carry a leaked
49
+ * locator; generated HTML/CSS legitimately contains `#rrggbb` colors and
50
+ * escaped entities, so it is checked against the same rules via its source
51
+ * text rather than its markup.
52
+ */
53
+ const FORBIDDEN_CAPTURE_CONTENT = /:\/\/user:|@[^\s/]+|\?[^\s]*=|token|\/Users\/|\/home\//i;
54
+
55
+ function sha256(content: string): string {
56
+ return new Bun.CryptoHasher("sha256").update(content).digest("hex");
57
+ }
58
+
59
+ function json(value: unknown): string {
60
+ return `${JSON.stringify(value, null, 2)}\n`;
61
+ }
62
+
63
+ function escapeHtml(text: string): string {
64
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
65
+ }
66
+
67
+ function ansiToHtml(ansi: string): string {
68
+ const sgr = /\x1b\[([0-9;]*)m/g;
69
+ let html = "";
70
+ let offset = 0;
71
+ let style = "";
72
+ for (const match of ansi.matchAll(sgr)) {
73
+ html += `<span style="${style}">${escapeHtml(ansi.slice(offset, match.index))}</span>`;
74
+ offset = (match.index ?? 0) + match[0].length;
75
+ const codes = (match[1] || "0").split(";").map(Number);
76
+ for (let index = 0; index < codes.length; index += 1) {
77
+ const code = codes[index];
78
+ if (code === 0) style = "";
79
+ else if (code === 1) style = `${style}font-weight:700;`;
80
+ else if (code === 2) style = `${style}opacity:.65;`;
81
+ else if (code === 22) style = style.replace("font-weight:700;", "").replace("opacity:.65;", "");
82
+ else if (code === 38 && codes[index + 1] === 2) {
83
+ const [red, green, blue] = codes.slice(index + 2, index + 5);
84
+ if ([red, green, blue].every(Number.isInteger)) style = `${style}color:rgb(${red},${green},${blue});`;
85
+ index += 4;
86
+ } else if (code === 39) style = style.replace(/color:[^;]+;/g, "");
87
+ }
88
+ }
89
+ html += `<span style="${style}">${escapeHtml(ansi.slice(offset))}</span>`;
90
+ return `<!doctype html>
91
+ <html lang="en">
92
+ <head><meta charset="utf-8"><meta name="color-scheme" content="dark"><title>GJC Bundles settings</title><style>body{margin:0;background:#111;color:#eee}pre{margin:0;padding:1em;white-space:pre-wrap;font-family:ui-monospace,monospace;line-height:1.2}</style></head>
93
+ <body><pre>${html}</pre></body>
94
+ </html>
95
+ `;
96
+ }
97
+
98
+ function asciiText(text: string): string {
99
+ return text
100
+ .replaceAll("─", "-")
101
+ .replaceAll("…", "...")
102
+ .replaceAll("·", ".")
103
+ .replaceAll("→", "->")
104
+ .replaceAll("−", "-");
105
+ }
106
+
107
+ function cloneSummary(summary: GjcBundleSummary): GjcBundleSummary {
108
+ return {
109
+ ...summary,
110
+ identity: { ...summary.identity },
111
+ source: { ...summary.source },
112
+ surfaces: summary.surfaces.map(surface => ({ ...surface })),
113
+ };
114
+ }
115
+
116
+ class FixtureLifecyclePort implements GjcBundleLifecyclePort {
117
+ constructor(private readonly fixture: GjcBundleSettingsFixture) {}
118
+
119
+ async listGjcBundles(_ctx: GjcLifecycleContext): Promise<GjcBundleSummary[]> {
120
+ return this.fixture.bundles.map(cloneSummary);
121
+ }
122
+
123
+ async getGjcBundle(
124
+ _ctx: GjcLifecycleContext,
125
+ identity: GjcBundleIdentity,
126
+ ): Promise<GjcLifecycleResult<GjcBundleSummary>> {
127
+ const summary = this.fixture.bundles.find(
128
+ bundle =>
129
+ bundle.identity.kind === identity.kind &&
130
+ bundle.identity.scope === identity.scope &&
131
+ bundle.identity.name === identity.name,
132
+ );
133
+ return summary
134
+ ? { ok: true, value: cloneSummary(summary) }
135
+ : { ok: false, error: { code: "not_installed", message: "Bundle is not installed." } };
136
+ }
137
+
138
+ async previewGjcBundleUpdate(
139
+ _ctx: GjcLifecycleContext,
140
+ _identity: GjcBundleIdentity,
141
+ ): Promise<GjcLifecycleResult<GjcUpdatePreview>> {
142
+ return this.fixture.updatePreview
143
+ ? { ok: true, value: this.fixture.updatePreview }
144
+ : { ok: false, error: { code: "source_unsupported", message: "Update is unavailable." } };
145
+ }
146
+
147
+ async applyGjcBundleUpdate(
148
+ _ctx: GjcLifecycleContext,
149
+ _token: GjcUpdatePreview["token"],
150
+ ): Promise<GjcLifecycleResult<GjcUpdateApplyResult>> {
151
+ return { ok: false, error: { code: "stale_candidate", message: "Capture fixtures do not apply updates." } };
152
+ }
153
+
154
+ async setGjcBundleEnabled(
155
+ _ctx: GjcLifecycleContext,
156
+ _identity: GjcBundleIdentity,
157
+ _enabled: boolean,
158
+ ): Promise<GjcLifecycleResult<GjcToggleResult>> {
159
+ return { ok: false, error: { code: "invalid_target", message: "Capture fixtures do not mutate bundles." } };
160
+ }
161
+
162
+ async setGjcBundleSurfaceEnabled(
163
+ _ctx: GjcLifecycleContext,
164
+ _identity: GjcBundleIdentity,
165
+ _surfaceId: string,
166
+ _enabled: boolean,
167
+ ): Promise<GjcLifecycleResult<GjcToggleResult>> {
168
+ return { ok: false, error: { code: "invalid_target", message: "Capture fixtures do not mutate surfaces." } };
169
+ }
170
+ }
171
+
172
+ function fixtureFor(stateId: string): GjcBundleSettingsFixture {
173
+ const state = GJC_BUNDLE_SETTINGS_STATES.find(candidate => candidate.id === stateId);
174
+ if (!state) throw new Error(`Unknown GJC Bundle settings state: ${stateId}`);
175
+ return state.fixture;
176
+ }
177
+
178
+ function viewportFor(viewportId: string): { id: string; cols: number; rows: number } {
179
+ const viewport = GJC_BUNDLE_SETTINGS_VIEWPORTS.find(candidate => candidate.id === viewportId);
180
+ if (viewport) return viewport;
181
+ if (viewportId === "48x36") return { id: "48x36", cols: 48, rows: 36 };
182
+ throw new Error(`Unknown GJC Bundle settings viewport: ${viewportId}`);
183
+ }
184
+
185
+ async function settle(): Promise<void> {
186
+ for (let index = 0; index < 8; index += 1) await Promise.resolve();
187
+ }
188
+
189
+ export async function renderGjcBundleSettingsEntry(
190
+ entry: FixtureEntry,
191
+ ): Promise<{ terminalText: string; terminalAnsiText: string; viewport: { id: string; cols: number; rows: number } }> {
192
+ const fixture = fixtureFor(entry.stateId);
193
+ const viewport = viewportFor(entry.viewportId);
194
+ const runtime: GjcRuntimeSnapshotProvider = { current: () => fixture.runtime };
195
+ await setTheme("red-claw");
196
+ const component = new GjcBundleSettingsComponent(
197
+ "/fixture/project",
198
+ { onClose: () => {} },
199
+ {
200
+ lifecycle: new FixtureLifecyclePort(fixture),
201
+ runtimeSnapshotProvider: runtime,
202
+ activationGeneration: 7,
203
+ },
204
+ );
205
+ await settle();
206
+ const rendered = component.render(viewport.cols).join("\n");
207
+ component.dispose();
208
+ const terminalAnsiText = entry.renderMode === "ascii-no-color" ? asciiText(Bun.stripANSI(rendered)) : rendered;
209
+ const terminalText = Bun.stripANSI(terminalAnsiText);
210
+ return { terminalText, terminalAnsiText, viewport };
211
+ }
212
+
213
+ export function gjcBundleSettingsCapturePlan(
214
+ entries: readonly FixtureEntry[] = GJC_BUNDLE_SETTINGS_ENTRIES,
215
+ ): CapturePlanItem[] {
216
+ return entries.flatMap(entry =>
217
+ GJC_BUNDLE_SETTINGS_CAPTURE_FILES.map(fileName => ({ entryId: entry.entryId, fileName })),
218
+ );
219
+ }
220
+
221
+ function assertSafeContent(label: string, content: string): void {
222
+ if (FORBIDDEN_CAPTURE_CONTENT.test(content)) throw new Error(`Unsafe locator content in ${label}`);
223
+ }
224
+
225
+ async function artifactContents(entry: FixtureEntry): Promise<Record<CaptureFileName, string>> {
226
+ const rendered = await renderGjcBundleSettingsEntry(entry);
227
+ const terminalHtml = ansiToHtml(rendered.terminalAnsiText);
228
+ const metadata: CaptureMetadata = {
229
+ entryId: entry.entryId,
230
+ stateId: entry.stateId,
231
+ viewport: rendered.viewport,
232
+ variant: { renderMode: entry.renderMode },
233
+ sha256: {
234
+ "terminal.txt": sha256(rendered.terminalText),
235
+ "terminal-ansi.txt": sha256(rendered.terminalAnsiText),
236
+ "terminal.html": sha256(terminalHtml),
237
+ },
238
+ };
239
+ return {
240
+ "terminal.txt": rendered.terminalText,
241
+ "terminal-ansi.txt": rendered.terminalAnsiText,
242
+ "terminal.html": terminalHtml,
243
+ "metadata.json": json(metadata),
244
+ };
245
+ }
246
+
247
+ async function writeEntry(entry: FixtureEntry, outputRoot: string): Promise<void> {
248
+ const artifacts = await artifactContents(entry);
249
+ // The HTML artifact is a pure rendering of the ANSI text, so proving the two
250
+ // text surfaces and the metadata are clean proves the whole entry is clean.
251
+ for (const name of ["terminal.txt", "terminal-ansi.txt", "metadata.json"] as const) {
252
+ assertSafeContent(`${entry.entryId}/${name}`, artifacts[name]);
253
+ }
254
+ const directory = path.join(outputRoot, entry.entryId);
255
+ await fs.mkdir(directory, { recursive: true });
256
+ await Promise.all(
257
+ GJC_BUNDLE_SETTINGS_CAPTURE_FILES.map(fileName => Bun.write(path.join(directory, fileName), artifacts[fileName])),
258
+ );
259
+ }
260
+
261
+ export async function verifyGjcBundleSettingsCapture(outputRoot: string): Promise<void> {
262
+ for (const entry of GJC_BUNDLE_SETTINGS_ENTRIES) {
263
+ const directory = path.join(outputRoot, entry.entryId);
264
+ const names = (await fs.readdir(directory)).sort();
265
+ if (
266
+ names.length !== GJC_BUNDLE_SETTINGS_CAPTURE_FILES.length ||
267
+ names.some((name, index) => name !== GJC_BUNDLE_SETTINGS_CAPTURE_FILES.slice().sort()[index])
268
+ ) {
269
+ throw new Error(`Expected exactly four capture files for ${entry.entryId}`);
270
+ }
271
+ const artifacts = await artifactContents(entry);
272
+ for (const fileName of GJC_BUNDLE_SETTINGS_CAPTURE_FILES) {
273
+ const content = await Bun.file(path.join(directory, fileName)).text();
274
+ assertSafeContent(`${entry.entryId}/${fileName}`, content);
275
+ if (content !== artifacts[fileName])
276
+ throw new Error(`Capture does not match deterministic fixture render for ${entry.entryId}/${fileName}`);
277
+ }
278
+ const metadata = JSON.parse(artifacts["metadata.json"]) as CaptureMetadata;
279
+ for (const fileName of ["terminal.txt", "terminal-ansi.txt", "terminal.html"] as const) {
280
+ if (metadata.sha256[fileName] !== sha256(artifacts[fileName]))
281
+ throw new Error(`SHA-256 mismatch for ${entry.entryId}/${fileName}`);
282
+ }
283
+ }
284
+ }
285
+
286
+ function parseArgs(args: string[]): { mode: "capture" | "verify"; outputRoot: string } {
287
+ if (args.length === 2 && args[0] === "--output" && args[1]) return { mode: "capture", outputRoot: args[1] };
288
+ if (args.length === 2 && args[0] === "--verify" && args[1]) return { mode: "verify", outputRoot: args[1] };
289
+ throw new Error("Usage: bun scripts/capture-gjc-bundle-settings.ts --output <directory> | --verify <directory>");
290
+ }
291
+
292
+ async function main(): Promise<void> {
293
+ const { mode, outputRoot } = parseArgs(process.argv.slice(2));
294
+ const resolvedOutputRoot = path.resolve(outputRoot);
295
+ if (mode === "verify") {
296
+ await verifyGjcBundleSettingsCapture(resolvedOutputRoot);
297
+ process.stdout.write(
298
+ `Verified ${GJC_BUNDLE_SETTINGS_ENTRIES.length} deterministic GJC Bundle settings entries.\n`,
299
+ );
300
+ return;
301
+ }
302
+ for (const entry of GJC_BUNDLE_SETTINGS_ENTRIES) await writeEntry(entry, resolvedOutputRoot);
303
+ process.stdout.write(`Captured ${GJC_BUNDLE_SETTINGS_ENTRIES.length} deterministic GJC Bundle settings entries.\n`);
304
+ }
305
+
306
+ if (import.meta.main) await main();