agent-yes 1.226.1 → 1.227.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 (49) hide show
  1. package/dist/{SUPPORTED_CLIS-xM8S6xqo.js → SUPPORTED_CLIS-CFd1V2O5.js} +2 -2
  2. package/dist/SUPPORTED_CLIS-CJiLdm01.js +11 -0
  3. package/dist/{agent-yes.config-DmW_TVFI.js → agent-yes.config-0G11aPWe.js} +2 -2
  4. package/dist/{agentShare-DSYm87vH.js → agentShare-C_rydTfo.js} +7 -7
  5. package/dist/{callback-DlzoC3fU.js → callback-BV3AhWeB.js} +5 -5
  6. package/dist/callback-BgWZmpqC.js +12 -0
  7. package/dist/{callbackCore-DSxbO_6a.js → callbackCore-B3DNe_SP.js} +1 -1
  8. package/dist/{callbackCore-CgnOT1Cp.js → callbackCore-BlNdDILy.js} +1 -1
  9. package/dist/cli.js +9 -9
  10. package/dist/{configShared-nnIwDYY-.js → configShared-YKfS6NMe.js} +1 -1
  11. package/dist/{cwdConflictWarn-CnxkLs7T.js → cwdConflictWarn-CJZRp4S0.js} +1 -1
  12. package/dist/{e2e-D5jaSHyY.js → e2e-DB7BDdEA.js} +1 -1
  13. package/dist/{expose-CKWXAkzK.js → expose-GWRXTGpj.js} +1 -1
  14. package/dist/{forkNested-DJ2QzBsc.js → forkNested-BWdv2mXC.js} +1 -1
  15. package/dist/index.js +3 -4
  16. package/dist/{nodeRuntime-CR0NwnnO.js → nodeRuntime-CgXWHe9p.js} +1 -1
  17. package/dist/{notifyDaemon-4qxS_FjO.js → notifyDaemon-CyLvaAwc.js} +6 -6
  18. package/dist/{openBrowser-D9VVE6cq.js → openBrowser-CSfI8zCG.js} +1 -1
  19. package/dist/{remotes-DZ_G2wkM.js → remotes-BCgowsiu.js} +3 -3
  20. package/dist/{remotes-BtXom7Rp.js → remotes-ZqD0FHl_.js} +3 -3
  21. package/dist/{rustBinary-KhrcXvB6.js → rustBinary-DHgMI1uK.js} +2 -2
  22. package/dist/{schedule-HHBT6eGE.js → schedule-0p4V_khb.js} +7 -7
  23. package/dist/{serve-BeHHkDIn.js → serve-C2KCUxTP.js} +29 -29
  24. package/dist/{setup-nAQJffmr.js → setup-CCKS4rtW.js} +3 -3
  25. package/dist/{share-BwEUEHfy.js → share-CrvxZO7o.js} +2 -2
  26. package/dist/{share-B7VP4_UF.js → share-ieE7D0C1.js} +2 -2
  27. package/dist/{spawnGate-CfcpmQmk.js → spawnGate-CIC7b7nm.js} +1 -1
  28. package/dist/{spawnGate-ChYCj6Dm.js → spawnGate-CeFYH-Ib.js} +2 -2
  29. package/dist/{subcommands-DYeJJ2Rh.js → subcommands-CsVEI376.js} +5 -5
  30. package/dist/{subcommands-D8zJxifk.js → subcommands-FTkZkqKy.js} +15 -15
  31. package/dist/{todoAutomation-fxtfT_44.js → todoAutomation-Cwnh4aWo.js} +300 -4
  32. package/dist/todoCli-6v1CYdxN.js +427 -0
  33. package/dist/{tray-BZqJtEmL.js → tray-DBt8X-Qj.js} +1 -1
  34. package/dist/{ts-yPc1x2-7.js → ts-CT75CpeM.js} +3 -3
  35. package/dist/{versionChecker-8h_rjT-C.js → versionChecker-Bd09mqkZ.js} +2 -2
  36. package/dist/{webrtcLink-DknpJQ0y.js → webrtcLink-CjckgIZe.js} +2 -2
  37. package/dist/{webrtcRemote-C974zjSt.js → webrtcRemote-BMzEt9Cp.js} +3 -3
  38. package/dist/{workspaceConfig-BRl-GwSN.js → workspaceConfig-De5tgjeO.js} +1 -1
  39. package/dist/{ws-YCNt3rNz.js → ws-B4zHUwc6.js} +7 -7
  40. package/package.json +1 -1
  41. package/ts/todoCli.spec.ts +80 -5
  42. package/ts/todoCli.ts +471 -336
  43. package/ts/todoStore.spec.ts +58 -0
  44. package/ts/todoStore.ts +42 -0
  45. package/dist/SUPPORTED_CLIS-C5vBin1r.js +0 -11
  46. package/dist/callback-BmgFE-2j.js +0 -12
  47. package/dist/todoCli-D8bL164c.js +0 -336
  48. package/dist/todoDigest-DSTG9ksk.js +0 -284
  49. package/dist/todoDigest-gKP1XibO.js +0 -3
