codsh-bundle 0.6.0 → 0.7.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.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Running a `!` line in the person's own shell.
3
+ * @module codsh-bundle/src/bang
4
+ */
5
+ /** The login shell, falling back to sh. */
6
+ export declare function userShell(): string;
@@ -30,6 +30,17 @@ export type CompletionResult = [completions: string[], substring: string];
30
30
  * @returns the score, or undefined when it does not match.
31
31
  */
32
32
  export declare function fuzzyScore(needle: string, hay: string): number | undefined;
33
+ /**
34
+ * Rank candidates that contain `typed`, prefix hits first.
35
+ *
36
+ * A fragment anywhere in the name is enough to offer it; an exact prefix still
37
+ * outranks a buried hit, so `/p` keeps `plan` above `compact`.
38
+ * @param items - the candidates.
39
+ * @param typed - what was typed, without a leading `/` `$` `@`.
40
+ * @param nameOf - the name to match against.
41
+ * @returns matching items, prefix then substring.
42
+ */
43
+ export declare function rankContains<T>(items: readonly T[], typed: string, nameOf: (item: T) => string): T[];
33
44
  /** Drop the cached workspace walk, so a test controls what the next Tab sees. */
34
45
  export declare function resetFileIndex(): void;
35
46
  /**
@@ -43,3 +54,12 @@ export declare function resetFileIndex(): void;
43
54
  * @returns a completer over the word under the cursor.
44
55
  */
45
56
  export declare function createCompleter(commands: () => readonly CompletableCommand[], cwd: string): (line: string) => CompletionResult;
