@gajae-code/coding-agent 0.11.10 → 0.12.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 (249) hide show
  1. package/CHANGELOG.md +86 -2
  2. package/dist/types/cli/read-cli.d.ts +1 -0
  3. package/dist/types/cli.d.ts +8 -1
  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-profile-activation.d.ts +1 -0
  8. package/dist/types/config/model-profile-contract.d.ts +2 -0
  9. package/dist/types/config/model-registry.d.ts +4 -0
  10. package/dist/types/config/provider-auth-health.d.ts +14 -0
  11. package/dist/types/config/provider-ranking.d.ts +59 -0
  12. package/dist/types/config/settings-schema.d.ts +86 -3
  13. package/dist/types/config/settings.d.ts +3 -0
  14. package/dist/types/extensibility/extensions/types.d.ts +43 -2
  15. package/dist/types/extensibility/shared-events.d.ts +6 -0
  16. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  17. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  18. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  19. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  20. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  21. package/dist/types/gjc-runtime/linux-proc.d.ts +22 -14
  22. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  23. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +112 -1
  24. package/dist/types/gjc-runtime/state-runtime.d.ts +3 -1
  25. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  26. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  27. package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
  28. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  29. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  30. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
  31. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  32. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  33. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  34. package/dist/types/main.d.ts +1 -1
  35. package/dist/types/memories/index.d.ts +2 -0
  36. package/dist/types/modes/acp/acp-agent.d.ts +22 -9
  37. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  38. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  39. package/dist/types/modes/components/queue-pane.d.ts +8 -0
  40. package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
  41. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  42. package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
  43. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  44. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  45. package/dist/types/modes/components/welcome.d.ts +1 -0
  46. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  47. package/dist/types/modes/interactive-mode.d.ts +7 -1
  48. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  49. package/dist/types/modes/types.d.ts +2 -0
  50. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  51. package/dist/types/runtime/memory-domain.d.ts +9 -0
  52. package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
  53. package/dist/types/runtime/memory-guard.d.ts +39 -0
  54. package/dist/types/runtime/memory-limit.d.ts +11 -0
  55. package/dist/types/runtime-mcp/manager.d.ts +8 -0
  56. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  57. package/dist/types/sdk/acp/final-text.d.ts +19 -0
  58. package/dist/types/sdk/acp/mcp.d.ts +23 -0
  59. package/dist/types/sdk/broker/lifecycle.d.ts +7 -0
  60. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
  61. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  62. package/dist/types/sdk/bus/index.d.ts +18 -2
  63. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
  64. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  65. package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
  66. package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
  67. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -4
  68. package/dist/types/sdk/bus/telegram-daemon.d.ts +23 -3
  69. package/dist/types/sdk/host/control/operations.d.ts +2 -1
  70. package/dist/types/sdk/host/host.d.ts +2 -0
  71. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  72. package/dist/types/sdk/lifecycle-session.d.ts +10 -1
  73. package/dist/types/sdk/startup-capability.d.ts +6 -0
  74. package/dist/types/session/agent-session.d.ts +51 -19
  75. package/dist/types/session/fallback-chain-controller.d.ts +18 -2
  76. package/dist/types/session/internal/managed-session-scope.d.ts +27 -0
  77. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  78. package/dist/types/session/session-manager.d.ts +59 -13
  79. package/dist/types/session/streaming-output.d.ts +60 -5
  80. package/dist/types/skill-state/active-state.d.ts +0 -4
  81. package/dist/types/skill-state/workflow-hud.d.ts +3 -0
  82. package/dist/types/task/executor.d.ts +6 -0
  83. package/dist/types/task/types.d.ts +36 -0
  84. package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
  85. package/dist/types/tools/browser/launch.d.ts +7 -0
  86. package/dist/types/tools/fetch.d.ts +17 -1
  87. package/dist/types/tools/output-meta.d.ts +21 -1
  88. package/dist/types/tools/read-internals.d.ts +12 -0
  89. package/dist/types/tools/read.d.ts +45 -1
  90. package/dist/types/tools/resource-gc.d.ts +23 -0
  91. package/dist/types/tools/tool-result.d.ts +7 -1
  92. package/package.json +9 -13
  93. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  94. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  95. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  96. package/scripts/run-test-manifest.ts +6 -4
  97. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  98. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  99. package/src/capability/index.ts +8 -0
  100. package/src/cli/args.ts +14 -8
  101. package/src/cli/fast-help.ts +1 -1
  102. package/src/cli/read-cli.ts +2 -1
  103. package/src/cli.ts +49 -2
  104. package/src/commands/deep-interview.ts +29 -9
  105. package/src/commands/harness.ts +35 -12
  106. package/src/commands/read.ts +11 -2
  107. package/src/commands/sdk.ts +91 -7
  108. package/src/commands/team.ts +1 -1
  109. package/src/config/model-profile-activation.ts +1 -0
  110. package/src/config/model-profile-contract.ts +13 -0
  111. package/src/config/model-profiles.ts +24 -0
  112. package/src/config/model-registry.ts +21 -2
  113. package/src/config/provider-auth-health.ts +42 -0
  114. package/src/config/provider-ranking.ts +119 -0
  115. package/src/config/settings-schema.ts +84 -3
  116. package/src/config/settings.ts +254 -108
  117. package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
  118. package/src/defaults/gjc/skills/ralplan/SKILL.md +78 -26
  119. package/src/defaults/gjc/skills/team/SKILL.md +1 -1
  120. package/src/defaults/gjc/skills/ultragoal/SKILL.md +26 -16
  121. package/src/eval/py/executor.ts +7 -2
  122. package/src/extensibility/extensions/runner.ts +4 -0
  123. package/src/extensibility/extensions/types.ts +42 -4
  124. package/src/extensibility/shared-events.ts +5 -0
  125. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -68
  126. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  127. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  128. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  129. package/src/gjc-runtime/deep-interview-state.ts +12 -921
  130. package/src/gjc-runtime/gc-render.ts +1 -0
  131. package/src/gjc-runtime/gc-runtime.ts +11 -1
  132. package/src/gjc-runtime/linux-proc.ts +70 -36
  133. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  134. package/src/gjc-runtime/ralplan-runtime.ts +979 -71
  135. package/src/gjc-runtime/state-runtime.ts +50 -12
  136. package/src/gjc-runtime/state-writer.ts +70 -222
  137. package/src/gjc-runtime/team-launch.ts +34 -5
  138. package/src/gjc-runtime/team-runtime.ts +970 -33
  139. package/src/gjc-runtime/team-store.ts +32 -0
  140. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  141. package/src/gjc-runtime/team-workers.ts +1 -0
  142. package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
  143. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  144. package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
  145. package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
  146. package/src/gjc-runtime/workflow-manifest.ts +34 -179
  147. package/src/hooks/native-skill-hook.ts +17 -4
  148. package/src/hooks/skill-state.ts +1 -23
  149. package/src/internal-urls/docs-index.generated.ts +10 -11
  150. package/src/internal-urls/local-protocol.ts +23 -4
  151. package/src/internal-urls/local-root-gc.ts +127 -0
  152. package/src/main.ts +9 -5
  153. package/src/memories/index.ts +10 -0
  154. package/src/modes/DESIGN.md +47 -0
  155. package/src/modes/acp/acp-agent.ts +597 -67
  156. package/src/modes/acp/acp-event-mapper.ts +106 -12
  157. package/src/modes/acp/acp-mode.ts +39 -3
  158. package/src/modes/components/assistant-message.ts +123 -22
  159. package/src/modes/components/irc-sidebar.ts +176 -62
  160. package/src/modes/components/model-selector.ts +76 -5
  161. package/src/modes/components/notifications-settings-editor.ts +1 -1
  162. package/src/modes/components/oauth-selector.ts +42 -12
  163. package/src/modes/components/queue-pane.ts +55 -9
  164. package/src/modes/components/queued-message-selector.ts +53 -10
  165. package/src/modes/components/read-tool-group.ts +82 -14
  166. package/src/modes/components/session-observer-overlay.ts +187 -32
  167. package/src/modes/components/settings-selector.ts +60 -18
  168. package/src/modes/components/tool-execution.ts +138 -27
  169. package/src/modes/components/tool-status-header.ts +1 -4
  170. package/src/modes/components/welcome.ts +18 -3
  171. package/src/modes/controllers/command-controller.ts +4 -0
  172. package/src/modes/controllers/event-controller.ts +109 -27
  173. package/src/modes/controllers/extension-ui-controller.ts +23 -12
  174. package/src/modes/controllers/input-controller.ts +62 -6
  175. package/src/modes/controllers/selector-controller.ts +83 -37
  176. package/src/modes/interactive-mode.ts +127 -29
  177. package/src/modes/irc-observation-ledger.ts +71 -15
  178. package/src/modes/runtime-init.ts +20 -2
  179. package/src/modes/types.ts +2 -0
  180. package/src/modes/utils/ui-helpers.ts +94 -1
  181. package/src/prompts/agents/architect.md +9 -1
  182. package/src/prompts/agents/critic.md +9 -0
  183. package/src/prompts/agents/executor.md +1 -1
  184. package/src/prompts/system/system-prompt.md +0 -1
  185. package/src/prompts/tools/read.md +4 -2
  186. package/src/runtime/memory-domain.ts +66 -0
  187. package/src/runtime/memory-guard-contract.ts +73 -0
  188. package/src/runtime/memory-guard.ts +292 -0
  189. package/src/runtime/memory-limit.ts +50 -0
  190. package/src/runtime-mcp/manager.ts +26 -6
  191. package/src/runtime-mcp/smithery-auth.ts +17 -2
  192. package/src/runtime-mcp/smithery-connect.ts +11 -1
  193. package/src/sdk/acp/adapter.ts +8 -1
  194. package/src/sdk/acp/final-text.ts +40 -0
  195. package/src/sdk/acp/mcp.ts +26 -0
  196. package/src/sdk/broker/lifecycle.ts +107 -7
  197. package/src/sdk/bus/chat-daemon-cli.ts +2 -2
  198. package/src/sdk/bus/chat-daemon-control.ts +4 -3
  199. package/src/sdk/bus/config.ts +1 -1
  200. package/src/sdk/bus/control-drain-lease.ts +104 -0
  201. package/src/sdk/bus/index.ts +503 -60
  202. package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
  203. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  204. package/src/sdk/bus/rate-limit-pool.ts +8 -0
  205. package/src/sdk/bus/reconciliation-store.ts +224 -0
  206. package/src/sdk/bus/telegram-daemon-contract.ts +6 -4
  207. package/src/sdk/bus/telegram-daemon.ts +531 -53
  208. package/src/sdk/host/control/dispatch.ts +7 -1
  209. package/src/sdk/host/control/operations.ts +2 -1
  210. package/src/sdk/host/host.ts +25 -3
  211. package/src/sdk/host/query/handlers.ts +55 -0
  212. package/src/sdk/lifecycle-session.ts +19 -3
  213. package/src/sdk/mcp/server.ts +46 -18
  214. package/src/sdk/protocol/operation-inventory.generated.json +76 -0
  215. package/src/sdk/protocol/operation-registry.ts +8 -2
  216. package/src/sdk/session.ts +25 -2
  217. package/src/sdk/startup-capability.ts +6 -0
  218. package/src/session/agent-session.ts +720 -190
  219. package/src/session/fallback-chain-controller.ts +47 -8
  220. package/src/session/internal/managed-session-scope.ts +40 -10
  221. package/src/session/internal/native-publish-outcome.ts +8 -3
  222. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  223. package/src/session/session-manager.ts +1372 -42
  224. package/src/session/streaming-output.ts +262 -43
  225. package/src/setup/provider-onboarding.ts +19 -4
  226. package/src/skill-state/active-state.ts +0 -10
  227. package/src/skill-state/workflow-hud.ts +25 -2
  228. package/src/skill-state/workflow-mutation-guard.ts +65 -7
  229. package/src/task/executor.ts +11 -3
  230. package/src/task/index.ts +2 -0
  231. package/src/task/types.ts +22 -11
  232. package/src/task/ultragoal-redteam-activation.ts +75 -0
  233. package/src/tools/ask.ts +89 -69
  234. package/src/tools/browser/launch.ts +41 -6
  235. package/src/tools/fetch.ts +114 -23
  236. package/src/tools/output-meta.ts +176 -9
  237. package/src/tools/path-utils.ts +5 -1
  238. package/src/tools/read-internals.ts +44 -0
  239. package/src/tools/read.ts +1542 -310
  240. package/src/tools/resource-gc.ts +665 -125
  241. package/src/tools/tool-result.ts +14 -1
  242. package/src/web/search/providers/codex.ts +1 -1
  243. package/src/workflow/workflow-intent-diff.ts +4 -1
  244. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  245. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  246. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  247. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  248. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  249. package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
