@isaacriehm/cairn-core 0.13.3 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/gc/entity-orphan.d.ts +55 -0
  3. package/dist/gc/entity-orphan.js +171 -0
  4. package/dist/gc/entity-orphan.js.map +1 -0
  5. package/dist/gc/index.d.ts +4 -0
  6. package/dist/gc/index.js +2 -0
  7. package/dist/gc/index.js.map +1 -1
  8. package/dist/gc/retire.d.ts +57 -0
  9. package/dist/gc/retire.js +189 -0
  10. package/dist/gc/retire.js.map +1 -0
  11. package/dist/gc/sweep.js +11 -0
  12. package/dist/gc/sweep.js.map +1 -1
  13. package/dist/gc/types.d.ts +2 -2
  14. package/dist/hooks/ask-user-blocked.d.ts +8 -0
  15. package/dist/hooks/ask-user-blocked.js +13 -0
  16. package/dist/hooks/ask-user-blocked.js.map +1 -0
  17. package/dist/hooks/post-tool-use/ask-user-blocked.d.ts +18 -0
  18. package/dist/hooks/post-tool-use/ask-user-blocked.js +113 -0
  19. package/dist/hooks/post-tool-use/ask-user-blocked.js.map +1 -0
  20. package/dist/hooks/post-tool-use/index.d.ts +1 -0
  21. package/dist/hooks/post-tool-use/index.js +1 -0
  22. package/dist/hooks/post-tool-use/index.js.map +1 -1
  23. package/dist/hooks/runners/context-threshold.js +2 -2
  24. package/dist/hooks/runners/context-threshold.js.map +1 -1
  25. package/dist/hooks/runners/gc-autotrigger.d.ts +7 -4
  26. package/dist/hooks/runners/gc-autotrigger.js +7 -4
  27. package/dist/hooks/runners/gc-autotrigger.js.map +1 -1
  28. package/dist/hooks/runners/stop.js +173 -90
  29. package/dist/hooks/runners/stop.js.map +1 -1
  30. package/dist/init/ingest-docs.d.ts +2 -4
  31. package/dist/init/ingest-docs.js +2 -4
  32. package/dist/init/ingest-docs.js.map +1 -1
  33. package/dist/mcp/errors.d.ts +1 -1
  34. package/dist/mcp/errors.js.map +1 -1
  35. package/dist/mcp/schemas.d.ts +7 -94
  36. package/dist/mcp/schemas.js +23 -87
  37. package/dist/mcp/schemas.js.map +1 -1
  38. package/dist/mcp/serve.js +8 -6
  39. package/dist/mcp/serve.js.map +1 -1
  40. package/dist/mcp/tools/index.js +6 -29
  41. package/dist/mcp/tools/index.js.map +1 -1
  42. package/dist/mcp/tools/retire-entity.d.ts +22 -0
  43. package/dist/mcp/tools/retire-entity.js +62 -0
  44. package/dist/mcp/tools/retire-entity.js.map +1 -0
  45. package/dist/mcp/tools/task-complete.js +7 -19
  46. package/dist/mcp/tools/task-complete.js.map +1 -1
  47. package/dist/mcp/tools/task-create.d.ts +0 -1
  48. package/dist/mcp/tools/task-create.js +31 -10
  49. package/dist/mcp/tools/task-create.js.map +1 -1
  50. package/dist/mcp/tools/task-journal-append.js +23 -2
  51. package/dist/mcp/tools/task-journal-append.js.map +1 -1
  52. package/dist/session-start/build.d.ts +26 -0
  53. package/dist/session-start/build.js +30 -0
  54. package/dist/session-start/build.js.map +1 -1
  55. package/dist/session-start/index.d.ts +1 -1
  56. package/dist/session-start/index.js +1 -1
  57. package/dist/session-start/index.js.map +1 -1
  58. package/dist/tasks/index.d.ts +2 -2
  59. package/dist/tasks/index.js +1 -1
  60. package/dist/tasks/index.js.map +1 -1
  61. package/dist/tasks/lifecycle.d.ts +14 -22
  62. package/dist/tasks/lifecycle.js +14 -47
  63. package/dist/tasks/lifecycle.js.map +1 -1
  64. package/package.json +2 -2
  65. package/dist/mcp/tools/align-drain.d.ts +0 -7
  66. package/dist/mcp/tools/align-drain.js +0 -26
  67. package/dist/mcp/tools/align-drain.js.map +0 -1
  68. package/dist/mcp/tools/archive.d.ts +0 -8
  69. package/dist/mcp/tools/archive.js +0 -72
  70. package/dist/mcp/tools/archive.js.map +0 -1
  71. package/dist/mcp/tools/attention-restore.d.ts +0 -14
  72. package/dist/mcp/tools/attention-restore.js +0 -22
  73. package/dist/mcp/tools/attention-restore.js.map +0 -1
  74. package/dist/mcp/tools/decisions-for-symbol.d.ts +0 -7
  75. package/dist/mcp/tools/decisions-for-symbol.js +0 -42
  76. package/dist/mcp/tools/decisions-for-symbol.js.map +0 -1
  77. package/dist/mcp/tools/get-full.d.ts +0 -7
  78. package/dist/mcp/tools/get-full.js +0 -46
  79. package/dist/mcp/tools/get-full.js.map +0 -1
  80. package/dist/mcp/tools/ground-get.d.ts +0 -7
  81. package/dist/mcp/tools/ground-get.js +0 -77
  82. package/dist/mcp/tools/ground-get.js.map +0 -1
  83. package/dist/mcp/tools/mission-close.d.ts +0 -8
  84. package/dist/mcp/tools/mission-close.js +0 -53
  85. package/dist/mcp/tools/mission-close.js.map +0 -1
  86. package/dist/mcp/tools/mission-reopen.d.ts +0 -13
  87. package/dist/mcp/tools/mission-reopen.js +0 -56
  88. package/dist/mcp/tools/mission-reopen.js.map +0 -1
  89. package/dist/mcp/tools/reject-candidate.d.ts +0 -24
  90. package/dist/mcp/tools/reject-candidate.js +0 -71
  91. package/dist/mcp/tools/reject-candidate.js.map +0 -1
  92. package/dist/mcp/tools/search-candidates.d.ts +0 -20
  93. package/dist/mcp/tools/search-candidates.js +0 -93
  94. package/dist/mcp/tools/search-candidates.js.map +0 -1
  95. package/dist/mcp/tools/supersedes-chain.d.ts +0 -6
  96. package/dist/mcp/tools/supersedes-chain.js +0 -66
  97. package/dist/mcp/tools/supersedes-chain.js.map +0 -1
  98. package/dist/mcp/tools/timeline.d.ts +0 -9
  99. package/dist/mcp/tools/timeline.js +0 -61
  100. package/dist/mcp/tools/timeline.js.map +0 -1
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PostToolUse hook on `AskUserQuestion` — auto-stamps the current active
3
+ * task with `blocked_on: operator` in `.cairn/tasks/active/<id>/status.yaml`
4
+ * so the Stop hook's stalled-task scanner skips it (the work can't
5
+ * progress until the operator answers).
6
+ *
7
+ * Wired into `bug-mine 0.13.8` / Phase 5. Producer pair for the
8
+ * existing `blocked_on: operator` skip rule in
9
+ * `runners/stop.ts:scanStalledRunningTasks`.
10
+ *
11
+ * No-op when:
12
+ * - `tool_name !== "AskUserQuestion"` (manifest matcher narrows but
13
+ * defense-in-depth).
14
+ * - No active task in `.cairn/tasks/active/`.
15
+ * - status.yaml missing or unparseable.
16
+ * - `blocked_on: operator` already present (idempotent).
17
+ */
18
+ export declare function runAskUserBlockedHook(): Promise<void>;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * PostToolUse hook on `AskUserQuestion` — auto-stamps the current active
3
+ * task with `blocked_on: operator` in `.cairn/tasks/active/<id>/status.yaml`
4
+ * so the Stop hook's stalled-task scanner skips it (the work can't
5
+ * progress until the operator answers).
6
+ *
7
+ * Wired into `bug-mine 0.13.8` / Phase 5. Producer pair for the
8
+ * existing `blocked_on: operator` skip rule in
9
+ * `runners/stop.ts:scanStalledRunningTasks`.
10
+ *
11
+ * No-op when:
12
+ * - `tool_name !== "AskUserQuestion"` (manifest matcher narrows but
13
+ * defense-in-depth).
14
+ * - No active task in `.cairn/tasks/active/`.
15
+ * - status.yaml missing or unparseable.
16
+ * - `blocked_on: operator` already present (idempotent).
17
+ */
18
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { z } from "zod";
21
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
22
+ import { resolveRepoRoot } from "../../session-start/index.js";
23
+ import { findCurrentActiveTask } from "../../tasks/index.js";
24
+ import { readHookStdin } from "../runners/payload.js";
25
+ import { logger } from "../../logger.js";
26
+ const log = logger("hooks.post-tool-use.ask-user-blocked");
27
+ const PayloadSchema = z
28
+ .object({
29
+ session_id: z.string().optional(),
30
+ transcript_path: z.string().optional(),
31
+ cwd: z.string().optional(),
32
+ tool_name: z.string().optional(),
33
+ })
34
+ .passthrough();
35
+ function parsePayload(text) {
36
+ if (text.trim().length === 0)
37
+ return {};
38
+ try {
39
+ const raw = JSON.parse(text);
40
+ const result = PayloadSchema.safeParse(raw);
41
+ return result.success ? result.data : {};
42
+ }
43
+ catch {
44
+ return {};
45
+ }
46
+ }
47
+ function emitShapeB(additionalContext) {
48
+ const out = {
49
+ continue: true,
50
+ hookSpecificOutput: {
51
+ hookEventName: "PostToolUse",
52
+ additionalContext,
53
+ },
54
+ };
55
+ process.stdout.write(JSON.stringify(out));
56
+ process.stdout.write("\n");
57
+ }
58
+ export async function runAskUserBlockedHook() {
59
+ try {
60
+ const raw = await readHookStdin();
61
+ const payload = parsePayload(raw);
62
+ if (payload.tool_name !== "AskUserQuestion") {
63
+ emitShapeB("");
64
+ return;
65
+ }
66
+ const cwd = payload.cwd ?? process.cwd();
67
+ const repoRoot = resolveRepoRoot(cwd);
68
+ if (repoRoot === null) {
69
+ emitShapeB("");
70
+ return;
71
+ }
72
+ const taskId = findCurrentActiveTask(repoRoot);
73
+ if (taskId === null) {
74
+ emitShapeB("");
75
+ return;
76
+ }
77
+ const statusPath = join(repoRoot, ".cairn", "tasks", "active", taskId, "status.yaml");
78
+ if (!existsSync(statusPath)) {
79
+ emitShapeB("");
80
+ return;
81
+ }
82
+ let parsed;
83
+ try {
84
+ parsed = parseYaml(readFileSync(statusPath, "utf8"));
85
+ }
86
+ catch {
87
+ emitShapeB("");
88
+ return;
89
+ }
90
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
91
+ emitShapeB("");
92
+ return;
93
+ }
94
+ const status = parsed;
95
+ if (status["blocked_on"] === "operator") {
96
+ emitShapeB("");
97
+ return;
98
+ }
99
+ status["blocked_on"] = "operator";
100
+ try {
101
+ writeFileSync(statusPath, stringifyYaml(status), "utf8");
102
+ }
103
+ catch (err) {
104
+ log.warn({ err: err instanceof Error ? err.message : String(err), taskId }, "ask-user-blocked: write to status.yaml failed");
105
+ }
106
+ emitShapeB("");
107
+ }
108
+ catch (err) {
109
+ log.warn({ err: err instanceof Error ? err.message : String(err) }, "ask-user-blocked hook failed; degrading to no-op");
110
+ emitShapeB("");
111
+ }
112
+ }
113
+ //# sourceMappingURL=ask-user-blocked.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask-user-blocked.js","sourceRoot":"","sources":["../../../src/hooks/post-tool-use/ask-user-blocked.ts"],"names":[],"mappings":"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"}
@@ -18,4 +18,5 @@ export type { CopySafetyConfig } from "./allowlist-reader.js";
18
18
  export { alignFile, runSotAlign, executeSotAlign } from "./sot-align.js";
