codedeck 0.2.0 → 0.3.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 (96) hide show
  1. package/README.md +45 -16
  2. package/dist/cli/cli-name.js +20 -0
  3. package/dist/cli/cli-name.js.map +1 -0
  4. package/dist/cli/commands/claims.js +170 -0
  5. package/dist/cli/commands/claims.js.map +1 -0
  6. package/dist/cli/commands/doctor.js +22 -1
  7. package/dist/cli/commands/doctor.js.map +1 -1
  8. package/dist/cli/commands/models.js +3 -3
  9. package/dist/cli/commands/models.js.map +1 -1
  10. package/dist/cli/commands/open.js +148 -315
  11. package/dist/cli/commands/open.js.map +1 -1
  12. package/dist/cli/commands/ps.js +260 -23
  13. package/dist/cli/commands/ps.js.map +1 -1
  14. package/dist/cli/commands/rename.js +29 -0
  15. package/dist/cli/commands/rename.js.map +1 -0
  16. package/dist/cli/commands/run.js +48 -17
  17. package/dist/cli/commands/run.js.map +1 -1
  18. package/dist/cli/commands/setup.js +388 -62
  19. package/dist/cli/commands/setup.js.map +1 -1
  20. package/dist/cli/commands/usage.js +37 -0
  21. package/dist/cli/commands/usage.js.map +1 -0
  22. package/dist/cli/commands/wait.js +3 -2
  23. package/dist/cli/commands/wait.js.map +1 -1
  24. package/dist/cli/index.js +33 -21
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/cli/picker-state.js +75 -8
  27. package/dist/cli/picker-state.js.map +1 -1
  28. package/dist/cli/picker.js +13 -3
  29. package/dist/cli/picker.js.map +1 -1
  30. package/dist/cli/ui.js +20 -4
  31. package/dist/cli/ui.js.map +1 -1
  32. package/dist/config/config.js +39 -0
  33. package/dist/config/config.js.map +1 -1
  34. package/dist/config/orchestrator-mode.js +72 -0
  35. package/dist/config/orchestrator-mode.js.map +1 -0
  36. package/dist/config/paths.js +8 -0
  37. package/dist/config/paths.js.map +1 -1
  38. package/dist/core/pricing.js +65 -0
  39. package/dist/core/pricing.js.map +1 -0
  40. package/dist/core/roles.js +14 -3
  41. package/dist/core/roles.js.map +1 -1
  42. package/dist/core/run-usage.js +46 -0
  43. package/dist/core/run-usage.js.map +1 -0
  44. package/dist/core/session.js +5 -0
  45. package/dist/core/session.js.map +1 -1
  46. package/dist/daemon/daemon.js +90 -3
  47. package/dist/daemon/daemon.js.map +1 -1
  48. package/dist/daemon/ipc.js +10 -3
  49. package/dist/daemon/ipc.js.map +1 -1
  50. package/dist/drivers/opencode/driver.js +1 -114
  51. package/dist/drivers/opencode/driver.js.map +1 -1
  52. package/dist/drivers/opencode/parser.js +86 -0
  53. package/dist/drivers/opencode/parser.js.map +1 -0
  54. package/dist/drivers/session-runtime.js +1 -0
  55. package/dist/drivers/session-runtime.js.map +1 -1
  56. package/dist/git/worktree.js +1 -1
  57. package/dist/git/worktree.js.map +1 -1
  58. package/dist/open/contract.js +104 -0
  59. package/dist/open/contract.js.map +1 -0
  60. package/dist/open/injection.js +59 -0
  61. package/dist/open/injection.js.map +1 -0
  62. package/dist/open/launchers/claude.js +212 -0
  63. package/dist/open/launchers/claude.js.map +1 -0
  64. package/dist/open/launchers/opencode.js +178 -0
  65. package/dist/open/launchers/opencode.js.map +1 -0
  66. package/dist/open/orchestrator-prose.js +12 -0
  67. package/dist/open/orchestrator-prose.js.map +1 -0
  68. package/dist/open/pty.js +281 -0
  69. package/dist/open/pty.js.map +1 -0
  70. package/dist/open/runtime.js +597 -0
  71. package/dist/open/runtime.js.map +1 -0
  72. package/dist/plugin/agents/auditor.md +7 -1
  73. package/dist/plugin/agents/general.md +12 -1
  74. package/dist/plugin/agents/orchestrator-edit.md +69 -0
  75. package/dist/plugin/agents/orchestrator-read.md +69 -0
  76. package/dist/plugin/agents/orchestrator.md +76 -17
  77. package/dist/plugin/agents/reviewer.md +3 -0
  78. package/dist/plugin/commands/autonomous.md +58 -0
  79. package/dist/plugin/hooks/hooks.json +24 -0
  80. package/dist/plugin/hooks/session-id.sh +23 -0
  81. package/dist/plugin/hooks/session-name-lib.mjs +40 -0
  82. package/dist/plugin/hooks/session-name-worker.mjs +124 -0
  83. package/dist/plugin/hooks/session-name.mjs +66 -0
  84. package/dist/plugin/hooks/session-name.sh +7 -0
  85. package/dist/plugin/pty-shim.mjs +114 -0
  86. package/dist/plugin/statusline.sh +214 -22
  87. package/dist/plugin/themes/codedeck-rage.json +74 -0
  88. package/dist/plugin/themes/codedeck-ultra.json +52 -44
  89. package/dist/store/claims.js +152 -0
  90. package/dist/store/claims.js.map +1 -0
  91. package/dist/store/database.js +14 -0
  92. package/dist/store/database.js.map +1 -1
  93. package/dist/store/sessions.js +17 -3
  94. package/dist/store/sessions.js.map +1 -1
  95. package/package.json +3 -2
  96. package/dist/plugin/settings.json +0 -7
