@monoes/monomindcli 2.9.3 → 2.9.4

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 (189) hide show
  1. package/.claude/helpers/intelligence.cjs +27 -6
  2. package/.claude/skills/agentic-jujutsu/SKILL.md +17 -15
  3. package/.claude/skills/hive-mind-advanced/SKILL.md +212 -559
  4. package/.claude/skills/hooks-automation/SKILL.md +1 -1
  5. package/.claude/skills/mastermind-createorg/SKILL.md +2 -1
  6. package/.claude/skills/pair-programming/SKILL.md +1 -1
  7. package/.claude/skills/performance-analysis/SKILL.md +228 -484
  8. package/.claude/skills/swarm-advanced/SKILL.md +2 -2
  9. package/.claude/skills/swarm-orchestration/SKILL.md +220 -150
  10. package/.claude/skills/verification-quality/SKILL.md +247 -571
  11. package/README.md +6 -2
  12. package/dist/src/autopilot-state.d.ts +16 -4
  13. package/dist/src/autopilot-state.d.ts.map +1 -1
  14. package/dist/src/autopilot-state.js +42 -19
  15. package/dist/src/autopilot-state.js.map +1 -1
  16. package/dist/src/commands/agent-lifecycle.js +3 -3
  17. package/dist/src/commands/agent-lifecycle.js.map +1 -1
  18. package/dist/src/commands/autopilot.d.ts.map +1 -1
  19. package/dist/src/commands/autopilot.js +113 -26
  20. package/dist/src/commands/autopilot.js.map +1 -1
  21. package/dist/src/commands/doc.d.ts.map +1 -1
  22. package/dist/src/commands/doc.js +14 -3
  23. package/dist/src/commands/doc.js.map +1 -1
  24. package/dist/src/commands/doctor-env-checks.d.ts +1 -1
  25. package/dist/src/commands/doctor-env-checks.d.ts.map +1 -1
  26. package/dist/src/commands/doctor.d.ts.map +1 -1
  27. package/dist/src/commands/doctor.js +30 -1
  28. package/dist/src/commands/doctor.js.map +1 -1
  29. package/dist/src/commands/hooks-routing-commands.d.ts.map +1 -1
  30. package/dist/src/commands/hooks-routing-commands.js +12 -26
  31. package/dist/src/commands/hooks-routing-commands.js.map +1 -1
  32. package/dist/src/commands/hooks-workers.js +8 -8
  33. package/dist/src/commands/hooks-workers.js.map +1 -1
  34. package/dist/src/commands/hooks.d.ts.map +1 -1
  35. package/dist/src/commands/hooks.js +96 -54
  36. package/dist/src/commands/hooks.js.map +1 -1
  37. package/dist/src/commands/init.d.ts.map +1 -1
  38. package/dist/src/commands/init.js +80 -7
  39. package/dist/src/commands/init.js.map +1 -1
  40. package/dist/src/commands/mcp.d.ts.map +1 -1
  41. package/dist/src/commands/mcp.js +78 -2
  42. package/dist/src/commands/mcp.js.map +1 -1
  43. package/dist/src/commands/memory-list.d.ts.map +1 -1
  44. package/dist/src/commands/memory-list.js +79 -36
  45. package/dist/src/commands/memory-list.js.map +1 -1
  46. package/dist/src/commands/org.d.ts.map +1 -1
  47. package/dist/src/commands/org.js +114 -1
  48. package/dist/src/commands/org.js.map +1 -1
  49. package/dist/src/commands/performance.js +1 -1
  50. package/dist/src/commands/performance.js.map +1 -1
  51. package/dist/src/commands/swarm.d.ts.map +1 -1
  52. package/dist/src/commands/swarm.js +144 -98
  53. package/dist/src/commands/swarm.js.map +1 -1
  54. package/dist/src/init/claudemd-generator.js +1 -1
  55. package/dist/src/init/executor.d.ts.map +1 -1
  56. package/dist/src/init/executor.js +13 -3
  57. package/dist/src/init/executor.js.map +1 -1
  58. package/dist/src/init/kimi-generator.d.ts +3 -2
  59. package/dist/src/init/kimi-generator.d.ts.map +1 -1
  60. package/dist/src/init/kimi-generator.js +45 -13
  61. package/dist/src/init/kimi-generator.js.map +1 -1
  62. package/dist/src/init/statusline-generator.d.ts +1 -1
  63. package/dist/src/init/statusline-generator.d.ts.map +1 -1
  64. package/dist/src/init/statusline-generator.js +48 -46
  65. package/dist/src/init/statusline-generator.js.map +1 -1
  66. package/dist/src/init/types.d.ts +1 -1
  67. package/dist/src/init/types.d.ts.map +1 -1
  68. package/dist/src/init/types.js +5 -1
  69. package/dist/src/init/types.js.map +1 -1
  70. package/dist/src/mcp-server.d.ts +6 -0
  71. package/dist/src/mcp-server.d.ts.map +1 -1
  72. package/dist/src/mcp-server.js +47 -22
  73. package/dist/src/mcp-server.js.map +1 -1
  74. package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
  75. package/dist/src/mcp-tools/hive-mind-tools.js +13 -11
  76. package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
  77. package/dist/src/mcp-tools/hooks-intelligence.d.ts.map +1 -1
  78. package/dist/src/mcp-tools/hooks-intelligence.js +97 -40
  79. package/dist/src/mcp-tools/hooks-intelligence.js.map +1 -1
  80. package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -1
  81. package/dist/src/mcp-tools/hooks-routing.js +406 -205
  82. package/dist/src/mcp-tools/hooks-routing.js.map +1 -1
  83. package/dist/src/mcp-tools/monograph/impact-tools.d.ts.map +1 -1
  84. package/dist/src/mcp-tools/monograph/impact-tools.js +109 -44
  85. package/dist/src/mcp-tools/monograph/impact-tools.js.map +1 -1
  86. package/dist/src/mcp-tools/monograph/query-tools.d.ts.map +1 -1
  87. package/dist/src/mcp-tools/monograph/query-tools.js +48 -18
  88. package/dist/src/mcp-tools/monograph/query-tools.js.map +1 -1
  89. package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
  90. package/dist/src/mcp-tools/system-tools.js +100 -38
  91. package/dist/src/mcp-tools/system-tools.js.map +1 -1
  92. package/dist/src/memory/embedding-operations.d.ts +4 -0
  93. package/dist/src/memory/embedding-operations.d.ts.map +1 -1
  94. package/dist/src/memory/embedding-operations.js +125 -32
  95. package/dist/src/memory/embedding-operations.js.map +1 -1
  96. package/dist/src/memory/hnsw-operations.d.ts +1 -1
  97. package/dist/src/memory/hnsw-operations.js +1 -1
  98. package/dist/src/memory/memory-bridge.d.ts.map +1 -1
  99. package/dist/src/memory/memory-bridge.js +172 -69
  100. package/dist/src/memory/memory-bridge.js.map +1 -1
  101. package/dist/src/memory/memory-read.d.ts +1 -1
  102. package/dist/src/memory/memory-read.js +2 -2
  103. package/dist/src/memory/memory-read.js.map +1 -1
  104. package/dist/src/orgrt/checkpoint-ops.d.ts +11 -1
  105. package/dist/src/orgrt/checkpoint-ops.d.ts.map +1 -1
  106. package/dist/src/orgrt/checkpoint-ops.js +15 -1
  107. package/dist/src/orgrt/checkpoint-ops.js.map +1 -1
  108. package/dist/src/orgrt/checkpoint.js +1 -1
  109. package/dist/src/orgrt/checkpoint.js.map +1 -1
  110. package/dist/src/orgrt/daemon.d.ts +11 -3
  111. package/dist/src/orgrt/daemon.d.ts.map +1 -1
  112. package/dist/src/orgrt/daemon.js +317 -93
  113. package/dist/src/orgrt/daemon.js.map +1 -1
  114. package/dist/src/orgrt/decisions.d.ts +13 -0
  115. package/dist/src/orgrt/decisions.d.ts.map +1 -1
  116. package/dist/src/orgrt/decisions.js +95 -0
  117. package/dist/src/orgrt/decisions.js.map +1 -1
  118. package/dist/src/orgrt/remote.d.ts.map +1 -1
  119. package/dist/src/orgrt/remote.js +12 -6
  120. package/dist/src/orgrt/remote.js.map +1 -1
  121. package/dist/src/orgrt/server.d.ts.map +1 -1
  122. package/dist/src/orgrt/server.js +71 -14
  123. package/dist/src/orgrt/server.js.map +1 -1
  124. package/dist/src/orgrt/session.d.ts +27 -1
  125. package/dist/src/orgrt/session.d.ts.map +1 -1
  126. package/dist/src/orgrt/session.js +42 -3
  127. package/dist/src/orgrt/session.js.map +1 -1
  128. package/dist/src/orgrt/task-dag.d.ts +11 -1
  129. package/dist/src/orgrt/task-dag.d.ts.map +1 -1
  130. package/dist/src/orgrt/task-dag.js +93 -2
  131. package/dist/src/orgrt/task-dag.js.map +1 -1
  132. package/dist/src/orgrt/templates.d.ts.map +1 -1
  133. package/dist/src/orgrt/templates.js +24 -0
  134. package/dist/src/orgrt/templates.js.map +1 -1
  135. package/dist/src/orgrt/types.d.ts +70 -1
  136. package/dist/src/orgrt/types.d.ts.map +1 -1
  137. package/dist/src/orgrt/types.js +19 -0
  138. package/dist/src/orgrt/types.js.map +1 -1
  139. package/dist/src/orgrt/vercel-providers.d.ts.map +1 -1
  140. package/dist/src/orgrt/vercel-providers.js +9 -4
  141. package/dist/src/orgrt/vercel-providers.js.map +1 -1
  142. package/dist/src/orgrt/vercel-runner.d.ts +0 -23
  143. package/dist/src/orgrt/vercel-runner.d.ts.map +1 -1
  144. package/dist/src/orgrt/vercel-runner.js +26 -1
  145. package/dist/src/orgrt/vercel-runner.js.map +1 -1
  146. package/dist/src/types.d.ts +1 -1
  147. package/dist/src/types.d.ts.map +1 -1
  148. package/dist/src/ui/server.mjs +2647 -838
  149. package/dist/tsconfig.tsbuildinfo +1 -1
  150. package/package.json +7 -7
  151. package/.claude/commands/mastermind/approvev1.md +0 -94
  152. package/.claude/commands/mastermind/architect.md +0 -52
  153. package/.claude/commands/mastermind/autodev.md +0 -28
  154. package/.claude/commands/mastermind/build.md +0 -23
  155. package/.claude/commands/mastermind/finish.md +0 -17
  156. package/.claude/commands/mastermind/runorgv1.md +0 -159
  157. package/.claude/commands/mastermind/taskdev.md +0 -23
  158. package/.claude/commands/mastermind/tdd.md +0 -19
  159. package/.claude/commands/mastermind/verify.md +0 -19
  160. package/.claude/skills/mastermind-approvev1/SKILL.md +0 -191
  161. package/.claude/skills/mastermind-architect/SKILL.md +0 -862
  162. package/.claude/skills/mastermind-autodev/SKILL.md +0 -360
  163. package/.claude/skills/mastermind-build/SKILL.md +0 -169
  164. package/.claude/skills/mastermind-companies/SKILL.md +0 -256
  165. package/.claude/skills/mastermind-content/SKILL.md +0 -197
  166. package/.claude/skills/mastermind-costs/SKILL.md +0 -151
  167. package/.claude/skills/mastermind-finance/SKILL.md +0 -166
  168. package/.claude/skills/mastermind-finish/SKILL.md +0 -251
  169. package/.claude/skills/mastermind-heartbeatv1/SKILL.md +0 -167
  170. package/.claude/skills/mastermind-instance-settings/SKILL.md +0 -315
  171. package/.claude/skills/mastermind-marketing/SKILL.md +0 -228
  172. package/.claude/skills/mastermind-marketing/references/copywriting-frameworks.md +0 -181
  173. package/.claude/skills/mastermind-marketing/references/persuasion-psychology.md +0 -158
  174. package/.claude/skills/mastermind-ops/SKILL.md +0 -168
  175. package/.claude/skills/mastermind-org-chart/SKILL.md +0 -209
  176. package/.claude/skills/mastermind-project-detail/SKILL.md +0 -249
  177. package/.claude/skills/mastermind-project-workspace/SKILL.md +0 -244
  178. package/.claude/skills/mastermind-projects/SKILL.md +0 -167
  179. package/.claude/skills/mastermind-runorgv1/SKILL.md +0 -731
  180. package/.claude/skills/mastermind-sales/SKILL.md +0 -170
  181. package/.claude/skills/mastermind-taskdev/SKILL.md +0 -377
  182. package/.claude/skills/mastermind-taskdev/code-quality-reviewer-prompt.md +0 -60
  183. package/.claude/skills/mastermind-taskdev/final-reviewer-prompt.md +0 -144
  184. package/.claude/skills/mastermind-taskdev/implementer-prompt.md +0 -114
  185. package/.claude/skills/mastermind-taskdev/spec-reviewer-prompt.md +0 -80
  186. package/.claude/skills/mastermind-tdd/SKILL.md +0 -424
  187. package/.claude/skills/mastermind-verify/SKILL.md +0 -196
  188. package/.claude/skills/mastermind-wiki/SKILL.md +0 -314
  189. package/.claude/skills/monolean-review/SKILL.md +0 -57