57
+ /**
58
+ * Turn `$name` skill gestures into the `/name` tokens dsh injects.
59
+ *
60
+ * Only names in `known` are rewritten, so an ordinary `$amount` stays prose.
61
+ * @param text - the submitted line.
62
+ * @param known - user-invocable skill names.
63
+ * @returns the line with known `$name` tokens rewritten as `/name`.
64
+ */
65
+ export declare function expandSkillGestures(text: string, known: ReadonlySet<string>): string;
@@ -56,10 +56,13 @@ export declare class TerminalConsole {
56
56
  /** Display columns available for one line, never below {@link MIN_COLUMNS}. */
57
57
  get columns(): number;
58
58
  /**
59
- * Columns content may be laid out for: one less than the width, because the
60
- * viewport wraps at that boundary. Markdown layout MUST use this figure — a
61
- * table laid out one column wider is refolded by the viewport, and its rows
62
- * shear apart.
59
+ * Columns content may be laid out for.
60
+ *
61
+ * One less than the width so a row cannot wrap the terminal, and on a
62
+ * viewport two less again for the left gutter. Markdown, the live line, and
63
+ * the chrome MUST use this figure — a box laid out one gutter wider is
64
+ * truncated with an ellipsis on every row, and a live line that fills the
65
+ * width wraps into the box beneath it.
63
66
  */
64
67
  get contentColumns(): number;
65
68
  /** Whether the output stream is a terminal. */
@@ -89,6 +92,15 @@ export declare class TerminalConsole {
89
92
  * Give the terminal back. Idempotent: every exit path calls it.
90
93
  */
91
94
  leaveScreen(): void;
95
+ /**
96
+ * Hand the real TTY to a child, then take the viewport back.
97
+ *
98
+ * Raw mode and the alternate screen both have to go: the shell needs cooked
99
+ * input and the person's own buffer, the way Claude Code and opencode yield
100
+ * `!` to sh. SIGINT is swallowed here so Ctrl-C reaches the child.
101
+ * @param work - runs while this process is not reading the keyboard.
102
+ */
103
+ runInForeground<T>(work: () => Promise<T>): Promise<T>;
92
104
  /** Whether this surface currently holds its own screen. */
93
105
  get owningScreen(): boolean;
94
106
  /**
@@ -101,6 +113,11 @@ export declare class TerminalConsole {
101
113
  * @param text - the styled line, or the empty string for none.
102
114
  */
103
115
  setScrollNotice(text: string): void;
116
+ /**
117
+ * Float rows over the transcript just above the chrome.
118
+ * @param rows - the overlay, or empty to clear it.
119
+ */
120
+ setOverlay(rows: readonly string[]): void;
104
121
  /**
105
122
  * Scroll the transcript by a whole viewport.
106
123
  * @param direction - -1 for back into history, 1 towards the tail.
@@ -108,6 +125,32 @@ export declare class TerminalConsole {
108
125
  scrollPage(direction: -1 | 1): void;
109
126
  /** Return to the tail of the transcript. */
110
127
  scrollToBottom(): void;
128
+ /**
129
+ * Search the owned scrollback.
130
+ * @param query - the needle.
131
+ */
132
+ searchTranscript(query: string): {
133
+ query: string;
134
+ hits: number;
135
+ index: number;
136
+ } | undefined;
137
+ /**
138
+ * Step to another hit of the current query.
139
+ * @param direction - 1 towards the tail, -1 towards the head.
140
+ */
141
+ nextTranscriptHit(direction: 1 | -1): {
142
+ query: string;
143
+ hits: number;
144
+ index: number;
145
+ } | undefined;
146
+ /** Close find. Transcript content is untouched. */
147
+ clearTranscriptSearch(): void;
148
+ /** Incremental find over the scrollback, absent when find is closed. */
149
+ get transcriptSearch(): {
150
+ query: string;
151
+ hits: number;
152
+ index: number;
153
+ } | undefined;
111
154
  /** Physical rows currently scrolled out of view; zero means at the tail. */
112
155
  get scrolledBy(): number;
113
156
  /**
@@ -172,8 +215,14 @@ export declare class TerminalConsole {
172
215
  * @param rule - a styled left rule for the whole block, `''` for none.
173
216
  * @param label - what the block is, for the readout naming what the pointer
174
217
  * is over.
218
+ * @param enter - child session a click opens instead of folding, when set.
175
219
  */
176
- appendFold(summary: readonly string[], full: readonly string[], rule?: string, label?: string): void;
220
+ appendFold(summary: readonly string[], full: readonly string[], rule?: string, label?: string, enter?: string): void;
221
+ /**
222
+ * What a click on a view-card does.
223
+ * @param handler - receives the child session id; omit to restore folding.
224
+ */
225
+ setEnter(handler: ((id: string) => void) | undefined): void;
177
226
  /**
178
227
  * Swap every collapsible block between summary and full form.
179
228
  * @returns false when there is nothing to toggle.
@@ -241,6 +290,11 @@ export declare class TerminalConsole {
241
290
  * @param handler - receives the raw payload, e.g. `rgb:1e1e/1e1e/2e2e`.
242
291
  */
243
292
  onBackground(handler: (payload: string) => void): void;
293
+ /**
294
+ * Adopt the light- or dark-background hover fill.
295
+ * @param light - true when OSC 11 named a light color.
296
+ */
297
+ setLight(light: boolean): void;
244
298
  /**
245
299
  * Set the terminal window title.
246
300
  * @param title - the title text; control bytes are the terminal's to reject.
@@ -30,6 +30,29 @@ export interface EditorView {
30
30
  selected: number;
31
31
  /** The token under the cursor, which is what the candidates matched. */
32
32
  token: string;
33
+ /** Reverse history search, absent when not searching. */
34
+ search?: {
35
+ query: string;
36
+ hits: number;
37
+ index: number;
38
+ };
39
+ /**
40
+ * Known `/command` and `$skill` spans in the buffer, in code points.
41
+ *
42
+ * Painted in the box so a finished gesture reads as one, not as prose.
43
+ */
44
+ hits: readonly GestureHit[];
45
+ }
46
+ /** A `/command` or `$skill` in the buffer that names something real. */
47
+ export interface GestureHit {
48
+ /** Buffer line index. */
49
+ row: number;
50
+ /** First code point of the token. */
51
+ start: number;
52
+ /** Code point after the token. */
53
+ end: number;
54
+ /** Which kind of gesture it is. */
55
+ kind: 'command' | 'skill';
33
56
  }
34
57
  /** What the caller must do after a key. */
35
58
  export type EditorAction = {
@@ -61,6 +84,15 @@ export interface EditorSources {
61
84
  * @returns candidates to offer, best first.
62
85
  */
63
86
  commandArguments?(command: string, typed: string): readonly Candidate[];
87
+ /**
88
+ * User-invocable skills for a `$` mention.
89
+ *
90
+ * Absent or empty means `$` is ordinary text.
91
+ */
92
+ skills?(): readonly {
93
+ name: string;
94
+ description: string;
95
+ }[];
64
96
  }
65
97
  /** A multi-line prompt editor. */
66
98
  export declare class Editor {
@@ -75,6 +107,8 @@ export declare class Editor {
75
107
  private browsing;
76
108
  /** The buffer set aside while history is being browsed. */
77
109
  private stashed;
110
+ /** Reverse-i-search over {@link history}, absent when the box is typing. */
111
+ private search;
78
112
  constructor(sources: EditorSources);
79
113
  /** What to render. */
80
114
  get view(): EditorView;
@@ -137,6 +171,13 @@ export declare class Editor {
137
171
  private tokenStart;
138
172
  /** The token under the cursor. */
139
173
  private token;
174
+ /**
175
+ * Spans in the buffer that name a registered command or skill.
176
+ *
177
+ * `/` only counts at the start of the first line, matching how a command is
178
+ * submitted. `$` counts as a word anywhere, matching how a skill is invoked.
179
+ */
180
+ private gestureHits;
140
181
  /**
141
182
  * Recompute the candidate list for the token under the cursor.
142
183
  *
@@ -178,6 +219,23 @@ export declare class Editor {
178
219
  private wordRight;
179
220
  /** Drop the word before the cursor. */
180
221
  private killWord;
222
+ /**
223
+ * Open reverse search over history, stashing the draft.
224
+ * @returns always `none`.
225
+ */
226
+ private openSearch;
227
+ /**
228
+ * Keys while reverse-i-search is open.
229
+ * @param key - the decoded keystroke.
230
+ * @returns what the caller must do about it.
231
+ */
232
+ private handleSearch;
233
+ /** History entries matching the query, newest first. */
234
+ private searchHits;
235
+ /** Put the current hit in the buffer, or the stashed draft when none match. */
236
+ private applySearch;
237
+ /** Put the draft that search set aside back in the buffer. */
238
+ private restoreSearchStash;
181
239
  /**
182
240
  * Close the menu, or report Escape when there is none to close.
183
241
  * @returns `none` when a menu was dismissed, otherwise `escape`.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The input box: a framed, multi-line prompt that wraps long lines, grows with
3
3
  * its content, and windows when it grows past its budget — with the completion
4
- * menu under it.
4
+ * menu overlaid on the transcript above it.
5
5
  *
6
6
  * Pure layout. It turns an {@link EditorView} into the rows of the bottom region
7
7
  * and says where the terminal cursor belongs, so the drawing code has no opinion
@@ -24,11 +24,23 @@ export interface BoxOptions {
24
24
  hint?: string | undefined;
25
25
  /** Styles the frame; absent frames dim. A mode announces itself here. */
26
26
  accent?: ((text: string) => string) | undefined;
27
+ /**
28
+ * Whether the box is in shell mode (`!` at the start of the first line).
29
+ *
30
+ * The frame and gutter announce it; the leading `!` is the gutter, not a
31
+ * second character in the buffer.
32
+ */
33
+ shell?: boolean | undefined;
27
34
  }
28
35
  /** The rows to draw and where the cursor goes among them. */
29
36
  export interface BoxLayout {
30
37
  /** Rows, top to bottom, each already fitted to the terminal. */
31
38
  rows: string[];
39
+ /**
40
+ * Completion menu, painted over the transcript just above the box so
41
+ * opening it cannot grow the chrome or shake the output.
42
+ */
43
+ overlay: string[];
32
44
  /** Index into {@link rows} where the cursor belongs. */
33
45
  cursorRow: number;
34
46
  /** Display column of the cursor on that row, from zero. */
@@ -60,6 +60,10 @@ export type Key = {
60
60
  kind: 'expand-output';
61
61
  } | {
62
62
  kind: 'toggle-todos';
63
+ } | {
64
+ kind: 'history-search';
65
+ } | {
66
+ kind: 'transcript-search';
63
67
  } | {
64
68
  kind: 'page';
65
69
  direction: -1 | 1;
@@ -91,6 +91,10 @@ export declare class Prompt {
91
91
  private todos;
92
92
  /** Whether the todo readout shows every item or only the one in flight. */
93
93
  private todosExpanded;
94
+ /** Incremental find over the transcript, absent when find is closed. */
95
+ private finding;
96
+ /** Whether the shortcuts overlay is occupying chrome. */
97
+ private shortcutsOpen;
94
98
  /** The assistant line still arriving, shown above the box. */
95
99
  private streaming;
96
100
  /** Frame styling for the current mode, e.g. plan mode's accent. */
@@ -145,7 +149,8 @@ export declare class Prompt {
145
149
  setFlash(text: string): void;
146
150
  /**
147
151
  * Set the status row, the region's always-current last line.
148
- * @param text - the styled row, or undefined to drop it.
152
+ * @param text - the full styled row, or undefined to drop it. Truncation is
153
+ * applied at paint time so a resize can grow the line back.
149
154
  */
150
155
  setStatus(text: string | undefined): void;
151
156
  /**
@@ -230,6 +235,17 @@ export declare class Prompt {
230
235
  * @returns the rows, empty when no list is live.
231
236
  */
232
237
  private todoRows;
238
+ /**
239
+ * Keys while transcript find is open: typing is the query, arrows step,
240
+ * Escape closes. The transcript is not edited.
241
+ * @param key - the decoded keystroke.
242
+ */
243
+ private onFindKey;
244
+ /**
245
+ * The find readout, or undefined when find is closed.
246
+ * @param columns - display columns available.
247
+ */
248
+ private findRow;
233
249
  /** Recompose and redraw the bottom region. */
234
250
  private render;
235
251
  }
@@ -15,6 +15,8 @@
15
15
  * that relative erase arithmetic keeps producing.
16
16
  * @module codsh-bundle/src/screen
17
17
  */
18
+ /** Blank columns to the left of every painted row, so text is not flush to the window. */
19
+ export declare const GUTTER = 2;
18
20
  /** Where the cursor belongs within the chrome rows. */
19
21
  export interface ChromeCursor {
20
22
  row: number;
@@ -28,6 +30,8 @@ export interface HoverBlock {
28
30
  lines: number;
29
31
  /** Whether it is showing that full form now. */
30
32
  expanded: boolean;
33
+ /** Whether a click opens the named child session rather than folding. */
34
+ enter?: boolean;
31
35
  }
32
36
  /** What the screen writes to and measures itself against. */
33
37
  export interface ScreenHost {
@@ -64,12 +68,16 @@ export declare class Screen {
64
68
  private offset;
65
69
  /** What to show while scrolled back, drawn over the viewport's top row. */
66
70
  private notice;
71
+ /** Completion menu painted over the viewport, just above the chrome. */
72
+ private overlay;
67
73
  /** A mouse selection over the transcript, in physical-row coordinates. */
68
74
  private selection;
69
75
  /** Collapsed blocks in the transcript, in order, with both of their forms. */
70
76
  private folds;
71
77
  /** The block the pointer rests on, or undefined when it rests on none. */
72
78
  private hovered;
79
+ /** Whether OSC 11 named a light background; the hover fill picks a shade. */
80
+ private light;
73
81
  /**
74
82
  * Physical row ranges the blocks occupy, or undefined when they need
75
83
  * measuring again.
@@ -82,16 +90,36 @@ export declare class Screen {
82
90
  private ranges;
83
91
  /** Whether the folds currently show their full form. */
84
92
  private expanded;
93
+ /** Incremental find over the owned scrollback, absent when find is closed. */
94
+ private find;
85
95
  /** The last painted frame, so a repaint only touches rows that changed. */
86
96
  private painted;
87
97
  /** Width the current frame was painted at, to detect a resize. */
88
98
  private paintedColumns;
89
99
  private active;
100
+ /** Opens a child session when a view-card is clicked. */
101
+ private enterHandler;
90
102
  constructor(host: ScreenHost);
103
+ /**
104
+ * What a click on a view-card does.
105
+ * @param handler - receives the child session id; omit to restore folding.
106
+ */
107
+ setEnter(handler: ((id: string) => void) | undefined): void;
91
108
  /** Whether the alternate screen is currently held. */
92
109
  get entered(): boolean;
110
+ /**
111
+ * Adopt the light- or dark-background hover fill.
112
+ * @param light - true when OSC 11 named a light color.
113
+ */
114
+ setLight(light: boolean): void;
93
115
  /** Physical rows scrolled up out of view; zero means the tail is showing. */
94
116
  get scrolledBy(): number;
117
+ /** Incremental find over the scrollback, absent when find is closed. */
118
+ get transcriptSearch(): {
119
+ query: string;
120
+ hits: number;
121
+ index: number;
122
+ } | undefined;
95
123
  /** Take the alternate screen and start reporting the mouse. */
96
124
  enter(): void;
97
125
  /**
@@ -119,8 +147,9 @@ export declare class Screen {
119
147
  * @param full - the expanded lines, already styled.
120
148
  * @param rule - a styled left rule for the whole block, `''` for none.
121
149
  * @param label - what the block is, for the hover readout that names it.
150
+ * @param enter - child session a click opens instead of folding, when set.
122
151
  */
123
- appendFold(summary: readonly string[], full: readonly string[], rule?: string, label?: string): void;
152
+ appendFold(summary: readonly string[], full: readonly string[], rule?: string, label?: string, enter?: string): void;
124
153
  /**
125
154
  * Turn the last `count` appended lines into a collapsible block after the
126
155
  * fact.
@@ -206,6 +235,14 @@ export declare class Screen {
206
235
  * @param text - the styled notice, already fitted.
207
236
  */
208
237
  setScrollNotice(text: string): void;
238
+ /**
239
+ * Float rows over the viewport just above the chrome.
240
+ *
241
+ * The chrome's height does not change, so opening a completion menu cannot
242
+ * shake the transcript. Empty clears the layer.
243
+ * @param rows - the overlay, top to bottom.
244
+ */
245
+ setOverlay(rows: readonly string[]): void;
209
246
  /**
210
247
  * Scroll the transcript.
211
248
  * @param delta - rows to move; negative scrolls back into history.
@@ -218,6 +255,33 @@ export declare class Screen {
218
255
  scrollPage(direction: -1 | 1): void;
219
256
  /** Jump back to the tail, which is also what a new submission does. */
220
257
  scrollToBottom(): void;
258
+ /**
259
+ * Search the owned scrollback.
260
+ *
261
+ * Hits are physical rows, case-insensitive. A new query starts on the
262
+ * newest hit so recent output is what find lands on first.
263
+ * @param query - the needle; empty means no hits yet.
264
+ * @returns the current find state.
265
+ */
266
+ searchTranscript(query: string): {
267
+ query: string;
268
+ hits: number;
269
+ index: number;
270
+ };
271
+ /**
272
+ * Step to another hit of the current query.
273
+ * @param direction - 1 towards the tail, -1 towards the head.
274
+ * @returns the current find state, or undefined when find is closed.
275
+ */
276
+ nextTranscriptHit(direction: 1 | -1): {
277
+ query: string;
278
+ hits: number;
279
+ index: number;
280
+ } | undefined;
281
+ /** Close find. Transcript content is untouched. */
282
+ clearTranscriptSearch(): void;
283
+ /** Scroll so the current hit is in the viewport, then paint. */
284
+ private revealFindHit;
221
285
  /**
222
286
  * Drop the transcript, keeping the chrome.
223
287
  *
@@ -278,6 +342,8 @@ export declare class Screen {
278
342
  * way dragging past an edge keeps selecting, instead of refusing it.
279
343
  * @returns the position, or undefined when it misses the content.
280
344
  */
345
+ /** Whether a terminal row sits on the floating completion layer. */
346
+ private coversOverlay;
281
347
  private locate;
282
348
  /** Rows the transcript viewport occupies. */
283
349
  private viewportHeight;
@@ -27,6 +27,8 @@ export interface SelectSpec {
27
27
  multi?: boolean;
28
28
  /** Label for a trailing "type your own" row; absent offers none. */
29
29
  custom?: string;
30
+ /** Whether typing filters the list instead of digits/shortcuts settling. */
31
+ filterable?: boolean;
30
32
  }
31
33
  /** How one selection ended. */
32
34
  export type SelectOutcome = {
@@ -47,11 +49,14 @@ export type SelectorStep = {
47
49
  export declare class Selector {
48
50
  private readonly spec;
49
51
  private selected;
52
+ private query;
50
53
  private readonly checked;
51
54
  constructor(spec: SelectSpec);
55
+ /** Original option indices currently shown, in order. */
56
+ private matching;
52
57
  /** How many rows the widget offers, the custom row included. */
53
58
  private get count();
54
- /** Whether a row index is the custom "type your own" row. */
59
+ /** Whether a visible row index is the custom "type your own" row. */
55
60
  private isCustom;
56
61
  /**
57
62
  * Apply one key.
@@ -66,11 +71,17 @@ export declare class Selector {
66
71
  */
67
72
  private typed;
68
73
  /**
69
- * Settle on a row.
70
- * @param index - the row accepted.
74
+ * Settle on a visible row.
75
+ * @param index - the visible row accepted.
71
76
  * @returns the settled step.
72
77
  */
73
78
  private accept;
79
+ /**
80
+ * Settle on an original option index.
81
+ * @param original - the option's index in the spec.
82
+ * @returns the settled step.
83
+ */
84
+ private acceptOriginal;
74
85
  /**
75
86
  * Render the widget.
76
87
  * @param theme - styling for the marker, shortcuts, and details.
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * The `/ship` prompt: a canned workflow that takes a one-sentence requirement
3
- * from idea to shipped, verified code — a research-grounded interview, a
4
- * confirmed spec (gate 1), an approved plan (gate 2), then autonomous landing
5
- * until the spec's acceptance criteria pass.
3
+ * from idea to shipped, verified code — a grill-me interview (design tree,
4
+ * frontier rounds), then automatic to-spec (gate 1), automatic to-tickets
5
+ * (gate 2), then autonomous TDD landing until the spec's acceptance criteria
6
+ * pass.
6
7
  *
7
8
  * The spec FILE is the workflow's memory, not the conversation: the approved
8
- * plan is written into it, its Status line names the phase, its checkboxes
9
+ * tickets are written into it, its Status line names the phase, its checkboxes
9
10
  * are the progress, and a bare /ship offers to resume whatever it finds
10
11
  * unfinished. Conversations get interrupted, compacted, and cleared; the file
11
12
  * survives all three, which is what makes the landing reliable rather than
12
13
  * merely well-intentioned.
13
14
  */
14
15
  /** The `/ship` prompt body; `$ARGUMENTS` is the typed one-sentence requirement. */
15
- export declare const SHIP_PROMPT = "Run the /ship workflow: take the one-sentence requirement below from idea to shipped, verified code in this repository. The requirement, exactly as typed:\n\n<idea>\n$ARGUMENTS\n</idea>\n\nIf the idea between the <idea> tags is empty, that is not an error. First look for unfinished work: scan the repository's spec directory (docs/specs/, or the repo's own design-document convention) for a spec whose Status line is not `shipped` \u2014 a bare /ship most likely means \"carry on\", so offer through ask_user_question to resume that spec from the phase its Status names, with everything below applying from that phase onward. Only when there is nothing to resume, ask for the one-sentence requirement with ask_user_question and use the answer as the idea. Images accompanying the command \u2014 [Image #N] tokens, <pasted-image> context, attached image blocks \u2014 are part of the requirement: a mockup or a screenshot is requirements material, so read it and cite what it shows in the interview.\n\nPhase 1 \u2014 grounded interview. Research before you ask: read the repository layout, the docs, and the code paths the idea touches, so every question is informed by what actually exists. Then interrogate the idea with ask_user_question, one focused question per call, never a batch. Cover, as far as they are genuinely open: who this is for and what success looks like, scope and explicit non-goals, constraints (compatibility, performance, security, dependencies), edge cases and failure behavior, and how the result should be verified. Prefer concrete options grounded in what you found over open-ended prompts. Do not ask what inspection can answer \u2014 where code lives or how current behavior works is yours to find out. Stop when answers stop changing the design; do not pad the interview to look thorough.\n\nPhase 2 \u2014 the spec (gate 1). Write the agreed design to a spec file inside the repository. Follow the repo's existing convention for design documents if one exists (a specs, rfcs, or ADR directory); otherwise create docs/specs/<kebab-case-slug>.md. The spec must stand alone for a reader without this conversation: the one-sentence requirement, background, each interview decision with its reason, scope and non-goals, constraints, edge cases, and a numbered list of acceptance criteria where every criterion names the exact command that proves it and the output that counts as passing \u2014 the final phase runs those commands verbatim, so a criterion without a command is not finished. Give the file a `Status:` line (interviewing, confirmed, planned, landing, shipped) and keep it current at every phase change: it is what lets an interrupted /ship resume instead of starting over. Present the spec file path and a compact summary through ask_user_question and get an explicit yes. If the answer amends or rejects it, update the file and ask again. Do not proceed on silence or a vague reply.\n\nPhase 3 \u2014 the plan (gate 2). Only after the spec is confirmed, produce an implementation plan: ordered milestones with the files each touches, the tests each milestone adds or changes, which acceptance criterion each milestone satisfies, and the commands that prove the whole thing (build, typecheck, test). Present the plan through ask_user_question and get an explicit yes; fold rejections back in and present again. Once approved, write the plan into the spec file as a `## Plan` section with one checkbox per milestone \u2014 an approved plan lives on disk, not in a conversation that can be compacted or lost. Then, still before any implementation code, establish the ground: check the working tree is clean (uncommitted unrelated changes are the user's to decide about \u2014 ask), and run the plan's proof commands once, recording the baseline in the spec. A baseline that is already red changes what \"green\" will mean, so surface it here rather than discovering it under your own diff. Write no implementation code before this gate passes, and do not use todo_write before it either \u2014 it tracks landing, not the interview.\n\nPhase 4 \u2014 landing. After gate 2, work autonomously; return to the user only for a genuine blocker that contradicts the spec, never for routine decisions. Either way the spec file \u2014 not this conversation \u2014 is the working memory: re-read it before starting each milestone, tick the milestone's checkbox and update Status as you go, and commit after each milestone turns green \u2014 small commits are the progress that survives a crash and the history a reviewer can walk. Choose the mechanism by the approved plan's size. If it has at most three milestones and you expect the whole change to fit comfortably in this session's context, implement in-session: track the milestones with todo_write, and for each one implement, run the tests, fix until green, then commit before moving on. If it is larger \u2014 four or more substantially independent milestones, or work you expect to exceed what one session can hold \u2014 the user running /ship is their explicit request for a fresh-agent Ralph loop: call the ralph tool once, with an objective that names the spec file path as the single source of truth, instructs each round to read the spec from disk (plan, checkboxes, baseline), pick the first unchecked milestone, implement and test it, then commit and tick its checkbox, and defines completion as every acceptance criterion in the spec passing. Bound the loop: budget about three rounds per milestone, and instruct it to stop and report rather than continue past two consecutive rounds that tick nothing.\n\nPhase 5 \u2014 done means verified. The workflow ends only when every acceptance criterion passes with you actually running its named command and reading the real output. After a Ralph loop returns, run every proof command again yourself \u2014 the loop's word is a report, not a verification. Never report a result you did not run, and never weaken a criterion to make it pass; if one cannot be met, say so plainly and why. When a decision changes mid-flight, update the spec file first so the file on disk stays the truth. Set Status to shipped only after that final run, and close with an honest report listing each criterion, the command that proved it, and what it printed \u2014 plus anything left open.\n\nIf the session is in plan mode, the plan-mode rules win: nothing here authorizes writes while it is active. Tell the user this workflow needs to write the spec file and ask them to leave plan mode before continuing past the interview.";
16
+ export declare const SHIP_PROMPT = "Run the /ship workflow: take the one-sentence requirement below from idea to shipped, verified code in this repository. The requirement, exactly as typed:\n\n<idea>\n$ARGUMENTS\n</idea>\n\nIf the idea between the <idea> tags is empty, that is not an error. First look for unfinished work: scan the repository's spec directory (docs/specs/, or the repo's own design-document convention) for a spec whose Status line is not `shipped` \u2014 a bare /ship most likely means \"carry on\", so offer through ask_user_question to resume that spec from the phase its Status names, with everything below applying from that phase onward. Only when there is nothing to resume, ask for the one-sentence requirement with ask_user_question and use the answer as the idea. Images accompanying the command \u2014 [Image #N] tokens, <pasted-image> context, attached image blocks \u2014 are part of the requirement: a mockup or a screenshot is requirements material, so read it and cite what it shows in the interview.\n\nPhase 1 \u2014 grill-me. Research before you ask: read the repository layout, CONTEXT.md if it exists, the docs, ADRs, and the code paths the idea touches. Finding facts is your job, never the user's \u2014 where code lives or how current behavior works is yours to find out; do not ask anything inspection can answer. Then map the idea as a design tree: every decision branches into the decisions that hang off it. Work the tree in rounds. The frontier is every decision whose prerequisites are already settled \u2014 the questions you can ask now without guessing at answers you have not heard yet. Each round, put the whole frontier into a single ask_user_question call (the tool accepts a list of questions; do not serialize independent frontier questions across separate calls). For each question: a short title, a body grounded in what you found, concrete options rather than an open prompt, and your recommended answer as the first option with a description that says so. Then wait for that call to return before the next round. A question whose answer depends on another still open in this round belongs to a later round. Each round of answers reshapes the tree \u2014 settled decisions push the frontier outward. The session is done when the frontier is empty: every branch visited, nothing left silently assumed. Confirm shared understanding through ask_user_question before writing the spec. Do not pad the interview to look thorough, and do not act on the design until that confirmation.\n\nPhase 2 \u2014 automatic to-spec (gate 1). Do not interview further \u2014 synthesize what the grill already settled and what the codebase already is. Write the spec to a file inside the repository. Follow the repo's existing convention for design documents if one exists (a specs, rfcs, or ADR directory); otherwise create docs/specs/<kebab-case-slug>.md. Use the project's domain glossary throughout, and respect ADRs in the area you are touching. The spec must stand alone for a reader without this conversation, with these sections in order: a `Status:` line (interviewing, confirmed, planned, landing, shipped) kept current at every phase change; the one-sentence requirement; Problem Statement (from the user's perspective); Solution (from the user's perspective); each grill decision with its reason; User Stories (numbered, \"As an <actor>, I want a <feature>, so that <benefit>\", covering the feature); Implementation Decisions (modules, interfaces, architecture, contracts \u2014 no file paths or code snippets unless a prototype encoded a decision more precisely than prose); Testing Decisions (what a good test is here: external behavior at public seams, not internals; the seams this spec will be tested at, preferring existing ones; prior art in the repo); Out of Scope; and a numbered list of acceptance criteria where every criterion names the exact command that proves it and the output that counts as passing \u2014 the final phase runs those commands verbatim, so a criterion without a command is not finished. Record the proposed seams in Testing Decisions; the fewer across the codebase, the better \u2014 the ideal number is one. Present the spec file path and a compact summary through ask_user_question and get an explicit yes. If the answer amends or rejects it, update the file and ask again. Do not proceed on silence or a vague reply.\n\nPhase 3 \u2014 automatic to-tickets (gate 2). Only after the spec is confirmed, and without another interview, break the spec into tracer-bullet tickets: each a narrow but complete vertical slice through every layer it needs (not a horizontal slice of one layer), demoable or verifiable on its own, sized to fit a single fresh context window. Give each ticket its blocking edges \u2014 the other tickets that must complete before it can start. Prefactoring that makes the change easy comes first. A wide refactor (one mechanical change whose blast radius fans across the codebase) is the exception: sequence it expand\u2013contract, not as a fake tracer bullet. Present the breakdown through ask_user_question as a numbered list (title, blocked by, what it delivers) and get an explicit yes; fold rejections back in and present again. Once approved, write the tickets into the spec file as a `## Plan` section with one checkbox per ticket \u2014 an approved plan lives on disk, not in a conversation that can be compacted or lost. Each checkbox names the ticket, what it delivers, and which tickets block it. Then, still before any implementation code, establish the ground: check the working tree is clean (uncommitted unrelated changes are the user's to decide about \u2014 ask), and run the spec's proof commands once, recording the baseline in the spec. A baseline that is already red changes what \"green\" will mean, so surface it here rather than discovering it under your own diff. Write no implementation code before this gate passes, and do not use todo_write before it either \u2014 it tracks landing, not the grill.\n\nPhase 4 \u2014 automatic landing. After gate 2, work autonomously; return to the user only for a genuine blocker that contradicts the spec, never for routine decisions. Either way the spec file \u2014 not this conversation \u2014 is the working memory: re-read it before starting each ticket, tick the ticket's checkbox and update Status as you go, and commit after each ticket turns green \u2014 small commits are the progress that survives a crash and the history a reviewer can walk. Implement test-first at the seams the spec recorded: red before green, one seam and one test and one minimal implementation per cycle, through the public interface, never internals. Do not write a test at an unconfirmed seam. Do not bulk-write tests then bulk-implement \u2014 vertical slices, matching the tickets. Run typecheck and the focused tests each cycle; run the full suite the spec named once at the end of the ticket. Choose the mechanism by the approved plan's size. If it has at most three tickets and you expect the whole change to fit comfortably in this session's context, implement in-session: track the tickets with todo_write, and for each one red-green, run the tests, fix until green, then commit before moving on. If it is larger \u2014 four or more substantially independent tickets, or work you expect to exceed what one session can hold \u2014 the user running /ship is their explicit request for a fresh-agent Ralph loop: call the ralph tool once, with an objective that names the spec file path as the single source of truth, instructs each round to read the spec from disk (plan, checkboxes, baseline, seams), pick the first unchecked ticket whose blockers are ticked, implement it test-first at those seams, then commit and tick its checkbox, and defines completion as every acceptance criterion in the spec passing. Bound the loop: budget about three rounds per ticket, and instruct it to stop and report rather than continue past two consecutive rounds that tick nothing.\n\nPhase 5 \u2014 done means verified. The workflow ends only when every acceptance criterion passes with you actually running its named command and reading the real output. After a Ralph loop returns, run every proof command again yourself \u2014 the loop's word is a report, not a verification. Never report a result you did not run, and never weaken a criterion to make it pass; if one cannot be met, say so plainly and why. When a decision changes mid-flight, update the spec file first so the file on disk stays the truth. Set Status to shipped only after that final run, and close with an honest report listing each criterion, the command that proved it, and what it printed \u2014 plus anything left open.\n\nIf the session is in plan mode, the plan-mode rules win: nothing here authorizes writes while it is active. Tell the user this workflow needs to write the spec file and ask them to leave plan mode before continuing past the interview.";
@@ -43,6 +43,8 @@ export declare class Spinner {
43
43
  constructor(surface: LiveSurface, theme: Theme, label: SpinnerLabel,
44
44
  /** Injected so tests advance time without waiting for it. */
45
45
  now?: () => number);
46
+ /** Current verb, updated as tools fire so the line names what is in flight. */
47
+ private activity;
46
48
  /** Whether the indicator is running. */
47
49
  get running(): boolean;
48
50
  /**
@@ -58,6 +60,11 @@ export declare class Spinner {
58
60
  pause(): void;
59
61
  /** Stop the indicator: the turn is over, and the next one starts at zero. */
60
62
  stop(): void;
63
+ /**
64
+ * Name what is in flight. The next tick (or this one, if running) shows it.
65
+ * @param verb - e.g. `Reading`, `Running`. Empty restores the default verb.
66
+ */
67
+ setActivity(verb: string): void;
61
68
  /** Paint the current frame. */
62
69
  private draw;
63
70
  }
@@ -78,10 +78,11 @@ export declare function gitBranch(cwd: string): Promise<string | undefined>;
78
78
  * a fresh session reads as short rather than as broken.
79
79
  * @param facts - what to report.
80
80
  * @param theme - styling for the segments.
81
- * @param columns - display columns available; a longer line is cut, never wrapped.
82
- * @returns the line, unstyled when the theme is plain.
83
- */
84
- export declare function statusLine(facts: StatusFacts, theme: Theme, columns: number): string;
81
+ * @param columns - display columns available; a longer line is cut, never
82
+ * wrapped. Omit to keep the full line, so a later paint can re-fit it.
83
+ * @returns the line, unstyled when the theme is plain.
84
+ */
85
+ export declare function statusLine(facts: StatusFacts, theme: Theme, columns?: number): string;
85
86
  /**
86
87
  * Render the fuller readout `/status` answers with.
87
88
  *
@@ -68,6 +68,17 @@ export declare const FOLD_LABELS: {
68
68
  readonly thinking: "thinking";
69
69
  readonly answer: "answer";
70
70
  };
71
+ /**
72
+ * The child session a continuable subagent result names, when the card can
73
+ * open that session.
74
+ *
75
+ * Continuable starts return `started subagent <id>` (and a JSON form with the
76
+ * same id). One-shot background jobs name a job, not a session, and are not a
77
+ * view.
78
+ * @param text - the tool result's visible text.
79
+ * @returns the child session id, or undefined when this result is not a view.
80
+ */
81
+ export declare function childSessionId(text: string): string | undefined;
71
82
  /** A finished answer longer than this many rendered lines becomes a fold. */
72
83
  export declare const ANSWER_FOLD_LINES = 24;
73
84
  /** How many of its head lines a collapsed answer keeps visible. */
@@ -111,6 +122,8 @@ export declare class Transcript {
111
122
  private label;
112
123
  /** The left rule the block {@link render} just returned belongs to. */
113
124
  private rule;
125
+ /** Child session a click on this card should open, when the result names one. */
126
+ private enter;
114
127
  constructor(options: TranscriptOptions, presenters: ToolPresenters);
115
128
  /**
116
129
  * Shorten an absolute path inside the workspace to a workspace-relative one.
@@ -171,6 +184,14 @@ export declare class Transcript {
171
184
  * @returns the label, or `''` when the block has no name of its own.
172
185
  */
173
186
  takeLabel(): string;
187
+ /**
188
+ * The child session the block {@link render} just returned can open.
189
+ *
190
+ * A click on that card enters the child's transcript rather than folding
191
+ * the card. Taken once, like {@link takeFold}.
192
+ * @returns the child session id, or undefined when the card is not a view.
193
+ */
194
+ takeEnter(): string | undefined;
174
195
  /**
175
196
  * The left rule for the block {@link render} just returned, `''` when the
176
197
  * block stands flush.