harnery 0.31.6 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/README.md +2 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +5 -1
  4. package/dist/commands/agents.d.ts +0 -9
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +280 -94
  7. package/dist/commands/browse-session.d.ts +21 -0
  8. package/dist/commands/browse-session.d.ts.map +1 -0
  9. package/dist/commands/browse-session.js +157 -0
  10. package/dist/commands/browse.d.ts.map +1 -1
  11. package/dist/commands/browse.js +205 -37
  12. package/dist/commands/checkpoint.js +1 -1
  13. package/dist/commands/claude-desktop.d.ts +2 -2
  14. package/dist/commands/claude-desktop.d.ts.map +1 -1
  15. package/dist/commands/claude-desktop.js +74 -3
  16. package/dist/commands/deinit.d.ts.map +1 -1
  17. package/dist/commands/deinit.js +4 -0
  18. package/dist/commands/docs.d.ts.map +1 -1
  19. package/dist/commands/docs.js +40 -0
  20. package/dist/commands/doctor.d.ts +4 -2
  21. package/dist/commands/doctor.d.ts.map +1 -1
  22. package/dist/commands/doctor.js +141 -19
  23. package/dist/commands/init.d.ts +2 -2
  24. package/dist/commands/init.d.ts.map +1 -1
  25. package/dist/commands/init.js +26 -5
  26. package/dist/commands/instructions.d.ts +4 -0
  27. package/dist/commands/instructions.d.ts.map +1 -0
  28. package/dist/commands/instructions.js +100 -0
  29. package/dist/commands/presence.d.ts.map +1 -1
  30. package/dist/commands/presence.js +4 -1
  31. package/dist/commands/tunnel.d.ts.map +1 -1
  32. package/dist/commands/tunnel.js +2 -3
  33. package/dist/core/agents/canonical-emit.d.ts +2 -3
  34. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  35. package/dist/core/agents/canonical-emit.js +6 -5
  36. package/dist/core/agents/cli.js +128 -89
  37. package/dist/core/agents/coord-client.d.ts +18 -3
  38. package/dist/core/agents/coord-client.d.ts.map +1 -1
  39. package/dist/core/agents/coord-client.js +5 -4
  40. package/dist/core/agents/finalization.d.ts +68 -0
  41. package/dist/core/agents/finalization.d.ts.map +1 -0
  42. package/dist/core/agents/finalization.js +443 -0
  43. package/dist/core/agents/git-hook.d.ts +51 -0
  44. package/dist/core/agents/git-hook.d.ts.map +1 -0
  45. package/dist/core/agents/git-hook.js +118 -0
  46. package/dist/core/agents/index.d.ts +1 -0
  47. package/dist/core/agents/index.d.ts.map +1 -1
  48. package/dist/core/agents/index.js +1 -0
  49. package/dist/core/agents/render/prompt-context.d.ts +12 -5
  50. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  51. package/dist/core/agents/render/prompt-context.js +70 -16
  52. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  53. package/dist/core/agents/render/session-context.js +23 -4
  54. package/dist/core/agents/rules/claim-conflict.js +3 -3
  55. package/dist/core/agents/rules/commit-conflict.d.ts +15 -6
  56. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  57. package/dist/core/agents/rules/commit-conflict.js +21 -5
  58. package/dist/core/agents/rules/stop-hook.d.ts +3 -0
  59. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  60. package/dist/core/agents/rules/stop-hook.js +59 -23
  61. package/dist/core/agents/session-events.d.ts +8 -16
  62. package/dist/core/agents/session-events.d.ts.map +1 -1
  63. package/dist/core/agents/session-events.js +12 -26
  64. package/dist/core/agents/state/heartbeat-projector.d.ts +14 -0
  65. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
  66. package/dist/core/agents/state/heartbeat-projector.js +98 -1
  67. package/dist/core/agents/state/heartbeat-writer.d.ts +47 -5
  68. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  69. package/dist/core/agents/state/heartbeat-writer.js +86 -17
  70. package/dist/core/agents/state/names.d.ts +22 -1
  71. package/dist/core/agents/state/names.d.ts.map +1 -1
  72. package/dist/core/agents/state/names.js +40 -2
  73. package/dist/core/agents/state/session-state.d.ts +44 -0
  74. package/dist/core/agents/state/session-state.d.ts.map +1 -0
  75. package/dist/core/agents/state/session-state.js +81 -0
  76. package/dist/core/config.d.ts +30 -2
  77. package/dist/core/config.d.ts.map +1 -1
  78. package/dist/core/config.js +74 -11
  79. package/dist/core/governor/planning.d.ts.map +1 -1
  80. package/dist/core/governor/planning.js +12 -13
  81. package/dist/core/hooks/adapter/detect.d.ts +2 -4
  82. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  83. package/dist/core/hooks/adapter/detect.js +4 -14
  84. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  85. package/dist/core/hooks/adapter/events.js +1 -0
  86. package/dist/core/hooks/adapter/output.d.ts +1 -1
  87. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  88. package/dist/core/hooks/adapter/output.js +5 -4
  89. package/dist/core/hooks/adapter/parse.d.ts +1 -1
  90. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  91. package/dist/core/hooks/adapter/parse.js +2 -0
  92. package/dist/core/hooks/adapter/wiring.d.ts +23 -0
  93. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  94. package/dist/core/hooks/adapter/wiring.js +32 -0
  95. package/dist/core/hooks/cli.d.ts +1 -2
  96. package/dist/core/hooks/cli.d.ts.map +1 -1
  97. package/dist/core/hooks/cli.js +258 -32
  98. package/dist/core/hooks/codex-authorization.d.ts +30 -0
  99. package/dist/core/hooks/codex-authorization.d.ts.map +1 -0
  100. package/dist/core/hooks/codex-authorization.js +199 -0
  101. package/dist/core/hooks/codex-wsl-bridge.d.ts +46 -0
  102. package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -0
  103. package/dist/core/hooks/codex-wsl-bridge.js +137 -0
  104. package/dist/core/hooks/effects/index.d.ts +1 -1
  105. package/dist/core/hooks/effects/index.js +3 -3
  106. package/dist/core/hooks/events/schema.d.ts +57 -1
  107. package/dist/core/hooks/events/schema.d.ts.map +1 -1
  108. package/dist/core/hooks/resolve/owner.d.ts +7 -1
  109. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  110. package/dist/core/hooks/resolve/owner.js +15 -0
  111. package/dist/core/hooks/resolve/transcript.d.ts +44 -0
  112. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  113. package/dist/core/hooks/resolve/transcript.js +176 -1
  114. package/dist/core/hooks/session-name-presence.d.ts +30 -0
  115. package/dist/core/hooks/session-name-presence.d.ts.map +1 -0
  116. package/dist/core/hooks/session-name-presence.js +43 -0
  117. package/dist/core/hooks/unsafe-cross-shell.d.ts +13 -0
  118. package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -0
  119. package/dist/core/hooks/unsafe-cross-shell.js +151 -0
  120. package/dist/core/presence/blob.d.ts +4 -0
  121. package/dist/core/presence/blob.d.ts.map +1 -1
  122. package/dist/core/presence/blob.js +12 -0
  123. package/dist/core/presence/index.js +15 -1
  124. package/dist/core/work/state.d.ts +4 -5
  125. package/dist/core/work/state.d.ts.map +1 -1
  126. package/dist/core/work/state.js +5 -8
  127. package/dist/core/workflow/index.d.ts +1 -1
  128. package/dist/core/workflow/index.d.ts.map +1 -1
  129. package/dist/core/workflow/proof.d.ts +2 -2
  130. package/dist/core/workflow/proof.d.ts.map +1 -1
  131. package/dist/core/workflow/proof.js +2 -2
  132. package/dist/core/workflow/run-state.d.ts +2 -2
  133. package/dist/core/workflow/run-state.d.ts.map +1 -1
  134. package/dist/core/workflow/run-state.js +2 -2
  135. package/dist/core/workflow/types.d.ts +3 -3
  136. package/dist/core/workflow/types.d.ts.map +1 -1
  137. package/dist/core/workflow/workspaces/execution.d.ts +2 -2
  138. package/dist/core/workflow/workspaces/execution.d.ts.map +1 -1
  139. package/dist/core/workflow/workspaces/execution.js +5 -0
  140. package/dist/core/workflow/workspaces/index.d.ts +1 -1
  141. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  142. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  143. package/dist/core/workflow/workspaces/local-git.js +1 -2
  144. package/dist/core/workflow/workspaces/paths.d.ts +2 -2
  145. package/dist/core/workflow/workspaces/types.d.ts +0 -6
  146. package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
  147. package/dist/core/workflow/workspaces/validate.d.ts +0 -2
  148. package/dist/core/workflow/workspaces/validate.d.ts.map +1 -1
  149. package/dist/core/workflow/workspaces/validate.js +0 -2
  150. package/dist/lib/browser/client.d.ts +118 -1
  151. package/dist/lib/browser/client.d.ts.map +1 -1
  152. package/dist/lib/browser/client.js +435 -6
  153. package/dist/lib/browser/geometry.d.ts.map +1 -1
  154. package/dist/lib/browser/geometry.js +180 -37
  155. package/dist/lib/browser/index.d.ts +5 -1
  156. package/dist/lib/browser/index.d.ts.map +1 -1
  157. package/dist/lib/browser/index.js +5 -1
  158. package/dist/lib/browser/netscape-cookies.d.ts +6 -0
  159. package/dist/lib/browser/netscape-cookies.d.ts.map +1 -0
  160. package/dist/lib/browser/netscape-cookies.js +31 -0
  161. package/dist/lib/browser/proxy.d.ts +24 -0
  162. package/dist/lib/browser/proxy.d.ts.map +1 -0
  163. package/dist/lib/browser/proxy.js +84 -0
  164. package/dist/lib/browser/runts.d.ts +6 -0
  165. package/dist/lib/browser/runts.d.ts.map +1 -1
  166. package/dist/lib/browser/runts.js +21 -4
  167. package/dist/lib/browser/session-control.d.ts +112 -0
  168. package/dist/lib/browser/session-control.d.ts.map +1 -0
  169. package/dist/lib/browser/session-control.js +670 -0
  170. package/dist/lib/claude-desktop.d.ts +47 -0
  171. package/dist/lib/claude-desktop.d.ts.map +1 -1
  172. package/dist/lib/claude-desktop.js +169 -1
  173. package/dist/lib/docs-links.d.ts +108 -0
  174. package/dist/lib/docs-links.d.ts.map +1 -0
  175. package/dist/lib/docs-links.js +555 -0
  176. package/dist/lib/exec.d.ts +1 -1
  177. package/dist/lib/exec.js +1 -1
  178. package/dist/lib/identities/assume.d.ts +4 -2
  179. package/dist/lib/identities/assume.d.ts.map +1 -1
  180. package/dist/lib/identities/assume.js +15 -2
  181. package/dist/lib/instructions/bundle.d.ts +33 -0
  182. package/dist/lib/instructions/bundle.d.ts.map +1 -0
  183. package/dist/lib/instructions/bundle.js +149 -0
  184. package/dist/lib/instructions/git-hooks.d.ts +75 -0
  185. package/dist/lib/instructions/git-hooks.d.ts.map +1 -0
  186. package/dist/lib/instructions/git-hooks.js +238 -0
  187. package/dist/lib/instructions/splice.d.ts +10 -4
  188. package/dist/lib/instructions/splice.d.ts.map +1 -1
  189. package/dist/lib/instructions/splice.js +19 -11
  190. package/package.json +2 -2
  191. package/schemas/config.schema.json +32 -0
  192. package/src/commander.ts +5 -1
  193. package/src/commands/agents.ts +341 -113
  194. package/src/commands/browse-session.ts +245 -0
  195. package/src/commands/browse.ts +287 -44
  196. package/src/commands/checkpoint.ts +1 -1
  197. package/src/commands/claude-desktop.ts +106 -5
  198. package/src/commands/deinit.ts +7 -1
  199. package/src/commands/docs.ts +56 -0
  200. package/src/commands/doctor.ts +155 -21
  201. package/src/commands/init.ts +28 -9
  202. package/src/commands/instructions.ts +129 -0
  203. package/src/commands/presence.ts +7 -1
  204. package/src/commands/tunnel.ts +2 -5
  205. package/src/core/agents/canonical-emit.ts +7 -6
  206. package/src/core/agents/cli.ts +143 -89
  207. package/src/core/agents/coord-client.ts +21 -5
  208. package/src/core/agents/finalization.ts +595 -0
  209. package/src/core/agents/git-hook.ts +126 -0
  210. package/src/core/agents/index.ts +1 -0
  211. package/src/core/agents/render/prompt-context.ts +96 -16
  212. package/src/core/agents/render/session-context.ts +29 -4
  213. package/src/core/agents/rules/claim-conflict.ts +3 -3
  214. package/src/core/agents/rules/commit-conflict.ts +35 -8
  215. package/src/core/agents/rules/stop-hook.ts +71 -23
  216. package/src/core/agents/session-events.ts +14 -41
  217. package/src/core/agents/state/heartbeat-projector.ts +108 -1
  218. package/src/core/agents/state/heartbeat-writer.ts +129 -16
  219. package/src/core/agents/state/names.ts +54 -2
  220. package/src/core/agents/state/session-state.ts +131 -0
  221. package/src/core/config.ts +92 -12
  222. package/src/core/governor/planning.ts +17 -12
  223. package/src/core/hooks/adapter/detect.ts +4 -13
  224. package/src/core/hooks/adapter/events.ts +1 -0
  225. package/src/core/hooks/adapter/output.ts +9 -4
  226. package/src/core/hooks/adapter/parse.ts +3 -0
  227. package/src/core/hooks/adapter/wiring.ts +43 -0
  228. package/src/core/hooks/cli.ts +296 -27
  229. package/src/core/hooks/codex-authorization.ts +253 -0
  230. package/src/core/hooks/codex-wsl-bridge.ts +188 -0
  231. package/src/core/hooks/effects/index.ts +5 -5
  232. package/src/core/hooks/events/schema.ts +71 -0
  233. package/src/core/hooks/resolve/owner.ts +22 -1
  234. package/src/core/hooks/resolve/transcript.ts +164 -1
  235. package/src/core/hooks/session-name-presence.ts +52 -0
  236. package/src/core/hooks/unsafe-cross-shell.ts +160 -0
  237. package/src/core/presence/blob.ts +18 -0
  238. package/src/core/presence/index.ts +18 -1
  239. package/src/core/work/state.ts +8 -12
  240. package/src/core/workflow/engine.ts +2 -2
  241. package/src/core/workflow/index.ts +0 -1
  242. package/src/core/workflow/proof.ts +4 -4
  243. package/src/core/workflow/run-state.ts +5 -5
  244. package/src/core/workflow/types.ts +3 -3
  245. package/src/core/workflow/workspaces/execution.ts +11 -4
  246. package/src/core/workflow/workspaces/index.ts +0 -3
  247. package/src/core/workflow/workspaces/local-git.ts +1 -2
  248. package/src/core/workflow/workspaces/paths.ts +2 -2
  249. package/src/core/workflow/workspaces/types.ts +0 -9
  250. package/src/core/workflow/workspaces/validate.ts +1 -5
  251. package/src/lib/browser/client.ts +528 -6
  252. package/src/lib/browser/geometry.ts +197 -37
  253. package/src/lib/browser/index.ts +39 -0
  254. package/src/lib/browser/netscape-cookies.ts +39 -0
  255. package/src/lib/browser/proxy.ts +105 -0
  256. package/src/lib/browser/runts.ts +27 -3
  257. package/src/lib/browser/session-control.ts +892 -0
  258. package/src/lib/claude-desktop.ts +232 -1
  259. package/src/lib/docs-links.ts +674 -0
  260. package/src/lib/exec.ts +1 -1
  261. package/src/lib/identities/assume.ts +22 -1
  262. package/src/lib/instructions/bundle.ts +199 -0
  263. package/src/lib/instructions/git-hooks.ts +259 -0
  264. package/src/lib/instructions/splice.ts +41 -11