@@ -1,7 +1,7 @@
1
1
  // packages/@monomind/cli/src/orgrt/daemon.ts
2
2
  // monolean: single-process inter-org — upgrade path = daemon-to-daemon HTTP when multi-host is real
3
3
  import { readFileSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
4
- import { execSync } from 'node:child_process';
4
+ import { execFileSync } from 'node:child_process';
5
5
  import { join, isAbsolute } from 'node:path';
6
6
  import { writeJsonFileAtomic } from '../utils/json-file.js';
7
7
  import { reapOrphanedSdkProcesses } from '../utils/resource-governor.js';
@@ -12,9 +12,9 @@ import { runAgentSession } from './session.js';
12
12
  import { attachForwarder } from './forwarder.js';
13
13
  import { BrokerLease, normalizeCredential } from './broker.js';
14
14
  import { drainInbox } from './inbox.js';
15
- import { OrgDefSchema, ORG_DIR } from './types.js';
15
+ import { OrgDefSchema, ORG_DIR, } from './types.js';
16
16
  import { TaskDag } from './task-dag.js';
17
- import { summarizeRun, readRunEvents, readHistory, historyFile } from './reporting.js';
17
+ import { summarizeRun, readRunEvents, readHistory, historyFile, } from './reporting.js';
18
18
  import { getResourceLimits, configureResourceLimits } from '../utils/resource-governor.js';
19
19
  import { OpencodeAgentRunner } from './opencode-runner.js';
20
20
  import { KimiCodeAgentRunner } from './kimicode-runner.js';
@@ -22,7 +22,7 @@ import { VercelAgentRunner } from './vercel-runner.js';
22
22
  import { CodexAgentRunner } from './codex-runner.js';
23
23
  import { AntigravityAgentRunner } from './antigravity-runner.js';
24
24
  import { captureCheckpoint } from './checkpoint.js';
25
- import { loadGlobalFenceConfig, mergeFenceConfigs, createFenceForRole } from './fence.js';
25
+ import { loadGlobalFenceConfig, mergeFenceConfigs, createFenceForRole, } from './fence.js';
26
26
  // ── Extracted module imports ────────────────────────────────────────────
27
27
  import * as approvalOps from './approvals.js';
28
28
  import * as questionOps from './questions.js';
@@ -35,7 +35,9 @@ import * as checkpointOps from './checkpoint-ops.js';
35
35
  class OtelTracer {
36
36
  enabled = false;
37
37
  spans = new Map();
38
- enable() { this.enabled = true; }
38
+ enable() {
39
+ this.enabled = true;
40
+ }
39
41
  startSpan(name, metadata = {}) {
40
42
  if (!this.enabled)
41
43
  return;
@@ -72,7 +74,9 @@ function autoRuntimeFromProvider(kind) {
72
74
  return undefined;
73
75
  }
74
76
  export function resolveRunner(orgRuntime, providerKind) {
75
- const selected = orgRuntime ?? autoRuntimeFromProvider(providerKind) ?? process.env.MONOMIND_RUNTIME;
77
+ const selected = orgRuntime ??
78
+ autoRuntimeFromProvider(providerKind) ??
79
+ process.env.MONOMIND_RUNTIME;
76
80
  if (selected === 'opencode')
77
81
  return new OpencodeAgentRunner();
78
82
  if (selected === 'kimicode')
@@ -99,7 +103,7 @@ export function resolveRoleRunner(roleRuntime, orgRuntime, roleProviderKind, org
99
103
  /** Per-role token budget: a role's own `budget_tokens` wins; otherwise the
100
104
  * even split of run_config.budget_tokens across all roles. */
101
105
  export function roleTokenBudget(role, def) {
102
- return role.budget_tokens ?? Math.floor((def.run_config.budget_tokens ?? 1_000_000) / def.roles.length);
106
+ return (role.budget_tokens ?? Math.floor((def.run_config.budget_tokens ?? 1_000_000) / def.roles.length));
103
107
  }
104
108
  /** Bounded ring buffer for agent terminal scrollback. */
105
109
  export class ScrollbackBuffer {
@@ -113,8 +117,12 @@ export class ScrollbackBuffer {
113
117
  if (this.lines.length > this.maxLines)
114
118
  this.lines.splice(0, this.lines.length - this.maxLines);
115
119
  }
116
- snapshot() { return [...this.lines]; }
117
- clear() { this.lines.length = 0; }
120
+ snapshot() {
121
+ return [...this.lines];
122
+ }
123
+ clear() {
124
+ this.lines.length = 0;
125
+ }
118
126
  }
119
127
  export class OrgDaemon {
120
128
  root;
@@ -142,7 +150,9 @@ export class OrgDaemon {
142
150
  /** @internal */ recallUsage = new Map();
143
151
  /** @internal */ orgLearnedRuns = new Set();
144
152
  /** @internal */ abandoned = new Map();
145
- constructor(/** @internal */ root, /** @internal */ opts = {}) {
153
+ constructor(
154
+ /** @internal */ root,
155
+ /** @internal */ opts = {}) {
146
156
  this.root = root;
147
157
  this.opts = opts;
148
158
  }
@@ -157,8 +167,12 @@ export class OrgDaemon {
157
167
  this.globalSubscribers.add(fn);
158
168
  return () => this.globalSubscribers.delete(fn);
159
169
  }
160
- listOrgs() { return [...this.orgs.values()]; }
161
- getOrg(name) { return this.orgs.get(name); }
170
+ listOrgs() {
171
+ return [...this.orgs.values()];
172
+ }
173
+ getOrg(name) {
174
+ return this.orgs.get(name);
175
+ }
162
176
  /** Hot-reload an org definition from disk without stopping running sessions.
163
177
  * Applies: goal, run_config, schedule. New roles are added as pending (lazy-spawnable).
164
178
  * Removed roles are NOT killed — they finish their current work and won't be re-spawned.
@@ -184,8 +198,8 @@ export class OrgDaemon {
184
198
  changed.push(`run_config.${key}`);
185
199
  }
186
200
  }
187
- const existingRoleIds = new Set(running.def.roles.map(r => r.id));
188
- const newRoleIds = new Set(newDef.roles.map(r => r.id));
201
+ const existingRoleIds = new Set(running.def.roles.map((r) => r.id));
202
+ const newRoleIds = new Set(newDef.roles.map((r) => r.id));
189
203
  for (const role of newDef.roles) {
190
204
  if (!existingRoleIds.has(role.id)) {
191
205
  running.def.roles.push(role);
@@ -199,16 +213,23 @@ export class OrgDaemon {
199
213
  if (!newRoleIds.has(id))
200
214
  removedRoles.push(id);
201
215
  }
202
- running.bus.emit({ type: 'audit', reason: 'hot-reload',
216
+ running.bus.emit({
217
+ type: 'audit',
218
+ reason: 'hot-reload',
203
219
  msg: `org def reloaded: ${changed.length} fields changed, ${newRoles.length} new roles, ${removedRoles.length} removed roles`,
204
- data: { changed, newRoles, removedRoles } });
220
+ data: { changed, newRoles, removedRoles },
221
+ });
205
222
  return { changed, newRoles, removedRoles };
206
223
  }
207
224
  /** Names of the orgs this daemon currently has running. Snapshot — safe to
208
225
  * iterate while stopOrg() mutates the underlying map. */
209
- listRunning() { return [...this.orgs.keys()]; }
226
+ listRunning() {
227
+ return [...this.orgs.keys()];
228
+ }
210
229
  /** Hook for the SSE server — registers a listener for all bus events across all orgs. */
211
- onBusEvent = (fn) => { this.subscribe(fn); };
230
+ onBusEvent = (fn) => {
231
+ this.subscribe(fn);
232
+ };
212
233
  /** Snapshot of all running orgs for dashboard initial load. */
213
234
  getStatusSnapshot = () => {
214
235
  const orgs = [];
@@ -216,13 +237,15 @@ export class OrgDaemon {
216
237
  const roles = [];
217
238
  for (const [roleId, agent] of running.agents) {
218
239
  roles.push({
219
- id: roleId, status: agent.status,
240
+ id: roleId,
241
+ status: agent.status,
220
242
  worktree: agent.worktreePath ?? null,
221
243
  metrics: agent.metrics,
222
244
  });
223
245
  }
224
246
  orgs.push({
225
- name, run: running.run,
247
+ name,
248
+ run: running.run,
226
249
  roles,
227
250
  pendingRoles: running.pendingRoles ? [...running.pendingRoles.keys()] : [],
228
251
  tasks: running.taskDag?.all() ?? [],
@@ -275,23 +298,37 @@ export class OrgDaemon {
275
298
  let worktreePath;
276
299
  if (ws === 'worktree') {
277
300
  worktreePath = join(this.root, ORG_DIR, name, 'worktree');
278
- const { execSync } = await import('node:child_process');
301
+ const { execFileSync } = await import('node:child_process');
279
302
  try {
280
303
  // Remove stale worktree from a previous run
281
304
  if (existsSync(worktreePath)) {
282
305
  // R4: bound the call — a wedged git hook (git-lfs, gc lock, gpg sign
283
306
  // prompt) would otherwise hang the whole daemon indefinitely.
284
- execSync(`git worktree remove --force "${worktreePath}"`, { cwd: this.root, stdio: 'ignore', timeout: 30_000 });
307
+ // SEC-5: execFileSync + argv — no shell interpolation of worktreePath.
308
+ execFileSync('git', ['worktree', 'remove', '--force', worktreePath], {
309
+ cwd: this.root,
310
+ stdio: 'ignore',
311
+ timeout: 30_000,
312
+ });
285
313
  }
286
314
  }
287
- catch { /* best-effort cleanup */ }
288
- execSync(`git worktree add "${worktreePath}" HEAD --detach`, { cwd: this.root, stdio: 'ignore', timeout: 30_000 });
315
+ catch {
316
+ /* best-effort cleanup */
317
+ }
318
+ execFileSync('git', ['worktree', 'add', worktreePath, 'HEAD', '--detach'], {
319
+ cwd: this.root,
320
+ stdio: 'ignore',
321
+ timeout: 30_000,
322
+ });
289
323
  cwd = worktreePath;
290
324
  }
291
325
  else {
292
- cwd = ws === 'repo' ? this.root
293
- : ws === 'isolated' ? join(this.root, ORG_DIR, name, 'workspace')
294
- : ws;
326
+ cwd =
327
+ ws === 'repo'
328
+ ? this.root
329
+ : ws === 'isolated'
330
+ ? join(this.root, ORG_DIR, name, 'workspace')
331
+ : ws;
295
332
  }
296
333
  mkdirSync(cwd, { recursive: true });
297
334
  // An org must be able to seat its whole roster. maxSdkProcesses is sized for
@@ -306,7 +343,8 @@ export class OrgDaemon {
306
343
  // OpencodeAgentRunner, and CodexAgentRunner each spawn one subprocess per role.
307
344
  // The current sizing (def.roles.length) is therefore safe — it over-provisions
308
345
  // for in-process runners but never under-provisions for subprocess runners.
309
- if (!process.env.MONOMIND_MAX_SDK_PROCS && getResourceLimits().maxSdkProcesses < def.roles.length) {
346
+ if (!process.env.MONOMIND_MAX_SDK_PROCS &&
347
+ getResourceLimits().maxSdkProcesses < def.roles.length) {
310
348
  configureResourceLimits({ maxSdkProcesses: def.roles.length });
311
349
  }
312
350
  // Validate per-role providers before spawning anything (fail-fast: a
@@ -329,10 +367,8 @@ export class OrgDaemon {
329
367
  const MAX_COLLECTED = 1000;
330
368
  const collected = [];
331
369
  let lastActivity = Date.now();
332
- bus.subscribe(e => {
333
- const slim = e.data?.content != null
334
- ? { ...e, data: { ...e.data, content: undefined } }
335
- : e;
370
+ bus.subscribe((e) => {
371
+ const slim = e.data?.content != null ? { ...e, data: { ...e.data, content: undefined } } : e;
336
372
  collected.push(slim);
337
373
  if (collected.length > MAX_COLLECTED)
338
374
  collected.splice(0, collected.length - MAX_COLLECTED);
@@ -347,7 +383,7 @@ export class OrgDaemon {
347
383
  // against a concurrent manual stop.
348
384
  if (e.type === 'status' && e.reason === 'org-complete') {
349
385
  const t = setTimeout(() => {
350
- this.stopOrg(name, { drainMs: COMPLETE_DRAIN_MS }).catch(err => console.error(`org ${name}: auto-stop after org_complete failed:`, err instanceof Error ? err.message : err));
386
+ this.stopOrg(name, { drainMs: COMPLETE_DRAIN_MS }).catch((err) => console.error(`org ${name}: auto-stop after org_complete failed:`, err instanceof Error ? err.message : err));
351
387
  }, 1000);
352
388
  t.unref?.();
353
389
  }
@@ -381,7 +417,14 @@ export class OrgDaemon {
381
417
  });
382
418
  if (this.opts.forward !== false)
383
419
  this.forwarders.set(name, attachForwarder(bus, this.opts.controlJson ?? join(this.root, '.monomind/control.json')));
384
- const running = { def, run, bus, agents: new Map(), busEvents: () => [...collected], workdir: cwd };
420
+ const running = {
421
+ def,
422
+ run,
423
+ bus,
424
+ agents: new Map(),
425
+ busEvents: () => [...collected],
426
+ workdir: cwd,
427
+ };
385
428
  this.orgs.set(name, running);
386
429
  // ── MonoFence guardrail: pre-create per-role instances ────────────────
387
430
  const globalFence = loadGlobalFenceConfig(this.root);
@@ -404,7 +447,9 @@ export class OrgDaemon {
404
447
  });
405
448
  }
406
449
  }
407
- catch { /* monofence-ai not installed — skip silently */ }
450
+ catch {
451
+ /* monofence-ai not installed — skip silently */
452
+ }
408
453
  }
409
454
  if (roleFences.size > 0)
410
455
  running.fences = roleFences;
@@ -415,7 +460,7 @@ export class OrgDaemon {
415
460
  // kickoff went to (type==='boss' || reports_to===null || roles[0]), so a
416
461
  // fallback-selected boss could be told to call org_complete without having
417
462
  // the tool.
418
- const bossRole = def.roles.find(r => r.type === 'boss' || r.reports_to === null) ?? def.roles[0];
463
+ const bossRole = def.roles.find((r) => r.type === 'boss' || r.reports_to === null) ?? def.roles[0];
419
464
  // Canonical entity names from the org KG — injected into the coordinator
420
465
  // prompt so org_learn extractions reuse them instead of minting duplicates.
421
466
  const glossary = await (async () => {
@@ -443,26 +488,54 @@ export class OrgDaemon {
443
488
  if (ws === 'worktree-per-role' && role.id !== bossRole.id) {
444
489
  const wtPath = join(this.root, ORG_DIR, name, `worktree-${role.id}`);
445
490
  try {
446
- // Q7: top-level `import { execSync }` replaces the inlined
491
+ // Q7: top-level `import { execFileSync }` replaces the inlined
447
492
  // `require('node:child_process')` that broke ESM at runtime —
448
493
  // vitest's CJS shim masked it in tests but the built package
449
494
  // threw "require is not defined" in real Node ESM execution.
495
+ // SEC-5: argv-array form, no shell.
450
496
  if (existsSync(wtPath)) {
451
497
  try {
452
- execSync(`git worktree remove --force "${wtPath}"`, { cwd: this.root, stdio: 'ignore', timeout: 30_000 });
498
+ execFileSync('git', ['worktree', 'remove', '--force', wtPath], {
499
+ cwd: this.root,
500
+ stdio: 'ignore',
501
+ timeout: 30_000,
502
+ });
503
+ }
504
+ catch {
505
+ /* best-effort */
453
506
  }
454
- catch { /* best-effort */ }
455
507
  }
456
- execSync(`git worktree add "${wtPath}" HEAD --detach`, { cwd: this.root, stdio: 'ignore', timeout: 30_000 });
508
+ execFileSync('git', ['worktree', 'add', wtPath, 'HEAD', '--detach'], {
509
+ cwd: this.root,
510
+ stdio: 'ignore',
511
+ timeout: 30_000,
512
+ });
457
513
  roleCwd = wtPath;
458
514
  }
459
- catch { /* fallback to shared cwd if git worktree fails */ }
515
+ catch {
516
+ /* fallback to shared cwd if git worktree fails */
517
+ }
460
518
  }
461
519
  const mailbox = new Mailbox();
462
520
  const policy = new PolicyEngine(role.id, { maxTokens: role.budget_tokens ?? perRoleBudget, ...(role.policy ?? {}) }, bus, roleCwd);
463
- const runtime = { mailbox, policy, status: 'running', done: Promise.resolve(), metrics: { tokens: 0, costUsd: 0 }, worktreePath: roleCwd !== cwd ? roleCwd : undefined, scrollback: new ScrollbackBuffer() };
521
+ const runtime = {
522
+ mailbox,
523
+ policy,
524
+ status: 'running',
525
+ done: Promise.resolve(),
526
+ metrics: { tokens: 0, costUsd: 0 },
527
+ sessionId: undefined,
528
+ worktreePath: roleCwd !== cwd ? roleCwd : undefined,
529
+ scrollback: new ScrollbackBuffer(),
530
+ };
464
531
  const sessionOpts = {
465
- org: name, role, bus, policy, mailbox, cwd: roleCwd, def,
532
+ org: name,
533
+ role,
534
+ bus,
535
+ policy,
536
+ mailbox,
537
+ cwd: roleCwd,
538
+ def,
466
539
  // Pass the org state directory so runners that persist per-role state
467
540
  // (VercelAgentRunner session files) write under .monomind/orgs/<name>
468
541
  // instead of polluting the workspace cwd.
@@ -470,6 +543,7 @@ export class OrgDaemon {
470
543
  maxTurns: role.max_turns_per_message ?? def.run_config.max_turns_per_message,
471
544
  lastMessageId: () => runtime.lastMessageId,
472
545
  onOutput: (line) => runtime.scrollback.push(line),
546
+ onSessionId: (id) => { runtime.sessionId = id; },
473
547
  deliver: (from, to, subject, body) => this.deliver(name, from, to, subject, body),
474
548
  askHuman: (r, question) => this.askHuman(name, r, question),
475
549
  onGate: (r, gateName, gateDesc) => this.createGate(name, r, gateName, gateDesc),
@@ -483,7 +557,13 @@ export class OrgDaemon {
483
557
  fence: roleFences.get(role.id),
484
558
  onComplete: role.id === bossRole.id
485
559
  ? (r, outcome, summary) => {
486
- bus.emit({ type: 'status', from: r, reason: 'org-complete', msg: `run outcome: ${outcome}`, data: { outcome, summary } });
560
+ bus.emit({
561
+ type: 'status',
562
+ from: r,
563
+ reason: 'org-complete',
564
+ msg: `run outcome: ${outcome}`,
565
+ data: { outcome, summary },
566
+ });
487
567
  }
488
568
  : undefined,
489
569
  // #11: a boss that overflows its context window isn't a crash (it keeps
@@ -493,25 +573,50 @@ export class OrgDaemon {
493
573
  onContextLimit: role.id === bossRole.id ? () => this.scheduleBossRestart(name) : undefined,
494
574
  recall: async (r, q) => {
495
575
  const answer = await this.recallOrgMemory(name, def, q, r);
496
- bus.emit({ type: 'status', from: r, reason: 'org-recall', msg: `recall: ${q.slice(0, 80)}`, data: { hits: answer.hits } });
576
+ bus.emit({
577
+ type: 'status',
578
+ from: r,
579
+ reason: 'org-recall',
580
+ msg: `recall: ${q.slice(0, 80)}`,
581
+ data: { hits: answer.hits },
582
+ });
497
583
  return answer.text;
498
584
  },
499
585
  searchKnowledge: async (r, q) => {
500
586
  const answer = await this.searchProjectKnowledge(q);
501
- bus.emit({ type: 'status', from: r, reason: 'knowledge-search', msg: `knowledge: ${q.slice(0, 80)}`, data: { hits: answer.hits } });
587
+ bus.emit({
588
+ type: 'status',
589
+ from: r,
590
+ reason: 'knowledge-search',
591
+ msg: `knowledge: ${q.slice(0, 80)}`,
592
+ data: { hits: answer.hits },
593
+ });
502
594
  return answer.text;
503
595
  },
504
596
  glossary,
505
597
  remember: async (r, content, scope) => {
506
598
  const text = await this.rememberOrgMemory(name, def, r, content, scope, run);
507
- bus.emit({ type: 'status', from: r, reason: 'org-remember', msg: `remember (${scope}): ${content.slice(0, 80)}`, data: { scope } });
599
+ bus.emit({
600
+ type: 'status',
601
+ from: r,
602
+ reason: 'org-remember',
603
+ msg: `remember (${scope}): ${content.slice(0, 80)}`,
604
+ data: { scope },
605
+ });
508
606
  return text;
509
607
  },
510
608
  learn: async (r, payload) => {
511
609
  const text = await this.learnOrgKnowledge(name, run, payload);
512
610
  bus.emit({
513
- type: 'status', from: r, reason: 'org-learn', msg: `learn: ${text.slice(0, 120)}`,
514
- data: { nodes: payload.nodes?.length ?? 0, edges: payload.edges?.length ?? 0, rules: payload.rules?.length ?? 0 },
611
+ type: 'status',
612
+ from: r,
613
+ reason: 'org-learn',
614
+ msg: `learn: ${text.slice(0, 120)}`,
615
+ data: {
616
+ nodes: payload.nodes?.length ?? 0,
617
+ edges: payload.edges?.length ?? 0,
618
+ rules: payload.rules?.length ?? 0,
619
+ },
515
620
  });
516
621
  return text;
517
622
  },
@@ -525,6 +630,18 @@ export class OrgDaemon {
525
630
  const running = this.orgs.get(name);
526
631
  return JSON.stringify(running?.taskDag?.all() ?? [], null, 2);
527
632
  },
633
+ splitTask: (r, parentId, children) => {
634
+ return this.dagSplitTask(name, r, parentId, children);
635
+ },
636
+ mergeTask: (r, sourceId, targetId) => {
637
+ return this.dagMergeTask(name, r, sourceId, targetId);
638
+ },
639
+ cancelTask: (r, taskId, reason) => {
640
+ return this.dagCancelTask(name, r, taskId, reason);
641
+ },
642
+ planGraph: (r, specs) => {
643
+ return this.dagPlanGraph(name, r, specs);
644
+ },
528
645
  queryFn: this.opts.queryFn,
529
646
  // Runner resolution: explicit opts.runner > role `runtime` field >
530
647
  // org def `runtime` field > MONOMIND_RUNTIME env (opencode/kimicode) >
@@ -532,7 +649,8 @@ export class OrgDaemon {
532
649
  // Leaving it undefined for the default path is what keeps
533
650
  // Claude/Antigravity orgs byte-for-byte unchanged. Session opts are
534
651
  // built per role here in spawnRole, so each role gets its own runner.
535
- runner: this.opts.runner ?? resolveRoleRunner(role.runtime, def.runtime, role.provider?.kind, undefined),
652
+ runner: this.opts.runner ??
653
+ resolveRoleRunner(role.runtime, def.runtime, role.provider?.kind, undefined),
536
654
  };
537
655
  // Supervised session: transient crashes (provider blips, network) restart
538
656
  // with backoff; a crash with the mailbox already closed, or one that
@@ -562,7 +680,8 @@ export class OrgDaemon {
562
680
  if (killedByStop) {
563
681
  runtime.status = 'ended';
564
682
  bus.emit({
565
- type: 'status', from: role.id,
683
+ type: 'status',
684
+ from: role.id,
566
685
  msg: `agent "${role.id}" terminated by stop (was still working when drain window expired)`,
567
686
  reason: 'terminated-by-stop',
568
687
  });
@@ -576,10 +695,16 @@ export class OrgDaemon {
576
695
  mailbox.close();
577
696
  const isContextLimit = OrgDaemon.CONTEXT_LIMIT_RE.test(message);
578
697
  bus.emit({
579
- type: 'audit', from: role.id,
698
+ type: 'audit',
699
+ from: role.id,
580
700
  msg: `agent "${role.id}" crashed: ${message}`,
581
701
  reason: isContextLimit ? 'agent-context-limit' : 'agent-session-crash',
582
- data: { agentId: role.id, error: message, restarts: attempt, contextLimit: isContextLimit },
702
+ data: {
703
+ agentId: role.id,
704
+ error: message,
705
+ restarts: attempt,
706
+ contextLimit: isContextLimit,
707
+ },
583
708
  });
584
709
  if (role.id !== bossRole.id) {
585
710
  // #2/#3: a worker is gone for the rest of this run. Without this
@@ -594,8 +719,12 @@ export class OrgDaemon {
594
719
  ? ' This was a context-window overflow — re-dispatching the same task verbatim will fail identically. Break the work into smaller pieces (one file or section at a time) and do not paste large file contents in a single message.'
595
720
  : '';
596
721
  bossRt.mailbox.push(`[system] Worker "${role.id}" crashed and will not recover this run (${message}). It can no longer receive messages — stop messaging it. Reassign its outstanding work to another agent or take it on yourself.${guidance}`);
597
- bus.emit({ type: 'audit', from: bossRole.id, reason: 'worker-crashed',
598
- msg: `worker "${role.id}" crashed (contextLimit=${isContextLimit}); coordinator notified to reassign` });
722
+ bus.emit({
723
+ type: 'audit',
724
+ from: bossRole.id,
725
+ reason: 'worker-crashed',
726
+ msg: `worker "${role.id}" crashed (contextLimit=${isContextLimit}); coordinator notified to reassign`,
727
+ });
599
728
  }
600
729
  }
601
730
  else {
@@ -613,7 +742,9 @@ export class OrgDaemon {
613
742
  const fatal = err?.fatal === true;
614
743
  if (fatal) {
615
744
  bus.emit({
616
- type: 'status', from: role.id, reason: 'agent-fatal',
745
+ type: 'status',
746
+ from: role.id,
747
+ reason: 'agent-fatal',
617
748
  msg: `agent "${role.id}" hit a fatal (non-retryable) error — not restarting`,
618
749
  });
619
750
  crash();
@@ -624,10 +755,15 @@ export class OrgDaemon {
624
755
  return;
625
756
  }
626
757
  bus.emit({
627
- type: 'status', from: role.id, reason: 'agent-restart',
758
+ type: 'status',
759
+ from: role.id,
760
+ reason: 'agent-restart',
628
761
  msg: `agent "${role.id}" crashed (${message}) — restarting in ${BACKOFFS_MS[attempt]}ms (attempt ${attempt + 1}/${BACKOFFS_MS.length})`,
629
762
  });
630
- await new Promise(r => { const t = setTimeout(r, BACKOFFS_MS[attempt]); t.unref?.(); });
763
+ await new Promise((r) => {
764
+ const t = setTimeout(r, BACKOFFS_MS[attempt]);
765
+ t.unref?.();
766
+ });
631
767
  if (mailbox.isClosed) {
632
768
  crash();
633
769
  return;
@@ -661,7 +797,9 @@ export class OrgDaemon {
661
797
  // no-cjs-require-in-esm.test.ts.
662
798
  reapOrphanedSdkProcesses(new Set(), process.pid);
663
799
  }
664
- catch { /* best-effort */ }
800
+ catch {
801
+ /* best-effort */
802
+ }
665
803
  };
666
804
  process.on('exit', crashCleanup);
667
805
  running._crashCleanup = crashCleanup;
@@ -672,17 +810,24 @@ export class OrgDaemon {
672
810
  if (staleThreshold && staleThreshold > 0 && cwd === this.root) {
673
811
  try {
674
812
  const { execSync } = await import('node:child_process');
675
- const behind = execSync('git rev-list --count HEAD..@{upstream} 2>/dev/null', { cwd, encoding: 'utf8', timeout: 10_000 }).trim();
813
+ const behind = execSync('git rev-list --count HEAD..@{upstream} 2>/dev/null', {
814
+ cwd,
815
+ encoding: 'utf8',
816
+ timeout: 10_000,
817
+ }).trim();
676
818
  const count = parseInt(behind, 10);
677
819
  if (!isNaN(count) && count > staleThreshold) {
678
820
  bus.emit({
679
- type: 'audit', reason: 'stale-base',
821
+ type: 'audit',
822
+ reason: 'stale-base',
680
823
  msg: `working tree is ${count} commits behind upstream (threshold: ${staleThreshold}) — consider pulling before running`,
681
824
  data: { behind: count, threshold: staleThreshold },
682
825
  });
683
826
  }
684
827
  }
685
- catch { /* no upstream tracking or git unavailable — skip silently */ }
828
+ catch {
829
+ /* no upstream tracking or git unavailable — skip silently */
830
+ }
686
831
  }
687
832
  const boss = bossRole;
688
833
  // Cross-run memory: brief the coordinator on the previous run so scheduled
@@ -695,9 +840,15 @@ export class OrgDaemon {
695
840
  : `no recorded outcome (${prev.messages} messages, ${prev.assets.length} assets${prev.crashes.length ? `, ${prev.crashes.length} crashed agent(s)` : ''})`) +
696
841
  `\nBuild on that work — do not redo what is already done.`
697
842
  : '';
698
- running.agents.get(boss.id).mailbox.push(`Org "${name}" started (run ${run}).\nGoal: ${taskOverride ?? def.goal}\n` +
843
+ running.agents
844
+ .get(boss.id)
845
+ .mailbox.push(`Org "${name}" started (run ${run}).\nGoal: ${taskOverride ?? def.goal}\n` +
699
846
  `Coordinate your team via org_send. When the goal is achieved (or clearly can't be), record it with org_complete, then end your turn.${prevBrief}`);
700
- bus.emit({ type: 'status', msg: `org started (${def.roles.length} agents)`, data: { goal: taskOverride ?? def.goal } });
847
+ bus.emit({
848
+ type: 'status',
849
+ msg: `org started (${def.roles.length} agents)`,
850
+ data: { goal: taskOverride ?? def.goal },
851
+ });
701
852
  this.persistState(name, 'running', run);
702
853
  // Idle watchdog: a hung tool call (or a run that quietly finished without
703
854
  // org_complete) produces no bus events, and every agent just waits. After
@@ -712,13 +863,13 @@ export class OrgDaemon {
712
863
  let nudges = 0;
713
864
  const idleStop = (msg) => {
714
865
  bus.emit({ type: 'audit', reason: 'idle-stop', msg });
715
- this.stopOrg(name).catch(err => console.error(`org ${name}: idle-stop failed:`, err instanceof Error ? err.message : err));
866
+ this.stopOrg(name).catch((err) => console.error(`org ${name}: idle-stop failed:`, err instanceof Error ? err.message : err));
716
867
  };
717
868
  const wd = setInterval(() => {
718
869
  if (this.restarting.has(name))
719
870
  return; // boss auto-restart in flight — don't nudge or stop
720
871
  // A pending gate means the org is legitimately waiting for human input
721
- const pendingGates = this.readGates(name).gates.filter(g => g.status === 'pending');
872
+ const pendingGates = this.readGates(name).gates.filter((g) => g.status === 'pending');
722
873
  if (pendingGates.length > 0)
723
874
  return;
724
875
  const idleFor = Date.now() - lastActivity;
@@ -738,7 +889,12 @@ export class OrgDaemon {
738
889
  }
739
890
  nudges++;
740
891
  nudgedAt = Date.now();
741
- bus.emit({ type: 'audit', from: bossRole.id, reason: 'idle-nudge', msg: `no org activity for ${Math.round(idleFor / 60_000)}m — nudging boss (${nudges}/${MAX_IDLE_NUDGES})` });
892
+ bus.emit({
893
+ type: 'audit',
894
+ from: bossRole.id,
895
+ reason: 'idle-nudge',
896
+ msg: `no org activity for ${Math.round(idleFor / 60_000)}m — nudging boss (${nudges}/${MAX_IDLE_NUDGES})`,
897
+ });
742
898
  bossRt.mailbox.push(`[watchdog] No activity in org "${name}" for ${Math.round(idleFor / 60_000)} minute(s). ` +
743
899
  `If the goal is achieved (or clearly can't be), call org_complete now. Otherwise check on your team via org_send and reassign stalled work.`);
744
900
  }
@@ -769,7 +925,13 @@ export class OrgDaemon {
769
925
  }
770
926
  const agent = running.agents.get(msg.toRole);
771
927
  if (agent && !agent.mailbox.isClosed) {
772
- bus.emit({ type: 'xorg', from: msg.fromQualified, to: `${name}:${msg.toRole}`, subject: msg.subject, msg: msg.body });
928
+ bus.emit({
929
+ type: 'xorg',
930
+ from: msg.fromQualified,
931
+ to: `${name}:${msg.toRole}`,
932
+ subject: msg.subject,
933
+ msg: msg.body,
934
+ });
773
935
  agent.mailbox.push(this.mailBody(name, running, `[message from ${msg.fromQualified}] subject: ${msg.subject}`, msg.body, `inbox-${msg.ts}-${Math.random().toString(36).slice(2, 8)}`));
774
936
  }
775
937
  }
@@ -847,11 +1009,15 @@ export class OrgDaemon {
847
1009
  // "crashed") and threw the work away. allSettled resolves as soon as every
848
1010
  // session ends, so a long drain is a ceiling, not a delay.
849
1011
  const stopWaitMs = drainMs ?? this.opts.stopWaitMs ?? 15_000;
850
- const allDone = Promise.allSettled([...org.agents.values()].map(a => a.done)).then(() => false);
851
- const timedOut = await Promise.race([allDone, new Promise(r => setTimeout(() => r(true), stopWaitMs))]);
1012
+ const allDone = Promise.allSettled([...org.agents.values()].map((a) => a.done)).then(() => false);
1013
+ const timedOut = await Promise.race([
1014
+ allDone,
1015
+ new Promise((r) => setTimeout(() => r(true), stopWaitMs)),
1016
+ ]);
852
1017
  if (timedOut) {
853
1018
  org.bus.emit({
854
- type: 'audit', msg: `org stop timed out after ${stopWaitMs}ms waiting for agent sessions to finish — proceeding anyway`,
1019
+ type: 'audit',
1020
+ msg: `org stop timed out after ${stopWaitMs}ms waiting for agent sessions to finish — proceeding anyway`,
855
1021
  reason: 'stop-timeout',
856
1022
  });
857
1023
  // Reap only SDK processes spawned by THIS node process — ownerPid filter
@@ -859,9 +1025,15 @@ export class OrgDaemon {
859
1025
  try {
860
1026
  const reaped = reapOrphanedSdkProcesses(new Set(), process.pid);
861
1027
  if (reaped > 0)
862
- org.bus.emit({ type: 'audit', reason: 'orphan-reap', msg: `reaped ${reaped} orphaned SDK process(es) after stop timeout` });
1028
+ org.bus.emit({
1029
+ type: 'audit',
1030
+ reason: 'orphan-reap',
1031
+ msg: `reaped ${reaped} orphaned SDK process(es) after stop timeout`,
1032
+ });
1033
+ }
1034
+ catch {
1035
+ /* best-effort */
863
1036
  }
864
- catch { /* best-effort */ }
865
1037
  }
866
1038
  org.bus.emit({ type: 'status', msg: 'org stopped' });
867
1039
  await org.bus.flush();
@@ -893,7 +1065,10 @@ export class OrgDaemon {
893
1065
  if (forwarder) {
894
1066
  await Promise.race([
895
1067
  forwarder.settle(),
896
- new Promise(r => { const t = setTimeout(r, 5_000); t.unref?.(); }),
1068
+ new Promise((r) => {
1069
+ const t = setTimeout(r, 5_000);
1070
+ t.unref?.();
1071
+ }),
897
1072
  ]);
898
1073
  forwarder.unsubscribe();
899
1074
  // Only remove from the map if it's still OURS — an autoWake-restart may
@@ -908,27 +1083,41 @@ export class OrgDaemon {
908
1083
  this.persistState(name, 'stopped', org.run, org, stopCheckpoint);
909
1084
  // Clean up git worktrees — shared (workspace: 'worktree') and per-role.
910
1085
  try {
911
- const { execSync } = await import('node:child_process');
1086
+ const { execFileSync } = await import('node:child_process');
912
1087
  if (org.worktreePath) {
913
1088
  try {
914
- execSync(`git worktree remove --force "${org.worktreePath}"`, { cwd: this.root, stdio: 'ignore', timeout: 30_000 });
1089
+ execFileSync('git', ['worktree', 'remove', '--force', org.worktreePath], {
1090
+ cwd: this.root,
1091
+ stdio: 'ignore',
1092
+ timeout: 30_000,
1093
+ });
1094
+ }
1095
+ catch {
1096
+ /* best-effort */
915
1097
  }
916
- catch { /* best-effort */ }
917
1098
  }
918
1099
  for (const agent of org.agents.values()) {
919
1100
  if (agent.worktreePath) {
920
1101
  try {
921
- execSync(`git worktree remove --force "${agent.worktreePath}"`, { cwd: this.root, stdio: 'ignore', timeout: 30_000 });
1102
+ execFileSync('git', ['worktree', 'remove', '--force', agent.worktreePath], {
1103
+ cwd: this.root,
1104
+ stdio: 'ignore',
1105
+ timeout: 30_000,
1106
+ });
1107
+ }
1108
+ catch {
1109
+ /* best-effort */
922
1110
  }
923
- catch { /* best-effort */ }
924
1111
  }
925
1112
  }
926
1113
  }
927
- catch { /* node:child_process unavailable — skip */ }
1114
+ catch {
1115
+ /* node:child_process unavailable — skip */
1116
+ }
928
1117
  }
929
1118
  async stopAll() {
930
1119
  await Promise.all([
931
- ...[...this.orgs.keys()].map(n => this.stopOrg(n)),
1120
+ ...[...this.orgs.keys()].map((n) => this.stopOrg(n)),
932
1121
  ...this.stopping.values(), // detached self-stops still flushing
933
1122
  ]);
934
1123
  }
@@ -949,7 +1138,10 @@ export class OrgDaemon {
949
1138
  // would brick every subsequent `org status` / isOrgRunning / scheduler
950
1139
  // call. The state files in 6 other daemon paths already use this helper.
951
1140
  writeJsonFileAtomic(p, {
952
- status, run, pid: process.pid, updated: new Date().toISOString(),
1141
+ status,
1142
+ run,
1143
+ pid: process.pid,
1144
+ updated: new Date().toISOString(),
953
1145
  ...(missing.length ? { abandonedRoles: missing } : {}),
954
1146
  ...(checkpoint ? { checkpoint } : {}),
955
1147
  });
@@ -963,11 +1155,16 @@ export class OrgDaemon {
963
1155
  // C4: atomic write — crash handler is the most likely place to hit
964
1156
  // a partial write since the process is mid-teardown.
965
1157
  writeJsonFileAtomic(p, {
966
- status: 'crashed', run: org.run, pid: process.pid,
967
- updated: new Date().toISOString(), closedBy: 'crash-handler',
1158
+ status: 'crashed',
1159
+ run: org.run,
1160
+ pid: process.pid,
1161
+ updated: new Date().toISOString(),
1162
+ closedBy: 'crash-handler',
968
1163
  });
969
1164
  }
970
- catch { /* best effort — filesystem may be unavailable */ }
1165
+ catch {
1166
+ /* best effort — filesystem may be unavailable */
1167
+ }
971
1168
  }
972
1169
  }
973
1170
  heartbeatPath() {
@@ -982,17 +1179,22 @@ export class OrgDaemon {
982
1179
  // C4: atomic write — heartbeat corruption is how `org status` reports
983
1180
  // a phantom daemon after a crash.
984
1181
  writeJsonFileAtomic(p, {
985
- pid: process.pid, updatedAt: new Date().toISOString(),
1182
+ pid: process.pid,
1183
+ updatedAt: new Date().toISOString(),
986
1184
  running: this.listRunning(),
987
1185
  });
988
1186
  }
989
- catch { /* best effort */ }
1187
+ catch {
1188
+ /* best effort */
1189
+ }
990
1190
  }
991
1191
  clearHeartbeat() {
992
1192
  try {
993
1193
  unlinkSync(this.heartbeatPath());
994
1194
  }
995
- catch { /* already gone or never written */ }
1195
+ catch {
1196
+ /* already gone or never written */
1197
+ }
996
1198
  }
997
1199
  // ── Delegated methods — extracted to focused modules ──────────────────
998
1200
  // approvals.ts
@@ -1028,6 +1230,18 @@ export class OrgDaemon {
1028
1230
  dagCompleteTask(org, role, taskId, result) {
1029
1231
  return decisionOps.dagCompleteTask(this, org, role, taskId, result);
1030
1232
  }
1233
+ dagSplitTask(org, role, parentId, children) {
1234
+ return decisionOps.dagSplitTask(this, org, role, parentId, children);
1235
+ }
1236
+ dagMergeTask(org, role, sourceId, targetId) {
1237
+ return decisionOps.dagMergeTask(this, org, role, sourceId, targetId);
1238
+ }
1239
+ dagCancelTask(org, role, taskId, reason) {
1240
+ return decisionOps.dagCancelTask(this, org, role, taskId, reason);
1241
+ }
1242
+ dagPlanGraph(org, role, specs) {
1243
+ return decisionOps.dagPlanGraph(this, org, role, specs);
1244
+ }
1031
1245
  recordDecision(org, role, decision) {
1032
1246
  decisionOps.recordDecision(this, org, role, decision);
1033
1247
  }
@@ -1043,16 +1257,26 @@ export class OrgDaemon {
1043
1257
  }
1044
1258
  // scheduler-integration.ts
1045
1259
  /** @internal */
1046
- autoWake(name) { scheduler.autoWake(this, name); }
1047
- scheduleBossRestart(name) { scheduler.scheduleBossRestart(this, name); }
1260
+ autoWake(name) {
1261
+ scheduler.autoWake(this, name);
1262
+ }
1263
+ scheduleBossRestart(name) {
1264
+ scheduler.scheduleBossRestart(this, name);
1265
+ }
1048
1266
  /** @internal */
1049
1267
  scheduleDeferredSpawn(name, running, role, spawnRole) {
1050
1268
  scheduler.scheduleDeferredSpawn(this, name, running, role, spawnRole);
1051
1269
  }
1052
1270
  // org-memory.ts
1053
- orgMemoryNamespace(name, def) { return orgMemory.orgMemoryNamespace(name, def); }
1054
- orgMemoryDbPath() { return orgMemory.orgMemoryDbPath(this.root); }
1055
- orgMemoryUsable() { return orgMemory.orgMemoryUsable(this.root); }
1271
+ orgMemoryNamespace(name, def) {
1272
+ return orgMemory.orgMemoryNamespace(name, def);
1273
+ }
1274
+ orgMemoryDbPath() {
1275
+ return orgMemory.orgMemoryDbPath(this.root);
1276
+ }
1277
+ orgMemoryUsable() {
1278
+ return orgMemory.orgMemoryUsable(this.root);
1279
+ }
1056
1280
  async rememberOrgMemory(name, def, role, content, scope, run) {
1057
1281
  return orgMemory.rememberOrgMemory(this.root, name, def, role, content, scope, run);
1058
1282
  }