@isaacriehm/cairn-core 0.10.4 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/hooks/runners/context-threshold.d.ts +10 -42
- package/dist/hooks/runners/context-threshold.js +25 -136
- package/dist/hooks/runners/context-threshold.js.map +1 -1
- package/dist/hooks/runners/index.d.ts +0 -1
- package/dist/hooks/runners/index.js +0 -1
- package/dist/hooks/runners/index.js.map +1 -1
- package/dist/hooks/runners/phase-ready-surface.d.ts +56 -0
- package/dist/hooks/runners/phase-ready-surface.js +134 -0
- package/dist/hooks/runners/phase-ready-surface.js.map +1 -0
- package/dist/hooks/runners/stop.js +57 -32
- package/dist/hooks/runners/stop.js.map +1 -1
- package/dist/hooks/runners/user-prompt-submit.js +29 -7
- package/dist/hooks/runners/user-prompt-submit.js.map +1 -1
- package/dist/mcp/tools/mission-advance.js +9 -0
- package/dist/mcp/tools/mission-advance.js.map +1 -1
- package/dist/mcp/tools/task-complete.js +38 -1
- package/dist/mcp/tools/task-complete.js.map +1 -1
- package/dist/mcp/tools/task-create.js +36 -1
- package/dist/mcp/tools/task-create.js.map +1 -1
- package/dist/missions/task-link.d.ts +3 -0
- package/dist/missions/task-link.js +22 -0
- package/dist/missions/task-link.js.map +1 -1
- package/dist/session-start/build.js +55 -5
- package/dist/session-start/build.js.map +1 -1
- package/dist/status-line/format.d.ts +2 -0
- package/dist/status-line/format.js +5 -5
- package/dist/status-line/format.js.map +1 -1
- package/dist/tasks/lifecycle.d.ts +26 -0
- package/dist/tasks/lifecycle.js +16 -2
- package/dist/tasks/lifecycle.js.map +1 -1
- package/package.json +2 -2
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
* `decision: block` with an instructional reason that prompts main
|
|
9
9
|
* Claude to render the question.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* Single source of truth: Claude Code's statusline payload ships a
|
|
12
|
+
* `context_window` block with `total_tokens` (the active model's
|
|
13
|
+
* window — 200k Sonnet, 1M Opus-1m) + `remaining_percentage`. The
|
|
14
|
+
* statusline hook persists those numbers to
|
|
15
|
+
* `.cairn/sessions/<id>/ctx.json` on every prompt. The Stop hook reads
|
|
16
|
+
* that snapshot — there is no model-keyed fallback and no transcript-
|
|
17
|
+
* usage estimator. If CC omits the block, ctx.json is absent or stale
|
|
18
|
+
* and the threshold check stays silent rather than firing on a guess.
|
|
16
19
|
*
|
|
17
|
-
*
|
|
18
|
-
* — overcounts a little on JSON whitespace, undercounts on unicode-
|
|
19
|
-
* heavy turns. Good enough to fire near the threshold; not a budget
|
|
20
|
-
* check.
|
|
20
|
+
* Threshold defaults to 50 % of CC's reported window.
|
|
21
21
|
*
|
|
22
22
|
* Suppress re-fire within the same session by stamping
|
|
23
23
|
* `.cairn/sessions/<id>/ctx-threshold-warned.json`. Once stamped, the
|
|
@@ -25,54 +25,22 @@
|
|
|
25
25
|
* past the last warning.
|
|
26
26
|
*/
|
|
27
27
|
export interface ContextThresholdInput {
|
|
28
|
-
transcriptPath: string | null;
|
|
29
28
|
repoRoot: string;
|
|
30
29
|
sessionId: string;
|
|
31
|
-
/** Override the model lookup (rarely needed). */
|
|
32
|
-
modelOverride?: string | null;
|
|
33
30
|
/** Override the threshold fraction (default 0.5). */
|
|
34
31
|
thresholdFraction?: number;
|
|
35
|
-
/** Override the window size in tokens (default keyed on model). */
|
|
36
|
-
windowOverride?: number;
|
|
37
32
|
}
|
|
38
33
|
export interface ContextThresholdHit {
|
|
39
34
|
hit: true;
|
|
40
|
-
|
|
35
|
+
usedTokens: number;
|
|
41
36
|
windowTokens: number;
|
|
42
37
|
pct: number;
|
|
43
|
-
model: string;
|
|
44
38
|
taskId: string | null;
|
|
45
39
|
}
|
|
46
40
|
export interface ContextThresholdMiss {
|
|
47
41
|
hit: false;
|
|
48
42
|
}
|
|
49
43
|
export type ContextThresholdResult = ContextThresholdHit | ContextThresholdMiss;
|
|
50
|
-
export declare function modelWindow(model: string): number;
|
|
51
|
-
/**
|
|
52
|
-
* Walk the last ~64 KB of the transcript looking for the most recent
|
|
53
|
-
* `model` field. Claude Code transcript lines are JSON; each assistant
|
|
54
|
-
* turn carries a `message.model` string. Skipping the full file keeps
|
|
55
|
-
* the hook fast on long sessions.
|
|
56
|
-
*/
|
|
57
|
-
export declare function readModelFromTranscript(path: string): string | null;
|
|
58
|
-
/**
|
|
59
|
-
* Fallback estimator when no persisted ctx snapshot is available.
|
|
60
|
-
*
|
|
61
|
-
* Walks the last ~256 KB of the transcript backward looking for the
|
|
62
|
-
* most recent `message.usage` block from an assistant turn. Claude
|
|
63
|
-
* Code transcript lines are JSON; assistant turns carry an exact token
|
|
64
|
-
* count under `message.usage.{input_tokens, cache_creation_input_tokens,
|
|
65
|
-
* cache_read_input_tokens}` — summing those three gives the actual
|
|
66
|
-
* prompt size at that turn (output_tokens are produced, not consumed
|
|
67
|
-
* by context).
|
|
68
|
-
*
|
|
69
|
-
* Returns null when no usage block is found (e.g. fresh session, no
|
|
70
|
-
* assistant turn yet). Caller can choose to skip threshold check or
|
|
71
|
-
* fall back to bytes/4. We never fall back here — bytes/4 over-counts
|
|
72
|
-
* 1.5–2x because the transcript JSONL accumulates every tool I/O blob
|
|
73
|
-
* since session start, which is what produced the 113%-of-window bug.
|
|
74
|
-
*/
|
|
75
|
-
export declare function estimateTokensFromTranscript(transcriptPath: string): number | null;
|
|
76
44
|
/**
|
|
77
45
|
* Returns the current threshold result. Stamps the warned-state file
|
|
78
46
|
* on a hit so re-fires within the same session are suppressed until
|
|
@@ -8,79 +8,31 @@
|
|
|
8
8
|
* `decision: block` with an instructional reason that prompts main
|
|
9
9
|
* Claude to render the question.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* Single source of truth: Claude Code's statusline payload ships a
|
|
12
|
+
* `context_window` block with `total_tokens` (the active model's
|
|
13
|
+
* window — 200k Sonnet, 1M Opus-1m) + `remaining_percentage`. The
|
|
14
|
+
* statusline hook persists those numbers to
|
|
15
|
+
* `.cairn/sessions/<id>/ctx.json` on every prompt. The Stop hook reads
|
|
16
|
+
* that snapshot — there is no model-keyed fallback and no transcript-
|
|
17
|
+
* usage estimator. If CC omits the block, ctx.json is absent or stale
|
|
18
|
+
* and the threshold check stays silent rather than firing on a guess.
|
|
16
19
|
*
|
|
17
|
-
*
|
|
18
|
-
* — overcounts a little on JSON whitespace, undercounts on unicode-
|
|
19
|
-
* heavy turns. Good enough to fire near the threshold; not a budget
|
|
20
|
-
* check.
|
|
20
|
+
* Threshold defaults to 50 % of CC's reported window.
|
|
21
21
|
*
|
|
22
22
|
* Suppress re-fire within the same session by stamping
|
|
23
23
|
* `.cairn/sessions/<id>/ctx-threshold-warned.json`. Once stamped, the
|
|
24
24
|
* threshold prompt re-fires only when usage climbs another +10 %
|
|
25
25
|
* past the last warning.
|
|
26
26
|
*/
|
|
27
|
-
import { existsSync, readFileSync,
|
|
27
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
28
28
|
import { join } from "node:path";
|
|
29
|
-
const MODEL_WINDOW_FALLBACK = 1_000_000;
|
|
30
|
-
export function modelWindow(model) {
|
|
31
|
-
// Opus 4.7 ships with a 1M-token window when the `[1m]` variant is
|
|
32
|
-
// selected; the base alias still resolves to 200k. Treat any model
|
|
33
|
-
// string that contains `1m` (the variant suffix Claude Code prints)
|
|
34
|
-
// as the 1M tier so the statusline percentage reads correctly.
|
|
35
|
-
if (/1m/i.test(model))
|
|
36
|
-
return 1_000_000;
|
|
37
|
-
if (/opus/i.test(model))
|
|
38
|
-
return 1_000_000;
|
|
39
|
-
if (/sonnet/i.test(model))
|
|
40
|
-
return 200_000;
|
|
41
|
-
if (/haiku/i.test(model))
|
|
42
|
-
return 200_000;
|
|
43
|
-
return MODEL_WINDOW_FALLBACK;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Walk the last ~64 KB of the transcript looking for the most recent
|
|
47
|
-
* `model` field. Claude Code transcript lines are JSON; each assistant
|
|
48
|
-
* turn carries a `message.model` string. Skipping the full file keeps
|
|
49
|
-
* the hook fast on long sessions.
|
|
50
|
-
*/
|
|
51
|
-
export function readModelFromTranscript(path) {
|
|
52
|
-
try {
|
|
53
|
-
const stat = statSync(path);
|
|
54
|
-
const tail = Math.min(stat.size, 65_536);
|
|
55
|
-
const fd = readFileSync(path, "utf8");
|
|
56
|
-
const slice = fd.slice(Math.max(0, fd.length - tail));
|
|
57
|
-
const lines = slice.split(/\r?\n/);
|
|
58
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
59
|
-
const line = lines[i];
|
|
60
|
-
if (line === undefined || line.length === 0)
|
|
61
|
-
continue;
|
|
62
|
-
try {
|
|
63
|
-
const obj = JSON.parse(line);
|
|
64
|
-
const m = obj.message?.model;
|
|
65
|
-
if (typeof m === "string" && m.length > 0)
|
|
66
|
-
return m;
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
// skip malformed lines
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
29
|
const CTX_SNAPSHOT_STALE_MS = 5 * 60 * 1000;
|
|
79
30
|
/**
|
|
80
31
|
* Read the latest persisted ctx snapshot from the statusline writer.
|
|
81
32
|
* Statusline runs on every prompt so a fresh snapshot is normally
|
|
82
33
|
* <1s old. Returns null when missing, malformed, or older than 5min
|
|
83
|
-
* (e.g. session crashed, statusline hook misconfigured
|
|
34
|
+
* (e.g. session crashed, statusline hook misconfigured, or CC did
|
|
35
|
+
* not ship a `context_window` block on the last prompt).
|
|
84
36
|
*/
|
|
85
37
|
function readPersistedCtx(repoRoot, sessionId) {
|
|
86
38
|
const path = join(repoRoot, ".cairn", "sessions", sessionId, "ctx.json");
|
|
@@ -92,6 +44,8 @@ function readPersistedCtx(repoRoot, sessionId) {
|
|
|
92
44
|
parsed !== null &&
|
|
93
45
|
typeof parsed.usedPct === "number" &&
|
|
94
46
|
typeof parsed.usedTokens === "number" &&
|
|
47
|
+
typeof parsed.windowTokens === "number" &&
|
|
48
|
+
parsed.windowTokens > 0 &&
|
|
95
49
|
typeof parsed.ts === "number") {
|
|
96
50
|
if (Date.now() - parsed.ts > CTX_SNAPSHOT_STALE_MS)
|
|
97
51
|
return null;
|
|
@@ -103,60 +57,6 @@ function readPersistedCtx(repoRoot, sessionId) {
|
|
|
103
57
|
}
|
|
104
58
|
return null;
|
|
105
59
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Fallback estimator when no persisted ctx snapshot is available.
|
|
108
|
-
*
|
|
109
|
-
* Walks the last ~256 KB of the transcript backward looking for the
|
|
110
|
-
* most recent `message.usage` block from an assistant turn. Claude
|
|
111
|
-
* Code transcript lines are JSON; assistant turns carry an exact token
|
|
112
|
-
* count under `message.usage.{input_tokens, cache_creation_input_tokens,
|
|
113
|
-
* cache_read_input_tokens}` — summing those three gives the actual
|
|
114
|
-
* prompt size at that turn (output_tokens are produced, not consumed
|
|
115
|
-
* by context).
|
|
116
|
-
*
|
|
117
|
-
* Returns null when no usage block is found (e.g. fresh session, no
|
|
118
|
-
* assistant turn yet). Caller can choose to skip threshold check or
|
|
119
|
-
* fall back to bytes/4. We never fall back here — bytes/4 over-counts
|
|
120
|
-
* 1.5–2x because the transcript JSONL accumulates every tool I/O blob
|
|
121
|
-
* since session start, which is what produced the 113%-of-window bug.
|
|
122
|
-
*/
|
|
123
|
-
export function estimateTokensFromTranscript(transcriptPath) {
|
|
124
|
-
try {
|
|
125
|
-
const stat = statSync(transcriptPath);
|
|
126
|
-
const tail = Math.min(stat.size, 262_144);
|
|
127
|
-
const fd = readFileSync(transcriptPath, "utf8");
|
|
128
|
-
const slice = fd.slice(Math.max(0, fd.length - tail));
|
|
129
|
-
const lines = slice.split(/\r?\n/);
|
|
130
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
131
|
-
const line = lines[i];
|
|
132
|
-
if (line === undefined || line.length === 0)
|
|
133
|
-
continue;
|
|
134
|
-
try {
|
|
135
|
-
const obj = JSON.parse(line);
|
|
136
|
-
const u = obj.message?.usage;
|
|
137
|
-
if (u === undefined)
|
|
138
|
-
continue;
|
|
139
|
-
const i_t = typeof u.input_tokens === "number" ? u.input_tokens : 0;
|
|
140
|
-
const cc = typeof u.cache_creation_input_tokens === "number"
|
|
141
|
-
? u.cache_creation_input_tokens
|
|
142
|
-
: 0;
|
|
143
|
-
const cr = typeof u.cache_read_input_tokens === "number"
|
|
144
|
-
? u.cache_read_input_tokens
|
|
145
|
-
: 0;
|
|
146
|
-
const total = i_t + cc + cr;
|
|
147
|
-
if (total > 0)
|
|
148
|
-
return total;
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
// skip malformed lines
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
60
|
function warnedStatePath(repoRoot, sessionId) {
|
|
161
61
|
return join(repoRoot, ".cairn", "sessions", sessionId, "ctx-threshold-warned.json");
|
|
162
62
|
}
|
|
@@ -192,39 +92,28 @@ function writeWarned(repoRoot, sessionId, state) {
|
|
|
192
92
|
* usage climbs another +10 % of the window.
|
|
193
93
|
*/
|
|
194
94
|
export function checkContextThreshold(input) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
if (!existsSync(input.transcriptPath))
|
|
95
|
+
const snapshot = readPersistedCtx(input.repoRoot, input.sessionId);
|
|
96
|
+
if (snapshot === null)
|
|
199
97
|
return { hit: false };
|
|
200
|
-
const
|
|
201
|
-
const windowTokens = input.windowOverride ?? modelWindow(model);
|
|
98
|
+
const windowTokens = snapshot.windowTokens;
|
|
202
99
|
const fraction = input.thresholdFraction ?? 0.5;
|
|
203
100
|
const thresholdTokens = Math.floor(windowTokens * fraction);
|
|
204
|
-
|
|
205
|
-
const fallback = estimateTokensFromTranscript(input.transcriptPath);
|
|
206
|
-
// Skip the check when neither source is available — better to stay
|
|
207
|
-
// silent than fire on bytes/4 garbage like we used to.
|
|
208
|
-
if (snapshot === null && fallback === null)
|
|
209
|
-
return { hit: false };
|
|
210
|
-
const estimated = snapshot !== null ? snapshot.usedTokens : (fallback ?? 0);
|
|
211
|
-
if (estimated < thresholdTokens)
|
|
101
|
+
if (snapshot.usedTokens < thresholdTokens)
|
|
212
102
|
return { hit: false };
|
|
213
103
|
const warned = readWarned(input.repoRoot, input.sessionId);
|
|
214
104
|
const reFireSlackTokens = Math.floor(windowTokens * 0.1);
|
|
215
|
-
if (warned !== null &&
|
|
105
|
+
if (warned !== null && snapshot.usedTokens < warned.warned_at_tokens + reFireSlackTokens) {
|
|
216
106
|
return { hit: false };
|
|
217
107
|
}
|
|
218
108
|
writeWarned(input.repoRoot, input.sessionId, {
|
|
219
109
|
ts: Date.now(),
|
|
220
|
-
warned_at_tokens:
|
|
110
|
+
warned_at_tokens: snapshot.usedTokens,
|
|
221
111
|
});
|
|
222
112
|
return {
|
|
223
113
|
hit: true,
|
|
224
|
-
|
|
114
|
+
usedTokens: snapshot.usedTokens,
|
|
225
115
|
windowTokens,
|
|
226
|
-
pct: Math.min(100, Math.round((
|
|
227
|
-
model,
|
|
116
|
+
pct: Math.min(100, Math.round((snapshot.usedTokens / windowTokens) * 100)),
|
|
228
117
|
taskId: null,
|
|
229
118
|
};
|
|
230
119
|
}
|
|
@@ -242,7 +131,7 @@ export function renderContextThresholdHint(hit, taskId) {
|
|
|
242
131
|
const header = [
|
|
243
132
|
`## Cairn — context threshold reached`,
|
|
244
133
|
"",
|
|
245
|
-
|
|
134
|
+
`**${hit.usedTokens.toLocaleString()} / ${hit.windowTokens.toLocaleString()} tokens (${hit.pct}%)** in use. Trust degrades as context climbs — best to compact now.`,
|
|
246
135
|
"",
|
|
247
136
|
];
|
|
248
137
|
if (taskId === null) {
|
|
@@ -252,7 +141,7 @@ export function renderContextThresholdHint(hit, taskId) {
|
|
|
252
141
|
"",
|
|
253
142
|
"Render this question via the `AskUserQuestion` tool — do not skip:",
|
|
254
143
|
"",
|
|
255
|
-
"> Context at " + hit.pct + "% of
|
|
144
|
+
"> Context at " + hit.pct + "% of window. Pick:",
|
|
256
145
|
"> ",
|
|
257
146
|
"> - `[a]` keep going (warn re-fires every +10 %)",
|
|
258
147
|
"> - `[b]` `/clear` and start fresh (no task to resume)",
|
|
@@ -266,7 +155,7 @@ export function renderContextThresholdHint(hit, taskId) {
|
|
|
266
155
|
"",
|
|
267
156
|
"Render this question via the `AskUserQuestion` tool — do not skip:",
|
|
268
157
|
"",
|
|
269
|
-
"> Context at " + hit.pct + "% of
|
|
158
|
+
"> Context at " + hit.pct + "% of window. Pick:",
|
|
270
159
|
"> ",
|
|
271
160
|
"> - `[a]` keep going (warn re-fires every +10 %)",
|
|
272
161
|
"> - `[b]` `/clear` and resume now (Cairn writes the resume prompt)",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-threshold.js","sourceRoot":"","sources":["../../../src/hooks/runners/context-threshold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"context-threshold.js","sourceRoot":"","sources":["../../../src/hooks/runners/context-threshold.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
export { runSessionStartHook } from "./session-start.js";
|
|
7
7
|
export { runSessionEndHook } from "./session-end.js";
|
|
8
8
|
export { runStopHook } from "./stop.js";
|
|
9
|
-
export { estimateTokensFromTranscript, modelWindow, readModelFromTranscript, } from "./context-threshold.js";
|
|
10
9
|
export { runUserPromptSubmitHook } from "./user-prompt-submit.js";
|
|
11
10
|
export { runGcAutotriggerCheck } from "./gc-autotrigger.js";
|
|
12
11
|
export type { GcAutotriggerArgv, GcAutotriggerOptions, GcAutotriggerReason, GcAutotriggerResult, } from "./gc-autotrigger.js";
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
export { runSessionStartHook } from "./session-start.js";
|
|
7
7
|
export { runSessionEndHook } from "./session-end.js";
|
|
8
8
|
export { runStopHook } from "./stop.js";
|
|
9
|
-
export { estimateTokensFromTranscript, modelWindow, readModelFromTranscript, } from "./context-threshold.js";
|
|
10
9
|
export { runUserPromptSubmitHook } from "./user-prompt-submit.js";
|
|
11
10
|
export { runGcAutotriggerCheck } from "./gc-autotrigger.js";
|
|
12
11
|
export { renderBypassHint, scanBypassedCommits, } from "../bypass-detection.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/runners/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/runners/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAO5D,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,eAAe,GAChB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase-ready surface relocation.
|
|
3
|
+
*
|
|
4
|
+
* `phase-ready-to-exit` events used to inject straight into the Stop
|
|
5
|
+
* hook's `decision: block` reason. Claude Code labels every Stop
|
|
6
|
+
* decision-block as "Stop hook error" in the operator UI — even when
|
|
7
|
+
* the block carries informational Cairn context — which the operator
|
|
8
|
+
* reads as a real failure. That visual signal is what we wanted to
|
|
9
|
+
* avoid, so the surface moved off Stop entirely:
|
|
10
|
+
*
|
|
11
|
+
* - Stop hook collects phase-ready hints from drained events as
|
|
12
|
+
* before, but writes them to
|
|
13
|
+
* `.cairn/sessions/<id>/phase-ready-pending.json` instead of
|
|
14
|
+
* emitting `decision: block`. If no other surface is pending,
|
|
15
|
+
* Stop returns `{continue: true}` and the operator sees no
|
|
16
|
+
* banner.
|
|
17
|
+
* - UserPromptSubmit hook, fired on the next operator prompt,
|
|
18
|
+
* reads the pending file, renders it as `additionalContext`,
|
|
19
|
+
* and deletes the file. Claude Code stitches the
|
|
20
|
+
* `additionalContext` straight into the model's next turn — no
|
|
21
|
+
* red banner, no `decision: block`.
|
|
22
|
+
*
|
|
23
|
+
* Idempotency for the phase-ready emission itself is handled upstream
|
|
24
|
+
* in `task-link.ts` via the per-phase `ready_emitted` flag on
|
|
25
|
+
* `phase_progress`. This module is purely the Stop → UPS shuttle.
|
|
26
|
+
*/
|
|
27
|
+
export interface PhaseReadyHint {
|
|
28
|
+
mission_id: string;
|
|
29
|
+
mission_title: string;
|
|
30
|
+
phase_id: string;
|
|
31
|
+
phase_title: string;
|
|
32
|
+
exit_criteria: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Persist the hints the Stop hook collected so the next UPS hook can
|
|
36
|
+
* inject them as additionalContext. Overwrites any prior pending file
|
|
37
|
+
* for the session — the latest hint set wins (the operator hasn't
|
|
38
|
+
* seen the old one yet so there's nothing to merge).
|
|
39
|
+
*/
|
|
40
|
+
export declare function writePhaseReadyPending(repoRoot: string, sessionId: string, hints: PhaseReadyHint[]): void;
|
|
41
|
+
/**
|
|
42
|
+
* Read the pending file and delete it in one shot — UPS hook semantics
|
|
43
|
+
* are "show once, then forget". When the file is missing or malformed
|
|
44
|
+
* returns null so the caller can no-op cleanly.
|
|
45
|
+
*/
|
|
46
|
+
export declare function readAndConsumePhaseReadyPending(repoRoot: string, sessionId: string): PhaseReadyHint[] | null;
|
|
47
|
+
/**
|
|
48
|
+
* Render the operator-facing phase-ready prompt. Keeps option labels
|
|
49
|
+
* in plain English — no MCP tool-call syntax, no internal ids exposed
|
|
50
|
+
* raw — and instructs the model to surface via `AskUserQuestion`.
|
|
51
|
+
*
|
|
52
|
+
* The mission/phase ids stay in the body for traceability but the
|
|
53
|
+
* AskUserQuestion option labels themselves are human-readable phase
|
|
54
|
+
* titles, not phase ids.
|
|
55
|
+
*/
|
|
56
|
+
export declare function renderPhaseReadyHint(hints: PhaseReadyHint[]): string;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase-ready surface relocation.
|
|
3
|
+
*
|
|
4
|
+
* `phase-ready-to-exit` events used to inject straight into the Stop
|
|
5
|
+
* hook's `decision: block` reason. Claude Code labels every Stop
|
|
6
|
+
* decision-block as "Stop hook error" in the operator UI — even when
|
|
7
|
+
* the block carries informational Cairn context — which the operator
|
|
8
|
+
* reads as a real failure. That visual signal is what we wanted to
|
|
9
|
+
* avoid, so the surface moved off Stop entirely:
|
|
10
|
+
*
|
|
11
|
+
* - Stop hook collects phase-ready hints from drained events as
|
|
12
|
+
* before, but writes them to
|
|
13
|
+
* `.cairn/sessions/<id>/phase-ready-pending.json` instead of
|
|
14
|
+
* emitting `decision: block`. If no other surface is pending,
|
|
15
|
+
* Stop returns `{continue: true}` and the operator sees no
|
|
16
|
+
* banner.
|
|
17
|
+
* - UserPromptSubmit hook, fired on the next operator prompt,
|
|
18
|
+
* reads the pending file, renders it as `additionalContext`,
|
|
19
|
+
* and deletes the file. Claude Code stitches the
|
|
20
|
+
* `additionalContext` straight into the model's next turn — no
|
|
21
|
+
* red banner, no `decision: block`.
|
|
22
|
+
*
|
|
23
|
+
* Idempotency for the phase-ready emission itself is handled upstream
|
|
24
|
+
* in `task-link.ts` via the per-phase `ready_emitted` flag on
|
|
25
|
+
* `phase_progress`. This module is purely the Stop → UPS shuttle.
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
28
|
+
import { dirname, join } from "node:path";
|
|
29
|
+
function pendingPath(repoRoot, sessionId) {
|
|
30
|
+
return join(repoRoot, ".cairn", "sessions", sessionId, "phase-ready-pending.json");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Persist the hints the Stop hook collected so the next UPS hook can
|
|
34
|
+
* inject them as additionalContext. Overwrites any prior pending file
|
|
35
|
+
* for the session — the latest hint set wins (the operator hasn't
|
|
36
|
+
* seen the old one yet so there's nothing to merge).
|
|
37
|
+
*/
|
|
38
|
+
export function writePhaseReadyPending(repoRoot, sessionId, hints) {
|
|
39
|
+
if (hints.length === 0)
|
|
40
|
+
return;
|
|
41
|
+
const path = pendingPath(repoRoot, sessionId);
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
44
|
+
const payload = {
|
|
45
|
+
ts: new Date().toISOString(),
|
|
46
|
+
session_id: sessionId,
|
|
47
|
+
hints,
|
|
48
|
+
};
|
|
49
|
+
writeFileSync(path, JSON.stringify(payload, null, 2), "utf8");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// best-effort
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read the pending file and delete it in one shot — UPS hook semantics
|
|
57
|
+
* are "show once, then forget". When the file is missing or malformed
|
|
58
|
+
* returns null so the caller can no-op cleanly.
|
|
59
|
+
*/
|
|
60
|
+
export function readAndConsumePhaseReadyPending(repoRoot, sessionId) {
|
|
61
|
+
const path = pendingPath(repoRoot, sessionId);
|
|
62
|
+
if (!existsSync(path))
|
|
63
|
+
return null;
|
|
64
|
+
let raw;
|
|
65
|
+
try {
|
|
66
|
+
raw = readFileSync(path, "utf8");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(raw);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
try {
|
|
77
|
+
unlinkSync(path);
|
|
78
|
+
}
|
|
79
|
+
catch { /* ignore */ }
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
83
|
+
try {
|
|
84
|
+
unlinkSync(path);
|
|
85
|
+
}
|
|
86
|
+
catch { /* ignore */ }
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const p = parsed;
|
|
90
|
+
if (!Array.isArray(p.hints) || p.hints.length === 0) {
|
|
91
|
+
try {
|
|
92
|
+
unlinkSync(path);
|
|
93
|
+
}
|
|
94
|
+
catch { /* ignore */ }
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
unlinkSync(path);
|
|
99
|
+
}
|
|
100
|
+
catch { /* ignore */ }
|
|
101
|
+
return p.hints;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Render the operator-facing phase-ready prompt. Keeps option labels
|
|
105
|
+
* in plain English — no MCP tool-call syntax, no internal ids exposed
|
|
106
|
+
* raw — and instructs the model to surface via `AskUserQuestion`.
|
|
107
|
+
*
|
|
108
|
+
* The mission/phase ids stay in the body for traceability but the
|
|
109
|
+
* AskUserQuestion option labels themselves are human-readable phase
|
|
110
|
+
* titles, not phase ids.
|
|
111
|
+
*/
|
|
112
|
+
export function renderPhaseReadyHint(hints) {
|
|
113
|
+
const h = hints[0];
|
|
114
|
+
if (h === undefined)
|
|
115
|
+
return "";
|
|
116
|
+
const lines = [];
|
|
117
|
+
lines.push(`## Cairn — phase ready to exit`);
|
|
118
|
+
lines.push("");
|
|
119
|
+
lines.push(`**Mission:** ${h.mission_title}`);
|
|
120
|
+
lines.push(`**Phase:** ${h.phase_title}`);
|
|
121
|
+
lines.push("");
|
|
122
|
+
lines.push(`Exit criteria: ${h.exit_criteria}`);
|
|
123
|
+
lines.push("");
|
|
124
|
+
lines.push("Surface this question to the operator via `AskUserQuestion`. Do NOT call `cairn_mission_advance` yourself — the operator's answer is the only valid input.");
|
|
125
|
+
lines.push("");
|
|
126
|
+
lines.push(`> Phase \`${h.phase_title}\` looks done. Move on?`);
|
|
127
|
+
lines.push(">");
|
|
128
|
+
lines.push("> - `[a]` Mark phase done, advance to next phase");
|
|
129
|
+
lines.push("> - `[b]` Keep working on this phase");
|
|
130
|
+
lines.push("");
|
|
131
|
+
lines.push(`On \`[a]\`, call \`cairn_mission_advance({phase_id: "${h.phase_id}", choice: "exit"})\`. On \`[b]\`, call \`cairn_mission_advance({phase_id: "${h.phase_id}", choice: "not_yet"})\`.`);
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=phase-ready-surface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase-ready-surface.js","sourceRoot":"","sources":["../../../src/hooks/runners/phase-ready-surface.ts"],"names":[],"mappings":"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"}
|