@@ -18,7 +18,10 @@ import { homedir, tmpdir } from "node:os";
18
18
  import { basename, join, resolve } from "node:path";
19
19
  import { coordBinPath } from "../core/agents/coord-bin.js";
20
20
  import { readStreamTailBounded } from "../core/agents/events/consume.js";
21
+ import { checkGitFinalization, formatGitFinalizationFailure, readSessionWriteClaims, } from "../core/agents/finalization.js";
21
22
  import { emitCanonical, monorepoRoot, normalizeAdapter, readHeartbeat, resolveOwner, resolveOwnerBySessionEnv, resolveOwnerWithSource, } from "../core/agents/index.js";
23
+ import { buildLifecycleSuggestedName, buildSuggestedName, } from "../core/agents/state/heartbeat-writer.js";
24
+ import { foldSessionState, } from "../core/agents/state/session-state.js";
22
25
  import { coordFreshnessSeconds, resolveBinName } from "../core/config.js";
23
26
  import { readRemoteMachines } from "../core/presence/index.js";
24
27
  import { registerContextCommand } from "./context.js";
@@ -99,6 +102,16 @@ function formatPlatformLabel(platform) {
99
102
  return "Codex";
100
103
  return "CC";
101
104
  }
105
+ function activityOf(hb) {
106
+ return hb.activity ?? "unknown";
107
+ }
108
+ function taskStateOf(hb) {
109
+ return hb.task_state ?? "active";
110
+ }
111
+ function lifecycleLabel(hb) {
112
+ const state = taskStateOf(hb);
113
+ return state === "blocked" && hb.task_state_reason ? `${state}: ${hb.task_state_reason}` : state;
114
+ }
102
115
  let emit;