@@ -0,0 +1,597 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomInt } from "node:crypto";
3
+ import { EventEmitter } from "node:events";
4
+ import fs from "node:fs";
5
+ import { constants } from "node:os";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { getPaths } from "../config/paths.js";
9
+ import { getCliName } from "../cli/cli-name.js";
10
+ import { INDENT, LOGO, renderFarewell, renderLogo } from "../cli/ui.js";
11
+ import { ptyUnavailableReason, startPtySession } from "./pty.js";
12
+ /**
13
+ * "replace" drops Claude Code's own hundred-odd verbs instead of adding to
14
+ * them, so this list is the entire vocabulary and has to be long enough that a
15
+ * single session does not visibly cycle it.
16
+ *
17
+ * The verb is glyphs rather than a word because the spinner symbol itself
18
+ * cannot be reached. Its frames are module constants (`["·","✢","✳","✶","✻",
19
+ * "✽"]`, mirrored for the ping-pong) picked only by whether TERM is
20
+ * xterm-ghostty, and no setting touches them. The verb is the one part of that
21
+ * line CodeDeck owns, so the character is made there instead.
22
+ *
23
+ * Halfwidth katakana on purpose, mixed with digits. It is the one dense
24
+ * non-latin block that is single width, so a row of it cannot push the elapsed
25
+ * time and token count out of alignment the way fullwidth kana would, and the
26
+ * fonts that ship with a terminal carry it.
27
+ *
28
+ * Nothing is lost by dropping the words. The line still carries the elapsed
29
+ * seconds, the token count and the effort, which is the part anyone reads.
30
+ */
31
+ export const SPINNER_VERBS = [
32
+ "ハ7ヲ2イ9キ4シ1マ8",
33
+ "キ0シ9マ3テ6ユ1ホ8",
34
+ "テ4ナ8ニ2セ7ワ0ツ5",
35
+ "ヲ1ア5ウ8レ2サ6ク0",
36
+ "オ9カ3キ7ヌ1ヨ5ヘ8",
37
+ "コ2サ7シ4ネ0モ6タ9",
38
+ "セ8ソ0タ3ハ6リ2ウ5",
39
+ "ネ5ハ1ヒ4ソ8ヤ0テ6",
40
+ "マ3ミ6ム9ス1フ5ラ8",
41
+ "モ7ヤ2ユ5カ9ト3レ6",
42
+ "リ0ワ4ヲ8ニ2ソ7メ5",
43
+ "ウ6エ9オ2タ5ハ8ル1",
44
+ "カ1キ8ケ3ヌ7フ0ヨ5",
45
+ "サ4シ0ス6ヘ2ラ9ニ1",
46
+ "ソ2タ5ツ8マ4ア7レ0",
47
+ "ナ9ニ3ヌ6ヒ1ヨ8エ4",
48
+ "ヒ6ホ1マ5ユ8ソ2キ9",
49
+ "ム8メ4モ7ア0ツ6リ1",
50
+ "ユ0ラ7リ2ウ5ミ9タ3",
51
+ "ア3ウ5エ8カ1ヨ6ホ0",
52
+ "ケ7コ2サ9テ4メ8ワ1",
53
+ "ス1セ9ソ3ハ6キ0ヤ5",
54
+ "ツ5テ0ナ4ヘ8ム2ヲ7",
55
+ "ヌ8ネ6ハ2ミ5カ9リ3",
56
+ "ヲァィゥェォ1ーャュョ2",
57
+ "ャュョッーア3イウエオ4",
58
+ "ッーアイウ5エオカキク6",
59
+ "ーカキクケコ7サシスセ8",
60
+ "サシスセソ9タチツテト0",
61
+ "タチツテト1ナニヌネノ2",
62
+ "ナニヌネノ3ハヒフヘホ4",
63
+ "ハヒフヘホ5マミムメモ6",
64
+ "マミムメモ7ヤユヨラリ8",
65
+ "ヤユヨラリ9ルレロワン0",
66
+ "ルレロワン1ヲァィゥェ2",
67
+ "ヲァィゥェ3ォャュョッ4",
68
+ "ア9ン8イ7ン6ウ5ン4",
69
+ "カ8ン7キ6ン5ク4ン3",
70
+ "サ7ン6シ5ン4ス3ン2",
71
+ "タ6ン5チ4ン3ツ2ン1",
72
+ "ナ5ン4ニ3ン2ヌ1ン0",
73
+ "ハ4ン3ヒ2ン1フ0ン9",
74
+ "マ3ン2ミ1ン0ム9ン8",
75
+ "ヤ2ン1ユ0ン9ヨ8ン7",
76
+ "ル1ン0レ9ン8ロ7ン6",
77
+ "アイ2ウエ3オカ4キク5",
78
+ "ケコ6サシ7スセ8ソタ9",
79
+ "チツ0テト1ナニ2ヌネ3",
80
+ "ノハ4ヒフ5ヘホ6マミ7",
81
+ "ムメ8モヤ9ユヨ0ラリ1",
82
+ "ルレ2ロワ3ンヲ4ァィ5",
83
+ "ゥェ6ォャ7ュョ8ッー9",
84
+ "0ア1イ2ウ3エ4オ5カ",
85
+ "6キ7ク8ケ9コ0サ1シ",
86
+ "1シ2ス3セ4ソ5タ6チ",
87
+ "7ツ8テ9ト0ナ1ニ2ヌ",
88
+ "2ヌ3ネ4ノ5ハ6ヒ7フ",
89
+ "8フ9ヘ0ホ1マ2ミ3ム",
90
+ "3ム4メ5モ6ヤ7ユ8ヨ",
91
+ "9ヨ0ラ1リ2ル3レ4ロ",
92
+ "5ロ6ワ7ン8ヲ9ァ0ワ",
93
+ "ィ1ゥ2ェ3ォ4ャ5ュ6",
94
+ "ョ7ッ8ー9ア0イ1ウエ",
95
+ "エ2オ3カ4キ5ク6ケオ",
96
+ ];
97
+ /**
98
+ * Shown under the spinner as "ULTRA: <tip>". Every one of these names a command
99
+ * this repository actually ships, because a tip that describes a flag nobody
100
+ * has is worse than no tip at all.
101
+ */
102
+ export function spinnerTips() {
103
+ const cli = getCliName();
104
+ return [
105
+ `${cli} run --bg hands work to another harness so this session keeps its own context.`,
106
+ `${cli} run --worktree gives each session its own checkout, so two of them cannot fight over a file.`,
107
+ `${cli} ps lists the recent sessions with the harness and model each one ran on.`,
108
+ `${cli} logs <id> prints what a background session actually reported.`,
109
+ `${cli} wait <id> --json blocks until a session reaches a terminal state.`,
110
+ `${cli} diff <id> shows what a worktree session changed, against its base commit.`,
111
+ `${cli} stop <id> interrupts a session, then escalates to SIGTERM and SIGKILL.`,
112
+ `${cli} send <id> continues a session with a new message instead of restarting it.`,
113
+ `${cli} show <id> prints one session in full: status, worktree, usage, recent events.`,
114
+ `${cli} open reviewer opens a session that can read and run but never edit.`,
115
+ `${cli} setup binds each role to a harness and a model, one screen per role.`,
116
+ `${cli} run --role auditor sends a one-off deep review to another harness.`,
117
+ ];
118
+ }
119
+ /**
120
+ * statusLine.command is handed to a shell, so an install directory carrying a
121
+ * space, a `$`, a backtick or a quote would break the command or inject into
122
+ * it. Single quotes take every one of those literally, and the only character
123
+ * that can end them is a quote, which is why that one is spliced.
124
+ */
125
+ export function shellQuote(value) {
126
+ return `'${value.replaceAll("'", `'\\''`)}'`;
127
+ }
128
+ /**
129
+ * Gives Claude's child sessions a `codedeck` command even when this CLI came
130
+ * from a checkout or an npx process that never installed a global shim.
131
+ */
132
+ function writeShim(file, entry, cliName) {
133
+ fs.mkdirSync(path.dirname(file), { recursive: true });
134
+ // Rewriting is deliberate because the Node binary or checkout can move
135
+ // after a previous launch, and writeFileSync preserves an existing mode.
136
+ // The alias shim carries its own name so it prints right even when the
137
+ // parent environment did not propagate CODEDECK_CLI_NAME.
138
+ const rename = cliName !== undefined ? `export CODEDECK_CLI_NAME=${shellQuote(cliName)}\n` : "";
139
+ fs.writeFileSync(file, `#!/usr/bin/env sh\n${rename}exec ${shellQuote(process.execPath)} ${shellQuote(entry)} "$@"\n`, { mode: 0o700 });
140
+ fs.chmodSync(file, 0o700);
141
+ }
142
+ export function ensureCodedeckShim() {
143
+ try {
144
+ const binDir = path.join(getPaths().base, "bin");
145
+ const entry = fileURLToPath(new URL("../cli/index.js", import.meta.url));
146
+ // The shim leads PATH so a stale global install cannot take over from the
147
+ // CLI instance that is running this checkout.
148
+ writeShim(path.join(binDir, "codedeck"), entry);
149
+ // A renamed alias (CODEDECK_CLI_NAME) gets its own shim so workers type
150
+ // the same name they were launched with.
151
+ const alias = getCliName();
152
+ if (alias !== "codedeck" && /^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(alias)) {
153
+ writeShim(path.join(binDir, alias), entry, alias);
154
+ }
155
+ return binDir;
156
+ }
157
+ catch {
158
+ // A missing shim must not turn an otherwise valid Claude launch into a
159
+ // failure.
160
+ }
161
+ }
162
+ export function sanitizeEnv(env) {
163
+ const sanitized = { ...env };
164
+ delete sanitized.CLAUDE_CODE_CHILD_SESSION;
165
+ // A mise shim announces the tool it resolved on every run. It costs 36ms,
166
+ // which nobody would notice, and one line of output, which lands on top of
167
+ // the boot screen. Only the line matters. A setting of the user's own is left
168
+ // alone, since silencing mise everywhere is not this command's call.
169
+ sanitized.MISE_QUIET ??= "1";
170
+ return sanitized;
171
+ }
172
+ export function withCodedeckOnPath(env, binDir) {
173
+ const currentPath = env.PATH;
174
+ const entries = currentPath ? currentPath.split(path.delimiter) : [];
175
+ if (entries[0] === binDir)
176
+ return { ...env };
177
+ return {
178
+ ...env,
179
+ PATH: [binDir, ...entries].join(path.delimiter),
180
+ };
181
+ }
182
+ /**
183
+ * What to say once the session is over, so the id is there when it is wanted
184
+ * rather than buried in a picker.
185
+ *
186
+ * It cannot be said any earlier. The id does not exist when the boot screen
187
+ * prints, and the launcher cannot read it off the session either, because
188
+ * stdout is inherited by the terminal so that the TUI can paint straight to it.
189
+ * A SessionStart hook is the one thing that sees it, and it can only leave it
190
+ * in a file for afterwards.
191
+ */
192
+ export function resumeHint(role, id) {
193
+ if (id === undefined || !/^[0-9a-fA-F-]{8,}$/.test(id))
194
+ return undefined;
195
+ return `${INDENT}resume: ${getCliName()} open ${role} --resume ${id}\n`;
196
+ }
197
+ /**
198
+ * The way out, on the primary screen the alternate one just handed back.
199
+ *
200
+ * Drawn even when there is no id to offer, because the sign-off is the point
201
+ * and a session that ended without one still ended.
202
+ */
203
+ export function renderExit(role, id) {
204
+ const farewell = renderFarewell()
205
+ .split("\n")
206
+ .map((line) => (line.trim() === "" ? line : blood(line)))
207
+ .join("\n");
208
+ const hint = resumeHint(role, id);
209
+ return hint ? `${farewell}${muted(hint)}` : farewell;
210
+ }
211
+ /** Reads what the SessionStart hook left, and takes both sidecars with it. */
212
+ function takeSessionId(file) {
213
+ let id;
214
+ try {
215
+ id = fs.readFileSync(file, "utf8").trim() || undefined;
216
+ return id;
217
+ }
218
+ catch {
219
+ // No file means the hook never ran: an older Claude Code, a session that
220
+ // died before startup, or a plugin the launch could not load. None of those
221
+ // are worth a diagnostic on the way out of a session that otherwise worked.
222
+ }
223
+ finally {
224
+ try {
225
+ fs.rmSync(file, { force: true });
226
+ }
227
+ catch { }
228
+ if (id && /^[0-9a-fA-F-]{8,}$/.test(id)) {
229
+ try {
230
+ fs.rmSync(`${file}.${id}.name`, { force: true });
231
+ }
232
+ catch { }
233
+ }
234
+ }
235
+ }
236
+ const SIGINT_ESCALATION_COUNT = 3;
237
+ const SIGINT_KILL_GRACE_MS = 1000;
238
+ /**
239
+ * Keeps the parent alive while Claude leaves the terminal and CodeDeck writes
240
+ * the resume hint.
241
+ *
242
+ * Claude inherits stdin and stays in the parent's foreground process group
243
+ * (`detached: false`), so a tty sends Ctrl+C to both processes. This listener
244
+ * absorbs only the parent's copy. Claude still receives the first Ctrl+C and
245
+ * can exit normally. A flood eventually escalates the child itself so a stuck
246
+ * session cannot prevent the hint from being written.
247
+ */
248
+ export function installSigintGuard(getChild, signalHost = process) {
249
+ let signals = 0;
250
+ let escalationStarted = false;
251
+ let childClosed = false;
252
+ let disposed = false;
253
+ let killTimer;
254
+ const clearKillTimer = () => {
255
+ if (killTimer === undefined)
256
+ return;
257
+ clearTimeout(killTimer);
258
+ killTimer = undefined;
259
+ };
260
+ const send = (signal) => {
261
+ const child = getChild();
262
+ if (!child)
263
+ return;
264
+ try {
265
+ child.kill(signal);
266
+ }
267
+ catch { }
268
+ };
269
+ const onSigint = () => {
270
+ if (disposed || childClosed || escalationStarted)
271
+ return;
272
+ signals += 1;
273
+ if (signals < SIGINT_ESCALATION_COUNT || !getChild())
274
+ return;
275
+ escalationStarted = true;
276
+ send("SIGTERM");
277
+ killTimer = setTimeout(() => {
278
+ killTimer = undefined;
279
+ if (disposed || childClosed)
280
+ return;
281
+ send("SIGKILL");
282
+ }, SIGINT_KILL_GRACE_MS);
283
+ };
284
+ signalHost.on("SIGINT", onSigint);
285
+ return {
286
+ childClosed() {
287
+ childClosed = true;
288
+ clearKillTimer();
289
+ },
290
+ dispose() {
291
+ if (disposed)
292
+ return;
293
+ disposed = true;
294
+ clearKillTimer();
295
+ signalHost.removeListener("SIGINT", onSigint);
296
+ },
297
+ };
298
+ }
299
+ export function writeStdoutSync(text) {
300
+ try {
301
+ const bytes = Buffer.from(text);
302
+ let offset = 0;
303
+ while (offset < bytes.length) {
304
+ const written = fs.writeSync(1, bytes, offset, bytes.length - offset);
305
+ if (written <= 0)
306
+ break;
307
+ offset += written;
308
+ }
309
+ }
310
+ catch { }
311
+ }
312
+ /** Takes the session id and writes the farewell while the SIGINT guard is live. */
313
+ export function finishOpenSession(role, sessionFile, write = writeStdoutSync) {
314
+ try {
315
+ write(renderExit(role, takeSessionId(sessionFile)));
316
+ }
317
+ catch { }
318
+ }
319
+ /**
320
+ * The boot screen, and it works because of the fullscreen renderer rather than
321
+ * in spite of it.
322
+ *
323
+ * Claude Code takes a moment to paint, and until it does the terminal shows
324
+ * whatever stood there. Since it opens on the alternate screen, this is drawn
325
+ * on the primary one, replaced the instant Claude takes over and restored,
326
+ * unseen, when the session ends. So it fills exactly the gap and cleans itself
327
+ * up, with nothing to tear down and no escape of ours left interleaved with
328
+ * Claude's output.
329
+ *
330
+ * Colour is written by hand here for the same reason the status line writes its
331
+ * own: this runs before Claude Code exists, so no theme is loaded yet.
332
+ */
333
+ /**
334
+ * The logo resolving out of katakana noise, one frame at a time.
335
+ *
336
+ * Same alphabet as the spinner, so the launch and the session read as one
337
+ * thing. Blanks in the logo stay blank: the mark keeps its silhouette the whole
338
+ * way through and the noise fills only the strokes, which is what makes it look
339
+ * like the letters arriving rather than a rectangle of static.
340
+ *
341
+ * Pure, with the noise supplied by the caller, so a test can pin an exact frame
342
+ * instead of asserting around randomness.
343
+ */
344
+ export function bootFrame(progress, noise) {
345
+ const width = LOGO[0].length;
346
+ const settled = Math.round(width * Math.min(1, Math.max(0, progress)));
347
+ return LOGO.map((line) => [...line]
348
+ .map((glyph, column) => {
349
+ if (column < settled || glyph === " ")
350
+ return glyph;
351
+ return noise(column);
352
+ })
353
+ .join(""));
354
+ }
355
+ const BOOT_STEPS = 18;
356
+ const BOOT_STEP_MS = 40;
357
+ const KATAKANA = [
358
+ ...new Set([...SPINNER_VERBS.join("")].filter((glyph) => !/[0-9]/.test(glyph))),
359
+ ];
360
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
361
+ /**
362
+ * Plays the launch animation, then leaves the finished banner on screen.
363
+ *
364
+ * It runs BEFORE the spawn, and that ordering is the whole design. Once Claude
365
+ * Code is spawned the two processes share one terminal, and the moment it
366
+ * switches to the alternate screen anything written here lands inside its TUI.
367
+ * There is no layer to sit on top of, so the choice is before or corrupted.
368
+ * The cost is honest: this animation is time added to the launch, not time
369
+ * borrowed from Claude Code starting up.
370
+ *
371
+ * A pipe gets the still banner. Cursor movement assumes a terminal that is
372
+ * showing the last thing written, which a redirect into a file is not.
373
+ */
374
+ export async function playBoot(role, model, effort) {
375
+ const out = process.stdout;
376
+ if (!out.isTTY) {
377
+ out.write(renderBanner(role, model, effort));
378
+ return;
379
+ }
380
+ const noise = () => KATAKANA[randomInt(KATAKANA.length)];
381
+ out.write("\n");
382
+ for (let step = 0; step <= BOOT_STEPS; step++) {
383
+ if (step > 0)
384
+ out.write(`\x1b[${LOGO.length}A`);
385
+ for (const line of bootFrame(step / BOOT_STEPS, noise)) {
386
+ out.write(`\r\x1b[2K${INDENT}${blood(line)}\n`);
387
+ }
388
+ await delay(BOOT_STEP_MS);
389
+ }
390
+ out.write(`${INDENT}${muted(`${role} · ${model} · ${effort}`)}\n`);
391
+ out.write(`${INDENT}${muted("booting…")}\n`);
392
+ }
393
+ const blood = (value) => `\x1b[38;2;225;29;72m${value}\x1b[0m`;
394
+ const muted = (value) => `\x1b[38;2;163;139;143m${value}\x1b[0m`;
395
+ export function renderBanner(role, model, effort) {
396
+ const logo = renderLogo()
397
+ .split("\n")
398
+ .map((line) => (line.trim() === "" ? line : blood(line)))
399
+ .join("\n");
400
+ return `${logo}${INDENT}${muted(`${role} · ${model} · ${effort}`)}\n${INDENT}${muted("booting…")}\n`;
401
+ }
402
+ export function assertPluginDirectory(pluginDir) {
403
+ try {
404
+ if (fs.statSync(pluginDir).isDirectory())
405
+ return;
406
+ }
407
+ catch { }
408
+ throw new Error(`CodeDeck plugin directory not found at ${pluginDir}. Rebuild or reinstall CodeDeck before running open.`);
409
+ }
410
+ export function currentWorkingDirectory() {
411
+ try {
412
+ const cwd = process.cwd();
413
+ if (fs.statSync(cwd).isDirectory())
414
+ return cwd;
415
+ }
416
+ catch { }
417
+ throw new Error("Cannot launch Claude Code: the current working directory no longer exists.");
418
+ }
419
+ export function errorDetails(error) {
420
+ if (typeof error !== "object" || error === null)
421
+ return { text: String(error) };
422
+ const value = error;
423
+ const text = [value.stderr, value.stdout, value.message]
424
+ .filter((part) => typeof part === "string" && part.length > 0)
425
+ .join("\n");
426
+ return {
427
+ code: typeof value.code === "string" ? value.code : undefined,
428
+ text: text || String(error),
429
+ };
430
+ }
431
+ /**
432
+ * Only stderr is piped, and only because Claude Code reports an unusable model
433
+ * there as a tagged line rather than a distinct exit code. stdout stays
434
+ * inherited so the TUI paints straight to the terminal, which also means
435
+ * `child.stdout` is null and there is nothing to relay.
436
+ */
437
+ function relayStderr(child, output) {
438
+ const stream = child.stderr;
439
+ if (!stream)
440
+ return;
441
+ stream.setEncoding?.("utf8");
442
+ stream.on("data", (chunk) => {
443
+ output.value += typeof chunk === "string" ? chunk : chunk.toString();
444
+ process.stderr.write(chunk);
445
+ });
446
+ }
447
+ /**
448
+ * How much of a pty session's output is kept for the entitlement check. Under
449
+ * a pty the harness's stderr is merged into stdout, so the tagged refusal
450
+ * arrives in the same stream the TUI paints with — and that stream never ends.
451
+ * The refusal is written before the first frame, so a small window is enough
452
+ * and the session does not accumulate its own transcript in memory.
453
+ */
454
+ const PTY_SCAN_LIMIT = 64 * 1024;
455
+ /**
456
+ * The pty variant of relayStderr: bytes are written through to the terminal
457
+ * untouched, and only the opening window is remembered.
458
+ */
459
+ function relayPtyOutput(child, output, stdout) {
460
+ child.stdout?.pipe(stdout, { end: false });
461
+ child.stdout?.on("data", (chunk) => {
462
+ if (output.value.length >= PTY_SCAN_LIMIT)
463
+ return;
464
+ output.value += typeof chunk === "string" ? chunk : chunk.toString();
465
+ });
466
+ relayStderr(child, output);
467
+ }
468
+ /**
469
+ * A signalled death carries no exit code, and collapsing it to 1 tells a caller
470
+ * the session failed rather than that it was killed. Shells report this as
471
+ * 128 plus the signal number, so Ctrl+C stays 130 and a SIGKILL stays 137.
472
+ */
473
+ export function exitCodeFor(code, signal) {
474
+ if (code !== null)
475
+ return code;
476
+ if (!signal)
477
+ return 1;
478
+ const number = constants.signals[signal];
479
+ return typeof number === "number" ? 128 + number : 1;
480
+ }
481
+ /** Left as a seam so a test can assert the fallback without a real terminal. */
482
+ export function ptyLaunchFor(opts) {
483
+ if (!opts.pty)
484
+ return undefined;
485
+ const reason = ptyUnavailableReason({
486
+ shim: opts.pty.shim,
487
+ stdinIsTty: process.stdin.isTTY === true,
488
+ stdoutIsTty: process.stdout.isTTY === true,
489
+ });
490
+ return reason === undefined ? opts.pty : undefined;
491
+ }
492
+ export function spawnHarness(bin, args, opts) {
493
+ return new Promise((resolve, reject) => {
494
+ let settled = false;
495
+ const output = { value: "" };
496
+ const binDir = ensureCodedeckShim();
497
+ const childEnv = binDir === undefined
498
+ ? sanitizeEnv(process.env)
499
+ : withCodedeckOnPath(sanitizeEnv(process.env), binDir);
500
+ let child;
501
+ let pty;
502
+ const launch = ptyLaunchFor(opts);
503
+ const signalHost = opts.signalHost ?? process;
504
+ // Under a pty the terminal is in raw mode, so Ctrl+C never reaches this
505
+ // process as a signal: it is a byte on the way to the harness. The
506
+ // escalation that kills a wedged session is fed from that byte instead,
507
+ // through the same guard, so the escape hatch behaves as it always did.
508
+ const keyboard = launch ? new EventEmitter() : undefined;
509
+ const relayInterrupt = keyboard ? () => keyboard.emit("SIGINT") : undefined;
510
+ if (relayInterrupt)
511
+ signalHost.on("SIGINT", relayInterrupt);
512
+ const sigintGuard = installSigintGuard(() => child, keyboard ?? signalHost);
513
+ const releaseKeyboard = () => {
514
+ if (relayInterrupt)
515
+ signalHost.removeListener("SIGINT", relayInterrupt);
516
+ };
517
+ const fail = (error) => {
518
+ if (settled)
519
+ return;
520
+ settled = true;
521
+ pty?.dispose();
522
+ releaseKeyboard();
523
+ sigintGuard.childClosed();
524
+ sigintGuard.dispose();
525
+ reject(error);
526
+ };
527
+ const spawnChild = opts.spawnChild ?? spawn;
528
+ const env = { ...childEnv, CODEDECK_SESSION_FILE: opts.sessionFile, ...(opts.envExtra ?? {}) };
529
+ try {
530
+ if (launch) {
531
+ pty = startPtySession({
532
+ target: { bin, args },
533
+ launch,
534
+ cwd: opts.cwd,
535
+ env,
536
+ spawnChild,
537
+ onInterrupt: relayInterrupt,
538
+ });
539
+ child = pty.child;
540
+ }
541
+ else {
542
+ child = spawnChild(bin, args, {
543
+ cwd: opts.cwd,
544
+ env,
545
+ // Keep Claude in the foreground process group so the terminal sends
546
+ // the first Ctrl+C to it as well as to this parent guard.
547
+ detached: false,
548
+ stdio: ["inherit", "inherit", "pipe"],
549
+ });
550
+ }
551
+ }
552
+ catch (error) {
553
+ fail(error);
554
+ return;
555
+ }
556
+ if (pty)
557
+ relayPtyOutput(child, output, process.stdout);
558
+ else
559
+ relayStderr(child, output);
560
+ child.once("error", (error) => {
561
+ if (settled)
562
+ return;
563
+ const details = errorDetails(error);
564
+ if (details.code === "ENOENT") {
565
+ fail(new Error(opts.notFoundMessage));
566
+ }
567
+ else {
568
+ fail(error);
569
+ }
570
+ });
571
+ child.once("close", (code, signal) => {
572
+ if (settled)
573
+ return;
574
+ settled = true;
575
+ pty?.dispose();
576
+ releaseKeyboard();
577
+ sigintGuard.childClosed();
578
+ const entitlement = opts.entitlementError?.(opts.model ?? "", output.value);
579
+ if (entitlement) {
580
+ sigintGuard.dispose();
581
+ reject(new Error(entitlement));
582
+ return;
583
+ }
584
+ process.exitCode = exitCodeFor(code, signal);
585
+ try {
586
+ opts.onClose();
587
+ sigintGuard.dispose();
588
+ resolve();
589
+ }
590
+ catch (error) {
591
+ sigintGuard.dispose();
592
+ reject(error);
593
+ }
594
+ });
595
+ });
596
+ }
597
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/open/runtime.ts"],"names":[],"mappings":"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"}
@@ -6,13 +6,19 @@ tools: Read, Grep, Glob, Bash, WebFetch, WebSearch, Task
6
6
 
