@bradygaster/squad-cli 0.9.1 → 0.9.2-insider.6

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 (320) hide show
  1. package/README.md +329 -329
  2. package/dist/cli/commands/build.d.ts.map +1 -1
  3. package/dist/cli/commands/build.js +10 -10
  4. package/dist/cli/commands/build.js.map +1 -1
  5. package/dist/cli/commands/config.d.ts +12 -0
  6. package/dist/cli/commands/config.d.ts.map +1 -0
  7. package/dist/cli/commands/config.js +157 -0
  8. package/dist/cli/commands/config.js.map +1 -0
  9. package/dist/cli/commands/consult.d.ts.map +1 -1
  10. package/dist/cli/commands/consult.js +9 -4
  11. package/dist/cli/commands/consult.js.map +1 -1
  12. package/dist/cli/commands/copilot.d.ts.map +1 -1
  13. package/dist/cli/commands/copilot.js +8 -7
  14. package/dist/cli/commands/copilot.js.map +1 -1
  15. package/dist/cli/commands/doctor.d.ts.map +1 -1
  16. package/dist/cli/commands/doctor.js +50 -17
  17. package/dist/cli/commands/doctor.js.map +1 -1
  18. package/dist/cli/commands/economy.d.ts.map +1 -1
  19. package/dist/cli/commands/economy.js +3 -2
  20. package/dist/cli/commands/economy.js.map +1 -1
  21. package/dist/cli/commands/export.d.ts.map +1 -1
  22. package/dist/cli/commands/export.js +22 -16
  23. package/dist/cli/commands/export.js.map +1 -1
  24. package/dist/cli/commands/extract.d.ts.map +1 -1
  25. package/dist/cli/commands/extract.js +14 -10
  26. package/dist/cli/commands/extract.js.map +1 -1
  27. package/dist/cli/commands/import.d.ts.map +1 -1
  28. package/dist/cli/commands/import.js +21 -18
  29. package/dist/cli/commands/import.js.map +1 -1
  30. package/dist/cli/commands/init-remote.d.ts.map +1 -1
  31. package/dist/cli/commands/init-remote.js +7 -6
  32. package/dist/cli/commands/init-remote.js.map +1 -1
  33. package/dist/cli/commands/link.d.ts.map +1 -1
  34. package/dist/cli/commands/link.js +11 -10
  35. package/dist/cli/commands/link.js.map +1 -1
  36. package/dist/cli/commands/migrate.d.ts.map +1 -1
  37. package/dist/cli/commands/migrate.js +19 -18
  38. package/dist/cli/commands/migrate.js.map +1 -1
  39. package/dist/cli/commands/personal.d.ts.map +1 -1
  40. package/dist/cli/commands/personal.js +57 -65
  41. package/dist/cli/commands/personal.js.map +1 -1
  42. package/dist/cli/commands/plugin.d.ts.map +1 -1
  43. package/dist/cli/commands/plugin.js +8 -7
  44. package/dist/cli/commands/plugin.js.map +1 -1
  45. package/dist/cli/commands/rc.d.ts.map +1 -1
  46. package/dist/cli/commands/rc.js +19 -12
  47. package/dist/cli/commands/rc.js.map +1 -1
  48. package/dist/cli/commands/schedule.d.ts.map +1 -1
  49. package/dist/cli/commands/schedule.js +6 -5
  50. package/dist/cli/commands/schedule.js.map +1 -1
  51. package/dist/cli/commands/start.d.ts.map +1 -1
  52. package/dist/cli/commands/start.js +18 -11
  53. package/dist/cli/commands/start.js.map +1 -1
  54. package/dist/cli/commands/streams.d.ts.map +1 -1
  55. package/dist/cli/commands/streams.js +3 -2
  56. package/dist/cli/commands/streams.js.map +1 -1
  57. package/dist/cli/commands/upstream.d.ts.map +1 -1
  58. package/dist/cli/commands/upstream.js +23 -19
  59. package/dist/cli/commands/upstream.js.map +1 -1
  60. package/dist/cli/commands/watch/capabilities/board.d.ts +22 -0
  61. package/dist/cli/commands/watch/capabilities/board.d.ts.map +1 -0
  62. package/dist/cli/commands/watch/capabilities/board.js +121 -0
  63. package/dist/cli/commands/watch/capabilities/board.js.map +1 -0
  64. package/dist/cli/commands/watch/capabilities/budget-check.d.ts +29 -0
  65. package/dist/cli/commands/watch/capabilities/budget-check.d.ts.map +1 -0
  66. package/dist/cli/commands/watch/capabilities/budget-check.js +38 -0
  67. package/dist/cli/commands/watch/capabilities/budget-check.js.map +1 -0
  68. package/dist/cli/commands/watch/capabilities/circuit-breaker.d.ts +52 -0
  69. package/dist/cli/commands/watch/capabilities/circuit-breaker.d.ts.map +1 -0
  70. package/dist/cli/commands/watch/capabilities/circuit-breaker.js +152 -0
  71. package/dist/cli/commands/watch/capabilities/circuit-breaker.js.map +1 -0
  72. package/dist/cli/commands/watch/capabilities/decision-hygiene.d.ts +14 -0
  73. package/dist/cli/commands/watch/capabilities/decision-hygiene.d.ts.map +1 -0
  74. package/dist/cli/commands/watch/capabilities/decision-hygiene.js +72 -0
  75. package/dist/cli/commands/watch/capabilities/decision-hygiene.js.map +1 -0
  76. package/dist/cli/commands/watch/capabilities/execute.d.ts +33 -0
  77. package/dist/cli/commands/watch/capabilities/execute.d.ts.map +1 -0
  78. package/dist/cli/commands/watch/capabilities/execute.js +156 -0
  79. package/dist/cli/commands/watch/capabilities/execute.js.map +1 -0
  80. package/dist/cli/commands/watch/capabilities/health-check.d.ts +29 -0
  81. package/dist/cli/commands/watch/capabilities/health-check.d.ts.map +1 -0
  82. package/dist/cli/commands/watch/capabilities/health-check.js +139 -0
  83. package/dist/cli/commands/watch/capabilities/health-check.js.map +1 -0
  84. package/dist/cli/commands/watch/capabilities/heartbeat.d.ts +48 -0
  85. package/dist/cli/commands/watch/capabilities/heartbeat.d.ts.map +1 -0
  86. package/dist/cli/commands/watch/capabilities/heartbeat.js +115 -0
  87. package/dist/cli/commands/watch/capabilities/heartbeat.js.map +1 -0
  88. package/dist/cli/commands/watch/capabilities/index.d.ts +9 -0
  89. package/dist/cli/commands/watch/capabilities/index.d.ts.map +1 -0
  90. package/dist/cli/commands/watch/capabilities/index.js +40 -0
  91. package/dist/cli/commands/watch/capabilities/index.js.map +1 -0
  92. package/dist/cli/commands/watch/capabilities/lockfile.d.ts +30 -0
  93. package/dist/cli/commands/watch/capabilities/lockfile.d.ts.map +1 -0
  94. package/dist/cli/commands/watch/capabilities/lockfile.js +100 -0
  95. package/dist/cli/commands/watch/capabilities/lockfile.js.map +1 -0
  96. package/dist/cli/commands/watch/capabilities/machine-capabilities.d.ts +30 -0
  97. package/dist/cli/commands/watch/capabilities/machine-capabilities.d.ts.map +1 -0
  98. package/dist/cli/commands/watch/capabilities/machine-capabilities.js +103 -0
  99. package/dist/cli/commands/watch/capabilities/machine-capabilities.js.map +1 -0
  100. package/dist/cli/commands/watch/capabilities/monitor-email.d.ts +14 -0
  101. package/dist/cli/commands/watch/capabilities/monitor-email.d.ts.map +1 -0
  102. package/dist/cli/commands/watch/capabilities/monitor-email.js +54 -0
  103. package/dist/cli/commands/watch/capabilities/monitor-email.js.map +1 -0
  104. package/dist/cli/commands/watch/capabilities/monitor-teams.d.ts +14 -0
  105. package/dist/cli/commands/watch/capabilities/monitor-teams.d.ts.map +1 -0
  106. package/dist/cli/commands/watch/capabilities/monitor-teams.js +55 -0
  107. package/dist/cli/commands/watch/capabilities/monitor-teams.js.map +1 -0
  108. package/dist/cli/commands/watch/capabilities/post-failure.d.ts +19 -0
  109. package/dist/cli/commands/watch/capabilities/post-failure.d.ts.map +1 -0
  110. package/dist/cli/commands/watch/capabilities/post-failure.js +58 -0
  111. package/dist/cli/commands/watch/capabilities/post-failure.js.map +1 -0
  112. package/dist/cli/commands/watch/capabilities/priority.d.ts +59 -0
  113. package/dist/cli/commands/watch/capabilities/priority.d.ts.map +1 -0
  114. package/dist/cli/commands/watch/capabilities/priority.js +101 -0
  115. package/dist/cli/commands/watch/capabilities/priority.js.map +1 -0
  116. package/dist/cli/commands/watch/capabilities/rate-pool.d.ts +67 -0
  117. package/dist/cli/commands/watch/capabilities/rate-pool.d.ts.map +1 -0
  118. package/dist/cli/commands/watch/capabilities/rate-pool.js +187 -0
  119. package/dist/cli/commands/watch/capabilities/rate-pool.js.map +1 -0
  120. package/dist/cli/commands/watch/capabilities/retro.d.ts +14 -0
  121. package/dist/cli/commands/watch/capabilities/retro.d.ts.map +1 -0
  122. package/dist/cli/commands/watch/capabilities/retro.js +81 -0
  123. package/dist/cli/commands/watch/capabilities/retro.js.map +1 -0
  124. package/dist/cli/commands/watch/capabilities/self-pull.d.ts +14 -0
  125. package/dist/cli/commands/watch/capabilities/self-pull.d.ts.map +1 -0
  126. package/dist/cli/commands/watch/capabilities/self-pull.js +33 -0
  127. package/dist/cli/commands/watch/capabilities/self-pull.js.map +1 -0
  128. package/dist/cli/commands/watch/capabilities/stale-reclaim.d.ts +23 -0
  129. package/dist/cli/commands/watch/capabilities/stale-reclaim.d.ts.map +1 -0
  130. package/dist/cli/commands/watch/capabilities/stale-reclaim.js +87 -0
  131. package/dist/cli/commands/watch/capabilities/stale-reclaim.js.map +1 -0
  132. package/dist/cli/commands/watch/capabilities/two-pass.d.ts +14 -0
  133. package/dist/cli/commands/watch/capabilities/two-pass.d.ts.map +1 -0
  134. package/dist/cli/commands/watch/capabilities/two-pass.js +66 -0
  135. package/dist/cli/commands/watch/capabilities/two-pass.js.map +1 -0
  136. package/dist/cli/commands/watch/capabilities/wave-dispatch.d.ts +14 -0
  137. package/dist/cli/commands/watch/capabilities/wave-dispatch.d.ts.map +1 -0
  138. package/dist/cli/commands/watch/capabilities/wave-dispatch.js +117 -0
  139. package/dist/cli/commands/watch/capabilities/wave-dispatch.js.map +1 -0
  140. package/dist/cli/commands/watch/capabilities/webhook-alerts.d.ts +29 -0
  141. package/dist/cli/commands/watch/capabilities/webhook-alerts.d.ts.map +1 -0
  142. package/dist/cli/commands/watch/capabilities/webhook-alerts.js +114 -0
  143. package/dist/cli/commands/watch/capabilities/webhook-alerts.js.map +1 -0
  144. package/dist/cli/commands/watch/config.d.ts +40 -0
  145. package/dist/cli/commands/watch/config.d.ts.map +1 -0
  146. package/dist/cli/commands/watch/config.js +129 -0
  147. package/dist/cli/commands/watch/config.js.map +1 -0
  148. package/dist/cli/commands/watch/index.d.ts +109 -0
  149. package/dist/cli/commands/watch/index.d.ts.map +1 -0
  150. package/dist/cli/commands/watch/index.js +757 -0
  151. package/dist/cli/commands/watch/index.js.map +1 -0
  152. package/dist/cli/commands/watch/registry.d.ts +19 -0
  153. package/dist/cli/commands/watch/registry.d.ts.map +1 -0
  154. package/dist/cli/commands/watch/registry.js +28 -0
  155. package/dist/cli/commands/watch/registry.js.map +1 -0
  156. package/dist/cli/commands/watch/types.d.ts +57 -0
  157. package/dist/cli/commands/watch/types.d.ts.map +1 -0
  158. package/dist/cli/commands/watch/types.js +8 -0
  159. package/dist/cli/commands/watch/types.js.map +1 -0
  160. package/dist/cli/core/cast.d.ts.map +1 -1
  161. package/dist/cli/core/cast.js +15 -19
  162. package/dist/cli/core/cast.js.map +1 -1
  163. package/dist/cli/core/detect-squad-dir.d.ts.map +1 -1
  164. package/dist/cli/core/detect-squad-dir.js +12 -10
  165. package/dist/cli/core/detect-squad-dir.js.map +1 -1
  166. package/dist/cli/core/email-scrub.d.ts.map +1 -1
  167. package/dist/cli/core/email-scrub.js +12 -11
  168. package/dist/cli/core/email-scrub.js.map +1 -1
  169. package/dist/cli/core/gh-cli.d.ts +13 -0
  170. package/dist/cli/core/gh-cli.d.ts.map +1 -1
  171. package/dist/cli/core/gh-cli.js +24 -0
  172. package/dist/cli/core/gh-cli.js.map +1 -1
  173. package/dist/cli/core/init.d.ts +2 -0
  174. package/dist/cli/core/init.d.ts.map +1 -1
  175. package/dist/cli/core/init.js +22 -5
  176. package/dist/cli/core/init.js.map +1 -1
  177. package/dist/cli/core/migrate-directory.d.ts.map +1 -1
  178. package/dist/cli/core/migrate-directory.js +14 -13
  179. package/dist/cli/core/migrate-directory.js.map +1 -1
  180. package/dist/cli/core/migrations.d.ts.map +1 -1
  181. package/dist/cli/core/migrations.js +22 -8
  182. package/dist/cli/core/migrations.js.map +1 -1
  183. package/dist/cli/core/nap.d.ts.map +1 -1
  184. package/dist/cli/core/nap.js +116 -49
  185. package/dist/cli/core/nap.js.map +1 -1
  186. package/dist/cli/core/project-type.d.ts.map +1 -1
  187. package/dist/cli/core/project-type.js +11 -10
  188. package/dist/cli/core/project-type.js.map +1 -1
  189. package/dist/cli/core/team-md.d.ts.map +1 -1
  190. package/dist/cli/core/team-md.js +43 -38
  191. package/dist/cli/core/team-md.js.map +1 -1
  192. package/dist/cli/core/templates.d.ts.map +1 -1
  193. package/dist/cli/core/templates.js +4 -3
  194. package/dist/cli/core/templates.js.map +1 -1
  195. package/dist/cli/core/upgrade.d.ts.map +1 -1
  196. package/dist/cli/core/upgrade.js +68 -55
  197. package/dist/cli/core/upgrade.js.map +1 -1
  198. package/dist/cli/core/version.d.ts.map +1 -1
  199. package/dist/cli/core/version.js +8 -7
  200. package/dist/cli/core/version.js.map +1 -1
  201. package/dist/cli/index.d.ts +1 -1
  202. package/dist/cli/index.d.ts.map +1 -1
  203. package/dist/cli/index.js +1 -1
  204. package/dist/cli/index.js.map +1 -1
  205. package/dist/cli/self-update.d.ts.map +1 -1
  206. package/dist/cli/self-update.js +7 -4
  207. package/dist/cli/self-update.js.map +1 -1
  208. package/dist/cli/shell/agent-name-parser.d.ts +16 -0
  209. package/dist/cli/shell/agent-name-parser.d.ts.map +1 -0
  210. package/dist/cli/shell/agent-name-parser.js +54 -0
  211. package/dist/cli/shell/agent-name-parser.js.map +1 -0
  212. package/dist/cli/shell/commands.d.ts.map +1 -1
  213. package/dist/cli/shell/commands.js +4 -3
  214. package/dist/cli/shell/commands.js.map +1 -1
  215. package/dist/cli/shell/coordinator.d.ts +4 -1
  216. package/dist/cli/shell/coordinator.d.ts.map +1 -1
  217. package/dist/cli/shell/coordinator.js +29 -26
  218. package/dist/cli/shell/coordinator.js.map +1 -1
  219. package/dist/cli/shell/index.d.ts.map +1 -1
  220. package/dist/cli/shell/index.js +33 -35
  221. package/dist/cli/shell/index.js.map +1 -1
  222. package/dist/cli/shell/lifecycle.d.ts +13 -2
  223. package/dist/cli/shell/lifecycle.d.ts.map +1 -1
  224. package/dist/cli/shell/lifecycle.js +26 -13
  225. package/dist/cli/shell/lifecycle.js.map +1 -1
  226. package/dist/cli/shell/session-store.d.ts.map +1 -1
  227. package/dist/cli/shell/session-store.js +16 -12
  228. package/dist/cli/shell/session-store.js.map +1 -1
  229. package/dist/cli/shell/spawn.d.ts +4 -1
  230. package/dist/cli/shell/spawn.d.ts.map +1 -1
  231. package/dist/cli/shell/spawn.js +28 -10
  232. package/dist/cli/shell/spawn.js.map +1 -1
  233. package/dist/cli-entry.js +136 -12
  234. package/dist/cli-entry.js.map +1 -1
  235. package/package.json +8 -4
  236. package/scripts/patch-esm-imports.mjs +105 -105
  237. package/scripts/patch-ink-rendering.mjs +115 -115
  238. package/templates/casting/Futurama.json +9 -9
  239. package/templates/casting-history.json +4 -4
  240. package/templates/casting-policy.json +37 -37
  241. package/templates/casting-reference.md +104 -104
  242. package/templates/casting-registry.json +3 -3
  243. package/templates/ceremonies.md +41 -41
  244. package/templates/charter.md +53 -53
  245. package/templates/constraint-tracking.md +38 -38
  246. package/templates/cooperative-rate-limiting.md +229 -229
  247. package/templates/copilot-instructions.md +46 -46
  248. package/templates/history.md +10 -10
  249. package/templates/identity/now.md +9 -9
  250. package/templates/identity/wisdom.md +15 -15
  251. package/templates/issue-lifecycle.md +412 -412
  252. package/templates/keda-scaler.md +164 -164
  253. package/templates/machine-capabilities.md +74 -74
  254. package/templates/mcp-config.md +90 -90
  255. package/templates/multi-agent-format.md +28 -28
  256. package/templates/orchestration-log.md +27 -27
  257. package/templates/plugin-marketplace.md +49 -49
  258. package/templates/ralph-circuit-breaker.md +313 -313
  259. package/templates/raw-agent-output.md +37 -37
  260. package/templates/roster.md +60 -60
  261. package/templates/routing.md +39 -39
  262. package/templates/run-output.md +50 -50
  263. package/templates/scribe-charter.md +123 -119
  264. package/templates/skill.md +24 -24
  265. package/templates/skills/agent-collaboration/SKILL.md +42 -42
  266. package/templates/skills/agent-conduct/SKILL.md +24 -24
  267. package/templates/skills/architectural-proposals/SKILL.md +151 -151
  268. package/templates/skills/ci-validation-gates/SKILL.md +84 -84
  269. package/templates/skills/cli-wiring/SKILL.md +47 -47
  270. package/templates/skills/client-compatibility/SKILL.md +89 -89
  271. package/templates/skills/cross-machine-coordination/SKILL.md +434 -0
  272. package/templates/skills/cross-squad/SKILL.md +114 -114
  273. package/templates/skills/distributed-mesh/SKILL.md +287 -287
  274. package/templates/skills/distributed-mesh/mesh.json.example +30 -30
  275. package/templates/skills/distributed-mesh/sync-mesh.ps1 +111 -111
  276. package/templates/skills/distributed-mesh/sync-mesh.sh +104 -104
  277. package/templates/skills/docs-standards/SKILL.md +71 -71
  278. package/templates/skills/economy-mode/SKILL.md +114 -114
  279. package/templates/skills/error-recovery/SKILL.md +99 -0
  280. package/templates/skills/external-comms/SKILL.md +329 -329
  281. package/templates/skills/gh-auth-isolation/SKILL.md +183 -183
  282. package/templates/skills/git-workflow/SKILL.md +204 -204
  283. package/templates/skills/github-multi-account/SKILL.md +95 -95
  284. package/templates/skills/history-hygiene/SKILL.md +36 -36
  285. package/templates/skills/humanizer/SKILL.md +105 -105
  286. package/templates/skills/init-mode/SKILL.md +102 -102
  287. package/templates/skills/iterative-retrieval/SKILL.md +165 -0
  288. package/templates/skills/model-selection/SKILL.md +117 -117
  289. package/templates/skills/nap/SKILL.md +24 -24
  290. package/templates/skills/notification-routing/SKILL.md +105 -0
  291. package/templates/skills/personal-squad/SKILL.md +57 -57
  292. package/templates/skills/pr-screenshots/SKILL.md +149 -0
  293. package/templates/skills/ralph-two-pass-scan/SKILL.md +35 -0
  294. package/templates/skills/reflect/SKILL.md +229 -0
  295. package/templates/skills/release-process/SKILL.md +131 -423
  296. package/templates/skills/reskill/SKILL.md +92 -92
  297. package/templates/skills/retro-enforcement/SKILL.md +148 -0
  298. package/templates/skills/reviewer-protocol/SKILL.md +79 -79
  299. package/templates/skills/secret-handling/SKILL.md +200 -200
  300. package/templates/skills/session-recovery/SKILL.md +155 -155
  301. package/templates/skills/squad-conventions/SKILL.md +69 -69
  302. package/templates/skills/test-discipline/SKILL.md +37 -37
  303. package/templates/skills/tiered-memory/SKILL.md +234 -0
  304. package/templates/skills/windows-compatibility/SKILL.md +98 -74
  305. package/templates/{squad.agent.md → squad.agent.md.template} +1316 -1287
  306. package/templates/workflows/squad-ci.yml +24 -24
  307. package/templates/workflows/squad-docs.yml +54 -54
  308. package/templates/workflows/squad-heartbeat.yml +0 -4
  309. package/templates/workflows/squad-insider-release.yml +61 -61
  310. package/templates/workflows/squad-issue-assign.yml +161 -161
  311. package/templates/workflows/squad-label-enforce.yml +181 -181
  312. package/templates/workflows/squad-preview.yml +55 -55
  313. package/templates/workflows/squad-promote.yml +120 -120
  314. package/templates/workflows/squad-release.yml +77 -77
  315. package/templates/workflows/squad-triage.yml +260 -260
  316. package/templates/workflows/sync-squad-labels.yml +169 -169
  317. package/dist/cli/commands/watch.d.ts +0 -18
  318. package/dist/cli/commands/watch.d.ts.map +0 -1
  319. package/dist/cli/commands/watch.js +0 -306
  320. package/dist/cli/commands/watch.js.map +0 -1