103
116
  export function registerAgentsCommand(program, emitParam, programContext) {
104
117
  emit = emitParam;
@@ -126,6 +139,7 @@ export function registerAgentsCommand(program, emitParam, programContext) {
126
139
  .command("status")
127
140
  .description("End-of-turn status box (name + session age + files held + peer count)")
128
141
  .option("--json", "JSON output instead of the box")
142
+ .option("--end-turn", "Treat this as the turn's closing status: issue the box only when this session's held paths are committed and their repositories are pushed")
129
143
  .option("--session-id <id>", "Lookup heartbeat by session_id directly, bypassing the ppid walk. " +
130
144
  "Use this when calling from a hook (the hook's process tree may not lead back to Claude Code's session pid). " +
131
145
  "The Stop hook payload includes session_id; pass it through.")
@@ -181,6 +195,14 @@ export function registerAgentsCommand(program, emitParam, programContext) {
181
195
  .action((text, opts) => {
182
196
  runSetTask(text.join(" "), opts);
183
197
  });
198
+ cmd
199
+ .command("lifecycle <state>")
200
+ .description("Declare this session's task lifecycle: active, blocked, or done")
201
+ .option("--reason <text>", "Explain the lifecycle declaration")
202
+ .option("--session-id <id>", "Target the heartbeat with this session_id directly, bypassing the ppid walk.")
203
+ .action((state, opts) => {
204
+ runLifecycle(state, opts);
205
+ });
184
206
  cmd
185
207
  .command("release-claim <path>")
186
208
  .description("Drop a file claim from your heartbeat. Operator escape hatch when a " +
@@ -203,7 +225,7 @@ export function registerAgentsCommand(program, emitParam, programContext) {
203
225
  .command("wait <name>")
204
226
  .description(`Block until a peer agent releases files (their \`files_touched\` becomes empty, OR they exit). Pair with \`${resolveBinName()} agents ping\` to coordinate hand-offs.`)
205
227
  .option("--file <path>", "Wait only for these specific paths (repeatable)", collectPath, [])
206
- .option("--timeout <dur>", "Give up after this duration; suffix s/m/h/d (e.g. 30s, 5m, 1h). Bare integer = minutes. Default 60m.", "60m")
228
+ .option("--timeout <dur>", "Give up after this duration; suffix s/m/h/d is required (e.g. 30s, 5m, 1h). Default 60m.", "60m")
207
229
  .option("--poll-secs <n>", "Poll interval in seconds (default 5)", "5")
208
230
  .option("--quiet", "Suppress progress lines")
209
231
  .option("--json", "JSON output (terminal status, only printed at exit)")
@@ -223,8 +245,8 @@ export function registerAgentsCommand(program, emitParam, programContext) {
223
245
  });
224
246
  cmd
225
247
  .command("health")
226
- .description("One-screen coord-layer health rollup: heal events, schema validity, " +
227
- "commit-guard activity, council activity, anomalies. Designed for " +
248
+ .description("One-screen coord-layer health rollup: heal events, council activity, " +
249
+ "zombie detection, and anomalies. Designed for " +
228
250
  "daily glance + dashboard ingestion. Reads .harnery/.")
229
251
  .option("--since <window>", "Window (Nh | Nd). Default: 24h.", "24h")
230
252
  .option("--json", "JSON envelope output")
@@ -236,7 +258,7 @@ export function registerAgentsCommand(program, emitParam, programContext) {
236
258
  .description("Adapter wiring probe: ppid chain, comm names, pid-map anchor, sample payload paths. " +
237
259
  "With --replay-samples, also replays every checked-in sample payload against the live " +
238
260
  "adapter in an isolated sandbox to catch adapter / payload-shape drift. " +
239
- "Complements heal-events (drift telemetry). Id: claude_code | cursor.")
261
+ "Complements heal-events (drift telemetry). Id: claude-code | cursor.")
240
262
  .option("--json", "JSON envelope output")
241
263
  .option("--replay-samples", "Replay docs/api/<adapter>-hooks/samples/*.json against the live adapter in an isolated sandbox. " +
242
264
  "Exits non-zero if any sample crashes the adapter.")
@@ -359,6 +381,9 @@ function registerIdentityCommands(parent) {
359
381
  "the event ledger, and the heartbeat.")
360
382
  .option("--json", "JSON envelope output")
361
383
  .option("--session-id <id>", "Bind the heartbeat with this session_id directly, bypassing the ppid walk.")
384
+ .option("--force-ancestor", "Allow assuming a persona this session's fork lineage descends from " +
385
+ "(default: refused, because a fork's inherited transcript asserting that " +
386
+ "name is not a role handoff).")
362
387
  .action((target, opts) => {
363
388
  runIdentityAssume(target, opts);
364
389
  });
@@ -385,7 +410,9 @@ function runIdentityAssume(target, opts) {
385
410
  process.exit(1);
386
411
  }
387
412
  try {
388
- const result = assumeIdentity(root, owner, target);
413
+ const result = assumeIdentity(root, owner, target, {
414
+ forceAncestor: opts.forceAncestor ?? false,
415
+ });
389
416
  emit.data({
390
417
  ...result,
391
418
  display_name: displayAgentName(result.name),
@@ -590,9 +617,15 @@ function runWhoami(opts) {
590
617
  last_tool: hb.last_tool ?? null,
591
618
  last_tool_target: hb.last_tool_target ?? null,
592
619
  task: hb.task ?? null,
620
+ activity: activityOf(hb),
621
+ activity_updated_at: hb.activity_updated_at ?? null,
622
+ activity_source: hb.activity_source ?? null,
623
+ task_state: taskStateOf(hb),
624
+ task_state_updated_at: hb.task_state_updated_at ?? null,
625
+ task_state_reason: hb.task_state_reason ?? null,
593
626
  turn_summary: hb.turn_summary ?? null,
594
627
  turn_summary_updated_at: hb.turn_summary_updated_at ?? null,
595
- platform: hb.platform ?? "claude_code",
628
+ platform: hb.platform ?? "claude-code",
596
629
  };
597
630
  emit.data({ ...row, resolution_source: resolved.source, note: SUBAGENT_NOTE });
598
631
  if (process.stdout.isTTY && !opts.json) {
@@ -679,9 +712,15 @@ function runList(opts) {
679
712
  last_tool: h.last_tool ?? null,
680
713
  last_tool_target: h.last_tool_target ?? null,
681
714
  task: h.task ?? null,
715
+ activity: activityOf(h),
716
+ activity_updated_at: h.activity_updated_at ?? null,
717
+ activity_source: h.activity_source ?? null,
718
+ task_state: taskStateOf(h),
719
+ task_state_updated_at: h.task_state_updated_at ?? null,
720
+ task_state_reason: h.task_state_reason ?? null,
682
721
  turn_summary: h.turn_summary ?? null,
683
722
  turn_summary_updated_at: h.turn_summary_updated_at ?? null,
684
- platform: h.platform ?? "claude_code",
723
+ platform: h.platform ?? "claude-code",
685
724
  });
686
725
  continue;
687
726
  }
@@ -704,9 +743,15 @@ function runList(opts) {
704
743
  last_tool: h.last_tool ?? null,
705
744
  last_tool_target: h.last_tool_target ?? null,
706
745
  task: h.task ?? null,
746
+ activity: activityOf(h),
747
+ activity_updated_at: h.activity_updated_at ?? null,
748
+ activity_source: h.activity_source ?? null,
749
+ task_state: taskStateOf(h),
750
+ task_state_updated_at: h.task_state_updated_at ?? null,
751
+ task_state_reason: h.task_state_reason ?? null,
707
752
  turn_summary: h.turn_summary ?? null,
708
753
  turn_summary_updated_at: h.turn_summary_updated_at ?? null,
709
- platform: h.platform ?? "claude_code",
754
+ platform: h.platform ?? "claude-code",
710
755
  });
711
756
  }
712
757
  // Guard missing started_at: a heartbeat seeded from a stray event can lack it
@@ -730,9 +775,15 @@ function runList(opts) {
730
775
  last_tool: a.last_tool ?? null,
731
776
  last_tool_target: null,
732
777
  task: a.task ?? null,
778
+ activity: a.activity ?? "unknown",
779
+ activity_updated_at: null,
780
+ activity_source: null,
781
+ task_state: a.task_state ?? "active",
782
+ task_state_updated_at: null,
783
+ task_state_reason: a.task_state_reason ?? null,
733
784
  turn_summary: a.turn_summary ?? null,
734
785
  turn_summary_updated_at: null,
735
- platform: a.platform ?? "claude_code",
786
+ platform: a.platform ?? "claude-code",
736
787
  machine: rm.machine,
737
788
  });
738
789
  }
@@ -975,6 +1026,12 @@ async function runShow(name, opts) {
975
1026
  age_secs: ageSecs,
976
1027
  last_heartbeat_secs_ago: heartbeatAgeSecs,
977
1028
  task: hb.task ?? null,
1029
+ activity: activityOf(hb),
1030
+ activity_updated_at: hb.activity_updated_at ?? null,
1031
+ activity_source: hb.activity_source ?? null,
1032
+ task_state: taskStateOf(hb),
1033
+ task_state_updated_at: hb.task_state_updated_at ?? null,
1034
+ task_state_reason: hb.task_state_reason ?? null,
978
1035
  turn_summary: hb.turn_summary ?? null,
979
1036
  turn_summary_updated_at: hb.turn_summary_updated_at ?? null,
980
1037
  title: report?.title ?? null,
@@ -1002,6 +1059,8 @@ async function runShow(name, opts) {
1002
1059
  lines.push(`agent-${data.name} ${subtitle}`);
1003
1060
  lines.push(` session ${formatAge(ageSecs)} old · kind=${data.kind} · session_id=${data.session_id.slice(0, 8)}…`);
1004
1061
  lines.push(` last heartbeat: ${formatAge(heartbeatAgeSecs)} ago`);
1062
+ lines.push(` activity: ${data.activity}`);
1063
+ lines.push(` lifecycle: ${data.task_state}${data.task_state === "blocked" && data.task_state_reason ? `: ${data.task_state_reason}` : ""}`);
1005
1064
  if (data.last_tool) {
1006
1065
  const target = data.last_tool_target ? ` ${truncate(data.last_tool_target, 80)}` : "";
1007
1066
  lines.push(` last activity: ${data.last_tool}${target}`);
@@ -1168,15 +1227,17 @@ function runSetTask(task, opts) {
1168
1227
  });
1169
1228
  process.exit(1);
1170
1229
  }
1171
- // Snapshot BEFORE the mutation: a heartbeat that has never carried a task
1172
- // has no `task_updated_at` stamp (setTask always stamps it, even on a clear),
1173
- // so its absence is the "focus never declared this session" signal — i.e. the
1174
- // first set-task of the session. That's the natural moment to also hand the
1175
- // operator a session name built from the same declaration (one source of
1176
- // truth: the focus you declare names the session). Distinct from task===""
1177
- // (a deliberate clear), which carries a stamp and does NOT re-trigger.
1230
+ // Snapshot BEFORE the mutation: the writer stamps `suggested_session_name`
1231
+ // on the session's first NON-EMPTY declaration (one source of truth: the
1232
+ // focus you declare names the session). A bare clear never consumes the
1233
+ // naming window, and subagent/workflow kinds are never named — see
1234
+ // heartbeat-writer.setTask. This call is the naming call exactly when the
1235
+ // stamp appears across the mutation.
1178
1236
  const priorHb = readHeartbeat(myOwner);
1179
- const firstOfSession = !priorHb?.task_updated_at;
1237
+ const normalizedTask = task.length > 0 ? task : undefined;
1238
+ const lifecycleWarning = priorHb && (priorHb.task_state ?? "active") !== "active" && priorHb.task !== normalizedTask
1239
+ ? `Task text changed while lifecycle is ${priorHb.task_state}; run \`${resolveBinName(root)} agents lifecycle active\` to reopen it explicitly.`
1240
+ : null;
1180
1241
  // Heartbeat mutation goes through agent-coord (atomic temp+rename).
1181
1242
  const helper = agentCoordOrExit(root);
1182
1243
  const result = spawnSync(helper, ["set-task", myOwner, task], {
@@ -1191,38 +1252,172 @@ function runSetTask(task, opts) {
1191
1252
  process.exit(1);
1192
1253
  }
1193
1254
  const hb = readHeartbeat(myOwner);
1255
+ // The naming call: the mutation just stamped `suggested_session_name` for
1256
+ // the first time. `first_of_session: true` means exactly "this call produced
1257
+ // the session name" — it is never true with a null name (an empty first
1258
+ // declaration or a `--session-id` relay both keep the window open instead).
1259
+ const suggestedName = !priorHb?.suggested_session_name && hb?.suggested_session_name
1260
+ ? hb.suggested_session_name
1261
+ : null;
1262
+ const firstOfSession = suggestedName !== null;
1194
1263
  emitCanonical({
1195
1264
  type: "state.task_set",
1196
1265
  owner: myOwner,
1197
1266
  session: hb?.session_id ?? myOwner,
1198
1267
  adapter: normalizeAdapter(hb?.platform),
1199
- data: { task, cleared: !task || task.length === 0 },
1268
+ data: {
1269
+ task,
1270
+ cleared: !task || task.length === 0,
1271
+ first_of_session: firstOfSession,
1272
+ suggested_session_name: suggestedName,
1273
+ },
1200
1274
  });
1201
- // On the first focus declaration of a session, surface a suggested session
1202
- // name for the operator to set as their adapter tab title. Read the result
1203
- // (`suggested_session_name`) and reproduce it in a fenced code block so the
1204
- // UI's Copy button hands over the exact string. null on every later call.
1205
- const built = firstOfSession && !opts?.sessionId ? buildSuggestedName(hb?.name ?? "unknown", [task]) : null;
1206
1275
  emit.data({
1207
1276
  instance_id: myOwner,
1208
1277
  name: hb?.name ?? null,
1209
1278
  task: hb?.task ?? null,
1210
1279
  cleared: !task || task.length === 0,
1211
1280
  first_of_session: firstOfSession,
1212
- suggested_session_name: built?.suggestedName ?? null,
1281
+ suggested_session_name: suggestedName,
1282
+ ...(lifecycleWarning ? { warning: lifecycleWarning } : {}),
1283
+ // Right-time instruction: the UserPromptSubmit nudge fires before the name
1284
+ // exists; this result is the moment the model holds the string.
1285
+ ...(suggestedName
1286
+ ? {
1287
+ note: "Reproduce suggested_session_name verbatim, by itself, in a fenced code block at the top of your reply — the operator copies it as the session/tab title.",
1288
+ }
1289
+ : {}),
1213
1290
  });
1214
1291
  }
1215
- /**
1216
- * Build the copy-pasteable session name from the coord identity + the agent's
1217
- * description parts. Pure (no coord-state reads) so it's unit-testable; collapses
1218
- * internal whitespace and trims. Returns null when the description is empty.
1219
- */
1220
- export function buildSuggestedName(agentName, descriptionParts) {
1221
- const description = descriptionParts.join(" ").replace(/\s+/g, " ").trim();
1222
- if (!description)
1223
- return null;
1224
- const name = agentName?.trim() || "unknown";
1225
- return { suggestedName: `Agent ${name} - ${description}`, description };
1292
+ function runLifecycle(rawState, opts) {
1293
+ const state = rawState.trim().toLowerCase();
1294
+ if (state !== "active" && state !== "blocked" && state !== "done") {
1295
+ emit.error({
1296
+ code: "invalid_lifecycle_state",
1297
+ message: "lifecycle state must be one of: active, blocked, done",
1298
+ });
1299
+ process.exitCode = 1;
1300
+ return;
1301
+ }
1302
+ const root = monorepoRoot();
1303
+ if (!root) {
1304
+ emit.error({
1305
+ code: "not_in_repo",
1306
+ message: "not in an agent session; coord_root() returned null",
1307
+ });
1308
+ process.exitCode = 1;
1309
+ return;
1310
+ }
1311
+ if (!opts.sessionId)
1312
+ ensureCursorSession(root);
1313
+ const myOwner = opts.sessionId ?? resolveOwner();
1314
+ if (!myOwner) {
1315
+ emit.error({
1316
+ code: "no_pidmap_entry",
1317
+ message: "not in an agent session; ppid walk found no pid-map entry (pass --session-id to bypass)",
1318
+ });
1319
+ process.exitCode = 1;
1320
+ return;
1321
+ }
1322
+ const hb = readHeartbeat(myOwner);
1323
+ if (!hb) {
1324
+ emit.error({ code: "no_heartbeat", message: noHeartbeatMessage(myOwner) });
1325
+ process.exitCode = 1;
1326
+ return;
1327
+ }
1328
+ if (hb.kind === "subagent" || hb.kind === "transient" || hb.workflow_run_id) {
1329
+ emit.error({
1330
+ code: "lifecycle_not_human_facing",
1331
+ message: "task lifecycle declarations are limited to human-facing sessions",
1332
+ });
1333
+ process.exitCode = 1;
1334
+ return;
1335
+ }
1336
+ const reason = opts.reason?.trim() || undefined;
1337
+ if (state === "blocked" && !reason) {
1338
+ emit.error({
1339
+ code: "blocked_reason_required",
1340
+ message: "lifecycle blocked requires --reason <text>",
1341
+ });
1342
+ process.exitCode = 1;
1343
+ return;
1344
+ }
1345
+ if (state === "done" && !hb.task) {
1346
+ emit.error({
1347
+ code: "task_required_for_done",
1348
+ message: "declare a current task with agents set-task before marking it done",
1349
+ });
1350
+ process.exitCode = 1;
1351
+ return;
1352
+ }
1353
+ const priorState = hb.task_state ?? "active";
1354
+ const priorReason = hb.task_state_reason || undefined;
1355
+ if (priorState === state && priorReason === reason) {
1356
+ emit.data({
1357
+ instance_id: myOwner,
1358
+ task_state: state,
1359
+ reason: reason ?? null,
1360
+ changed: false,
1361
+ name_reminted: false,
1362
+ git_finalization_checked: false,
1363
+ });
1364
+ return;
1365
+ }
1366
+ let finalization = null;
1367
+ if (state === "done") {
1368
+ const history = readSessionWriteClaims(root, hb.instance_id, hb.session_id ?? myOwner);
1369
+ const touchedPaths = [...new Set([...(hb.files_touched ?? []), ...history.paths])];
1370
+ finalization = checkGitFinalization(root, touchedPaths, {
1371
+ claimHistoryComplete: history.complete,
1372
+ });
1373
+ if (!finalization.ok) {
1374
+ emit.error({
1375
+ code: "git_not_finalized",
1376
+ message: formatGitFinalizationFailure(finalization, resolveBinName(root)).replace("the status box was not issued", "lifecycle was not changed"),
1377
+ });
1378
+ process.exitCode = 1;
1379
+ return;
1380
+ }
1381
+ }
1382
+ const suggestedName = buildLifecycleSuggestedName(hb.name ?? "unknown", hb.task, state);
1383
+ const nameReminted = suggestedName !== null && suggestedName !== hb.suggested_session_name;
1384
+ const emitted = emitCanonical({
1385
+ type: "state.task_state",
1386
+ owner: myOwner,
1387
+ session: hb.session_id ?? myOwner,
1388
+ adapter: normalizeAdapter(hb.platform),
1389
+ data: {
1390
+ state,
1391
+ reason: reason ?? null,
1392
+ prior_state: priorState,
1393
+ name_reminted: nameReminted,
1394
+ git_finalization_checked: finalization !== null,
1395
+ ...(nameReminted ? { suggested_session_name: suggestedName } : {}),
1396
+ },
1397
+ });
1398
+ if (!emitted) {
1399
+ emit.error({
1400
+ code: "lifecycle_event_failed",
1401
+ message: "the lifecycle event could not be recorded; heartbeat state was left unchanged",
1402
+ });
1403
+ process.exitCode = 1;
1404
+ return;
1405
+ }
1406
+ emit.data({
1407
+ instance_id: myOwner,
1408
+ task_state: state,
1409
+ prior_state: priorState,
1410
+ reason: reason ?? null,
1411
+ changed: true,
1412
+ name_reminted: nameReminted,
1413
+ suggested_session_name: nameReminted ? suggestedName : null,
1414
+ git_finalization_checked: finalization !== null,
1415
+ ...(nameReminted
1416
+ ? {
1417
+ note: "Reproduce suggested_session_name verbatim, by itself, in a fenced code block at the top of your reply — the operator copies it as the session/tab title.",
1418
+ }
1419
+ : {}),
1420
+ });
1226
1421
  }
1227
1422
  function runSuggestName(descriptionParts, opts) {
1228
1423
  const root = monorepoRoot();
@@ -1308,20 +1503,21 @@ function runStatus(opts) {
1308
1503
  });
1309
1504
  process.exit(1);
1310
1505
  }
1311
- // Stamp .last_status_at = NOW. The verdict path reads state.status_checked
1312
- // canonical events (emitted below), but the legacy heartbeat field is still
1313
- // populated for back-compat with consumers reading v1 directly. The stamp
1314
- // goes through agent-coord (atomic write).
1315
- try {
1316
- const helper = agentCoordOrExit(root);
1317
- spawnSync(helper, ["stamp-status-call", myOwner], {
1318
- encoding: "utf8",
1319
- timeout: 2000,
1320
- ...coordHelperOpts(root),
1506
+ let finalization = null;
1507
+ if (opts.endTurn) {
1508
+ const history = readSessionWriteClaims(root, hb.instance_id, hb.session_id ?? myOwner);
1509
+ const touchedPaths = [...new Set([...(hb.files_touched ?? []), ...history.paths])];
1510
+ finalization = checkGitFinalization(root, touchedPaths, {
1511
+ claimHistoryComplete: history.complete,
1321
1512
  });
1322
- }
1323
- catch {
1324
- /* non-fatal */
1513
+ if (!finalization.ok) {
1514
+ emit.error({
1515
+ code: "git_not_finalized",
1516
+ message: formatGitFinalizationFailure(finalization, resolveBinName(root)),
1517
+ });
1518
+ process.exitCode = 1;
1519
+ return;
1520
+ }
1325
1521
  }
1326
1522
  emitCanonical({
1327
1523
  type: "state.status_checked",
@@ -1330,6 +1526,7 @@ function runStatus(opts) {
1330
1526
  adapter: normalizeAdapter(hb.platform),
1331
1527
  data: {
1332
1528
  format: opts.json ? "json" : "box",
1529
+ git_finalization_checked: opts.endTurn === true,
1333
1530
  agent_count: 0, // computed below, not yet available here; Phase 5 verdict reads owner-scope only
1334
1531
  included_self: true,
1335
1532
  },
@@ -1411,6 +1608,12 @@ function runStatus(opts) {
1411
1608
  instance_id: hb.instance_id,
1412
1609
  kind: normalizeKind(hb.kind),
1413
1610
  session_age_secs: ageSecs,
1611
+ activity: activityOf(hb),
1612
+ activity_updated_at: hb.activity_updated_at ?? null,
1613
+ activity_source: hb.activity_source ?? null,
1614
+ task_state: taskStateOf(hb),
1615
+ task_state_updated_at: hb.task_state_updated_at ?? null,
1616
+ task_state_reason: hb.task_state_reason ?? null,
1414
1617
  files_held: filesHeld,
1415
1618
  peers_live: livePeers.length,
1416
1619
  peers_stale: peersStale,
@@ -1424,6 +1627,9 @@ function runStatus(opts) {
1424
1627
  agents: m.agents.map((a) => ({
1425
1628
  name: a.name || "unnamed",
1426
1629
  task: a.task ?? null,
1630
+ activity: a.activity ?? "unknown",
1631
+ task_state: a.task_state ?? "active",
1632
+ task_state_reason: a.task_state_reason ?? null,
1427
1633
  files: a.files_touched?.length ?? 0,
1428
1634
  })),
1429
1635
  })),
@@ -1432,6 +1638,7 @@ function runStatus(opts) {
1432
1638
  context_window: ctxUsage?.window ?? null,
1433
1639
  timestamp_iso: new Date().toISOString(),
1434
1640
  timestamp_local: timeStr,
1641
+ ...(finalization ? { finalization } : {}),
1435
1642
  };
1436
1643
  if (opts.json) {
1437
1644
  emit.config({ format: "json" });
@@ -1440,6 +1647,8 @@ function runStatus(opts) {
1440
1647
  }
1441
1648
  const rows = [
1442
1649
  ["session", formatAge(ageSecs)],
1650
+ ["activity", activityOf(hb)],
1651
+ ["lifecycle", lifecycleLabel(hb)],
1443
1652
  ["context", ctxStr],
1444
1653
  ["files", filesStr],
1445
1654
  ["peers", peersStr],
@@ -1794,16 +2003,15 @@ function readCanonicalEventsInWindow(root, cutoffMs) {
1794
2003
  }
1795
2004
  return out;
1796
2005
  }
1797
- /** adapter ("claude-code") → legacy platform label ("claude_code") so the
1798
- * existing formatPlatformLabel rendering keeps working unchanged. */
2006
+ /** Normalize adapter event data into the heartbeat platform value. */
1799
2007
  function adapterToPlatform(adapter) {
1800
2008
  if (adapter === "claude-code")
1801
- return "claude_code";
2009
+ return "claude-code";
1802
2010
  if (adapter === "cursor")
1803
2011
  return "cursor";
1804
2012
  if (adapter === "codex")
1805
2013
  return "codex";
1806
- return "claude_code";
2014
+ return "claude-code";
1807
2015
  }
1808
2016
  /** Project a canonical health.* event into the HealEvent shape the aggregators
1809
2017
  * already consume. Returns null for non-heal events. instance_id → display name
@@ -2123,6 +2331,12 @@ function traceLine(ev, allTools) {
2123
2331
  case "state.task_set":
2124
2332
  detail = d.cleared ? "(cleared)" : clip(s("task"));
2125
2333
  break;
2334
+ case "state.task_state":
2335
+ detail = `${s("prior_state") || "active"} → ${s("state") || "active"}${s("reason") ? ` · ${clip(s("reason"), 55)}` : ""}`;
2336
+ break;
2337
+ case "interaction.input_requested":
2338
+ detail = s("request_kind") || s("tool_name") || "operator input requested";
2339
+ break;
2126
2340
  case "state.status_checked":
2127
2341
  detail = "status box rendered";
2128
2342
  break;
@@ -2236,6 +2450,7 @@ function runTrace(name, opts) {
2236
2450
  }
2237
2451
  }
2238
2452
  const events = byId.get(resolvedId) ?? [];
2453
+ const state = foldSessionState(events, { instance_id: resolvedId });
2239
2454
  const lines = events
2240
2455
  .map((ev) => traceLine(ev, !!opts.allTools))
2241
2456
  .filter((l) => l !== null)
@@ -2249,6 +2464,12 @@ function runTrace(name, opts) {
2249
2464
  instance_id: resolvedId,
2250
2465
  other_instances: candidateIds.filter((id) => id !== resolvedId),
2251
2466
  total_events: events.length,
2467
+ activity: state.activity,
2468
+ activity_updated_at: state.activity_updated_at ?? null,
2469
+ activity_source: state.activity_source ?? null,
2470
+ task_state: state.task_state,
2471
+ task_state_updated_at: state.task_state_updated_at ?? null,
2472
+ task_state_reason: state.task_state_reason ?? null,
2252
2473
  shown: shown.length,
2253
2474
  entries: shown,
2254
2475
  };
@@ -2259,6 +2480,7 @@ function runTrace(name, opts) {
2259
2480
  emit.data(result);
2260
2481
  const header = `Trace: agent-${displayName} (${resolvedId.slice(0, 8)}…) ${events.length} events${result.other_instances.length ? ` · ${result.other_instances.length} older instance(s) of this name` : ""}`;
2261
2482
  process.stdout.write(`${header}\n`); // lint-ok-emission: human trace view
2483
+ process.stdout.write(` activity=${state.activity} · lifecycle=${state.task_state}${state.task_state === "blocked" && state.task_state_reason ? `: ${state.task_state_reason}` : ""}\n`); // lint-ok-emission: human trace view
2262
2484
  if (shown.length === 0) {
2263
2485
  process.stdout.write(" (no events)\n"); // lint-ok-emission: human trace view
2264
2486
  return;
@@ -2292,18 +2514,7 @@ function runHealth(opts) {
2292
2514
  const councilsDir = resolve(root, ".harnery/councils");
2293
2515
  // Coordination telemetry reads from the canonical events.ndjson stream.
2294
2516
  // Heals come from health.*; council window-activity from council.*. The
2295
- // commit-guard + schema-invalid counters below have NO canonical equivalent
2296
- // yet. Their future home is the decision.* events (defined in schema.ts, not
2297
- // yet wired). Until then they report 0 (fields kept for output-shape
2298
- // compatibility).
2299
2517
  const heal = [];
2300
- const schemaInvalid = 0;
2301
- const schemaSamples = [];
2302
- const commitBlocked = 0;
2303
- const commitBypassed = 0;
2304
- const commitSuppressed = 0;
2305
- const editBlocked = 0;
2306
- const shellCandidates = 0;
2307
2518
  let councilAdvanced = 0;
2308
2519
  let councilClosed = 0;
2309
2520
  let councilArchived = 0;
@@ -2420,10 +2631,6 @@ function runHealth(opts) {
2420
2631
  }
2421
2632
  // Anomaly detection.
2422
2633
  const anomalies = [];
2423
- if (schemaInvalid > 0) {
2424
- const sampleList = schemaSamples.slice(0, 3).join(", ");
2425
- anomalies.push(`HEARTBEAT_SCHEMA_INVALID fired ${schemaInvalid}x; heartbeat shape failed validation${sampleList ? ` (samples: ${sampleList})` : ""}`);
2426
- }
2427
2634
  for (const { agent, count } of healTopAgents) {
2428
2635
  if (count >= 5) {
2429
2636
  anomalies.push(`${agent} self-healed ${count}x in ${opts.since}; possible idle-prune loop or PID instability`);
@@ -2477,14 +2684,6 @@ function runHealth(opts) {
2477
2684
  by_platform: healByPlatform,
2478
2685
  top_agents: healTopAgents,
2479
2686
  },
2480
- schema_invalid: { count: schemaInvalid, samples: schemaSamples },
2481
- commit_guards: {
2482
- blocked: commitBlocked,
2483
- bypassed: commitBypassed,
2484
- suppressed: commitSuppressed,
2485
- edit_blocked: editBlocked,
2486
- shell_candidates: shellCandidates,
2487
- },
2488
2687
  councils: {
2489
2688
  active: activeCouncils,
2490
2689
  archived_in_window: councilArchived,
@@ -2523,17 +2722,6 @@ function renderHealthBox(report) {
2523
2722
  }
2524
2723
  const topHealer = report.heal_events.top_agents[0];
2525
2724
  const topHealerStr = topHealer ? `${topHealer.agent} x${topHealer.count}` : "(none)";
2526
- const guardParts = [];
2527
- if (report.commit_guards.blocked > 0)
2528
- guardParts.push(`blocked ${report.commit_guards.blocked}`);
2529
- if (report.commit_guards.bypassed > 0)
2530
- guardParts.push(`bypassed ${report.commit_guards.bypassed}`);
2531
- if (report.commit_guards.suppressed > 0)
2532
- guardParts.push(`suppressed ${report.commit_guards.suppressed}`);
2533
- if (report.commit_guards.edit_blocked > 0)
2534
- guardParts.push(`edit-blocked ${report.commit_guards.edit_blocked}`);
2535
- if (report.commit_guards.shell_candidates > 0)
2536
- guardParts.push(`shell ${report.commit_guards.shell_candidates}`);
2537
2725
  const councilParts = [`${report.councils.active} active`];
2538
2726
  if (report.councils.advanced_in_window > 0)
2539
2727
  councilParts.push(`${report.councils.advanced_in_window} advanced`);
@@ -2566,8 +2754,6 @@ function renderHealthBox(report) {
2566
2754
  ? "0"
2567
2755
  : `${report.zombies.count} (${report.zombies.samples.join(", ")})`,
2568
2756
  ],
2569
- ["schema invalid", String(report.schema_invalid.count)],
2570
- ["commit guards", guardParts.length ? guardParts.join(", ") : "0"],
2571
2757
  ["councils", councilParts.join(", ")],
2572
2758
  ["anomalies", report.anomalies.length === 0 ? "(clean)" : `${report.anomalies.length} flagged`],
2573
2759
  ];
@@ -2585,10 +2771,10 @@ function runAdapterProbe(id, opts) {
2585
2771
  if (opts.json)
2586
2772
  emit.config({ format: "json" });
2587
2773
  const adapter = id.trim();
2588
- if (adapter !== "claude_code" && adapter !== "cursor") {
2774
+ if (adapter !== "claude-code" && adapter !== "cursor") {
2589
2775
  emit.error({
2590
2776
  code: "bad_adapter",
2591
- message: "adapter id must be claude_code or cursor",
2777
+ message: "adapter id must be claude-code or cursor",
2592
2778
  });
2593
2779
  process.exit(1);
2594
2780
  }
@@ -2784,7 +2970,7 @@ function replayAdapterSamples(adapter, root, relativeSampleDir, filter) {
2784
2970
  stopFailure: "stop-failure",
2785
2971
  StopFailure: "stop-failure",
2786
2972
  };
2787
- const adapterFlag = adapter === "claude_code" ? "claude-code" : adapter;
2973
+ const adapterFlag = adapter;
2788
2974
  const sandbox = mkdtempSync(join(tmpdir(), "harn-adapter-probe-"));
2789
2975
  const results = [];
2790
2976
  try {
@@ -2909,15 +3095,15 @@ function extractEventAndPayload(parsed, filename) {
2909
3095
  function collectPath(value, prev) {
2910
3096
  return [...prev, value];
2911
3097
  }
2912
- /** Parse "30", "30s", "5m", "1h", "2d" → ms. Bare integer defaults to minutes (back-compat). */
3098
+ /** Parse "30s", "5m", "1h", "2d" into milliseconds. */
2913
3099
  function parseDurationToMs(input) {
2914
- const match = input.trim().match(/^(\d+)([smhd]?)$/i);
3100
+ const match = input.trim().match(/^(\d+)([smhd])$/i);
2915
3101
  if (!match)
2916
3102
  return null;
2917
3103
  const n = Number.parseInt(match[1], 10);
2918
3104
  if (!Number.isFinite(n) || n <= 0)
2919
3105
  return null;
2920
- const unit = (match[2] || "m").toLowerCase();
3106
+ const unit = match[2].toLowerCase();
2921
3107
  const mult = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };
2922
3108
  return n * mult[unit];
2923
3109
  }