7
7
  You are the CodeDeck auditor. You review a scope large enough that one pass would miss things, by splitting it and consolidating what comes back. You are read only, and so is everyone you dispatch: nothing in this review edits, stages, commits, or pushes.
8
8
 
9
+ Once the task is clear in a `codedeck run` worker, rename your session with `codedeck rename "$CODEDECK_SESSION_ID" <short-task-slug>`.
10
+
9
11
  ## Splitting
10
12
 
11
13
  - Slice by **dimension**, never by file. One agent per file duplicates findings, multiplies the spend, and still misses anything that spans two files. Dimensions look like: correctness on real inputs, error and failure paths, test coverage, contracts between modules, resource and lifecycle handling, security surface.
12
14
  - Read enough of the scope yourself to choose the dimensions. Splitting before you know what is in there produces slices that do not match the work.
13
15
  - Choose the mechanism by what the slice needs, not by an assumed cost gap. There is no measured one: `docs/harness-behaviour.md` records the attempt and why its two columns cannot be compared.
14
16
  - Neither mechanism starts with your context. A native subagent inherits this conversation only when it is a fork, and a separate worker begins around 80% cache reads rather than from nothing. Either way the briefing carries the whole task, and neither one is cheap because it already knows something.
15
- - Native subagents are the default for reading and research: no worktree, no second process, nothing to clean up. Reach for `codedeck run --no-worktree` when a slice wants a different harness or model, or a genuinely independent read. Never `--worktree`: there is nothing here to diff.
17
+ - Native subagents are the default for reading and research: no worktree, no second process, nothing to clean up. When a slice needs a separate process, use `codedeck run --role reviewer --no-worktree "<briefing>"`.
18
+ - `--role` selects the harness and model the human configured for that role. It also loads the role's contract into the worker prompt, including for non-Claude harnesses.
19
+ - Without `--role`, `codedeck run` uses the default harness and sends only a loose briefing. It ignores the user's role binding and gives a more expensive worker less direction.
20
+ - The role owns the harness and the model. `--agent` and `--model` are ignored for a bound role (run warns and keeps the binding), so you cannot swap the worker onto another harness. Changing the pairing is a `codedeck setup` decision, not a dispatch flag.
21
+ - A file-changing worker uses the canonical form `codedeck run --role <role> --worktree "<briefing>"`, but this review is read only. Never use `--worktree` for an audit slice.
16
22
  - Every slice carries the full reviewer contract: open the real file, cite `file:line` you actually opened, prove runtime claims with a probe you ran, valid only when it ties to a reproducible failure or a stated contract, and close with what you did not cover.
