@exla/rudder 0.0.0-stage → 3.4.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 (187) hide show
  1. package/LICENSE +100 -0
  2. package/README.md +733 -2
  3. package/assets/rudder.icns +0 -0
  4. package/assets/sounds/NOTICE.md +8 -0
  5. package/assets/sounds/ping.mp3 +0 -0
  6. package/dist/agent-attention.d.ts +5 -0
  7. package/dist/agent-attention.js +54 -0
  8. package/dist/agent-attention.js.map +1 -0
  9. package/dist/analytics.d.ts +36 -0
  10. package/dist/analytics.js +178 -0
  11. package/dist/analytics.js.map +1 -0
  12. package/dist/attach-session.d.ts +91 -0
  13. package/dist/attach-session.js +290 -0
  14. package/dist/attach-session.js.map +1 -0
  15. package/dist/auth.d.ts +36 -0
  16. package/dist/auth.js +397 -0
  17. package/dist/auth.js.map +1 -0
  18. package/dist/backends.d.ts +3 -0
  19. package/dist/backends.js +638 -0
  20. package/dist/backends.js.map +1 -0
  21. package/dist/board/board.css +1 -0
  22. package/dist/board/board.js +2 -0
  23. package/dist/board/daemon.d.ts +39 -0
  24. package/dist/board/daemon.js +1510 -0
  25. package/dist/board/daemon.js.map +1 -0
  26. package/dist/brain.d.ts +24 -0
  27. package/dist/brain.js +244 -0
  28. package/dist/brain.js.map +1 -0
  29. package/dist/bus.d.ts +9 -0
  30. package/dist/bus.js +23 -0
  31. package/dist/bus.js.map +1 -0
  32. package/dist/checkout-sync.d.ts +35 -0
  33. package/dist/checkout-sync.js +247 -0
  34. package/dist/checkout-sync.js.map +1 -0
  35. package/dist/cloud.d.ts +51 -0
  36. package/dist/cloud.js +4672 -0
  37. package/dist/cloud.js.map +1 -0
  38. package/dist/codex-binary.d.ts +12 -0
  39. package/dist/codex-binary.js +184 -0
  40. package/dist/codex-binary.js.map +1 -0
  41. package/dist/context-audit.d.ts +21 -0
  42. package/dist/context-audit.js +109 -0
  43. package/dist/context-audit.js.map +1 -0
  44. package/dist/daemon.d.ts +27 -0
  45. package/dist/daemon.js +203 -0
  46. package/dist/daemon.js.map +1 -0
  47. package/dist/effort.d.ts +9 -0
  48. package/dist/effort.js +72 -0
  49. package/dist/effort.js.map +1 -0
  50. package/dist/feedback.d.ts +46 -0
  51. package/dist/feedback.js +173 -0
  52. package/dist/feedback.js.map +1 -0
  53. package/dist/gc.d.ts +3 -0
  54. package/dist/gc.js +262 -0
  55. package/dist/gc.js.map +1 -0
  56. package/dist/git.d.ts +31 -0
  57. package/dist/git.js +455 -0
  58. package/dist/git.js.map +1 -0
  59. package/dist/goal.d.ts +33 -0
  60. package/dist/goal.js +95 -0
  61. package/dist/goal.js.map +1 -0
  62. package/dist/graph.d.ts +93 -0
  63. package/dist/graph.js +372 -0
  64. package/dist/graph.js.map +1 -0
  65. package/dist/handoff.d.ts +110 -0
  66. package/dist/handoff.js +565 -0
  67. package/dist/handoff.js.map +1 -0
  68. package/dist/improve/advisor.d.ts +10 -0
  69. package/dist/improve/advisor.js +115 -0
  70. package/dist/improve/advisor.js.map +1 -0
  71. package/dist/improve/collect.d.ts +49 -0
  72. package/dist/improve/collect.js +274 -0
  73. package/dist/improve/collect.js.map +1 -0
  74. package/dist/improve/gate.d.ts +17 -0
  75. package/dist/improve/gate.js +60 -0
  76. package/dist/improve/gate.js.map +1 -0
  77. package/dist/improve/index.d.ts +9 -0
  78. package/dist/improve/index.js +376 -0
  79. package/dist/improve/index.js.map +1 -0
  80. package/dist/improve/judge.d.ts +32 -0
  81. package/dist/improve/judge.js +114 -0
  82. package/dist/improve/judge.js.map +1 -0
  83. package/dist/improve/mine.d.ts +31 -0
  84. package/dist/improve/mine.js +192 -0
  85. package/dist/improve/mine.js.map +1 -0
  86. package/dist/improve/propose.d.ts +51 -0
  87. package/dist/improve/propose.js +150 -0
  88. package/dist/improve/propose.js.map +1 -0
  89. package/dist/improve/rank.d.ts +16 -0
  90. package/dist/improve/rank.js +49 -0
  91. package/dist/improve/rank.js.map +1 -0
  92. package/dist/improve/schedule.d.ts +1 -0
  93. package/dist/improve/schedule.js +95 -0
  94. package/dist/improve/schedule.js.map +1 -0
  95. package/dist/improve/ship.d.ts +25 -0
  96. package/dist/improve/ship.js +82 -0
  97. package/dist/improve/ship.js.map +1 -0
  98. package/dist/improve/state.d.ts +157 -0
  99. package/dist/improve/state.js +264 -0
  100. package/dist/improve/state.js.map +1 -0
  101. package/dist/index.d.ts +2 -0
  102. package/dist/index.js +88 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/jj-binary.d.ts +36 -0
  105. package/dist/jj-binary.js +143 -0
  106. package/dist/jj-binary.js.map +1 -0
  107. package/dist/jj.d.ts +163 -0
  108. package/dist/jj.js +926 -0
  109. package/dist/jj.js.map +1 -0
  110. package/dist/logger.d.ts +22 -0
  111. package/dist/logger.js +110 -0
  112. package/dist/logger.js.map +1 -0
  113. package/dist/main.d.ts +3 -0
  114. package/dist/main.js +1366 -0
  115. package/dist/main.js.map +1 -0
  116. package/dist/migration.d.ts +82 -0
  117. package/dist/migration.js +198 -0
  118. package/dist/migration.js.map +1 -0
  119. package/dist/models.d.ts +9 -0
  120. package/dist/models.js +623 -0
  121. package/dist/models.js.map +1 -0
  122. package/dist/move-home.d.ts +82 -0
  123. package/dist/move-home.js +472 -0
  124. package/dist/move-home.js.map +1 -0
  125. package/dist/native/darwin-arm64/rudder-native +0 -0
  126. package/dist/native/darwin-x64/rudder-native +0 -0
  127. package/dist/native/linux-x64/rudder-native +0 -0
  128. package/dist/native-agents.d.ts +8 -0
  129. package/dist/native-agents.js +178 -0
  130. package/dist/native-agents.js.map +1 -0
  131. package/dist/native-binary.d.ts +4 -0
  132. package/dist/native-binary.js +140 -0
  133. package/dist/native-binary.js.map +1 -0
  134. package/dist/onboarding.d.ts +11 -0
  135. package/dist/onboarding.js +111 -0
  136. package/dist/onboarding.js.map +1 -0
  137. package/dist/paste-bridge.d.ts +36 -0
  138. package/dist/paste-bridge.js +279 -0
  139. package/dist/paste-bridge.js.map +1 -0
  140. package/dist/plan-mode.d.ts +2 -0
  141. package/dist/plan-mode.js +22 -0
  142. package/dist/plan-mode.js.map +1 -0
  143. package/dist/planner.d.ts +27 -0
  144. package/dist/planner.js +587 -0
  145. package/dist/planner.js.map +1 -0
  146. package/dist/predict.d.ts +67 -0
  147. package/dist/predict.js +369 -0
  148. package/dist/predict.js.map +1 -0
  149. package/dist/qr.d.ts +13 -0
  150. package/dist/qr.js +377 -0
  151. package/dist/qr.js.map +1 -0
  152. package/dist/report.d.ts +74 -0
  153. package/dist/report.js +412 -0
  154. package/dist/report.js.map +1 -0
  155. package/dist/rudder-md.d.ts +33 -0
  156. package/dist/rudder-md.js +142 -0
  157. package/dist/rudder-md.js.map +1 -0
  158. package/dist/run-manager.d.ts +62 -0
  159. package/dist/run-manager.js +1400 -0
  160. package/dist/run-manager.js.map +1 -0
  161. package/dist/scheduler.d.ts +128 -0
  162. package/dist/scheduler.js +955 -0
  163. package/dist/scheduler.js.map +1 -0
  164. package/dist/state.d.ts +127 -0
  165. package/dist/state.js +704 -0
  166. package/dist/state.js.map +1 -0
  167. package/dist/surfaces.d.ts +91 -0
  168. package/dist/surfaces.js +460 -0
  169. package/dist/surfaces.js.map +1 -0
  170. package/dist/task-summary.d.ts +30 -0
  171. package/dist/task-summary.js +472 -0
  172. package/dist/task-summary.js.map +1 -0
  173. package/dist/types.d.ts +492 -0
  174. package/dist/types.js +2 -0
  175. package/dist/types.js.map +1 -0
  176. package/dist/usage.d.ts +198 -0
  177. package/dist/usage.js +830 -0
  178. package/dist/usage.js.map +1 -0
  179. package/dist/util.d.ts +73 -0
  180. package/dist/util.js +399 -0
  181. package/dist/util.js.map +1 -0
  182. package/dist/version-check.d.ts +16 -0
  183. package/dist/version-check.js +269 -0
  184. package/dist/version-check.js.map +1 -0
  185. package/package.json +68 -4
  186. package/scripts/copy-native.mjs +89 -0
  187. package/scripts/postinstall.mjs +34 -0