19
19
  export type { AlignFileArgs, AlignFileResult, CreationVerdict, DedupVerdict, } from "./sot-align.js";
20
20
  export { runPostWriteHook } from "./post-write.js";
21
+ export { runAskUserBlockedHook } from "./ask-user-blocked.js";
21
22
  export { containsEssayClassShape, isMarkdownPath } from "../sot-align-common.js";
@@ -13,5 +13,6 @@ export { scanForCopyLeakage } from "./copy-scanner.js";
13
13
  export { readCopySafetyConfig } from "./allowlist-reader.js";
14
14
  export { alignFile, runSotAlign, executeSotAlign } from "./sot-align.js";
15
15
  export { runPostWriteHook } from "./post-write.js";
16
+ export { runAskUserBlockedHook } from "./ask-user-blocked.js";
16
17
  export { containsEssayClassShape, isMarkdownPath } from "../sot-align-common.js";
17
18
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/post-tool-use/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAOzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/post-tool-use/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAOzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC"}
@@ -139,7 +139,7 @@ export function renderContextThresholdHint(hit, taskId) {
139
139
  ...header,
140
140
  "No active task — context climbed through general work, or the active task just graduated this tick. Nothing to resume from.",
141
141
  "",
142
- "Render this question via the `AskUserQuestion` tool do not skip:",
142
+ "When you reach a stopping point, surface to operator (e.g. via `AskUserQuestion`); if mid-flow, finish first:",
143
143
  "",
144
144
  "> Context at " + hit.pct + "% of window. Pick:",
145
145
  "> ",
@@ -153,7 +153,7 @@ export function renderContextThresholdHint(hit, taskId) {
153
153
  ...header,
154
154
  `Active task: \`${taskId}\`.`,
155
155
  "",
156
- "Render this question via the `AskUserQuestion` tool do not skip:",
156
+ "When you reach a stopping point, surface to operator (e.g. via `AskUserQuestion`); if mid-flow, finish first:",
157
157
  "",
158
158
  "> Context at " + hit.pct + "% of window. Pick:",
159
159
  "> ",
@@ -1 +1 @@
1
- {"version":3,"file":"context-threshold.js","sourceRoot":"","sources":["../../../src/hooks/runners/context-threshold.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context-threshold.js","sourceRoot":"","sources":["../../../src/hooks/runners/context-threshold.ts"],"names":[],"mappings":"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"}
@@ -8,10 +8,13 @@
8
8
  * wait for the spawned process — it stamps the marker, fires the spawn,
9
9
  * unrefs, and returns.
10
10
  *
11
- * `cairn gc sweep` is sweep-only (no commit). The findings flow through
12
- * `cairn-attention` for operator triage. `cairn gc run --apply-classes`
13
- * stays operator-driven until the autotrigger is proven safe in the
14
- * field.
11
+ * The spawned `cairn gc sweep` surfaces all detection passes through
12
+ * `cairn-attention` for operator triage (no commit). Because the spawn
13
+ * carries `CAIRN_GC_AUTOTRIGGERED=1`, that same process additionally
14
+ * auto-retires the SAFE entity-orphan subset (`runEntityRetire({ apply })`,
15
+ * canary-gated, rolled back on failure) — the one autonomous mutation in
16
+ * the daily tick. Every other pass's proposals stay operator-driven via
17
+ * `cairn gc run --apply-classes` until proven safe in the field.
15
18
  */
16
19
  export interface GcAutotriggerOptions {
17
20
  repoRoot: string;
@@ -8,10 +8,13 @@
8
8
  * wait for the spawned process — it stamps the marker, fires the spawn,
9
9
  * unrefs, and returns.
10
10
  *
11
- * `cairn gc sweep` is sweep-only (no commit). The findings flow through
12
- * `cairn-attention` for operator triage. `cairn gc run --apply-classes`
13
- * stays operator-driven until the autotrigger is proven safe in the
14
- * field.
11
+ * The spawned `cairn gc sweep` surfaces all detection passes through
12
+ * `cairn-attention` for operator triage (no commit). Because the spawn
13
+ * carries `CAIRN_GC_AUTOTRIGGERED=1`, that same process additionally
14
+ * auto-retires the SAFE entity-orphan subset (`runEntityRetire({ apply })`,
15
+ * canary-gated, rolled back on failure) — the one autonomous mutation in
16
+ * the daily tick. Every other pass's proposals stay operator-driven via
17
+ * `cairn gc run --apply-classes` until proven safe in the field.
15
18
  */
16
19
  import { spawn } from "node:child_process";
17
20
  import { existsSync, readFileSync } from "node:fs";
@@ -1 +1 @@
1
- {"version":3,"file":"gc-autotrigger.js","sourceRoot":"","sources":["../../../src/hooks/runners/gc-autotrigger.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gc-autotrigger.js","sourceRoot":"","sources":["../../../src/hooks/runners/gc-autotrigger.ts"],"names":[],"mappings":"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"}
@@ -22,7 +22,7 @@ import { isDeferActive, readDeferState } from "../defer.js";
22
22
  import { resolveRepoRoot } from "../../session-start/index.js";
23
23
  import { readEventsMarker, stampEventsPoll, } from "../../session/index.js";
24
24
  import { writeStatusJson } from "../../status-line/index.js";
25
- import { completeTask, findCurrentActiveTask, readTaskAttestationState, transitionTaskPhase, } from "../../tasks/index.js";
25
+ import { completeTask, findCurrentActiveTask, readTaskAttestationState, } from "../../tasks/index.js";
26
26
  import { effectivePhaseExitGate, findActiveMission, readMissionState, readRoadmap, } from "@isaacriehm/cairn-state";
27
27
  import { checkContextThreshold, renderContextThresholdHint, } from "./context-threshold.js";
28
28
  import { writePhaseReadyPending, } from "./phase-ready-surface.js";
@@ -66,7 +66,7 @@ const MAX_REASON_CHARS = 4_000;
66
66
  * CC convention, not a failure signal. One short line keeps the chat
67
67
  * tidy without dropping the cue entirely.
68
68
  */
69
- const REASON_PREAMBLE = "↳ Cairn cuerender any choice via `AskUserQuestion`.\n\n";
69
+ const REASON_PREAMBLE = "↳ Cairn hintsurface to operator at a natural stopping point. Don't interrupt productive work; quote / `AskUserQuestion` only when there's no obvious continuation.\n\n";
70
70
  function clampReason(body) {
71
71
  if (body.length === 0)
72
72
  return body;
@@ -85,6 +85,96 @@ function clampReason(body) {
85
85
  * the window or whenever the payload hash changes.
86
86
  */
87
87
  const CUE_DEBOUNCE_WINDOW_MS = 60 * 60 * 1000;
88
+ /**
89
+ * Bug-mine 0.13.8 — Phase 5 stall-cue tuning.
90
+ *
91
+ * `SESSION_ACTIVITY_WINDOW_MS` — when the transcript records any
92
+ * `tool_use` within this window, the AI is actively working; suppress
93
+ * the stalled-task surface so the cue doesn't interrupt productive
94
+ * flow. Mining showed stall cues firing while an Agent subagent was
95
+ * mid-dispatch and a research run was committing.
96
+ *
97
+ * `SESSION_STALLED_CUE_WINDOW_MS` — at most one stalled cue per
98
+ * session per hour, total (not per-task). Per-task throttle
99
+ * (`STALLED_FIRE_WINDOW_MS`) remains as a floor on top.
100
+ */
101
+ const SESSION_ACTIVITY_WINDOW_MS = 5 * 60 * 1000;
102
+ const SESSION_STALLED_CUE_WINDOW_MS = 60 * 60 * 1000;
103
+ /**
104
+ * Walk the transcript tail and return the millisecond age of the most
105
+ * recent `tool_use` entry whose timestamp parses, or `null` when no
106
+ * such entry is found. Best-effort — any read / parse failure returns
107
+ * `null` and the caller falls through to the threshold check.
108
+ *
109
+ * Claude Code transcript entries are JSONL; assistant messages with
110
+ * tool calls carry `type: "tool_use"` on a content block. We scan
111
+ * backwards from the tail and stop at the first matching line.
112
+ */
113
+ function lastToolUseAgeMs(transcriptPath, nowMs) {
114
+ if (transcriptPath === null || transcriptPath.length === 0)
115
+ return null;
116
+ if (!existsSync(transcriptPath))
117
+ return null;
118
+ let raw;
119
+ try {
120
+ raw = readFileSync(transcriptPath, "utf8");
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ const lines = raw.split("\n");
126
+ for (let i = lines.length - 1; i >= 0; i--) {
127
+ const line = lines[i];
128
+ if (line === undefined || line.length === 0)
129
+ continue;
130
+ if (!line.includes('"tool_use"'))
131
+ continue;
132
+ let entry;
133
+ try {
134
+ entry = JSON.parse(line);
135
+ }
136
+ catch {
137
+ continue;
138
+ }
139
+ if (entry === null || typeof entry !== "object")
140
+ continue;
141
+ const ts = entry["timestamp"]
142
+ ?? entry["ts"];
143
+ if (typeof ts !== "string")
144
+ continue;
145
+ const parsed = Date.parse(ts);
146
+ if (Number.isNaN(parsed))
147
+ continue;
148
+ return nowMs - parsed;
149
+ }
150
+ return null;
151
+ }
152
+ function lastSessionStalledCuePath(repoRoot, sessionId) {
153
+ return join(repoRoot, ".cairn", "sessions", sessionId, "last-stalled-cue.iso");
154
+ }
155
+ function lastSessionStalledCueMs(repoRoot, sessionId) {
156
+ const path = lastSessionStalledCuePath(repoRoot, sessionId);
157
+ if (!existsSync(path))
158
+ return null;
159
+ try {
160
+ const raw = readFileSync(path, "utf8").trim();
161
+ const ms = Date.parse(raw);
162
+ return Number.isNaN(ms) ? null : ms;
163
+ }
164
+ catch {
165
+ return null;
166
+ }
167
+ }
168
+ function stampSessionStalledCue(repoRoot, sessionId) {
169
+ const path = lastSessionStalledCuePath(repoRoot, sessionId);
170
+ try {
171
+ mkdirSync(dirname(path), { recursive: true });
172
+ writeFileSync(path, new Date().toISOString(), "utf8");
173
+ }
174
+ catch {
175
+ // best-effort
176
+ }
177
+ }
88
178
  function priorCuePath(repoRoot, sessionId) {
89
179
  return join(repoRoot, ".cairn", "sessions", sessionId, "last-stop-cue.json");
90
180
  }
@@ -121,6 +211,7 @@ export async function runStopHook() {
121
211
  const payload = parseHookPayload(raw);
122
212
  const sessionId = typeof payload.session_id === "string" ? payload.session_id : null;
123
213
  const cwdInput = typeof payload.cwd === "string" ? payload.cwd : process.cwd();
214
+ const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : null;
124
215
  const repoRoot = resolveRepoRoot(cwdInput);
125
216
  const warnings = [];
126
217
  let drained = [];
@@ -176,9 +267,6 @@ export async function runStopHook() {
176
267
  const hint = `## Cairn — ${grad.completed.length} ${noun} graduated\n\n✓ ${ids} → done. Final attestation written.`;
177
268
  reason = reason.length > 0 ? `${reason}\n\n${hint}` : hint;
178
269
  }
179
- if (grad.transitioned.length > 0) {
180
- warnings.push(`auto_graduated_review_ready:${grad.transitioned.length}`);
181
- }
182
270
  }
183
271
  catch (err) {
184
272
  warnings.push(`auto_graduate_failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -204,45 +292,73 @@ export async function runStopHook() {
204
292
  warnings.push(`pending_review_scan_failed: ${err instanceof Error ? err.message : String(err)}`);
205
293
  }
206
294
  // Stalled-task scanner — surfaces tasks stuck in phase=running
207
- // with no attestation for 30min+. Catches the failure mode where
208
- // the autonomous flow finished the work but skipped spawning the
209
- // reviewer subagent (no attestation auto-graduator never fires
210
- // task accumulates as orphaned). Only fires when no other
211
- // higher-priority surface (reviewer hint, ctx threshold) already
212
- // owns the reason channel stalled-task triage is informational
213
- // catch-up, not blocking.
295
+ // with no attestation for 2h+ (raised from 30 min in bug-mine
296
+ // 0.13.8 / Phase 5 see CONTEXT.md §2.4 for false-fire mining).
297
+ // Catches the failure mode where the autonomous flow finished
298
+ // the work but skipped spawning the reviewer subagent (no
299
+ // attestation → auto-graduator never fires task accumulates
300
+ // as orphaned). Only fires when no other higher-priority surface
301
+ // (reviewer hint, ctx threshold) already owns the reason channel
302
+ // — stalled-task triage is informational catch-up, not blocking.
303
+ //
304
+ // Three additional gates layered on top of the per-task window:
214
305
  //
215
- // Per-task suppression window: once a stalled hint fires for a
216
- // given task id, suppress re-fires on the same id for 60 min so
217
- // the operator isn't asked the same triage question every Stop
218
- // tick (bug-mine report #9same task flagged in 90s).
306
+ // 1. Session-activity gate when the transcript carries a
307
+ // `tool_use` event within the last 5 minutes, the AI is
308
+ // actively working; suppress entirely.
309
+ // 2. Per-session rate limitat most one stalled cue per
310
+ // session per hour, total (not per-task). Bug-mine showed
311
+ // three active tasks idle = three prompts per hour without
312
+ // a global cap.
313
+ // 3. Per-task throttle — 60 minute suppression window per
314
+ // task id so the operator isn't asked the same triage
315
+ // question every Stop tick (bug-mine report #9 — same
316
+ // task flagged 3× in 90s).
219
317
  if (reason.length === 0 && !isFirstTurnWarmup) {
220
318
  try {
221
319
  gcStalledWarnedMarkers(repoRoot);
222
- const stalled = scanStalledRunningTasks(repoRoot, Date.now(), {
223
- currentSessionId: sessionId,
224
- });
225
- const surfaced = stalled.filter((t) => !isStalledFireSuppressed(repoRoot, t.task_id));
226
- if (surfaced.length > 0) {
227
- const reviewDefer = readDeferState(repoRoot, "review");
228
- const suppressed = reviewDefer !== null &&
229
- isDeferActive(reviewDefer, new Date(), {
230
- kind: "task_ids",
231
- values: surfaced.map((t) => t.task_id),
232
- });
233
- if (suppressed) {
234
- warnings.push(`stalled_suppressed_until:${reviewDefer.deferred_at}`);
320
+ const lastSessionCue = sessionId !== null
321
+ ? lastSessionStalledCueMs(repoRoot, sessionId)
322
+ : null;
323
+ if (lastSessionCue !== null &&
324
+ Date.now() - lastSessionCue < SESSION_STALLED_CUE_WINDOW_MS) {
325
+ warnings.push(`stalled_session_rate_limited:${new Date(lastSessionCue).toISOString()}`);
326
+ }
327
+ else {
328
+ const recentToolUseAge = lastToolUseAgeMs(transcriptPath, Date.now());
329
+ const sessionActive = recentToolUseAge !== null && recentToolUseAge < SESSION_ACTIVITY_WINDOW_MS;
330
+ if (sessionActive) {
331
+ warnings.push(`stalled_session_active:${recentToolUseAge}`);
235
332
  }
236
333
  else {
237
- reason = renderStalledTasksHint(surfaced);
238
- for (const t of surfaced)
239
- stampStalledFire(repoRoot, t.task_id);
240
- warnings.push(`stalled_running_tasks:${surfaced.length}`);
334
+ const stalled = scanStalledRunningTasks(repoRoot, Date.now(), {
335
+ currentSessionId: sessionId,
336
+ });
337
+ const surfaced = stalled.filter((t) => !isStalledFireSuppressed(repoRoot, t.task_id));
338
+ if (surfaced.length > 0) {
339
+ const reviewDefer = readDeferState(repoRoot, "review");
340
+ const suppressed = reviewDefer !== null &&
341
+ isDeferActive(reviewDefer, new Date(), {
342
+ kind: "task_ids",
343
+ values: surfaced.map((t) => t.task_id),
344
+ });
345
+ if (suppressed) {
346
+ warnings.push(`stalled_suppressed_until:${reviewDefer.deferred_at}`);
347
+ }
348
+ else {
349
+ reason = renderStalledTasksHint(surfaced);
350
+ for (const t of surfaced)
351
+ stampStalledFire(repoRoot, t.task_id);
352
+ if (sessionId !== null)
353
+ stampSessionStalledCue(repoRoot, sessionId);
354
+ warnings.push(`stalled_running_tasks:${surfaced.length}`);
355
+ }
356
+ }
357
+ else if (stalled.length > 0) {
358
+ warnings.push(`stalled_window_suppressed:${stalled.length}`);
359
+ }
241
360
  }
242
361
  }
243
- else if (stalled.length > 0) {
244
- warnings.push(`stalled_window_suppressed:${stalled.length}`);
245
- }
246
362
  }
247
363
  catch (err) {
248
364
  warnings.push(`stalled_scan_failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -455,22 +571,6 @@ function readTaskPhase(taskDir) {
455
571
  }
456
572
  return null;
457
573
  }
458
- function checkNeedsReview(specPath) {
459
- try {
460
- const raw = readFileSync(specPath, "utf8");
461
- const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\n---/);
462
- if (!fmMatch)
463
- return true;
464
- const fm = fmMatch[1] ?? "";
465
- const m = fm.match(/^needs_review:\s*(true|false)/m);
466
- if (m && m[1] === "false")
467
- return false;
468
- return true;
469
- }
470
- catch {
471
- return true;
472
- }
473
- }
474
574
  function scanPendingReviews(repoRoot) {
475
575
  const activeDir = join(repoRoot, ".cairn", "tasks", "active");
476
576
  if (!existsSync(activeDir))
@@ -495,10 +595,10 @@ function scanPendingReviews(repoRoot) {
495
595
  const attestation = join(taskDir, "attestation.yaml");
496
596
  if (existsSync(attestation))
497
597
  continue;
498
- // Finding 4: Opt-in reviewer. Default to true, skip if explicitly false.
499
- if (!checkNeedsReview(tightenedSpec))
500
- continue;
501
598
  // Phase gate — `running` / `tightening` / etc. are not review-ready.
599
+ // Reviewer is now opt-in (bug-mine 0.13.5); this surface only fires
600
+ // when an explicit reviewer subagent set phase=ready_for_review and
601
+ // ended its turn before writing attestation.yaml.
502
602
  const phase = readTaskPhase(taskDir);
503
603
  if (phase !== null && !REVIEW_READY_PHASES.has(phase))
504
604
  continue;
@@ -591,7 +691,7 @@ function scanStalledRunningTasks(repoRoot, nowMs = Date.now(), opts = { currentS
591
691
  if (!existsSync(activeDir))
592
692
  return [];
593
693
  const out = [];
594
- const idleThresholdMs = 30 * 60 * 1000;
694
+ const idleThresholdMs = 2 * 60 * 60 * 1000;
595
695
  const upperBoundMs = 7 * 24 * 60 * 60 * 1000;
596
696
  // When a task's last journal write came from a DIFFERENT live session
597
697
  // within this window, treat it as "owned by that session" and don't
@@ -727,9 +827,9 @@ function renderStalledTasksHint(stalled) {
727
827
  const lines = [
728
828
  `## Cairn — ${stalled.length} stalled ${noun}`,
729
829
  ``,
730
- `${stalled.length} active ${noun} idle 30min+ with no attestation. ` +
830
+ `${stalled.length} active ${noun} idle 2h+ with no attestation. ` +
731
831
  `Either the autonomous flow skipped the reviewer-spawn step, or the ` +
732
- `session was interrupted mid-task. Triage before continuing:`,
832
+ `session was interrupted mid-task.`,
733
833
  ``,
734
834
  ];
735
835
  for (const t of stalled) {
@@ -737,7 +837,7 @@ function renderStalledTasksHint(stalled) {
737
837
  lines.push(`- \`${t.task_id}\` — ${t.title}${mod} (idle ${t.idle_minutes}m)`);
738
838
  }
739
839
  lines.push("");
740
- lines.push("Render this question via `AskUserQuestion` — do not skip:");
840
+ lines.push("If you reach a stopping point with no obvious continuation, surface to operator (e.g. via `AskUserQuestion`):");
741
841
  lines.push("");
742
842
  lines.push(`> ${stalled.length} stalled ${noun}. Pick once for all (or address one at a time after):`);
743
843
  lines.push(`>`);
@@ -747,26 +847,30 @@ function renderStalledTasksHint(stalled) {
747
847
  lines.push(``);
748
848
  lines.push("On [a], call `cairn_task_complete({task_id, outcome: \"succeeded\", summary: \"closing stalled task — work landed via prior session\"})` for each id above.");
749
849
  lines.push("On [b], dispatch the `reviewer` subagent for each task in turn (one task brief per Task call).");
750
- lines.push("On [c], end the turn — the prompt re-fires only when status.yaml stays idle past the next 30min mark.");
850
+ lines.push("On [c], end the turn — the prompt re-fires only when status.yaml stays idle past the next 2h mark.");
851
+ lines.push("");
852
+ lines.push("If you're actively working (Agent dispatch in flight, edits queued), ignore this hint and keep going — it will re-evaluate on the next Stop tick.");
751
853
  return lines.join("\n");
752
854
  }
753
855
  /**
754
856
  * Auto-graduate active tasks based on attestation presence.
755
857
  *
858
+ * Self-attest is the default path (bug-mine 0.13.5): the AI calls
859
+ * `cairn_task_complete({outcome, summary})` and the tool moves the
860
+ * directory itself. This auto-graduator is the fallback for the rare
861
+ * case where an opt-in reviewer subagent wrote attestation.yaml but
862
+ * ended its turn before the explicit close call.
863
+ *
756
864
  * Rules (only acts on tasks with phase=running):
757
- * 1. Task-root `attestation.yaml` exists → succeeded → tasks/done/
758
- * (reviewer subagent attested; nothing more to do)
759
- * 2. ≥1 subagents/<id>/attestation.yaml AND needs_review=false → succeeded → tasks/done/
760
- * (trivial task, no reviewer scheduled)
761
- * 3. ≥1 subagents/<id>/attestation.yaml AND needs_review=true → ready_for_review
762
- * (reviewer hasn't run yet — `scanPendingReviews` will surface a hint)
865
+ * 1. Task-root `attestation.yaml` exists → succeeded → tasks/done/
866
+ * 2. ≥1 subagents/<id>/attestation.yaml → succeeded tasks/done/
763
867
  *
764
868
  * Tasks with no attestation activity stay `running` — they're either
765
- * still in flight or stalled (Q4 surfaces stall detection separately).
869
+ * still in flight or stalled (stall detection runs separately).
766
870
  */
767
871
  function autoGraduateTasks(repoRoot) {
768
872
  const activeDir = join(repoRoot, ".cairn", "tasks", "active");
769
- const result = { completed: [], transitioned: [] };
873
+ const result = { completed: [] };
770
874
  if (!existsSync(activeDir))
771
875
  return result;
772
876
  let entries;
@@ -785,7 +889,7 @@ function autoGraduateTasks(repoRoot) {
785
889
  continue;
786
890
  if (state.phase !== "running")
787
891
  continue;
788
- if (state.rootAttestation) {
892
+ if (state.rootAttestation || state.subagentAttestations > 0) {
789
893
  const r = completeTask({
790
894
  repoRoot,
791
895
  taskId,
@@ -794,27 +898,6 @@ function autoGraduateTasks(repoRoot) {
794
898
  });
795
899
  if (r.ok)
796
900
  result.completed.push(taskId);
797
- continue;
798
- }
799
- if (state.subagentAttestations > 0) {
800
- if (!state.needsReview) {
801
- const r = completeTask({
802
- repoRoot,
803
- taskId,
804
- outcome: "succeeded",
805
- source: "cairn_stop_auto_graduate",
806
- });
807
- if (r.ok)
808
- result.completed.push(taskId);
809
- continue;
810
- }
811
- const ok = transitionTaskPhase({
812
- repoRoot,
813
- taskId,
814
- newPhase: "ready_for_review",
815
- });
816
- if (ok)
817
- result.transitioned.push(taskId);
818
901
  }
819
902
  }
820
903
  return result;