17
23
 
18
24
  ## Consolidating
@@ -13,10 +13,21 @@ You are the CodeDeck general session. You do the work yourself, here. Delegation
13
13
  - Preserve unrelated work in the worktree. Do not revert, reformat, or normalize files outside the task.
14
14
  - Scope every test run to what you touched, by file or by test name. Never run a whole suite to check one change.
15
15
  - Verify before you claim. Run the focused command and quote its output.
16
+ - Once the task is clear in a `codedeck run` worker, rename your session with `codedeck rename "$CODEDECK_SESSION_ID" <short-task-slug>`.
17
+ - When a branch or pull request has merge conflicts, inspect both sides. If the resolution is simple and mechanical, such as a clear union of both sides, resolve it and leave a short footnote in the report or pull request describing what was merged and why. If the resolution is semantically risky or ambiguous, stop and report it instead of guessing.
18
+ - After you push a branch or open or update a pull request, run 'gh pr checks <n>' and/or 'gh pr view <n>' and read external quality gates such as SonarCloud. A failing check, gate, or threshold means the deliverable is not done. Report it as incomplete, never round it to success.
16
19
 
17
20
  ## When you do delegate
18
21
 
19
- - Use `codedeck run --worktree` so the worker has an attributable worktree and diff.
22
+ - Before declaring a file-changing task ready, dispatch `codedeck run --role reviewer --no-worktree "<briefing>"` on your own change.
23
+ - The review is read-only, so use `--no-worktree`. The briefing names what changed and where because the reviewer starts with no conversation context.
24
+ - Act on the review result before declaring the task ready.
25
+ - If the human waived review, or the change is small enough that review would be wasteful, say that you skipped it and why. Do not skip silently.
26
+ - Use `codedeck run --role <role> --worktree "<briefing>"` so the worker has an attributable worktree, diff, and role contract.
27
+ - Worktree is a choice, not a default. `--worktree` is a fresh checkout of the current repo at HEAD, so it cannot reach another repository or an uncommitted working tree elsewhere. A slice that reproduces or fixes a bug in place, or that touches a different repo, runs `--no-worktree --cwd <target>` instead, on a harness whose file access can reach that target.
28
+ - Always include `--role`. It selects the harness and model the human configured for that role. It also loads that role's contract into the worker prompt, including for non-Claude harnesses.
29
+ - Without `--role`, `codedeck run` uses the default harness and sends only a loose briefing. It ignores the user's role binding and gives a more expensive worker less direction.
30
+ - The role owns the harness and the model. `--agent` and `--model` are ignored for a bound role (run warns and keeps the binding), so you cannot swap the worker onto another harness. Changing the pairing is a `codedeck setup` decision, not a dispatch flag.
20
31
  - Slice by ownership. A worker owns its files end to end. Two workers in one file is a merge you will pay for.
21
32
  - Workers start with none of this context. The briefing carries the goal, the files it owns, the interface it must produce, what is out of scope, and how it verifies itself. Never write "see the conversation".
22
33
  - Read `codedeck diff <id>` yourself before believing any worker. The artifact is authoritative, the success message is not.