@@ -3,9 +3,9 @@ import { n as logger } from "./logger-CDIsZ-Pp.js";
3
3
  import { t as agentYesHome } from "./agentYesHome-CtHb5b71.js";
4
4
  import { a as updateGlobalPidStatus, i as readGlobalPids } from "./globalPidIndex-CoNr7tS8.js";
5
5
  import { a as recordOutbox, i as recordMessage, n as readMailbox, t as partyMatches } from "./messageLog-Bb_Ie3Ck.js";
6
- import { t as loadSharedCliDefaults } from "./configShared-nnIwDYY-.js";
7
- import { n as isWebrtcSpec } from "./webrtcLink-DknpJQ0y.js";
8
- import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-BtXom7Rp.js";
6
+ import { t as loadSharedCliDefaults } from "./configShared-YKfS6NMe.js";
7
+ import { n as isWebrtcSpec } from "./webrtcLink-CjckgIZe.js";
8
+ import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-ZqD0FHl_.js";
9
9
  import ms from "ms";
10
10
  import yargs from "yargs";
11
11
  import { appendFile, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "fs/promises";
@@ -1239,35 +1239,35 @@ async function runSubcommand(argv) {
1239
1239
  case "restart": return await cmdRestart(rest);
1240
1240
  case "note": return await cmdNote(rest);
1241
1241
  case "todo": {
1242
- const { runTodoSubcommand } = await import("./todoCli-D8bL164c.js");
1242
+ const { runTodoSubcommand } = await import("./todoCli-6v1CYdxN.js");
1243
1243
  return runTodoSubcommand(rest);
1244
1244
  }
1245
1245
  case "serve": {
1246
- const { cmdServe } = await import("./serve-BeHHkDIn.js");
1246
+ const { cmdServe } = await import("./serve-C2KCUxTP.js");
1247
1247
  return cmdServe(rest);
1248
1248
  }
1249
1249
  case "setup": {
1250
- const { cmdSetup } = await import("./setup-nAQJffmr.js");
1250
+ const { cmdSetup } = await import("./setup-CCKS4rtW.js");
1251
1251
  return cmdSetup(rest);
1252
1252
  }
1253
1253
  case "ws": {
1254
- const { cmdWs } = await import("./ws-YCNt3rNz.js");
1254
+ const { cmdWs } = await import("./ws-B4zHUwc6.js");
1255
1255
  return cmdWs(rest);
1256
1256
  }
1257
1257
  case "schedule": {
1258
- const { cmdSchedule } = await import("./schedule-HHBT6eGE.js");
1258
+ const { cmdSchedule } = await import("./schedule-0p4V_khb.js");
1259
1259
  return cmdSchedule(rest);
1260
1260
  }
1261
1261
  case "remote": {
1262
- const { cmdRemote } = await import("./remotes-DZ_G2wkM.js");
1262
+ const { cmdRemote } = await import("./remotes-BCgowsiu.js");
1263
1263
  return cmdRemote(rest);
1264
1264
  }
1265
1265
  case "expose": {
1266
- const { cmdExpose } = await import("./expose-CKWXAkzK.js");
1266
+ const { cmdExpose } = await import("./expose-GWRXTGpj.js");
1267
1267
  return cmdExpose(rest);
1268
1268
  }
1269
1269
  case "callback": {
1270
- const { cmdCallback } = await import("./callback-BmgFE-2j.js");
1270
+ const { cmdCallback } = await import("./callback-BgWZmpqC.js");
1271
1271
  return cmdCallback(rest);
1272
1272
  }
1273
1273
  case "reap":
@@ -1685,7 +1685,7 @@ async function fetchRemoteRecordsRaw(url, token, opts) {
1685
1685
  let base = url;
1686
1686
  let bearer = token;
1687
1687
  if (isWebrtcSpec(url)) {
1688
- const { startWebrtcBridge } = await import("./webrtcRemote-C974zjSt.js");
1688
+ const { startWebrtcBridge } = await import("./webrtcRemote-BMzEt9Cp.js");
1689
1689
  bridge = await startWebrtcBridge(url);
1690
1690
  base = bridge.baseUrl;
1691
1691
  bearer = bridge.token;
@@ -4305,7 +4305,7 @@ async function cmdNotify(rest) {
4305
4305
  }
4306
4306
  const ensure = async () => {
4307
4307
  if (!argv["ensure-daemon"]) return;
4308
- const { ensureDaemon } = await import("./notifyDaemon-4qxS_FjO.js");
4308
+ const { ensureDaemon } = await import("./notifyDaemon-CyLvaAwc.js");
4309
4309
  await ensureDaemon().catch(() => null);
4310
4310
  };
4311
4311
  await heartbeatWatcher(parent, selfStartedAt);
@@ -4379,7 +4379,7 @@ async function cmdNotifyCursor(args) {
4379
4379
  }
4380
4380
  async function cmdNotifyd(rest) {
4381
4381
  const sub = rest[0] ?? "status";
4382
- const daemon = await import("./notifyDaemon-4qxS_FjO.js");
4382
+ const daemon = await import("./notifyDaemon-CyLvaAwc.js");
4383
4383
  switch (sub) {
4384
4384
  case "run": return daemon.runDaemon();
4385
4385
  case "once": return daemon.runDaemon({ once: true });
@@ -4406,4 +4406,4 @@ async function cmdNotifyd(rest) {
4406
4406
 
4407
4407
  //#endregion
4408
4408
  export { daemonLockDir as $, renderLogTailLines as A, stopTipForCli as B, matchKeyword as C, readPtysize as D, readNotes as E, resolveResumeArgs as F, appendEvent as G, waitForLogQuiet as H, restartHintLines as I, isTransientLockMkdirError as J, gcInboxes as K, runSubcommand as L, renderRawLogLines as M, resolveOne as N, recentMessageEdges as O, resolveReadWindow as P, shouldStealLock as Q, snapshotStatus as R, listRecords as S, readAgentPtysize as T, writeKeysPaced as U, submitAndConfirm as V, writeToIpc as W, liveWatchers as X, listInboxParents as Y, readInbox as Z, isPidAlive as _, cmdHelp as a, isUserTyping as b, deriveLiveState as c, extractMenu as d, daemonLockOwnerPath as et, extractNeedsInput as f, isExitRequest as g, isAgentStuck as h, backoffWhileTyping as i, renderRawLog as j, recentReadEdges as k, deriveLiveStatus as l, finalizedLines as m, READ_PAGE_DEFAULT as n, TYPING_BADGE as nt, controlCodeFromName as o, extractTaskCounts as p, hostId as q, TYPING_WINDOW_MS as r, cursorAbs as s, GRACEFUL_EXIT_COMMANDS as t, notifyDir as tt, extractBadges as u, isSlashCommand as v, menuSelectKeys as w, lastStdinAt as x, isSubcommand as y, stdinActivityPath as z };
4409
- //# sourceMappingURL=subcommands-D8zJxifk.js.map
4409
+ //# sourceMappingURL=subcommands-FTkZkqKy.js.map
@@ -1,8 +1,152 @@
1
1
  import { t as JsonlStore } from "./JsonlStore-Bm5Nj6v_.js";
2
- import { a as unblockedTasks, c as ORPHANED_STATE, l as canTransition, o as DONE_STATE, p as requiredGate, s as LIFECYCLES, u as initialState } from "./todoDigest-DSTG9ksk.js";
3
2
  import path from "path";
4
3
  import { lock } from "proper-lockfile";
5
4
 
5
+ //#region ts/todoLifecycle.ts
6
+ /**
7
+ * States considered "finished" across every kind. A finished task is never
8
+ * reopened by automation (see `todoAutomation.ts` in a later milestone) —
9
+ * only a human explicitly reopening it (e.g. `verify-failed` → the kind's
10
+ * doing state) moves a task out of a finished-adjacent state.
11
+ */
12
+ const DONE_STATE = "done";
13
+ /**
14
+ * Not a normal graph edge on any kind's `transitions` list — a task can be
15
+ * moved here from ANY state, by automation only (see `todoAutomation.ts`),
16
+ * when its owning agent process has vanished. Kept out of the declarative
17
+ * graphs deliberately: it is a side-channel signal about the WORKER, not a
18
+ * statement about the work itself, so it should never appear as a normal
19
+ * `canTransition`-reachable edge a human/CLI could aim for directly.
20
+ */
21
+ const ORPHANED_STATE = "orphaned";
22
+ const LIFECYCLES = {
23
+ code: {
24
+ states: [
25
+ "doing",
26
+ "merged",
27
+ "shipped",
28
+ "verifying",
29
+ "done",
30
+ "verify-failed",
31
+ "orphaned"
32
+ ],
33
+ transitions: [
34
+ {
35
+ from: "doing",
36
+ to: "merged"
37
+ },
38
+ {
39
+ from: "merged",
40
+ to: "shipped"
41
+ },
42
+ {
43
+ from: "shipped",
44
+ to: "verifying"
45
+ },
46
+ {
47
+ from: "verifying",
48
+ to: "done",
49
+ gate: "verify-green"
50
+ },
51
+ {
52
+ from: "verifying",
53
+ to: "verify-failed",
54
+ gate: "verify-red"
55
+ },
56
+ {
57
+ from: "verify-failed",
58
+ to: "doing"
59
+ }
60
+ ]
61
+ },
62
+ decision: {
63
+ states: [
64
+ "deciding",
65
+ "decided",
66
+ "done"
67
+ ],
68
+ transitions: [{
69
+ from: "deciding",
70
+ to: "decided",
71
+ gate: "human-decided"
72
+ }, {
73
+ from: "decided",
74
+ to: "done"
75
+ }]
76
+ },
77
+ doc: {
78
+ states: [
79
+ "drafting",
80
+ "review",
81
+ "done"
82
+ ],
83
+ transitions: [{
84
+ from: "drafting",
85
+ to: "review"
86
+ }, {
87
+ from: "review",
88
+ to: "done",
89
+ gate: "human-approved"
90
+ }]
91
+ },
92
+ investigation: {
93
+ states: [
94
+ "investigating",
95
+ "reported",
96
+ "done"
97
+ ],
98
+ transitions: [{
99
+ from: "investigating",
100
+ to: "reported"
101
+ }, {
102
+ from: "reported",
103
+ to: "done",
104
+ gate: "human-acknowledged"
105
+ }]
106
+ },
107
+ human: {
108
+ states: [
109
+ "pending",
110
+ "decided",
111
+ "done"
112
+ ],
113
+ transitions: [{
114
+ from: "pending",
115
+ to: "decided",
116
+ gate: "human-replied"
117
+ }, {
118
+ from: "decided",
119
+ to: "done"
120
+ }]
121
+ }
122
+ };
123
+ /** The state a newly-created task of `kind` starts in (its graph's first state). */
124
+ function initialState(kind) {
125
+ const first = LIFECYCLES[kind].states[0];
126
+ if (!first) throw new Error(`lifecycle kind "${kind}" has no states`);
127
+ return first;
128
+ }
129
+ /** States reachable in one transition from `currentState`, regardless of gates. */
130
+ function nextStates(kind, currentState) {
131
+ return LIFECYCLES[kind].transitions.filter((t) => t.from === currentState).map((t) => t.to);
132
+ }
133
+ /** Whether `from`→`to` is a transition that exists in `kind`'s graph at all (gate not evaluated here). */
134
+ function canTransition(kind, from, to) {
135
+ return LIFECYCLES[kind].transitions.some((t) => t.from === from && t.to === to);
136
+ }
137
+ /** The gate name required for `from`→`to`, or null if the edge is ungated or does not exist. */
138
+ function requiredGate(kind, from, to) {
139
+ return LIFECYCLES[kind].transitions.find((e) => e.from === from && e.to === to)?.gate ?? null;
140
+ }
141
+ /** All valid state names for `kind` — used to validate stored records and CLI input. */
142
+ function statesOf(kind) {
143
+ return LIFECYCLES[kind].states;
144
+ }
145
+ function isKnownKind(value) {
146
+ return value in LIFECYCLES;
147
+ }
148
+
149
+ //#endregion
6
150
  //#region ts/todoStore.ts
7
151
  /**
8
152
  * Persistence and gate-enforcement layer for the `ay todo` engine.
@@ -180,6 +324,7 @@ var TodoStore = class TodoStore {
180
324
  createdAt: now,
181
325
  updatedAt: now,
182
326
  ...input.targetTier ? { targetTier: input.targetTier } : {},
327
+ ...input.acceptanceCriteria?.trim() ? { acceptanceCriteria: input.acceptanceCriteria.trim() } : {},
183
328
  ...input.owner ? { owner: input.owner } : {}
184
329
  };
185
330
  await this.jsonl.append({
@@ -240,7 +385,8 @@ var TodoStore = class TodoStore {
240
385
  link: evidence?.link,
241
386
  gate: gateName,
242
387
  passedAt: (/* @__PURE__ */ new Date()).toISOString(),
243
- validator
388
+ validator,
389
+ ...rec.acceptanceCriteria ? { acceptanceCriteriaAtApproval: rec.acceptanceCriteria } : {}
244
390
  };
245
391
  await this.jsonl.updateById(id, {
246
392
  satisfiedGates: [...satisfied],
@@ -339,6 +485,19 @@ var TodoStore = class TodoStore {
339
485
  return this.mustGet(id);
340
486
  });
341
487
  }
488
+ /**
489
+ * Set or update a task's acceptance criteria (free text) after creation —
490
+ * criteria may firm up once a task is underway, not just at `create()`
491
+ * time. Requires non-empty text: this method updates criteria, it does
492
+ * not clear them (clearing would need a `null`-vs-`undefined` sentinel
493
+ * distinction, like `block` has, which isn't needed for the "set/update"
494
+ * use case this exists for).
495
+ */
496
+ async setAcceptanceCriteria(id, text) {
497
+ const trimmed = text.trim();
498
+ if (!trimmed) throw new Error(`task ${id}: acceptance criteria text must not be empty`);
499
+ return this.rawUpdate(id, () => ({ acceptanceCriteria: trimmed }));
500
+ }
342
501
  setBlock(id, block) {
343
502
  return this.rawUpdate(id, () => ({ block }));
344
503
  }
@@ -449,6 +608,143 @@ function describeBlock(block) {
449
608
  }
450
609
  }
451
610
 
611
+ //#endregion
612
+ //#region ts/todoDigest.ts
613
+ /**
614
+ * Read-side views over the `ay todo` store: dependency-tree rendering and a
615
+ * per-tag digest board. Pure functions over an already-loaded task list — no
616
+ * I/O — so they are trivially unit-testable and reusable by both the CLI
617
+ * (`todoCli.ts`) and any future web view.
618
+ *
619
+ * `unblockedTasks` is ported near-verbatim from the equivalent, already
620
+ * mutation-tested algorithm in symval-dev-cli's `deps.ts` (a closed-source
621
+ * downstream consumer of an earlier, less general version of this idea) —
622
+ * generalized here to the new `TodoRecord` shape and the `done` state name
623
+ * from `todoLifecycle.ts` instead of a hardcoded status string.
624
+ */
625
+ /**
626
+ * The full set of task ids `t` is waiting on: the structural `blockedBy`
627
+ * array PLUS, if `t.block` is a `blocked-by-task` shape, that block's own
628
+ * `taskId`. Without folding the latter in, a task blocked via
629
+ * `ay todo block --type blocked-by-task --task X` would never be detected
630
+ * as resolved once X reaches `done` — contradicting `todoBlock.ts`'s own
631
+ * documented promise that this block type "clears itself... pure data, no
632
+ * monitor needed" (codex-review Important: the two mechanisms didn't
633
+ * actually talk to each other, making `blocked-by-task` blocks inert).
634
+ */
635
+ function declaredTaskDeps(t) {
636
+ return t.block?.type === "blocked-by-task" ? [...t.blockedBy, t.block.taskId] : t.blockedBy;
637
+ }
638
+ /**
639
+ * A task is "unblocked" when it is not itself finished, it declares task
640
+ * dependencies (via `blockedBy` and/or a `blocked-by-task` block), and
641
+ * every one of them has reached `done`.
642
+ * Surfaced for a caller to act on — never auto-applied here (deciding to
643
+ * resume a task is left to automation, a later milestone, or a human).
644
+ */
645
+ function unblockedTasks(tasks) {
646
+ const byId = new Map(tasks.map((t) => [t._id, t]));
647
+ return tasks.filter((t) => {
648
+ if (t.state === DONE_STATE) return false;
649
+ const deps = declaredTaskDeps(t);
650
+ if (deps.length === 0) return false;
651
+ return deps.every((d) => byId.get(d)?.state === DONE_STATE);
652
+ });
653
+ }
654
+ /** Blockers of `t` that have not reached `done` yet (a missing id is reported as-is, still "open"). */
655
+ function openBlockers(t, byId) {
656
+ return declaredTaskDeps(t).filter((d) => byId.get(d)?.state !== DONE_STATE);
657
+ }
658
+ function nodeLine(t) {
659
+ return [`${t._id} [${t.state}] ${t.summary}`, ...t.owner ? [`owner:${t.owner}`] : []].join(" ");
660
+ }
661
+ /**
662
+ * Dependency tree, rendered from roots (tasks nothing depends on, that
663
+ * themselves declare at least one dependency) down their `blockedBy` edges:
664
+ * a parent's children are what it is waiting for.
665
+ */
666
+ function renderTree(tasks, rootId) {
667
+ const byId = new Map(tasks.map((t) => [t._id, t]));
668
+ const dependedOn = new Set(tasks.flatMap((t) => t.blockedBy));
669
+ const roots = rootId ? [byId.get(rootId) ?? null].filter((t) => t !== null) : tasks.filter((t) => !dependedOn.has(t._id) && t.blockedBy.length > 0);
670
+ if (rootId && roots.length === 0) throw new Error(`no such task: ${rootId}`);
671
+ if (roots.length === 0) return "(no dependency links)";
672
+ const lines = [];
673
+ const walk = (t, prefix, isLast, depth, seen) => {
674
+ lines.push(depth === 0 ? nodeLine(t) : `${prefix}${isLast ? "└─" : "├─"} ${nodeLine(t)}`);
675
+ if (seen.has(t._id)) return;
676
+ seen.add(t._id);
677
+ const deps = t.blockedBy.map((d) => byId.get(d)).filter((x) => x !== void 0);
678
+ deps.forEach((d, i) => {
679
+ walk(d, depth === 0 ? "" : `${prefix}${isLast ? " " : "│ "}`, i === deps.length - 1, depth + 1, seen);
680
+ });
681
+ };
682
+ roots.forEach((r) => walk(r, "", true, 0, /* @__PURE__ */ new Set()));
683
+ return lines.join("\n");
684
+ }
685
+ /**
686
+ * JSON-shaped equivalent of `renderTree` — same roots-and-edges logic, built
687
+ * as data instead of formatted lines, so a machine caller (`--format json`)
688
+ * gets the actual tree structure rather than a string it would have to
689
+ * re-parse (codex-review Important: the CLI previously ignored --format for
690
+ * this command entirely, always writing the human text with exit code 0).
691
+ */
692
+ function buildTreeJSON(tasks, rootId) {
693
+ const byId = new Map(tasks.map((t) => [t._id, t]));
694
+ const dependedOn = new Set(tasks.flatMap((t) => t.blockedBy));
695
+ const roots = rootId ? [byId.get(rootId) ?? null].filter((t) => t !== null) : tasks.filter((t) => !dependedOn.has(t._id) && t.blockedBy.length > 0);
696
+ if (rootId && roots.length === 0) throw new Error(`no such task: ${rootId}`);
697
+ const build = (t, ancestors) => {
698
+ const node = {
699
+ id: t._id,
700
+ state: t.state,
701
+ summary: t.summary,
702
+ ...t.owner ? { owner: t.owner } : {},
703
+ children: []
704
+ };
705
+ if (ancestors.has(t._id)) return node;
706
+ const nextAncestors = new Set(ancestors).add(t._id);
707
+ node.children = t.blockedBy.map((d) => byId.get(d)).filter((x) => x !== void 0).map((d) => build(d, nextAncestors));
708
+ return node;
709
+ };
710
+ return roots.map((r) => build(r, /* @__PURE__ */ new Set()));
711
+ }
712
+ /** Per-tag board: state counts per tag, plus an unblocked-tasks callout. */
713
+ function renderDigest(tasks) {
714
+ if (tasks.length === 0) return "(no tasks)";
715
+ const byId = new Map(tasks.map((t) => [t._id, t]));
716
+ const unblocked = new Set(unblockedTasks(tasks).map((t) => t._id));
717
+ const streams = /* @__PURE__ */ new Map();
718
+ for (const t of tasks) for (const tag of t.tags.length ? t.tags : ["(untagged)"]) {
719
+ if (!streams.has(tag)) streams.set(tag, []);
720
+ streams.get(tag).push(t);
721
+ }
722
+ const lines = [];
723
+ for (const [stream, items] of [...streams.entries()].sort(([a], [b]) => a.localeCompare(b))) {
724
+ const allStates = [...new Set(items.map((t) => t.state))].sort();
725
+ const counts = allStates.map((s) => `${s}:${items.filter((t) => t.state === s).length}`).join(" ");
726
+ lines.push(`## ${stream} (${counts})`);
727
+ for (const s of allStates) for (const t of items.filter((x) => x.state === s)) {
728
+ const open = openBlockers(t, byId);
729
+ const extra = [
730
+ ...t.owner ? [`owner:${t.owner}`] : [],
731
+ ...t.block ? [`block:${t.block.type}`] : [],
732
+ ...open.length ? [`blockedBy:${open.join(",")}`] : [],
733
+ ...unblocked.has(t._id) ? ["UNBLOCKED"] : []
734
+ ];
735
+ lines.push(` [${t.state}] ${t._id} ${t.summary}${extra.length ? ` [${extra.join(" ")}]` : ""}`);
736
+ }
737
+ lines.push("");
738
+ }
739
+ const ub = unblockedTasks(tasks);
740
+ if (ub.length) {
741
+ lines.push("## unblocked (all blockers reached done — resume these)");
742
+ for (const t of ub) lines.push(` ${t._id} ${t.summary}${t.owner ? ` (owner:${t.owner})` : ""}`);
743
+ lines.push("");
744
+ }
745
+ return lines.join("\n").trimEnd();
746
+ }
747
+
452
748
  //#endregion
453
749
  //#region ts/todoAutomation.ts
454
750
  /**
@@ -552,5 +848,5 @@ function reconcileTodos(tasks, agents, registeredGates) {
552
848
  }
553
849
 
554
850
  //#endregion
555
- export { TodoStore as a, CycleError as i, describeBlock as n, openStore as o, monitorHint as r, reconcileTodos as t };
556
- //# sourceMappingURL=todoAutomation-fxtfT_44.js.map
851
+ export { isKnownKind as _, renderTree as a, statesOf as b, monitorHint as c, openStore as d, DONE_STATE as f, initialState as g, canTransition as h, renderDigest as i, CycleError as l, ORPHANED_STATE as m, buildTreeJSON as n, unblockedTasks as o, LIFECYCLES as p, openBlockers as r, describeBlock as s, reconcileTodos as t, TodoStore as u, nextStates as v, requiredGate as y };
852
+ //# sourceMappingURL=todoAutomation-Cwnh4aWo.js.map