@@ -0,0 +1,757 @@
1
+ /**
2
+ * Watch command — Ralph's standalone polling process.
3
+ *
4
+ * Thin orchestrator that delegates opt-in features to capabilities.
5
+ * Core triage logic (runCheck, checkPRs) remains inline because it
6
+ * always runs — it is not an opt-in capability.
7
+ */
8
+ import path from 'node:path';
9
+ import { execFile, execFileSync } from 'node:child_process';
10
+ import { promisify } from 'node:util';
11
+ import { FSStorageProvider } from '@bradygaster/squad-sdk';
12
+ const storage = new FSStorageProvider();
13
+ const execFileAsync = promisify(execFile);
14
+ import { detectSquadDir } from '../../core/detect-squad-dir.js';
15
+ import { fatal } from '../../core/errors.js';
16
+ import { GREEN, RED, DIM, BOLD, RESET, YELLOW } from '../../core/output.js';
17
+ import { parseRoutingRules, parseModuleOwnership, parseRoster, triageIssue, } from '@bradygaster/squad-sdk/ralph/triage';
18
+ import { RalphMonitor } from '@bradygaster/squad-sdk/ralph';
19
+ import { EventBus } from '@bradygaster/squad-sdk/runtime/event-bus';
20
+ import { ghAvailable, ghAuthenticated, ghRateLimitCheck, isRateLimitError } from '../../core/gh-cli.js';
21
+ import { PredictiveCircuitBreaker, getTrafficLight, } from '@bradygaster/squad-sdk/ralph/rate-limiting';
22
+ import { createPlatformAdapter } from '@bradygaster/squad-sdk/platform';
23
+ import { createDefaultRegistry } from './capabilities/index.js';
24
+ // ── Watch parity imports (#743) ──────────────────────────────────
25
+ import { acquireLock, updateLock, releaseLock } from './capabilities/lockfile.js';
26
+ import { recordFailure, recordSuccess, markRoundStart, getConsecutiveFailures } from './capabilities/heartbeat.js';
27
+ import { runPostFailureRemediation } from './capabilities/post-failure.js';
28
+ import { ModelCircuitBreaker } from './capabilities/circuit-breaker.js';
29
+ export { loadWatchConfig } from './config.js';
30
+ export { CapabilityRegistry } from './registry.js';
31
+ export { createDefaultRegistry } from './capabilities/index.js';
32
+ // ── Watch parity re-exports (#743) ──────────────────────────────
33
+ export { ModelCircuitBreaker } from './capabilities/circuit-breaker.js';
34
+ export { HealthCheckCapability } from './capabilities/health-check.js';
35
+ export { scoreIssue, rankIssues } from './capabilities/priority.js';
36
+ export { detectCapabilities, checkMachineCapability } from './capabilities/machine-capabilities.js';
37
+ export { StaleReclaimCapability } from './capabilities/stale-reclaim.js';
38
+ export { HeartbeatCapability } from './capabilities/heartbeat.js';
39
+ export { WebhookAlertCapability } from './capabilities/webhook-alerts.js';
40
+ export { checkBudget } from './capabilities/budget-check.js';
41
+ export { acquireLock, updateLock, releaseLock } from './capabilities/lockfile.js';
42
+ export { runPostFailureRemediation } from './capabilities/post-failure.js';
43
+ // ── SDK Mapping Helpers ──────────────────────────────────────────
44
+ function toWatchWorkItem(wi) {
45
+ return {
46
+ number: wi.id,
47
+ title: wi.title,
48
+ labels: wi.tags.map(t => ({ name: t })),
49
+ assignees: wi.assignedTo ? [{ login: wi.assignedTo }] : [],
50
+ };
51
+ }
52
+ function toWatchPullRequest(pr) {
53
+ return {
54
+ number: pr.id,
55
+ title: pr.title,
56
+ author: { login: pr.author },
57
+ labels: [],
58
+ isDraft: pr.status === 'draft',
59
+ reviewDecision: pr.reviewStatus === 'approved' ? 'APPROVED'
60
+ : pr.reviewStatus === 'changes-requested' ? 'CHANGES_REQUESTED'
61
+ : pr.reviewStatus === 'pending' ? 'REVIEW_REQUIRED' : '',
62
+ state: pr.status === 'active' ? 'OPEN'
63
+ : pr.status === 'completed' ? 'MERGED'
64
+ : pr.status === 'abandoned' ? 'CLOSED' : 'OPEN',
65
+ headRefName: pr.sourceBranch,
66
+ statusCheckRollup: [],
67
+ };
68
+ }
69
+ async function listWatchWorkItems(adapter, options) {
70
+ const tags = options.label ? [options.label] : undefined;
71
+ const items = await adapter.listWorkItems({ tags, state: options.state, limit: options.limit });
72
+ return items.map(toWatchWorkItem);
73
+ }
74
+ async function listWatchPullRequests(adapter, options) {
75
+ let status;
76
+ if (options.state === 'open')
77
+ status = 'active';
78
+ else if (options.state === 'closed')
79
+ status = 'abandoned';
80
+ else if (options.state === 'merged')
81
+ status = 'completed';
82
+ else
83
+ status = options.state;
84
+ const prs = await adapter.listPullRequests({ status, limit: options.limit });
85
+ return prs.map(toWatchPullRequest);
86
+ }
87
+ async function editWorkItem(adapter, id, options) {
88
+ if (options.addLabel)
89
+ await adapter.addTag(id, options.addLabel);
90
+ if (options.removeLabel)
91
+ await adapter.removeTag(id, options.removeLabel);
92
+ if (options.addAssignee) {
93
+ if (adapter.type === 'github') {
94
+ try {
95
+ await execFileAsync('gh', ['issue', 'edit', String(id), '--add-assignee', options.addAssignee]);
96
+ }
97
+ catch { /* best-effort */ }
98
+ }
99
+ else if (adapter.type === 'azure-devops') {
100
+ const assignee = options.addAssignee === '@me' ? '' : options.addAssignee;
101
+ if (assignee) {
102
+ try {
103
+ execFileSync('az', [
104
+ 'boards', 'work-item', 'update',
105
+ '--id', String(id),
106
+ '--fields', `System.AssignedTo=${assignee}`,
107
+ '--output', 'json',
108
+ ], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
109
+ }
110
+ catch { /* best-effort */ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+ export function reportBoard(state, round) {
116
+ const total = Object.values(state).reduce((a, b) => a + b, 0);
117
+ if (total === 0) {
118
+ console.log(`${DIM}📋 Board is clear — Ralph is idling${RESET}`);
119
+ return;
120
+ }
121
+ console.log(`\n${BOLD}🔄 Ralph — Round ${round}${RESET}`);
122
+ console.log('━'.repeat(30));
123
+ if (state.untriaged > 0)
124
+ console.log(` 🔴 Untriaged: ${state.untriaged}`);
125
+ if (state.assigned > 0)
126
+ console.log(` 🟡 Assigned: ${state.assigned}`);
127
+ if (state.drafts > 0)
128
+ console.log(` 🟡 Draft PRs: ${state.drafts}`);
129
+ if (state.changesRequested > 0)
130
+ console.log(` ⚠️ Changes requested: ${state.changesRequested}`);
131
+ if (state.ciFailures > 0)
132
+ console.log(` ❌ CI failures: ${state.ciFailures}`);
133
+ if (state.needsReview > 0)
134
+ console.log(` 🔵 Needs review: ${state.needsReview}`);
135
+ if (state.readyToMerge > 0)
136
+ console.log(` 🟢 Ready to merge: ${state.readyToMerge}`);
137
+ if (state.executed > 0)
138
+ console.log(` 🚀 Executed: ${state.executed}`);
139
+ console.log();
140
+ }
141
+ function emptyBoardState() {
142
+ return { untriaged: 0, assigned: 0, drafts: 0, needsReview: 0, changesRequested: 0, ciFailures: 0, readyToMerge: 0, executed: 0 };
143
+ }
144
+ async function checkPRs(roster, adapter) {
145
+ const timestamp = new Date().toLocaleTimeString();
146
+ const prs = await listWatchPullRequests(adapter, { state: 'open', limit: 20 });
147
+ const squadPRs = prs.filter(pr => pr.labels.some(l => l.name.startsWith('squad')) || pr.headRefName.startsWith('squad/'));
148
+ if (squadPRs.length === 0) {
149
+ return { drafts: 0, needsReview: 0, changesRequested: 0, ciFailures: 0, readyToMerge: 0, totalOpen: 0 };
150
+ }
151
+ const drafts = squadPRs.filter(pr => pr.isDraft);
152
+ const changesRequested = squadPRs.filter(pr => pr.reviewDecision === 'CHANGES_REQUESTED');
153
+ const approved = squadPRs.filter(pr => pr.reviewDecision === 'APPROVED' && !pr.isDraft);
154
+ const ciFailures = squadPRs.filter(pr => pr.statusCheckRollup?.some(check => check.state === 'FAILURE' || check.state === 'ERROR'));
155
+ const readyToMerge = approved.filter(pr => !pr.statusCheckRollup?.some(c => c.state === 'FAILURE' || c.state === 'ERROR' || c.state === 'PENDING'));
156
+ const changesRequestedSet = new Set(changesRequested.map(pr => pr.number));
157
+ const ciFailureSet = new Set(ciFailures.map(pr => pr.number));
158
+ const readyToMergeSet = new Set(readyToMerge.map(pr => pr.number));
159
+ const needsReview = squadPRs.filter(pr => !pr.isDraft && !changesRequestedSet.has(pr.number) && !ciFailureSet.has(pr.number) && !readyToMergeSet.has(pr.number));
160
+ const memberNames = new Set(roster.map(m => m.name.toLowerCase()));
161
+ if (drafts.length > 0) {
162
+ console.log(`${DIM}[${timestamp}]${RESET} 🟡 ${drafts.length} draft PR(s) in progress`);
163
+ for (const pr of drafts)
164
+ console.log(` ${DIM}PR #${pr.number}: ${pr.title} (${pr.author.login})${RESET}`);
165
+ }
166
+ if (changesRequested.length > 0) {
167
+ console.log(`${YELLOW}[${timestamp}]${RESET} ⚠️ ${changesRequested.length} PR(s) need revision`);
168
+ for (const pr of changesRequested) {
169
+ const owner = memberNames.has(pr.author.login.toLowerCase()) ? ` — ${pr.author.login}` : '';
170
+ console.log(` PR #${pr.number}: ${pr.title} — changes requested${owner}`);
171
+ }
172
+ }
173
+ if (ciFailures.length > 0) {
174
+ console.log(`${RED}[${timestamp}]${RESET} ❌ ${ciFailures.length} PR(s) with CI failures`);
175
+ for (const pr of ciFailures) {
176
+ const failedChecks = pr.statusCheckRollup?.filter(c => c.state === 'FAILURE' || c.state === 'ERROR') || [];
177
+ const owner = memberNames.has(pr.author.login.toLowerCase()) ? ` — ${pr.author.login}` : '';
178
+ console.log(` PR #${pr.number}: ${pr.title}${owner} — ${failedChecks.map(c => c.name).join(', ')}`);
179
+ }
180
+ }
181
+ if (readyToMerge.length > 0) {
182
+ console.log(`${GREEN}[${timestamp}]${RESET} 🟢 ${readyToMerge.length} PR(s) ready to merge`);
183
+ for (const pr of readyToMerge)
184
+ console.log(` PR #${pr.number}: ${pr.title} — approved, CI green`);
185
+ }
186
+ return {
187
+ drafts: drafts.length,
188
+ needsReview: needsReview.length,
189
+ changesRequested: changesRequestedSet.size,
190
+ ciFailures: ciFailureSet.size,
191
+ readyToMerge: readyToMergeSet.size,
192
+ totalOpen: squadPRs.length,
193
+ };
194
+ }
195
+ // ── Core triage (always runs) ────────────────────────────────────
196
+ const BLOCKED_LABELS = new Set([
197
+ 'status:blocked', 'status:waiting-external', 'status:postponed',
198
+ 'status:scheduled', 'status:needs-action', 'status:needs-decision',
199
+ 'status:needs-review', 'pending-user', 'do-not-merge',
200
+ ]);
201
+ async function runCheck(rules, modules, roster, hasCopilot, autoAssign, capabilities, adapter) {
202
+ const timestamp = new Date().toLocaleTimeString();
203
+ try {
204
+ const issues = await listWatchWorkItems(adapter, { label: 'squad', state: 'open', limit: 20 });
205
+ const { filterByCapabilities } = await import('@bradygaster/squad-sdk/ralph/capabilities');
206
+ const { handled: capableIssues, skipped: incapableIssues } = filterByCapabilities(issues, capabilities);
207
+ for (const { issue, missing } of incapableIssues) {
208
+ console.log(`${DIM}[${timestamp}] ⏭️ Skipping #${issue.number} "${issue.title}" — missing: ${missing.join(', ')}${RESET}`);
209
+ }
210
+ const memberLabels = roster.map(m => m.label);
211
+ const untriaged = capableIssues.filter(issue => {
212
+ const issueLabels = issue.labels.map(l => l.name);
213
+ return !memberLabels.some(ml => issueLabels.includes(ml));
214
+ });
215
+ const assignedIssues = capableIssues.filter(issue => {
216
+ const issueLabels = issue.labels.map(l => l.name);
217
+ return memberLabels.some(ml => issueLabels.includes(ml));
218
+ });
219
+ let unassignedCopilot = [];
220
+ if (hasCopilot && autoAssign) {
221
+ try {
222
+ const copilotIssues = await listWatchWorkItems(adapter, { label: 'squad:copilot', state: 'open', limit: 10 });
223
+ unassignedCopilot = copilotIssues.filter(i => !i.assignees || i.assignees.length === 0);
224
+ }
225
+ catch { /* label may not exist */ }
226
+ }
227
+ for (const issue of untriaged) {
228
+ const triageInput = {
229
+ number: issue.number,
230
+ title: issue.title,
231
+ body: issue.body,
232
+ labels: issue.labels.map(l => l.name),
233
+ };
234
+ const triage = triageIssue(triageInput, rules, modules, roster);
235
+ if (triage) {
236
+ try {
237
+ await editWorkItem(adapter, issue.number, { addLabel: triage.agent.label });
238
+ console.log(`${GREEN}✓${RESET} [${timestamp}] Triaged #${issue.number} "${issue.title}" → ${triage.agent.name} (${triage.reason})`);
239
+ }
240
+ catch (e) {
241
+ console.error(`${RED}✗${RESET} [${timestamp}] Failed to label #${issue.number}: ${e.message}`);
242
+ }
243
+ }
244
+ }
245
+ for (const issue of unassignedCopilot) {
246
+ try {
247
+ await editWorkItem(adapter, issue.number, { addAssignee: 'copilot-swe-agent' });
248
+ console.log(`${GREEN}✓${RESET} [${timestamp}] Assigned @copilot to #${issue.number} "${issue.title}"`);
249
+ }
250
+ catch (e) {
251
+ console.error(`${RED}✗${RESET} [${timestamp}] Failed to assign @copilot to #${issue.number}: ${e.message}`);
252
+ }
253
+ }
254
+ const prState = await checkPRs(roster, adapter);
255
+ return { untriaged: untriaged.length, assigned: assignedIssues.length, executed: 0, ...prState };
256
+ }
257
+ catch (e) {
258
+ console.error(`${RED}✗${RESET} [${timestamp}] Check failed: ${e.message}`);
259
+ return emptyBoardState();
260
+ }
261
+ }
262
+ function discoverSubSquads(teamRoot) {
263
+ const subsquadDir = path.join(teamRoot, '.squad', 'subsquads');
264
+ if (!storage.existsSync(subsquadDir))
265
+ return [];
266
+ try {
267
+ const entries = storage.listSync?.(subsquadDir) ?? [];
268
+ const dirs = Array.isArray(entries) ? entries : [];
269
+ const squads = [];
270
+ for (const entry of dirs) {
271
+ const entryPath = path.join(subsquadDir, entry);
272
+ const teamMdPath = path.join(entryPath, 'team.md');
273
+ if (!storage.existsSync(teamMdPath))
274
+ continue;
275
+ const routingPath = path.join(entryPath, 'routing.md');
276
+ let labels = [];
277
+ if (storage.existsSync(routingPath)) {
278
+ try {
279
+ const content = storage.readSync(routingPath) ?? '';
280
+ const labelMatches = content.match(/label[s]?:\s*([^\n]+)/gi);
281
+ if (labelMatches) {
282
+ labels = labelMatches
283
+ .flatMap((m) => m.replace(/labels?:\s*/i, '').split(','))
284
+ .map((l) => l.trim())
285
+ .filter(Boolean);
286
+ }
287
+ }
288
+ catch { /* best-effort */ }
289
+ }
290
+ squads.push({ name: entry, dir: entryPath, labels });
291
+ }
292
+ return squads;
293
+ }
294
+ catch {
295
+ return [];
296
+ }
297
+ }
298
+ function defaultCBState() {
299
+ return {
300
+ status: 'closed', openedAt: null, cooldownMinutes: 2,
301
+ consecutiveFailures: 0, consecutiveSuccesses: 0,
302
+ lastRateLimitRemaining: null, lastRateLimitTotal: null,
303
+ };
304
+ }
305
+ function loadCBState(squadDir) {
306
+ const filePath = path.join(squadDir, 'ralph-circuit-breaker.json');
307
+ try {
308
+ const raw = storage.readSync(filePath);
309
+ if (!raw)
310
+ return defaultCBState();
311
+ return JSON.parse(raw);
312
+ }
313
+ catch {
314
+ return defaultCBState();
315
+ }
316
+ }
317
+ function saveCBState(squadDir, state) {
318
+ storage.writeSync(path.join(squadDir, 'ralph-circuit-breaker.json'), JSON.stringify(state, null, 2));
319
+ }
320
+ // ── Capability Phase Runner ──────────────────────────────────────
321
+ async function runPhase(phase, enabled, context, config) {
322
+ const results = new Map();
323
+ const phaseCapabilities = enabled.filter(c => c.phase === phase);
324
+ const ts = new Date().toLocaleTimeString();
325
+ for (const cap of phaseCapabilities) {
326
+ try {
327
+ const capConfig = config.capabilities[cap.name];
328
+ const capContext = {
329
+ ...context,
330
+ config: typeof capConfig === 'object' && capConfig !== null
331
+ ? capConfig
332
+ : { enabled: !!capConfig, maxConcurrent: config.maxConcurrent, timeout: config.timeout },
333
+ };
334
+ const result = await cap.execute(capContext);
335
+ results.set(cap.name, result);
336
+ if (!result.success) {
337
+ console.log(`${YELLOW}⚠${RESET} [${ts}] ${cap.name}: ${result.summary}`);
338
+ }
339
+ }
340
+ catch (e) {
341
+ const result = { success: false, summary: `${cap.name} crashed: ${e.message}` };
342
+ results.set(cap.name, result);
343
+ console.log(`${YELLOW}⚠${RESET} [${ts}] ${cap.name}: ${result.summary}`);
344
+ }
345
+ }
346
+ return results;
347
+ }
348
+ /** Preflight all capabilities, return only those that pass. */
349
+ async function preflightCapabilities(registry, config, context) {
350
+ const enabled = [];
351
+ const skipped = [];
352
+ for (const cap of registry.all()) {
353
+ // Check if this capability is enabled in config
354
+ const capConfig = config.capabilities[cap.name];
355
+ if (!capConfig)
356
+ continue;
357
+ const capContext = {
358
+ ...context,
359
+ config: typeof capConfig === 'object' && capConfig !== null
360
+ ? capConfig
361
+ : {},
362
+ };
363
+ try {
364
+ const result = await cap.preflight(capContext);
365
+ if (result.ok) {
366
+ enabled.push(cap);
367
+ }
368
+ else {
369
+ skipped.push({ name: cap.name, reason: result.reason ?? 'preflight failed' });
370
+ }
371
+ }
372
+ catch (e) {
373
+ skipped.push({ name: cap.name, reason: e.message });
374
+ }
375
+ }
376
+ // Print startup banner
377
+ if (enabled.length > 0) {
378
+ console.log(`${GREEN}✅${RESET} Capabilities: ${enabled.map(c => c.name).join(', ')}`);
379
+ }
380
+ if (skipped.length > 0) {
381
+ for (const s of skipped) {
382
+ console.log(`${YELLOW}⚠️${RESET} ${s.name} skipped: ${s.reason}`);
383
+ }
384
+ }
385
+ return enabled;
386
+ }
387
+ /** Convert legacy WatchOptions to WatchConfig. */
388
+ function legacyToConfig(options) {
389
+ const capabilities = {};
390
+ if (options.execute)
391
+ capabilities['execute'] = true;
392
+ if (options.monitorTeams)
393
+ capabilities['monitor-teams'] = true;
394
+ if (options.monitorEmail)
395
+ capabilities['monitor-email'] = true;
396
+ if (options.board)
397
+ capabilities['board'] = { projectNumber: options.boardProject ?? 1 };
398
+ if (options.twoPass)
399
+ capabilities['two-pass'] = true;
400
+ if (options.waveDispatch)
401
+ capabilities['wave-dispatch'] = true;
402
+ if (options.retro)
403
+ capabilities['retro'] = true;
404
+ if (options.decisionHygiene)
405
+ capabilities['decision-hygiene'] = true;
406
+ return {
407
+ interval: options.intervalMinutes,
408
+ execute: options.execute ?? false,
409
+ maxConcurrent: options.maxConcurrent ?? 1,
410
+ timeout: options.issueTimeoutMinutes ?? 30,
411
+ copilotFlags: options.copilotFlags,
412
+ agentCmd: options.agentCmd,
413
+ capabilities,
414
+ };
415
+ }
416
+ // ── Exported helpers (backward compat) ───────────────────────────
417
+ export { findExecutableIssues } from './capabilities/execute.js';
418
+ export function buildAgentCommand(issue, teamRoot, options) {
419
+ const prompt = `Work on issue #${issue.number}: ${issue.title}. Read the issue body for full details.`;
420
+ if (options.agentCmd) {
421
+ const parts = options.agentCmd.trim().split(/\s+/);
422
+ return { cmd: parts[0], args: [...parts.slice(1), '--message', prompt] };
423
+ }
424
+ const args = ['copilot', '--message', prompt];
425
+ if (options.copilotFlags)
426
+ args.push(...options.copilotFlags.trim().split(/\s+/));
427
+ return { cmd: 'gh', args };
428
+ }
429
+ export async function selfPull(teamRoot) {
430
+ try {
431
+ await new Promise((resolve, reject) => {
432
+ execFile('git', ['fetch', '--quiet'], { cwd: teamRoot }, (err) => (err ? reject(err) : resolve()));
433
+ });
434
+ await new Promise((resolve, reject) => {
435
+ execFile('git', ['pull', '--ff-only', '--quiet'], { cwd: teamRoot }, (err) => (err ? reject(err) : resolve()));
436
+ });
437
+ }
438
+ catch {
439
+ console.log(`${DIM}⚠ selfPull: git pull skipped (not on a tracking branch or conflicts)${RESET}`);
440
+ }
441
+ }
442
+ export async function executeIssue(issue, teamRoot, options, adapter) {
443
+ const ts = new Date().toLocaleTimeString();
444
+ const timeoutMs = (options.issueTimeoutMinutes ?? 30) * 60_000;
445
+ try {
446
+ await editWorkItem(adapter, issue.number, { addAssignee: '@me' });
447
+ }
448
+ catch { /* best-effort */ }
449
+ try {
450
+ await adapter.addComment(issue.number, '🤖 Ralph: starting autonomous work on this issue.');
451
+ }
452
+ catch { /* best-effort */ }
453
+ const { cmd, args } = buildAgentCommand(issue, teamRoot, options);
454
+ console.log(`${GREEN}▶${RESET} [${ts}] Executing #${issue.number} "${issue.title}" → ${cmd} ${args.join(' ')}`);
455
+ return new Promise((resolve) => {
456
+ execFile(cmd, args, { cwd: teamRoot, timeout: timeoutMs, maxBuffer: 50 * 1024 * 1024 }, (err) => {
457
+ if (err) {
458
+ const execErr = err;
459
+ const msg = execErr.killed ? `Timed out after ${options.issueTimeoutMinutes ?? 30}m` : execErr.message;
460
+ console.error(`${RED}✗${RESET} [${new Date().toLocaleTimeString()}] #${issue.number} failed: ${msg}`);
461
+ resolve({ success: false, error: msg });
462
+ }
463
+ else {
464
+ console.log(`${GREEN}✓${RESET} [${new Date().toLocaleTimeString()}] #${issue.number} completed`);
465
+ resolve({ success: true });
466
+ }
467
+ });
468
+ });
469
+ }
470
+ // ── Main Entry Point ─────────────────────────────────────────────
471
+ /**
472
+ * Run watch command — Ralph's local polling process.
473
+ *
474
+ * Accepts either the new {@link WatchConfig} or the legacy
475
+ * {@link WatchOptions} bag for backward compatibility.
476
+ */
477
+ export async function runWatch(dest, options) {
478
+ // Normalize to WatchConfig
479
+ const config = 'intervalMinutes' in options
480
+ ? legacyToConfig(options)
481
+ : options;
482
+ const { interval } = config;
483
+ if (isNaN(interval) || interval < 1) {
484
+ fatal('--interval must be a positive number of minutes');
485
+ }
486
+ // Detect squad directory
487
+ const squadDirInfo = detectSquadDir(dest);
488
+ const teamMd = path.join(squadDirInfo.path, 'team.md');
489
+ const routingMdPath = path.join(squadDirInfo.path, 'routing.md');
490
+ const teamRoot = path.dirname(squadDirInfo.path);
491
+ if (!storage.existsSync(teamMd)) {
492
+ fatal('No squad found — run init first.');
493
+ }
494
+ // Create platform adapter
495
+ let adapter;
496
+ try {
497
+ adapter = createPlatformAdapter(teamRoot);
498
+ console.log(`${DIM}Platform: ${adapter.type}${RESET}`);
499
+ }
500
+ catch (err) {
501
+ return fatal(`Could not detect platform: ${err.message}`);
502
+ }
503
+ // Verify platform CLI availability
504
+ if (adapter.type === 'github') {
505
+ if (!(await ghAvailable()))
506
+ fatal('gh CLI not found — install from https://cli.github.com');
507
+ if (!(await ghAuthenticated()))
508
+ fatal('gh CLI not authenticated — run: gh auth login');
509
+ }
510
+ else if (adapter.type === 'azure-devops') {
511
+ try {
512
+ await execFileAsync('az', ['devops', '-h']);
513
+ }
514
+ catch {
515
+ fatal('az CLI not found');
516
+ }
517
+ try {
518
+ await execFileAsync('az', ['account', 'show']);
519
+ }
520
+ catch {
521
+ fatal('az CLI not authenticated — run: az login');
522
+ }
523
+ }
524
+ // ── Lockfile guard (#743) ────────────────────────────────────────
525
+ if (!acquireLock(teamRoot)) {
526
+ console.log(`${YELLOW}⚠️${RESET} Another watch process holds the lock for this directory. Exiting.`);
527
+ return;
528
+ }
529
+ // Parse team.md
530
+ const content = storage.readSync(teamMd) ?? '';
531
+ const roster = parseRoster(content);
532
+ const routingContent = storage.existsSync(routingMdPath) ? (storage.readSync(routingMdPath) ?? '') : '';
533
+ const rules = parseRoutingRules(routingContent);
534
+ const modules = parseModuleOwnership(routingContent);
535
+ // Load machine capabilities (#514)
536
+ const { loadCapabilities } = await import('@bradygaster/squad-sdk/ralph/capabilities');
537
+ const capabilities = await loadCapabilities(teamRoot);
538
+ if (capabilities) {
539
+ console.log(`${DIM}📦 Machine: ${capabilities.machine} — ${capabilities.capabilities.length} capabilities loaded${RESET}`);
540
+ }
541
+ if (roster.length === 0) {
542
+ fatal('No squad members found in team.md');
543
+ }
544
+ const hasCopilot = content.includes('🤖 Coding Agent') || content.includes('@copilot');
545
+ const autoAssign = content.includes('<!-- copilot-auto-assign: true -->');
546
+ const monitorSessionId = 'ralph-watch';
547
+ const eventBus = new EventBus();
548
+ const monitor = new RalphMonitor({
549
+ teamRoot,
550
+ healthCheckInterval: interval * 60 * 1000,
551
+ staleSessionThreshold: interval * 60 * 1000 * 3,
552
+ statePath: path.join(squadDirInfo.path, '.ralph-state.json'),
553
+ });
554
+ await monitor.start(eventBus);
555
+ await eventBus.emit({
556
+ type: 'session:created', sessionId: monitorSessionId,
557
+ agentName: 'Ralph', payload: { interval }, timestamp: new Date(),
558
+ });
559
+ // ── Capability system setup ────────────────────────────────────
560
+ const registry = createDefaultRegistry();
561
+ const baseContext = {
562
+ teamRoot,
563
+ adapter,
564
+ round: 0,
565
+ roster: roster.map(r => ({ name: r.name, label: r.label, expertise: [] })),
566
+ config: {},
567
+ agentCmd: config.agentCmd,
568
+ copilotFlags: config.copilotFlags,
569
+ };
570
+ const enabledCapabilities = await preflightCapabilities(registry, config, baseContext);
571
+ // Print startup banner
572
+ const modeTag = config.execute ? ` ${BOLD}(Execute)${RESET}` : '';
573
+ const platformTag = ` [${adapter.type}]`;
574
+ console.log(`\n${BOLD}🔄 Ralph — Watch Mode${RESET}${modeTag}${platformTag}`);
575
+ console.log(`${DIM}Polling every ${interval} minute(s) for squad work. Ctrl+C to stop.${RESET}`);
576
+ if (config.execute && config.copilotFlags) {
577
+ console.log(`${DIM}Copilot flags: ${config.copilotFlags}${RESET}`);
578
+ }
579
+ if (config.execute) {
580
+ console.log(`${DIM}Max concurrent: ${config.maxConcurrent} | Timeout: ${config.timeout}m${RESET}`);
581
+ }
582
+ console.log();
583
+ // Initialize circuit breaker (#515)
584
+ const circuitBreaker = new PredictiveCircuitBreaker();
585
+ // Enhanced model circuit breaker (#743)
586
+ const modelCB = new ModelCircuitBreaker(squadDirInfo.path, {
587
+ preferredModel: config.capabilities['circuit-breaker']?.['preferredModel'],
588
+ fallbackChain: config.capabilities['circuit-breaker']?.['fallbackChain'],
589
+ });
590
+ let cbState = loadCBState(squadDirInfo.path);
591
+ let round = 0;
592
+ let roundInProgress = false;
593
+ async function executeRound() {
594
+ const ts = new Date().toLocaleTimeString();
595
+ markRoundStart(); // (#743) Track round duration for heartbeat
596
+ // (#743) Update lockfile to running
597
+ updateLock(teamRoot, 'running', round + 1, 0, getConsecutiveFailures());
598
+ // Circuit breaker gate
599
+ if (cbState.status === 'open') {
600
+ const elapsed = Date.now() - new Date(cbState.openedAt).getTime();
601
+ if (elapsed < cbState.cooldownMinutes * 60_000) {
602
+ const left = Math.ceil((cbState.cooldownMinutes * 60_000 - elapsed) / 1000);
603
+ console.log(`${YELLOW}⏸${RESET} [${ts}] Circuit open — cooling down (${left}s left)`);
604
+ updateLock(teamRoot, 'idle', round, 0, getConsecutiveFailures());
605
+ return;
606
+ }
607
+ cbState.status = 'half-open';
608
+ console.log(`${DIM}[${ts}] Circuit half-open — probing...${RESET}`);
609
+ saveCBState(squadDirInfo.path, cbState);
610
+ }
611
+ // Rate limit check (GitHub only)
612
+ if (adapter.type === 'github') {
613
+ try {
614
+ const rl = await ghRateLimitCheck();
615
+ if (rl) {
616
+ cbState.lastRateLimitRemaining = rl.remaining;
617
+ cbState.lastRateLimitTotal = rl.limit;
618
+ circuitBreaker.addSample(rl.remaining, rl.limit);
619
+ const light = getTrafficLight(rl.remaining, rl.limit);
620
+ if (light === 'red' || circuitBreaker.shouldOpen()) {
621
+ cbState.status = 'open';
622
+ cbState.openedAt = new Date().toISOString();
623
+ cbState.consecutiveFailures++;
624
+ cbState.consecutiveSuccesses = 0;
625
+ cbState.cooldownMinutes = Math.min(cbState.cooldownMinutes * 2, 30);
626
+ saveCBState(squadDirInfo.path, cbState);
627
+ modelCB.onRateLimit(); // (#743) Cascade to model CB
628
+ console.log(`${RED}🛑${RESET} [${ts}] Circuit opened — quota ${light === 'red' ? 'critical' : 'predicted low'} (${rl.remaining}/${rl.limit})`);
629
+ updateLock(teamRoot, 'idle', round, 0, getConsecutiveFailures());
630
+ return;
631
+ }
632
+ if (light === 'amber') {
633
+ console.log(`${YELLOW}⚠️${RESET} [${ts}] Quota amber (${rl.remaining}/${rl.limit}) — proceeding cautiously`);
634
+ }
635
+ }
636
+ }
637
+ catch { /* proceed anyway */ }
638
+ }
639
+ round++;
640
+ const roundContext = { ...baseContext, round };
641
+ // Phase 1: pre-scan (self-pull, health-check, stale-reclaim, subsquad discovery)
642
+ await runPhase('pre-scan', enabledCapabilities, roundContext, config);
643
+ // SubSquad discovery (informational, not a capability)
644
+ const subSquads = discoverSubSquads(teamRoot);
645
+ if (subSquads.length > 0 && round === 1) {
646
+ console.log(`${DIM}📂 Discovered ${subSquads.length} subsquad(s): ${subSquads.map(s => s.name).join(', ')}${RESET}`);
647
+ }
648
+ // Core: triage (always runs — not a capability)
649
+ const roundState = await runCheck(rules, modules, roster, hasCopilot, autoAssign, capabilities, adapter);
650
+ // Phase 2: post-triage (two-pass hydration)
651
+ await runPhase('post-triage', enabledCapabilities, roundContext, config);
652
+ // Phase 3: post-execute (execute issues, wave dispatch, board updates)
653
+ const execResults = await runPhase('post-execute', enabledCapabilities, roundContext, config);
654
+ // Update executed count from execute capability
655
+ const execResult = execResults.get('execute');
656
+ if (execResult?.data?.['executed']) {
657
+ roundState.executed = execResult.data['executed'];
658
+ }
659
+ // Phase 4: housekeeping (monitoring, retro, decision hygiene, heartbeat, webhook-alerts)
660
+ await runPhase('housekeeping', enabledCapabilities, roundContext, config);
661
+ await eventBus.emit({
662
+ type: 'agent:milestone', sessionId: monitorSessionId,
663
+ agentName: 'Ralph',
664
+ payload: { milestone: `Completed watch round ${round}`, task: 'watch cycle' },
665
+ timestamp: new Date(),
666
+ });
667
+ await monitor.healthCheck();
668
+ reportBoard(roundState, round);
669
+ // (#743) Record success for heartbeat tracking
670
+ recordSuccess();
671
+ modelCB.onSuccess();
672
+ // Post-round: update circuit breaker on success
673
+ if (cbState.status === 'half-open') {
674
+ cbState.consecutiveSuccesses++;
675
+ if (cbState.consecutiveSuccesses >= 2) {
676
+ cbState.status = 'closed';
677
+ cbState.cooldownMinutes = 2;
678
+ cbState.consecutiveFailures = 0;
679
+ console.log(`${GREEN}✓${RESET} [${new Date().toLocaleTimeString()}] Circuit closed — quota recovered`);
680
+ }
681
+ }
682
+ else {
683
+ cbState.consecutiveSuccesses = 0;
684
+ cbState.consecutiveFailures = 0;
685
+ }
686
+ saveCBState(squadDirInfo.path, cbState);
687
+ // (#743) Update lockfile to idle
688
+ updateLock(teamRoot, 'idle', round, 0, getConsecutiveFailures());
689
+ }
690
+ // Run immediately, then on interval
691
+ await executeRound();
692
+ return new Promise((resolve) => {
693
+ const intervalId = setInterval(async () => {
694
+ if (roundInProgress)
695
+ return;
696
+ roundInProgress = true;
697
+ try {
698
+ await executeRound();
699
+ }
700
+ catch (e) {
701
+ const err = e;
702
+ recordFailure(); // (#743) Track consecutive failures
703
+ if (adapter.type === 'github' && isRateLimitError(err)) {
704
+ cbState.status = 'open';
705
+ cbState.openedAt = new Date().toISOString();
706
+ cbState.consecutiveFailures++;
707
+ cbState.consecutiveSuccesses = 0;
708
+ cbState.cooldownMinutes = Math.min(cbState.cooldownMinutes * 2, 30);
709
+ saveCBState(squadDirInfo.path, cbState);
710
+ modelCB.onRateLimit();
711
+ console.log(`${RED}🛑${RESET} Rate limited — circuit opened, cooldown ${cbState.cooldownMinutes}m`);
712
+ }
713
+ else {
714
+ console.error(`${RED}✗${RESET} Round error: ${err.message}`);
715
+ }
716
+ updateLock(teamRoot, 'error', round, 1, getConsecutiveFailures());
717
+ // (#743) Post-failure remediation — tiered self-healing
718
+ const failures = getConsecutiveFailures();
719
+ if (failures >= 3) {
720
+ const remediation = await runPostFailureRemediation(failures, round, teamRoot, modelCB);
721
+ for (const action of remediation.actions) {
722
+ console.log(`${YELLOW}🔧${RESET} [self-heal] ${action}`);
723
+ }
724
+ if (remediation.pauseSeconds > 0) {
725
+ console.log(`${YELLOW}⏸${RESET} Pausing ${remediation.pauseSeconds / 60} minutes due to repeated failures`);
726
+ await new Promise(r => setTimeout(r, remediation.pauseSeconds * 1000));
727
+ }
728
+ }
729
+ }
730
+ finally {
731
+ roundInProgress = false;
732
+ }
733
+ }, interval * 60 * 1000);
734
+ // Graceful shutdown
735
+ let isShuttingDown = false;
736
+ const shutdown = async () => {
737
+ if (isShuttingDown)
738
+ return;
739
+ isShuttingDown = true;
740
+ clearInterval(intervalId);
741
+ process.off('SIGINT', shutdown);
742
+ process.off('SIGTERM', shutdown);
743
+ await eventBus.emit({
744
+ type: 'session:destroyed', sessionId: monitorSessionId,
745
+ agentName: 'Ralph', payload: null, timestamp: new Date(),
746
+ });
747
+ await monitor.stop();
748
+ saveCBState(squadDirInfo.path, cbState);
749
+ releaseLock(teamRoot); // (#743) Clean up lockfile
750
+ console.log(`\n${DIM}🔄 Ralph — Watch stopped${RESET}`);
751
+ resolve();
752
+ };
753
+ process.on('SIGINT', shutdown);
754
+ process.on('SIGTERM', shutdown);
755
+ });
756
+ }
757
+ //# sourceMappingURL=index.js.map