@@ -0,0 +1,93 @@
1
+ import type { DepType, NodeStatus, RudderGraph, RunRecord, TaskNode } from "./types.js";
2
+ export declare function graphPath(repoRoot: string): string;
3
+ /**
4
+ * Read the graph for a repo. Returns an empty graph (not null) when graph.json
5
+ * is absent or malformed, so callers never special-case the first-write path.
6
+ */
7
+ export declare function readGraph(repoRoot: string): Promise<RudderGraph>;
8
+ /**
9
+ * Content-hashed node id: stable mergeable key from the title, the current
10
+ * time, and a nonce so two nodes with the same title never collide.
11
+ */
12
+ export declare function newNodeId(title: string): string;
13
+ export declare function newEdgeId(from: string, to: string): string;
14
+ /**
15
+ * Atomically read-modify-write the graph under the per-path lock (daemon-only
16
+ * writer). The transform mutates/returns the graph; keys are sorted and the
17
+ * updatedAt stamp refreshed before write so diffs stay minimal.
18
+ */
19
+ export declare function updateGraph(repoRoot: string, fn: (graph: RudderGraph) => RudderGraph | void): Promise<RudderGraph>;
20
+ /** A dependency edge in the mirror payload: this node depends `on` another. */
21
+ export type MirrorDep = {
22
+ on: string;
23
+ type: DepType;
24
+ };
25
+ /** One node in the mirror payload. Only `id`, `title`, `status`, and `deps` are
26
+ * load-bearing; the rest enrich the board projection when the TUI knows them. */
27
+ export type MirrorNode = {
28
+ id: string;
29
+ title: string;
30
+ prompt?: string;
31
+ status: string;
32
+ runId?: string;
33
+ jjChangeId?: string;
34
+ backend?: string;
35
+ model?: string;
36
+ effort?: string;
37
+ workspacePath?: string;
38
+ deps?: MirrorDep[];
39
+ };
40
+ export type MirrorPayload = {
41
+ nodes?: MirrorNode[];
42
+ };
43
+ /** Map a TUI status string onto the DAG-level NodeStatus vocabulary. Unknown
44
+ * values fall back to "planned" so a malformed payload never crashes the mirror. */
45
+ export declare function mirrorNodeStatus(status: string | undefined): NodeStatus;
46
+ /**
47
+ * Project a TUI plan payload into the graph, IN PLACE. Upserts every node in the
48
+ * payload (mapping TUI status -> NodeStatus), rebuilds each node's incoming edges
49
+ * from its deps, and PRUNES any graph node/edge no longer present in the payload
50
+ * so the mirror exactly reflects the current TUI plan (not an append-only log).
51
+ * Pure (no IO): callers wrap it in updateGraph. The prompt is taken from the
52
+ * payload when present (so a re-plan that reuses a node id cannot leave a stale
53
+ * prompt from the previous plan); source/createdAt are preserved on upsert;
54
+ * everything else is overwritten from the payload.
55
+ */
56
+ export declare function mirrorPlanIntoGraph(graph: RudderGraph, payload: MirrorPayload): RudderGraph;
57
+ export declare function hardParents(graph: RudderGraph, id: string): string[];
58
+ export declare function softParents(graph: RudderGraph, id: string): string[];
59
+ /**
60
+ * Judge parents of a node: the variants a judge node compares. A judge edge
61
+ * gates readiness on the variant REACHING review (finishing its work), not on
62
+ * merge. Fan-out-and-judge uses this so the judge launches once every variant
63
+ * has produced a diff to compare.
64
+ */
65
+ export declare function judgeParents(graph: RudderGraph, id: string): string[];
66
+ /**
67
+ * Direct dependents (children) of a node: nodes with an edge `from` this id.
68
+ */
69
+ export declare function dependents(graph: RudderGraph, id: string): string[];
70
+ /**
71
+ * A node is ready when it is still `planned` AND every hard-dep parent has
72
+ * merged AND every judge parent has reached review or merged. Soft parents
73
+ * never block readiness. A judge node (which has judge parents) launches only
74
+ * once all of its variants have finished their work (review or merged), so it
75
+ * has every variant's diff to compare.
76
+ */
77
+ export declare function isReady(graph: RudderGraph, node: TaskNode): boolean;
78
+ /**
79
+ * Every node eligible to launch: planned with all hard parents merged.
80
+ */
81
+ export declare function readyNodes(graph: RudderGraph): TaskNode[];
82
+ /**
83
+ * The frontier: non-merged leaves. A node is on the frontier when it is not
84
+ * merged AND has no non-merged child (every dependent is already merged, or it
85
+ * has no dependents). Injection reconciliation attaches new nodes here.
86
+ */
87
+ export declare function frontier(graph: RudderGraph): TaskNode[];
88
+ /**
89
+ * Project a worker-owned RunStatus into the DAG-level NodeStatus. With no run
90
+ * yet, the node keeps its deps-derived status (planned/ready/blocked); the
91
+ * scheduler decides those. Mirrors plan section 2's projection table.
92
+ */
93
+ export declare function projectNodeStatus(node: TaskNode, run?: RunRecord): NodeStatus;
package/dist/graph.js ADDED
@@ -0,0 +1,372 @@
1
+ import path from "node:path";
2
+ import { adoptLegacyWorkspaceKey, ensureProjectRuntimeIgnored, projectStateDir } from "./state.js";
3
+ import { nowIso, pathExists, readJson, shortHash, updateJson } from "./util.js";
4
+ // ---------------------------------------------------------------------------
5
+ // graph.json IO. The graph is the daemon-owned DAG topology. nodes/edges are
6
+ // objects keyed by id so two branches that each add nodes merge as a clean key
7
+ // union (no line conflicts). Keys are sorted on write for minimal diffs.
8
+ // ---------------------------------------------------------------------------
9
+ export function graphPath(repoRoot) {
10
+ return path.join(projectStateDir(repoRoot), "graph.json");
11
+ }
12
+ function emptyGraph(repoRoot) {
13
+ return {
14
+ version: 1,
15
+ repoRoot,
16
+ nodes: {},
17
+ edges: {},
18
+ updatedAt: nowIso(),
19
+ };
20
+ }
21
+ /**
22
+ * Read the graph for a repo. Returns an empty graph (not null) when graph.json
23
+ * is absent or malformed, so callers never special-case the first-write path.
24
+ */
25
+ export async function readGraph(repoRoot) {
26
+ const graph = await readJson(graphPath(repoRoot));
27
+ if (graph && graph.version === 1 && graph.nodes && graph.edges) {
28
+ return {
29
+ version: 1,
30
+ repoRoot: graph.repoRoot || repoRoot,
31
+ ...(graph.integrationChangeId ? { integrationChangeId: graph.integrationChangeId } : {}),
32
+ nodes: adoptLegacyNodeWorkspaceKeys(graph.nodes),
33
+ edges: graph.edges,
34
+ updatedAt: graph.updatedAt || nowIso(),
35
+ };
36
+ }
37
+ return emptyGraph(repoRoot);
38
+ }
39
+ /**
40
+ * Nodes written before the worktree->workspace rename store isolation info under
41
+ * `worktree`. Promote it in place so the scheduler, board and TUI only ever read
42
+ * `workspace`; the next updateGraph persists the new key.
43
+ */
44
+ function adoptLegacyNodeWorkspaceKeys(nodes) {
45
+ for (const node of Object.values(nodes)) {
46
+ adoptLegacyWorkspaceKey(node);
47
+ }
48
+ return nodes;
49
+ }
50
+ /**
51
+ * Content-hashed node id: stable mergeable key from the title, the current
52
+ * time, and a nonce so two nodes with the same title never collide.
53
+ */
54
+ export function newNodeId(title) {
55
+ return shortHash(`${title}:${nowIso()}:${Math.random()}`);
56
+ }
57
+ export function newEdgeId(from, to) {
58
+ return shortHash(`${from}->${to}:${nowIso()}:${Math.random()}`);
59
+ }
60
+ /**
61
+ * Atomically read-modify-write the graph under the per-path lock (daemon-only
62
+ * writer). The transform mutates/returns the graph; keys are sorted and the
63
+ * updatedAt stamp refreshed before write so diffs stay minimal.
64
+ */
65
+ export async function updateGraph(repoRoot, fn) {
66
+ // graph.json is mutable scheduler state, never source code. Install the
67
+ // exclusion before the first write even when callers bypass `rudder plan`.
68
+ await ensureProjectRuntimeIgnored(repoRoot);
69
+ let result = emptyGraph(repoRoot);
70
+ const file = graphPath(repoRoot);
71
+ const existed = await pathExists(file);
72
+ await updateJson(file, (current) => {
73
+ // Once a graph exists, a transient/contended read must never be interpreted
74
+ // as an empty DAG. Throwing leaves the atomic file untouched and lets the
75
+ // scheduler retry; silently using emptyGraph here can erase the whole fleet.
76
+ if (existed && !(current && current.version === 1 && current.nodes && current.edges)) {
77
+ throw new Error(`Refusing to replace an unreadable existing graph: ${file}`);
78
+ }
79
+ const base = current && current.version === 1 && current.nodes && current.edges
80
+ ? current
81
+ : emptyGraph(repoRoot);
82
+ const next = fn(base) ?? base;
83
+ next.version = 1;
84
+ next.repoRoot = next.repoRoot || repoRoot;
85
+ next.updatedAt = nowIso();
86
+ const sorted = sortGraph(next);
87
+ result = sorted;
88
+ return sorted;
89
+ });
90
+ return result;
91
+ }
92
+ /** Map a TUI status string onto the DAG-level NodeStatus vocabulary. Unknown
93
+ * values fall back to "planned" so a malformed payload never crashes the mirror. */
94
+ export function mirrorNodeStatus(status) {
95
+ switch (status) {
96
+ case "ready":
97
+ return "ready";
98
+ case "running":
99
+ return "running";
100
+ case "review":
101
+ return "review";
102
+ case "merged":
103
+ return "merged";
104
+ case "failed":
105
+ return "failed";
106
+ case "blocked":
107
+ return "blocked";
108
+ default:
109
+ return "planned";
110
+ }
111
+ }
112
+ function mirrorBackend(backend) {
113
+ return backend === "codex" || backend === "acpx" || backend === "opencode" ? backend : "claude";
114
+ }
115
+ function mirrorEffort(effort) {
116
+ switch (effort) {
117
+ case "low":
118
+ case "medium":
119
+ case "high":
120
+ case "xhigh":
121
+ case "max":
122
+ return effort;
123
+ default:
124
+ return undefined;
125
+ }
126
+ }
127
+ /** Stable, mergeable edge id keyed only by endpoints + type (no timestamp/nonce)
128
+ * so re-mirroring an unchanged plan reuses the same key and the prune is a no-op. */
129
+ function mirrorEdgeId(from, to, type) {
130
+ return `e_${from}__${type}__${to}`;
131
+ }
132
+ /**
133
+ * Project a TUI plan payload into the graph, IN PLACE. Upserts every node in the
134
+ * payload (mapping TUI status -> NodeStatus), rebuilds each node's incoming edges
135
+ * from its deps, and PRUNES any graph node/edge no longer present in the payload
136
+ * so the mirror exactly reflects the current TUI plan (not an append-only log).
137
+ * Pure (no IO): callers wrap it in updateGraph. The prompt is taken from the
138
+ * payload when present (so a re-plan that reuses a node id cannot leave a stale
139
+ * prompt from the previous plan); source/createdAt are preserved on upsert;
140
+ * everything else is overwritten from the payload.
141
+ */
142
+ export function mirrorPlanIntoGraph(graph, payload) {
143
+ const inNodes = Array.isArray(payload.nodes) ? payload.nodes : [];
144
+ const createdAt = nowIso();
145
+ const keepNodeIds = new Set();
146
+ const keepEdgeIds = new Set();
147
+ for (const incoming of inNodes) {
148
+ if (!incoming || typeof incoming.id !== "string" || !incoming.id) {
149
+ continue;
150
+ }
151
+ keepNodeIds.add(incoming.id);
152
+ const title = (incoming.title || incoming.id).slice(0, 200);
153
+ const status = mirrorNodeStatus(incoming.status);
154
+ const incomingEdgeIds = [];
155
+ const deps = Array.isArray(incoming.deps) ? incoming.deps : [];
156
+ for (const dep of deps) {
157
+ if (!dep || typeof dep.on !== "string" || !dep.on) {
158
+ continue;
159
+ }
160
+ const type = dep.type === "soft" ? "soft" : "hard";
161
+ const edgeId = mirrorEdgeId(dep.on, incoming.id, type);
162
+ keepEdgeIds.add(edgeId);
163
+ graph.edges[edgeId] = { id: edgeId, from: dep.on, to: incoming.id, type };
164
+ incomingEdgeIds.push(edgeId);
165
+ }
166
+ const existing = graph.nodes[incoming.id];
167
+ const effort = mirrorEffort(incoming.effort);
168
+ // Prompt comes from the payload when it carries one, so a re-plan that reuses
169
+ // this id overwrites the previous plan's prompt instead of keeping it stale.
170
+ const prompt = typeof incoming.prompt === "string" && incoming.prompt.trim()
171
+ ? incoming.prompt
172
+ : existing?.prompt ?? title;
173
+ graph.nodes[incoming.id] = {
174
+ id: incoming.id,
175
+ title,
176
+ prompt,
177
+ backend: mirrorBackend(incoming.backend),
178
+ ...(incoming.model ? { model: incoming.model } : {}),
179
+ ...(effort ? { effort } : {}),
180
+ status,
181
+ ...(incoming.runId ? { runId: incoming.runId } : {}),
182
+ ...(incoming.workspacePath ? { workspace: { path: incoming.workspacePath } } : {}),
183
+ ...(incoming.jjChangeId ? { jjChangeId: incoming.jjChangeId } : {}),
184
+ deps: incomingEdgeIds,
185
+ source: existing?.source ?? "planner",
186
+ createdAt: existing?.createdAt ?? createdAt,
187
+ updatedAt: createdAt,
188
+ };
189
+ }
190
+ // PRUNE: drop graph nodes/edges absent from the payload so the mirror is an
191
+ // exact reflection of the live TUI plan.
192
+ for (const id of Object.keys(graph.nodes)) {
193
+ if (!keepNodeIds.has(id)) {
194
+ delete graph.nodes[id];
195
+ }
196
+ }
197
+ for (const id of Object.keys(graph.edges)) {
198
+ if (!keepEdgeIds.has(id)) {
199
+ delete graph.edges[id];
200
+ }
201
+ }
202
+ return graph;
203
+ }
204
+ function sortGraph(graph) {
205
+ const nodes = {};
206
+ for (const key of Object.keys(graph.nodes).sort()) {
207
+ const node = graph.nodes[key];
208
+ if (node) {
209
+ nodes[key] = node;
210
+ }
211
+ }
212
+ const edges = {};
213
+ for (const key of Object.keys(graph.edges).sort()) {
214
+ const edge = graph.edges[key];
215
+ if (edge) {
216
+ edges[key] = edge;
217
+ }
218
+ }
219
+ return {
220
+ version: 1,
221
+ repoRoot: graph.repoRoot,
222
+ ...(graph.integrationChangeId ? { integrationChangeId: graph.integrationChangeId } : {}),
223
+ nodes,
224
+ edges,
225
+ updatedAt: graph.updatedAt,
226
+ };
227
+ }
228
+ // ---------------------------------------------------------------------------
229
+ // Pure queries. None of these mutate or read disk.
230
+ // ---------------------------------------------------------------------------
231
+ export function hardParents(graph, id) {
232
+ const parents = [];
233
+ for (const edge of Object.values(graph.edges)) {
234
+ if (edge.to === id && edge.type === "hard") {
235
+ parents.push(edge.from);
236
+ }
237
+ }
238
+ return parents;
239
+ }
240
+ export function softParents(graph, id) {
241
+ const parents = [];
242
+ for (const edge of Object.values(graph.edges)) {
243
+ if (edge.to === id && edge.type === "soft") {
244
+ parents.push(edge.from);
245
+ }
246
+ }
247
+ return parents;
248
+ }
249
+ /**
250
+ * Judge parents of a node: the variants a judge node compares. A judge edge
251
+ * gates readiness on the variant REACHING review (finishing its work), not on
252
+ * merge. Fan-out-and-judge uses this so the judge launches once every variant
253
+ * has produced a diff to compare.
254
+ */
255
+ export function judgeParents(graph, id) {
256
+ const parents = [];
257
+ for (const edge of Object.values(graph.edges)) {
258
+ if (edge.to === id && edge.type === "judge") {
259
+ parents.push(edge.from);
260
+ }
261
+ }
262
+ return parents;
263
+ }
264
+ /**
265
+ * Direct dependents (children) of a node: nodes with an edge `from` this id.
266
+ */
267
+ export function dependents(graph, id) {
268
+ const out = [];
269
+ for (const edge of Object.values(graph.edges)) {
270
+ if (edge.from === id) {
271
+ out.push(edge.to);
272
+ }
273
+ }
274
+ return out;
275
+ }
276
+ /**
277
+ * A node is ready when it is still `planned` AND every hard-dep parent has
278
+ * merged AND every judge parent has reached review or merged. Soft parents
279
+ * never block readiness. A judge node (which has judge parents) launches only
280
+ * once all of its variants have finished their work (review or merged), so it
281
+ * has every variant's diff to compare.
282
+ */
283
+ export function isReady(graph, node) {
284
+ if (node.status !== "planned") {
285
+ return false;
286
+ }
287
+ // A hard dep id that is NOT part of the plan (pruned or never existed) is treated
288
+ // as satisfied, matching the Rust scheduler's permissive rule: never deadlock on a
289
+ // dangling reference. Otherwise the parent must be merged.
290
+ const hardMet = hardParents(graph, node.id).every((parentId) => {
291
+ const parent = graph.nodes[parentId];
292
+ return parent === undefined || parent.status === "merged";
293
+ });
294
+ if (!hardMet) {
295
+ return false;
296
+ }
297
+ return judgeParents(graph, node.id).every((parentId) => {
298
+ const parent = graph.nodes[parentId];
299
+ return parent === undefined || parent.status === "review" || parent.status === "merged";
300
+ });
301
+ }
302
+ /**
303
+ * Every node eligible to launch: planned with all hard parents merged.
304
+ */
305
+ export function readyNodes(graph) {
306
+ return Object.values(graph.nodes).filter((node) => isReady(graph, node));
307
+ }
308
+ /**
309
+ * The frontier: non-merged leaves. A node is on the frontier when it is not
310
+ * merged AND has no non-merged child (every dependent is already merged, or it
311
+ * has no dependents). Injection reconciliation attaches new nodes here.
312
+ */
313
+ export function frontier(graph) {
314
+ return Object.values(graph.nodes).filter((node) => {
315
+ if (node.status === "merged") {
316
+ return false;
317
+ }
318
+ const children = dependents(graph, node.id);
319
+ return children.every((childId) => graph.nodes[childId]?.status === "merged");
320
+ });
321
+ }
322
+ /**
323
+ * Project a worker-owned RunStatus into the DAG-level NodeStatus. With no run
324
+ * yet, the node keeps its deps-derived status (planned/ready/blocked); the
325
+ * scheduler decides those. Mirrors plan section 2's projection table.
326
+ */
327
+ export function projectNodeStatus(node, run) {
328
+ // Daemon-owned terminal graph statuses are STICKY against re-projection. The
329
+ // worker only ever writes "completed" to run.json (it never writes "merged" —
330
+ // merge state lives in graph.json, owned by the daemon). So once the daemon
331
+ // has merged a node, or blocked it on a merge conflict / handed it to a
332
+ // resolver, the run.json stays "completed" forever. Without this guard the
333
+ // next tick re-projects merged -> review, integration fires again on an
334
+ // already-merged change, and the node thrashes review<->merged, never settling
335
+ // as done. `blocked` is likewise daemon-owned (failed-parent propagation or a
336
+ // held merge conflict) and must not be re-projected back to review.
337
+ if (node.status === "merged" || node.status === "blocked") {
338
+ return node.status;
339
+ }
340
+ if (!run) {
341
+ return node.status === "ready" ? "ready" : "planned";
342
+ }
343
+ return nodeStatusFromRunStatus(run.status, node.status);
344
+ }
345
+ function nodeStatusFromRunStatus(runStatus, fallback) {
346
+ switch (runStatus) {
347
+ case "created":
348
+ case "running":
349
+ case "steering":
350
+ case "verifying":
351
+ case "migrated":
352
+ return "running";
353
+ case "completed":
354
+ return "review";
355
+ case "failed":
356
+ case "cancelled":
357
+ case "orphaned":
358
+ return "failed";
359
+ // Paused is RESUMABLE (quitting the dashboard pauses live workers);
360
+ // projecting it as "failed" turned every node red on quit as if the fleet
361
+ // had crashed. "blocked" reads as not-progressing without claiming death.
362
+ case "paused":
363
+ return "blocked";
364
+ case "merged":
365
+ return "merged";
366
+ case "merge-conflict":
367
+ return "failed";
368
+ default:
369
+ return fallback;
370
+ }
371
+ }
372
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ export type HandoffBackend = "claude" | "codex" | "opencode";
2
+ export interface HandoffRequest {
3
+ requestId: string;
4
+ sessionId: string;
5
+ backend: HandoffBackend;
6
+ /** "worker" = isolated jj workspace (mergeable); "here" = the main checkout. */
7
+ target: "worker" | "here";
8
+ instruction?: string;
9
+ title?: string;
10
+ sourceCwd: string;
11
+ createdAt: number;
12
+ }
13
+ export interface HandoffArgs {
14
+ sessionId?: string;
15
+ backend?: HandoffBackend;
16
+ instruction: string;
17
+ here: boolean;
18
+ list: boolean;
19
+ }
20
+ export interface ConversationCandidate {
21
+ sessionId: string;
22
+ title: string;
23
+ modifiedMs: number;
24
+ path: string;
25
+ }
26
+ export declare function parseHandoffArgs(args: string[]): HandoffArgs;
27
+ /**
28
+ * A session id is spliced onto a `claude`/`codex` command line by the dashboard.
29
+ * Accept only id-shaped strings — the same rule the Rust side enforces on read.
30
+ */
31
+ export declare function isValidSessionId(value: string): boolean;
32
+ /**
33
+ * Drop the wrapper blocks Claude Code injects around real user text
34
+ * (`<system-reminder>…`, slash-command echoes, the local-command caveat) and
35
+ * collapse what is left onto one line. Mirrors strip_wrapper_blocks in
36
+ * native/src/handoff.rs.
37
+ */
38
+ export declare function stripWrapperBlocks(text: string): string;
39
+ /** The conversation's opening user prompt: what a human would call the chat. */
40
+ export declare function firstUserPrompt(raw: string): string | undefined;
41
+ /**
42
+ * What the listing needs from a transcript, read from its head.
43
+ *
44
+ * `interactive` is whether a human talked to a REPL: Claude records its session
45
+ * mode at start, and the one-shot `claude -p` calls Rudder makes internally (task
46
+ * titles, completion notes) do not — which is how they stay out of the listing.
47
+ */
48
+ export declare function scanTranscriptHead(raw: string): {
49
+ title: string;
50
+ interactive: boolean;
51
+ } | undefined;
52
+ export declare function claudeProjectsDir(): string;
53
+ /**
54
+ * Recent Claude conversations recorded for `cwd` and its subdirectories, newest
55
+ * first. Claude names each project folder after the directory the session ran in,
56
+ * so a chat started in `src/` lives in a sibling folder — still this repo's.
57
+ * Rudder's own agent sessions (under `.rudder-workspaces`) are not candidates.
58
+ */
59
+ export declare function recentClaudeConversations(cwd: string, limit?: number, projects?: string, exclude?: ReadonlySet<string>): ConversationCandidate[];
60
+ export declare function claudeTranscriptPath(cwd: string, sessionId: string, projects?: string): string | undefined;
61
+ /**
62
+ * The conversation this command is running inside. Claude Code exports its session
63
+ * id to every process it spawns, which is the whole trick: an agent asked to "hand
64
+ * this off to rudder" can run the command with no arguments and mean THIS chat.
65
+ * Falls back to the newest transcript recorded for the directory.
66
+ */
67
+ export declare function resolveClaudeSessionId(cwd: string, env?: NodeJS.ProcessEnv): string | undefined;
68
+ /**
69
+ * Newest opencode session for `cwd`. opencode keeps sessions in a database and
70
+ * ships the query, so Rudder asks it rather than reading a schema it does not own.
71
+ * Running this from inside an opencode chat resolves to THAT chat: it is the one
72
+ * that was updated most recently.
73
+ */
74
+ export declare function resolveOpencodeSessionId(cwd: string): string | undefined;
75
+ export declare function recentOpencodeConversations(cwd: string, limit?: number): ConversationCandidate[];
76
+ export declare function parseOpencodeSessions(raw: string, cwd: string, limit?: number): ConversationCandidate[];
77
+ /** Newest Codex session recorded for `cwd`. Codex sessions live in one global tree. */
78
+ export declare function resolveCodexSessionId(cwd: string, root?: string): string | undefined;
79
+ export declare function codexSessionsDir(): string;
80
+ /**
81
+ * Recent Codex conversations for `cwd`, newest first. Codex keeps ONE global
82
+ * rollout tree, so this filters by the recorded cwd BEFORE taking the newest few —
83
+ * the newest rollouts overall are usually other repositories'. Mirrors
84
+ * `recent_codex_conversations_in` in native/src/handoff.rs.
85
+ */
86
+ export declare function recentCodexConversations(cwd: string, limit?: number, root?: string): ConversationCandidate[];
87
+ /**
88
+ * A Codex rollout's identity: the `session_meta` line carries the id and cwd, and
89
+ * the title is the first turn whose role is `user` — `developer` turns are the
90
+ * harness's own context blocks.
91
+ */
92
+ export declare function parseCodexSessionHead(raw: string): {
93
+ sessionId: string;
94
+ cwd: string;
95
+ title: string;
96
+ } | undefined;
97
+ /** `# AGENTS.md instructions for /path` — the harness talking, not a person. */
98
+ export declare function isInjectedInstructions(text: string): boolean;
99
+ /**
100
+ * Prompts RUDDER writes itself. A `codex exec` rollout looks exactly like a real
101
+ * chat in its metadata (`originator: codex-tui`, `source: cli`), so these are
102
+ * matched by their opening text — which Rudder controls. Mirrors
103
+ * `is_rudder_generated_prompt` in native/src/handoff.rs.
104
+ */
105
+ export declare function isRudderGeneratedPrompt(text: string): boolean;
106
+ /** `.rudder/` lives at the dashboard root, never inside an agent's workspace. */
107
+ export declare function dashboardRoot(cwd?: string): string;
108
+ export declare function handoffQueueDir(repoRoot: string): string;
109
+ export declare function writeHandoffRequest(repoRoot: string, request: HandoffRequest): string;
110
+ export declare function runHandoff(args: string[], backendFlag?: string): Promise<void>;