@@ -0,0 +1,611 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { ansiToHtml, xterm256Color } from "./capture-sticky-viewport-showcase";
4
+
5
+ const KEYS = [
6
+ "live-overflow/80x24/unicode-color",
7
+ "live-overflow/120x36/unicode-color",
8
+ "manual-history/80x24/unicode-color",
9
+ "manual-history/120x36/unicode-color",
10
+ "manual-new-output/80x24/unicode-color",
11
+ "manual-new-output/120x36/unicode-color",
12
+ "multiline-editor-hooks-pet/80x24/unicode-color",
13
+ "multiline-editor-hooks-pet/120x36/unicode-color",
14
+ "capacity-many/80x24/unicode-color",
15
+ "capacity-many/120x36/unicode-color",
16
+ "capacity-one/80x24/unicode-color",
17
+ "capacity-one/120x36/unicode-color",
18
+ "capacity-zero/80x24/unicode-color",
19
+ "capacity-zero/120x36/unicode-color",
20
+ "selection-boundary/80x24/unicode-color",
21
+ "selection-boundary/120x36/unicode-color",
22
+ "manual-new-output/80x24/ascii-no-color",
23
+ "capacity-zero/48x10/ascii-no-color",
24
+ "multiline-editor-hooks-pet/48x10/unicode-color",
25
+ "narrow-cjk/48x10/unicode-color",
26
+ ] as const;
27
+ const PAYLOADS = ["terminal.txt", "terminal-ansi.txt", "terminal.html", "metadata.json"] as const;
28
+ const COMMAND =
29
+ "bun packages/coding-agent/scripts/capture-sticky-viewport-showcase.ts --out .gjc/qa/sticky-viewport-<run>";
30
+ const TIMESTAMP = "1970-01-01T00:00:00.000Z";
31
+ const FIXTURE = "packages/coding-agent/test/fixtures/tui/sticky-viewport-showcase.ts";
32
+ const DEFAULT_FOREGROUND = "#ffe7dc";
33
+ const DEFAULT_BACKGROUND = "#110b0b";
34
+ const CJK = ["의미 있는 문장 경계", "意味のある文の境界", "保留语义短语边界"] as const;
35
+ const FONT_RENDERING_ASSUMPTIONS =
36
+ "Embedded red-claw theme at deterministic truecolor; HTML uses a monospace terminal fallback stack.";
37
+ const WRAPPING_TRUNCATION_POLICY =
38
+ "ANSI-aware terminal-cell wrapping preserves semantic CJK phrase boundaries; constrained height drops the notice, decorative pet, then low-priority hooks without truncating pinned status or the focused composer.";
39
+ const ACCEPTANCE_VERSION = "sticky-viewport-stage-03";
40
+ const DESIGN_VERSION = "modes-design-sticky-viewport-v3";
41
+ const HOST_MATRIX = { capture_host: "VirtualTerminal", live_pty: false, network: false } as const;
42
+ const ARTIFACT_CHECKS = {
43
+ terminal_txt: true,
44
+ terminal_ansi_txt: true,
45
+ terminal_html: true,
46
+ metadata_json: true,
47
+ } as const;
48
+ const INDEPENDENT_REVIEW_KEYS = [
49
+ "schema_version",
50
+ "manifest_sha256",
51
+ "reviewer_identity",
52
+ "reviewer_role",
53
+ "fixture_revision",
54
+ "expected_entry_count",
55
+ "observed_entry_count",
56
+ "final",
57
+ "checked_keys",
58
+ "defects",
59
+ "artifact_decision",
60
+ "cjk_semantic_line_breaks",
61
+ "host_matrix",
62
+ "per_key_results",
63
+ ] as const;
64
+ const INDEPENDENT_REVIEW_RESULT_KEYS = ["key", "result", "notes", "artifact_checks"] as const;
65
+ const INDEPENDENT_REVIEW_DEFECT_KEYS = ["description", "accepted"] as const;
66
+ const ARTIFACT_CHECK_KEYS = ["terminal_txt", "terminal_ansi_txt", "terminal_html", "metadata_json"] as const;
67
+ type Style = {
68
+ foreground: string;
69
+ background: string;
70
+ bold: boolean;
71
+ dim: boolean;
72
+ italic: boolean;
73
+ underline: boolean;
74
+ blink: boolean;
75
+ inverse: boolean;
76
+ invisible: boolean;
77
+ strikethrough: boolean;
78
+ overline: boolean;
79
+ };
80
+ type Run = { text: string; style: Style };
81
+ const hash = (value: string) => new Bun.CryptoHasher("sha256").update(value).digest("hex");
82
+ const fail = (message: string): never => {
83
+ throw new Error(`Sticky viewport evidence invalid: ${message}`);
84
+ };
85
+ const exactKeys = (value: Record<string, unknown>, expected: readonly string[], label: string) => {
86
+ const actual = Object.keys(value).sort();
87
+ const sortedExpected = [...expected].sort();
88
+ if (JSON.stringify(actual) !== JSON.stringify(sortedExpected)) fail(`${label} keys must be exact`);
89
+ };
90
+ const object = (value: unknown, label: string): Record<string, unknown> => {
91
+ if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${label} must be an object`);
92
+ return value as Record<string, unknown>;
93
+ };
94
+ const array = (value: unknown, label: string): unknown[] =>
95
+ Array.isArray(value) ? value : fail(`${label} must be an array`);
96
+ const strings = (value: unknown, expected: readonly string[], label: string) => {
97
+ if (
98
+ !Array.isArray(value) ||
99
+ value.length !== expected.length ||
100
+ value.some((item, index) => item !== expected[index])
101
+ )
102
+ fail(`${label} differs from immutable matrix`);
103
+ };
104
+ async function readJson(file: string, label: string): Promise<Record<string, unknown>> {
105
+ try {
106
+ return object(JSON.parse(await fs.readFile(file, "utf8")), label);
107
+ } catch (error) {
108
+ return fail(`${label} is unreadable: ${error instanceof Error ? error.message : String(error)}`);
109
+ }
110
+ }
111
+ async function allFiles(root: string): Promise<string[]> {
112
+ const result: string[] = [];
113
+ const walk = async (directory: string): Promise<void> => {
114
+ for (const item of await fs.readdir(directory, { withFileTypes: true })) {
115
+ const target = path.join(directory, item.name);
116
+ if (item.isDirectory()) await walk(target);
117
+ else if (item.isFile()) result.push(path.relative(root, target).split(path.sep).join("/"));
118
+ else fail(`unsupported filesystem entry ${target}`);
119
+ }
120
+ };
121
+ await walk(root);
122
+ return result.sort();
123
+ }
124
+ const baseStyle = (): Style => ({
125
+ foreground: DEFAULT_FOREGROUND,
126
+ background: DEFAULT_BACKGROUND,
127
+ bold: false,
128
+ dim: false,
129
+ italic: false,
130
+ underline: false,
131
+ blink: false,
132
+ inverse: false,
133
+ invisible: false,
134
+ strikethrough: false,
135
+ overline: false,
136
+ });
137
+ const color = (code: number): string | undefined => {
138
+ const colors: Record<number, string> = {
139
+ 30: "#000000",
140
+ 31: "#cc0000",
141
+ 32: "#4e9a06",
142
+ 33: "#c4a000",
143
+ 34: "#3465a4",
144
+ 35: "#75507b",
145
+ 36: "#06989a",
146
+ 37: "#d3d7cf",
147
+ 90: "#555753",
148
+ 91: "#ef2929",
149
+ 92: "#8ae234",
150
+ 93: "#fce94f",
151
+ 94: "#729fcf",
152
+ 95: "#ad7fa8",
153
+ 96: "#34e2e2",
154
+ 97: "#eeeeec",
155
+ };
156
+ return colors[code];
157
+ };
158
+ const pushRun = (runs: Run[], text: string, style: Style) => {
159
+ if (!text) return;
160
+ const effective = style.inverse
161
+ ? {
162
+ ...style,
163
+ foreground: style.background,
164
+ background: style.foreground,
165
+ inverse: false,
166
+ }
167
+ : { ...style };
168
+ runs.push({ text, style: effective });
169
+ };
170
+ function ansiRuns(ansi: string): Run[] {
171
+ const runs: Run[] = [];
172
+ let style = baseStyle(),
173
+ offset = 0;
174
+ for (const match of ansi.matchAll(/\x1b\[([0-9;]*)m/g)) {
175
+ pushRun(runs, ansi.slice(offset, match.index), style);
176
+ offset = (match.index ?? 0) + match[0].length;
177
+ const codes = (match[1] || "0").split(";").map(Number);
178
+ for (let index = 0; index < codes.length; index += 1) {
179
+ const code = codes[index]!;
180
+ if (code === 0) style = baseStyle();
181
+ else if (code === 1) style.bold = true;
182
+ else if (code === 2) style.dim = true;
183
+ else if (code === 3) style.italic = true;
184
+ else if (code === 4) style.underline = true;
185
+ else if (code === 5) style.blink = true;
186
+ else if (code === 7) style.inverse = true;
187
+ else if (code === 8) style.invisible = true;
188
+ else if (code === 9) style.strikethrough = true;
189
+ else if (code === 22) {
190
+ style.bold = false;
191
+ style.dim = false;
192
+ } else if (code === 23) style.italic = false;
193
+ else if (code === 24) style.underline = false;
194
+ else if (code === 25) style.blink = false;
195
+ else if (code === 27) style.inverse = false;
196
+ else if (code === 28) style.invisible = false;
197
+ else if (code === 29) style.strikethrough = false;
198
+ else if (code === 53) style.overline = true;
199
+ else if (code === 55) style.overline = false;
200
+ else if (code === 39) style.foreground = DEFAULT_FOREGROUND;
201
+ else if (code === 49) style.background = DEFAULT_BACKGROUND;
202
+ else if (color(code)) style.foreground = color(code)!;
203
+ else if (code >= 40 && code <= 47) style.background = color(code - 10)!;
204
+ else if (code >= 100 && code <= 107) style.background = color(code - 10)!;
205
+ else if ((code === 38 || code === 48) && codes[index + 1] === 5 && Number.isInteger(codes[index + 2])) {
206
+ const value = xterm256Color(codes[index + 2]!);
207
+ if (code === 38) style.foreground = value;
208
+ else style.background = value;
209
+ index += 2;
210
+ } else if (
211
+ (code === 38 || code === 48) &&
212
+ codes[index + 1] === 2 &&
213
+ [codes[index + 2], codes[index + 3], codes[index + 4]].every(Number.isInteger)
214
+ ) {
215
+ const value = `rgb(${codes[index + 2]},${codes[index + 3]},${codes[index + 4]})`;
216
+ if (code === 38) style.foreground = value;
217
+ else style.background = value;
218
+ index += 4;
219
+ }
220
+ }
221
+ }
222
+ pushRun(runs, ansi.slice(offset), style);
223
+ return runs;
224
+ }
225
+ const decode = (value: string) =>
226
+ value
227
+ .replace(/&quot;/g, '"')
228
+ .replace(/&amp;/g, "&")
229
+ .replace(/&lt;/g, "<")
230
+ .replace(/&gt;/g, ">");
231
+ function htmlRuns(html: string): Run[] {
232
+ const preMatch = html.match(/<pre>([\s\S]*)<\/pre>/);
233
+ if (preMatch?.[1] === undefined) return fail("HTML pre missing");
234
+ const pre = preMatch[1];
235
+ const runs: Run[] = [];
236
+ let style = baseStyle();
237
+ let offset = 0;
238
+ for (const match of pre.matchAll(/<span style="([^"]*)">|<\/span>/g)) {
239
+ pushRun(runs, decode(pre.slice(offset, match.index)), style);
240
+ offset = (match.index ?? 0) + match[0].length;
241
+ if (match[0] === "</span>") {
242
+ style = baseStyle();
243
+ continue;
244
+ }
245
+ const attributes = new Map<string, string>(
246
+ match[1]!
247
+ .split(";")
248
+ .filter(Boolean)
249
+ .map(part => {
250
+ const separator = part.indexOf(":");
251
+ if (separator < 0) return fail("HTML style declaration malformed");
252
+ return [part.slice(0, separator), part.slice(separator + 1)] as const;
253
+ }),
254
+ );
255
+ if (attributes.has("filter")) fail("HTML inverse must use effective colors, not CSS filter");
256
+ const decorations = new Set((attributes.get("text-decoration") ?? "").split(" "));
257
+ style = {
258
+ ...baseStyle(),
259
+ foreground: attributes.get("color") ?? DEFAULT_FOREGROUND,
260
+ background: attributes.get("background-color") ?? DEFAULT_BACKGROUND,
261
+ bold: attributes.get("font-weight") === "700",
262
+ dim: attributes.get("opacity") === ".65",
263
+ italic: attributes.get("font-style") === "italic",
264
+ underline: decorations.has("underline"),
265
+ blink: attributes.get("animation") === "blink 1s step-end infinite",
266
+ invisible: attributes.get("visibility") === "hidden",
267
+ strikethrough: decorations.has("line-through"),
268
+ overline: decorations.has("overline"),
269
+ };
270
+ }
271
+ pushRun(runs, decode(pre.slice(offset)), style);
272
+ return runs;
273
+ }
274
+ const normalized = (runs: Run[]) => {
275
+ const merged: Run[] = [];
276
+ for (const run of runs) {
277
+ const prior = merged.at(-1);
278
+ if (prior && JSON.stringify(prior.style) === JSON.stringify(run.style)) prior.text += run.text;
279
+ else merged.push({ ...run, style: { ...run.style } });
280
+ }
281
+ return merged;
282
+ };
283
+ const equalRuns = (left: Run[], right: Run[]) => JSON.stringify(normalized(left)) === JSON.stringify(normalized(right));
284
+ const transcriptCapacity = (text: string) => {
285
+ const row = text.split("\n").findIndex(line => line.includes("status:"));
286
+ return row < 0 ? fail("frame omits pinned status row") : row;
287
+ };
288
+ const expectedState = (state: string) => ({
289
+ manual: state !== "live-overflow",
290
+ notice: state === "manual-new-output",
291
+ status: state === "live-overflow" ? "status: live follow" : "status: manual history · composer pinned",
292
+ multiline: state === "multiline-editor-hooks-pet",
293
+ });
294
+ function validateOracle(
295
+ key: string,
296
+ text: string,
297
+ metadata: Record<string, unknown>,
298
+ stateEvidence: Record<string, unknown>,
299
+ ) {
300
+ const state = key.split("/")[0]!;
301
+ const expected = expectedState(state);
302
+ const lines = text.split("\n");
303
+ if (stateEvidence.manual !== expected.manual || stateEvidence.notice !== expected.notice)
304
+ fail(`immutable state oracle mismatch for ${key}`);
305
+ if (lines.filter(line => line.includes(expected.status)).length !== 1)
306
+ fail(`exact status oracle mismatch for ${key}`);
307
+ const markers = [
308
+ expected.status,
309
+ ...(expected.multiline
310
+ ? ["hook: ready", "completed: visual proof", "pet: ◕‿◕", "> ", "first composer line", "second composer line"]
311
+ : ["> "]),
312
+ ];
313
+ let position = -1;
314
+ for (const marker of markers) {
315
+ const next = text.indexOf(marker, position + 1);
316
+ if (next < 0) fail(`ordered suffix oracle missing ${marker} for ${key}`);
317
+ position = next;
318
+ }
319
+ if (text.split("New output — type to follow").length - 1 !== (expected.notice ? 1 : 0))
320
+ fail(`notice cardinality oracle mismatch for ${key}`);
321
+ if (
322
+ expected.multiline !==
323
+ (text.includes("hook: ready") &&
324
+ text.includes("pet: ◕‿◕") &&
325
+ text.includes("first composer line") &&
326
+ text.includes("second composer line"))
327
+ )
328
+ fail(`multiline editor/hooks/pet oracle mismatch for ${key}`);
329
+ if (metadata.output_revision !== (expected.notice ? "1" : "0")) fail(`manual notice invariant mismatch for ${key}`);
330
+ if (state === "selection-boundary") {
331
+ const copied = stateEvidence.selection_copied_text;
332
+ if (
333
+ stateEvidence.selection_scope !== "transcript" ||
334
+ typeof copied !== "string" ||
335
+ !copied.trim() ||
336
+ copied.includes("status:") ||
337
+ copied.includes("> ") ||
338
+ !lines.some(line => line.includes(copied.trim()))
339
+ )
340
+ fail(`selection oracle mismatch for ${key}`);
341
+ } else if (stateEvidence.selection_scope !== "none" || stateEvidence.selection_copied_text !== "")
342
+ fail(`selection oracle mismatch for ${key}`);
343
+ const historicalRows = stateEvidence.manual_historical_rows;
344
+ const preOutputCapacity = stateEvidence.manual_pre_output_capacity;
345
+ if (expected.manual) {
346
+ if (
347
+ !Number.isInteger(preOutputCapacity) ||
348
+ (preOutputCapacity as number) < 0 ||
349
+ !Array.isArray(historicalRows) ||
350
+ historicalRows.length !== ((preOutputCapacity as number) > 0 ? 1 : 0) ||
351
+ historicalRows.some(row => typeof row !== "string" || !row.trim() || !lines.includes(row))
352
+ )
353
+ fail(`manual historical transcript evidence mismatch for ${key}`);
354
+ } else if (preOutputCapacity !== 0 || !Array.isArray(historicalRows) || historicalRows.length !== 0)
355
+ fail(`manual historical transcript evidence mismatch for ${key}`);
356
+ }
357
+ export async function verifyStickyViewportShowcase(rootInput: string, requireIndependentReview = false): Promise<void> {
358
+ const root = path.resolve(rootInput);
359
+ const manifestText = await fs.readFile(path.join(root, "manifest.json"), "utf8");
360
+ const manifest = await readJson(path.join(root, "manifest.json"), "manifest");
361
+ if (
362
+ manifest.schema_version !== 2 ||
363
+ manifest.fixture_revision !== "sticky-viewport-showcase-v2" ||
364
+ manifest.expected_entry_count !== 20 ||
365
+ manifest.entry_count !== 20 ||
366
+ manifest.command !== COMMAND ||
367
+ manifest.capture_timestamp !== TIMESTAMP ||
368
+ manifest.review_input_file !== "review-input.json"
369
+ )
370
+ fail("manifest schema or provenance literals mismatch");
371
+ strings(manifest.ordered_keys, KEYS, "manifest ordered_keys");
372
+ const provenance = object(manifest.provenance, "manifest provenance");
373
+ if (
374
+ provenance.capture_mode !== "production-tui-virtual-terminal" ||
375
+ provenance.live_pty !== false ||
376
+ provenance.network !== false ||
377
+ provenance.fixed_clock !== true ||
378
+ typeof provenance.author_identity !== "string" ||
379
+ !provenance.author_identity.trim() ||
380
+ typeof provenance.executor_identity !== "string" ||
381
+ !provenance.executor_identity.trim()
382
+ )
383
+ fail("manifest provenance mismatch");
384
+ const entries = array(manifest.entries, "manifest entries");
385
+ if (entries.length !== KEYS.length) fail("manifest entries must contain exactly 20 entries");
386
+ for (let index = 0; index < KEYS.length; index += 1) {
387
+ const key = KEYS[index]!;
388
+ const entry = object(entries[index], `entry ${index}`);
389
+ const [state, id, mode] = key.split("/");
390
+ const [columns, rows] = id!.split("x").map(Number);
391
+ if (entry.key !== key || entry.state_id !== state || entry.render_mode !== mode)
392
+ fail(`entry ${key} variant mismatch`);
393
+ const viewport = object(entry.viewport, `entry ${key} viewport`);
394
+ if (viewport.id !== id || viewport.columns !== columns || viewport.rows !== rows)
395
+ fail(`entry ${key} viewport mismatch`);
396
+ const listed = array(entry.files, `entry ${key} files`);
397
+ if (listed.length !== PAYLOADS.length) fail(`entry ${key} file list is not exact`);
398
+ strings(
399
+ listed.map(value => object(value, `entry ${key} file`).path),
400
+ PAYLOADS.map(name => `${key}/${name}`),
401
+ `entry ${key} payload paths`,
402
+ );
403
+ for (const value of listed) {
404
+ const file = object(value, `entry ${key} file`);
405
+ if (typeof file.path !== "string" || typeof file.sha256 !== "string" || !Number.isInteger(file.byte_length))
406
+ fail(`entry ${key} malformed file manifest`);
407
+ const filePath = file.path as string;
408
+ const content = await fs.readFile(path.join(root, filePath), "utf8");
409
+ if (hash(content) !== file.sha256 || Buffer.byteLength(content) !== file.byte_length)
410
+ fail(`entry ${key} hash or byte length mismatch`);
411
+ }
412
+ const text = await fs.readFile(path.join(root, key, "terminal.txt"), "utf8");
413
+ const ansi = await fs.readFile(path.join(root, key, "terminal-ansi.txt"), "utf8");
414
+ const html = await fs.readFile(path.join(root, key, "terminal.html"), "utf8");
415
+ if (Bun.stripANSI(ansi) !== text) fail(`entry ${key} text/ANSI semantic evidence mismatch`);
416
+ if (html !== ansiToHtml(ansi)) fail(`entry ${key} HTML artifact is not canonical ANSI conversion`);
417
+ const ansiStyleRuns = ansiRuns(ansi);
418
+ const htmlStyleRuns = htmlRuns(html);
419
+ if (
420
+ ansiStyleRuns.map(run => run.text).join("") !== text ||
421
+ htmlStyleRuns.map(run => run.text).join("") !== text ||
422
+ !equalRuns(ansiStyleRuns, htmlStyleRuns)
423
+ )
424
+ fail(`entry ${key} ANSI/HTML style-run mismatch`);
425
+ if (state === "multiline-editor-hooks-pet" && (!ansi.includes("\x1b[9m") || !html.includes("line-through")))
426
+ fail(`entry ${key} strikethrough evidence missing`);
427
+ if (state === "selection-boundary" && !ansi.includes("\x1b[7m"))
428
+ fail(`entry ${key} inverse selection evidence missing`);
429
+ if (state === "narrow-cjk" && CJK.some(boundary => !text.includes(boundary)))
430
+ fail("narrow CJK visible terminal evidence missing");
431
+ if (
432
+ text
433
+ .split("\n")
434
+ .slice(0, -1)
435
+ .some(row => Bun.stringWidth(row) !== columns) ||
436
+ text.split("\n").length - 1 !== rows
437
+ )
438
+ fail(`entry ${key} terminal dimensions mismatch`);
439
+ if (mode === "ascii-no-color" ? /\x1b\[/.test(ansi) : !/\x1b\[[0-9;]*(?:3[0-9]|38;)/.test(ansi))
440
+ fail(`entry ${key} ANSI mode/color mismatch`);
441
+ const metadata = await readJson(path.join(root, key, "metadata.json"), `metadata ${key}`);
442
+ exactKeys(
443
+ metadata,
444
+ [
445
+ "schema_version",
446
+ "entry_key",
447
+ "fixture_revision",
448
+ "capture_timestamp",
449
+ "command_or_replay_source",
450
+ "fixture_source",
451
+ "terminal",
452
+ "render_mode",
453
+ "ansi_mode",
454
+ "source_revision",
455
+ "output_revision",
456
+ "state",
457
+ "provenance",
458
+ "cjk_phrase_boundaries",
459
+ ],
460
+ `metadata ${key}`,
461
+ );
462
+ const terminal = object(metadata.terminal, `metadata ${key} terminal`);
463
+ exactKeys(
464
+ terminal,
465
+ ["id", "columns", "rows", "font_rendering_assumptions", "wrapping_truncation_policy"],
466
+ `metadata ${key} terminal`,
467
+ );
468
+ const stateEvidence = object(metadata.state, `metadata ${key} state`);
469
+ const metaProvenance = object(metadata.provenance, `metadata ${key} provenance`);
470
+ if (
471
+ metadata.schema_version !== 2 ||
472
+ metadata.entry_key !== key ||
473
+ metadata.fixture_revision !== "sticky-viewport-showcase-v2" ||
474
+ metadata.capture_timestamp !== TIMESTAMP ||
475
+ metadata.command_or_replay_source !== COMMAND ||
476
+ metadata.fixture_source !== FIXTURE ||
477
+ metadata.render_mode !== mode ||
478
+ metadata.ansi_mode !== (mode === "unicode-color") ||
479
+ metadata.source_revision !== "production-tui-virtual-terminal-v2" ||
480
+ terminal.id !== id ||
481
+ terminal.columns !== columns ||
482
+ terminal.rows !== rows ||
483
+ terminal.font_rendering_assumptions !== FONT_RENDERING_ASSUMPTIONS ||
484
+ terminal.wrapping_truncation_policy !== WRAPPING_TRUNCATION_POLICY ||
485
+ metaProvenance.capture_mode !== provenance.capture_mode ||
486
+ metaProvenance.live_pty !== false ||
487
+ metaProvenance.network !== false ||
488
+ metaProvenance.fixed_clock !== true ||
489
+ metaProvenance.author_identity !== provenance.author_identity ||
490
+ metaProvenance.executor_identity !== provenance.executor_identity ||
491
+ stateEvidence.composer_visible !== true
492
+ )
493
+ fail(`metadata schema mismatch for ${key}`);
494
+ if (stateEvidence.transcript_capacity !== transcriptCapacity(text))
495
+ fail(`capacity metadata/frame mismatch for ${key}`);
496
+ validateOracle(key, text, metadata, stateEvidence);
497
+ if (state === "capacity-zero" && transcriptCapacity(text) !== 0)
498
+ fail(`zero capacity frame invariant mismatch for ${key}`);
499
+ if (state === "capacity-one" && transcriptCapacity(text) !== 1)
500
+ fail(`one capacity frame invariant mismatch for ${key}`);
501
+ if (state === "capacity-many" && transcriptCapacity(text) < 2)
502
+ fail(`many capacity frame invariant mismatch for ${key}`);
503
+ if (state === "narrow-cjk") {
504
+ strings(metadata.cjk_phrase_boundaries, CJK, "narrow CJK boundaries");
505
+ if (CJK.some(boundary => !text.includes(boundary))) fail("narrow CJK visible terminal evidence missing");
506
+ } else strings(metadata.cjk_phrase_boundaries, [], `non-narrow CJK boundaries for ${key}`);
507
+ }
508
+ const required = new Set([
509
+ "manifest.json",
510
+ "review-input.json",
511
+ ...KEYS.flatMap(key => PAYLOADS.map(file => `${key}/${file}`)),
512
+ ...(requireIndependentReview ? ["independent-review.json"] : []),
513
+ ]);
514
+ for (const file of await allFiles(root)) if (!required.has(file)) fail(`unexpected file ${file}`);
515
+ const reviewInput = await readJson(path.join(root, "review-input.json"), "review input");
516
+ if (
517
+ reviewInput.schema_version !== 2 ||
518
+ reviewInput.manifest_sha256 !== hash(manifestText) ||
519
+ reviewInput.command_or_replay_source !== COMMAND ||
520
+ reviewInput.capture_timestamp !== TIMESTAMP ||
521
+ reviewInput.fixture_source !== FIXTURE ||
522
+ reviewInput.fixed_clock !== true ||
523
+ reviewInput.live_pty !== false ||
524
+ reviewInput.network !== false ||
525
+ reviewInput.author_identity !== provenance.author_identity ||
526
+ reviewInput.executor_identity !== provenance.executor_identity
527
+ )
528
+ fail("review input manifest binding mismatch");
529
+ strings(reviewInput.expected_keys, KEYS, "review input expected_keys");
530
+ strings(reviewInput.required_artifacts, PAYLOADS, "review input required_artifacts");
531
+ const reviewHostMatrix = object(reviewInput.host_matrix, "review input host matrix");
532
+ if (
533
+ reviewInput.acceptance_version !== ACCEPTANCE_VERSION ||
534
+ reviewInput.design_version !== DESIGN_VERSION ||
535
+ reviewHostMatrix.capture_host !== HOST_MATRIX.capture_host ||
536
+ reviewHostMatrix.live_pty !== HOST_MATRIX.live_pty ||
537
+ reviewHostMatrix.network !== HOST_MATRIX.network
538
+ )
539
+ fail("review input acceptance, design, or host matrix mismatch");
540
+ const narrow = object(reviewInput.narrow_cjk, "review input narrow CJK");
541
+ if (narrow.entry_key !== "narrow-cjk/48x10/unicode-color") fail("review input narrow CJK mismatch");
542
+ strings(narrow.phrase_boundaries, CJK, "review input narrow CJK boundaries");
543
+ if (requireIndependentReview) {
544
+ const review = await readJson(path.join(root, "independent-review.json"), "independent review");
545
+ exactKeys(review, INDEPENDENT_REVIEW_KEYS, "independent review");
546
+ const reviewer = review.reviewer_identity;
547
+ const canonicalReviewer = typeof reviewer === "string" ? reviewer.trim() : "";
548
+ const canonicalAuthor = (provenance.author_identity as string).trim();
549
+ const canonicalExecutor = (provenance.executor_identity as string).trim();
550
+ const defects = array(review.defects, "independent review defects");
551
+ if (
552
+ review.schema_version !== 2 ||
553
+ review.manifest_sha256 !== hash(manifestText) ||
554
+ review.fixture_revision !== "sticky-viewport-showcase-v2" ||
555
+ review.expected_entry_count !== 20 ||
556
+ review.observed_entry_count !== 20 ||
557
+ review.final !== "accept" ||
558
+ review.reviewer_role !== "independent-terminal-reviewer" ||
559
+ typeof reviewer !== "string" ||
560
+ !canonicalReviewer ||
561
+ reviewer !== canonicalReviewer ||
562
+ canonicalReviewer === canonicalAuthor ||
563
+ canonicalReviewer === canonicalExecutor ||
564
+ review.artifact_decision !== "accept" ||
565
+ review.cjk_semantic_line_breaks !== "accept" ||
566
+ review.host_matrix !== "accept"
567
+ )
568
+ fail("independent review schema or decision mismatch");
569
+ for (const defect of defects) {
570
+ const item = object(defect, "independent review defect");
571
+ exactKeys(item, INDEPENDENT_REVIEW_DEFECT_KEYS, "independent review defect");
572
+ if (
573
+ typeof item.description !== "string" ||
574
+ !item.description.trim() ||
575
+ item.description !== item.description.trim() ||
576
+ item.accepted !== true
577
+ )
578
+ fail("independent review defect mismatch");
579
+ }
580
+ strings(review.checked_keys, KEYS, "independent review checked_keys");
581
+ const results = array(review.per_key_results, "independent review per-key results");
582
+ if (results.length !== KEYS.length) fail("independent review per-key results must contain exactly 20 entries");
583
+ for (let index = 0; index < KEYS.length; index += 1) {
584
+ const result = object(results[index], `independent review result ${index}`);
585
+ exactKeys(result, INDEPENDENT_REVIEW_RESULT_KEYS, `independent review result ${index}`);
586
+ if (
587
+ result.key !== KEYS[index] ||
588
+ result.result !== "accept" ||
589
+ typeof result.notes !== "string" ||
590
+ !result.notes.trim()
591
+ )
592
+ fail("independent review per-key result mismatch");
593
+ const checks = object(result.artifact_checks, "independent review per-key artifact checks");
594
+ exactKeys(checks, ARTIFACT_CHECK_KEYS, "independent review per-key artifact checks");
595
+ for (const [artifact, expected] of Object.entries(ARTIFACT_CHECKS))
596
+ if (checks[artifact] !== expected) fail("independent review per-key artifact checks missing");
597
+ }
598
+ }
599
+ }
600
+ async function main() {
601
+ const args = process.argv.slice(2);
602
+ const required = args.includes("--require-independent-review");
603
+ const rest = args.filter(value => value !== "--require-independent-review");
604
+ if (rest.length !== 2 || rest[0] !== "--root")
605
+ throw new Error(
606
+ "Usage: bun packages/coding-agent/scripts/verify-sticky-viewport-showcase.ts --root <root> [--require-independent-review]",
607
+ );
608
+ await verifyStickyViewportShowcase(rest[1]!, required);
609
+ process.stdout.write("Sticky viewport evidence verified\n");
610
+ }
611
+ if (import.meta.main) await main();
@@ -259,10 +259,18 @@ export function initializeWithSettings(activeSettings: Settings): void {
259
259
  for (const id of disabled) disabledProviders.add(id);
260
260
  }
261
261
 
262
+ function assertDisabledProvidersWritable(activeSettings: Settings): void {
263
+ if (!activeSettings.canWriteDurableConfig()) {
264
+ throw new Error(
265
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
266
+ );
267
+ }
268
+ }
262
269
  /**
263
270
  * Persist current disabled providers to settings.
264
271
  */
265
272
  function persistDisabledProviders(activeSettings: Settings, providers: ReadonlySet<string>): void {
273
+ assertDisabledProvidersWritable(activeSettings);
266
274
  activeSettings.set("disabledProviders", Array.from(providers));
267
275
  }
268
276
 
package/src/cli/args.ts CHANGED
@@ -205,17 +205,23 @@ export function parseArgs(args: string[]): Args {
205
205
  }
206
206
  }
207
207
  result.tools = validTools;
208
- } else if (arg === "--thinking" && i + 1 < args.length) {
208
+ } else if (arg === "--thinking") {
209
+ // Match --credential / --mcp-config: a missing value or a following flag is
210
+ // a usage error, not a silent no-op / accidental consumption of `-p`.
211
+ const next = args[i + 1];
212
+ if (!next || next.startsWith("-")) {
213
+ throw new CliParseError(`--thinking requires <level> (${THINKING_EFFORTS.join(", ")})`);
214
+ }
209
215
  const rawThinking = args[++i];
210
216
  const thinking = parseEffort(rawThinking);
211
- if (thinking !== undefined) {
212
- result.thinking = thinking;
213
- } else {
214
- logger.warn("Invalid thinking level passed to --thinking", {
215
- level: rawThinking,
216
- validThinkingLevels: THINKING_EFFORTS,
217
- });
217
+ if (thinking === undefined) {
218
+ // Fail closed: a silent ignore left users believing `ultra` (or any typo)
219
+ // was applied. Help / Flags.options advertise the real Effort enum.
220
+ throw new CliParseError(
221
+ `Invalid --thinking level "${rawThinking}". Expected one of: ${THINKING_EFFORTS.join(", ")}`,
222
+ );
218
223
  }
224
+ result.thinking = thinking;
219
225
  } else if (arg === "--print" || arg === "-p") {
220
226
  result.print = true;
221
227
  } else if (arg === "--export" && i + 1 < args.length) {
@@ -84,7 +84,7 @@ Environment Variables:
84
84
  GJC_LAUNCH_POLICY - Launch policy for --tmux startup: tmux or direct
85
85
  GJC_TMUX_SESSION - Explicit tmux session name override for --tmux startup
86
86
  GJC_TMUX_PROFILE - Apply GJC tmux scroll/mouse/clipboard profile to --tmux sessions (set 0/off to skip)
87
- GJC_MOUSE - Mouse-wheel scroll in --tmux sessions (set 0/off to let the host terminal scroll)
87
+ GJC_MOUSE - Apply tmux copy-mode mouse capture in --tmux sessions (set 0/off to skip)
88
88
 
89
89
  For complete environment variable reference, see:
90
90
  docs/environment-variables.md