@lumenflow/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +119 -0
  3. package/dist/active-wu-detector.d.ts +33 -0
  4. package/dist/active-wu-detector.js +106 -0
  5. package/dist/adapters/filesystem-metrics.adapter.d.ts +108 -0
  6. package/dist/adapters/filesystem-metrics.adapter.js +519 -0
  7. package/dist/adapters/terminal-renderer.adapter.d.ts +106 -0
  8. package/dist/adapters/terminal-renderer.adapter.js +337 -0
  9. package/dist/arg-parser.d.ts +63 -0
  10. package/dist/arg-parser.js +560 -0
  11. package/dist/backlog-editor.d.ts +98 -0
  12. package/dist/backlog-editor.js +179 -0
  13. package/dist/backlog-generator.d.ts +111 -0
  14. package/dist/backlog-generator.js +381 -0
  15. package/dist/backlog-parser.d.ts +45 -0
  16. package/dist/backlog-parser.js +102 -0
  17. package/dist/backlog-sync-validator.d.ts +78 -0
  18. package/dist/backlog-sync-validator.js +294 -0
  19. package/dist/branch-drift.d.ts +34 -0
  20. package/dist/branch-drift.js +51 -0
  21. package/dist/cleanup-install-config.d.ts +33 -0
  22. package/dist/cleanup-install-config.js +37 -0
  23. package/dist/cleanup-lock.d.ts +139 -0
  24. package/dist/cleanup-lock.js +313 -0
  25. package/dist/code-path-validator.d.ts +146 -0
  26. package/dist/code-path-validator.js +537 -0
  27. package/dist/code-paths-overlap.d.ts +55 -0
  28. package/dist/code-paths-overlap.js +245 -0
  29. package/dist/commands-logger.d.ts +77 -0
  30. package/dist/commands-logger.js +254 -0
  31. package/dist/commit-message-utils.d.ts +25 -0
  32. package/dist/commit-message-utils.js +41 -0
  33. package/dist/compliance-parser.d.ts +150 -0
  34. package/dist/compliance-parser.js +507 -0
  35. package/dist/constants/backlog-patterns.d.ts +20 -0
  36. package/dist/constants/backlog-patterns.js +23 -0
  37. package/dist/constants/dora-constants.d.ts +49 -0
  38. package/dist/constants/dora-constants.js +53 -0
  39. package/dist/constants/gate-constants.d.ts +15 -0
  40. package/dist/constants/gate-constants.js +15 -0
  41. package/dist/constants/linter-constants.d.ts +16 -0
  42. package/dist/constants/linter-constants.js +16 -0
  43. package/dist/constants/tokenizer-constants.d.ts +15 -0
  44. package/dist/constants/tokenizer-constants.js +15 -0
  45. package/dist/core/scope-checker.d.ts +97 -0
  46. package/dist/core/scope-checker.js +163 -0
  47. package/dist/core/tool-runner.d.ts +161 -0
  48. package/dist/core/tool-runner.js +393 -0
  49. package/dist/core/tool.constants.d.ts +105 -0
  50. package/dist/core/tool.constants.js +101 -0
  51. package/dist/core/tool.schemas.d.ts +226 -0
  52. package/dist/core/tool.schemas.js +226 -0
  53. package/dist/core/worktree-guard.d.ts +130 -0
  54. package/dist/core/worktree-guard.js +242 -0
  55. package/dist/coverage-gate.d.ts +108 -0
  56. package/dist/coverage-gate.js +196 -0
  57. package/dist/date-utils.d.ts +75 -0
  58. package/dist/date-utils.js +140 -0
  59. package/dist/dependency-graph.d.ts +142 -0
  60. package/dist/dependency-graph.js +550 -0
  61. package/dist/dependency-guard.d.ts +54 -0
  62. package/dist/dependency-guard.js +142 -0
  63. package/dist/dependency-validator.d.ts +105 -0
  64. package/dist/dependency-validator.js +154 -0
  65. package/dist/docs-path-validator.d.ts +36 -0
  66. package/dist/docs-path-validator.js +95 -0
  67. package/dist/domain/orchestration.constants.d.ts +99 -0
  68. package/dist/domain/orchestration.constants.js +97 -0
  69. package/dist/domain/orchestration.schemas.d.ts +280 -0
  70. package/dist/domain/orchestration.schemas.js +211 -0
  71. package/dist/domain/orchestration.types.d.ts +133 -0
  72. package/dist/domain/orchestration.types.js +12 -0
  73. package/dist/error-handler.d.ts +116 -0
  74. package/dist/error-handler.js +136 -0
  75. package/dist/file-classifiers.d.ts +62 -0
  76. package/dist/file-classifiers.js +108 -0
  77. package/dist/gates-agent-mode.d.ts +81 -0
  78. package/dist/gates-agent-mode.js +94 -0
  79. package/dist/generate-traceability.d.ts +107 -0
  80. package/dist/generate-traceability.js +411 -0
  81. package/dist/git-adapter.d.ts +395 -0
  82. package/dist/git-adapter.js +649 -0
  83. package/dist/git-staged-validator.d.ts +32 -0
  84. package/dist/git-staged-validator.js +48 -0
  85. package/dist/hardcoded-strings.d.ts +61 -0
  86. package/dist/hardcoded-strings.js +270 -0
  87. package/dist/incremental-lint.d.ts +78 -0
  88. package/dist/incremental-lint.js +129 -0
  89. package/dist/incremental-test.d.ts +39 -0
  90. package/dist/incremental-test.js +61 -0
  91. package/dist/index.d.ts +42 -0
  92. package/dist/index.js +61 -0
  93. package/dist/invariants/check-automated-tests.d.ts +50 -0
  94. package/dist/invariants/check-automated-tests.js +166 -0
  95. package/dist/invariants-runner.d.ts +103 -0
  96. package/dist/invariants-runner.js +527 -0
  97. package/dist/lane-checker.d.ts +50 -0
  98. package/dist/lane-checker.js +319 -0
  99. package/dist/lane-inference.d.ts +39 -0
  100. package/dist/lane-inference.js +195 -0
  101. package/dist/lane-lock.d.ts +211 -0
  102. package/dist/lane-lock.js +474 -0
  103. package/dist/lane-validator.d.ts +48 -0
  104. package/dist/lane-validator.js +114 -0
  105. package/dist/logs-lib.d.ts +104 -0
  106. package/dist/logs-lib.js +207 -0
  107. package/dist/lumenflow-config-schema.d.ts +272 -0
  108. package/dist/lumenflow-config-schema.js +207 -0
  109. package/dist/lumenflow-config.d.ts +95 -0
  110. package/dist/lumenflow-config.js +236 -0
  111. package/dist/manual-test-validator.d.ts +80 -0
  112. package/dist/manual-test-validator.js +200 -0
  113. package/dist/merge-lock.d.ts +115 -0
  114. package/dist/merge-lock.js +251 -0
  115. package/dist/micro-worktree.d.ts +159 -0
  116. package/dist/micro-worktree.js +427 -0
  117. package/dist/migration-deployer.d.ts +69 -0
  118. package/dist/migration-deployer.js +151 -0
  119. package/dist/orchestration-advisory-loader.d.ts +28 -0
  120. package/dist/orchestration-advisory-loader.js +87 -0
  121. package/dist/orchestration-advisory.d.ts +58 -0
  122. package/dist/orchestration-advisory.js +94 -0
  123. package/dist/orchestration-di.d.ts +48 -0
  124. package/dist/orchestration-di.js +57 -0
  125. package/dist/orchestration-rules.d.ts +57 -0
  126. package/dist/orchestration-rules.js +201 -0
  127. package/dist/orphan-detector.d.ts +131 -0
  128. package/dist/orphan-detector.js +226 -0
  129. package/dist/path-classifiers.d.ts +57 -0
  130. package/dist/path-classifiers.js +93 -0
  131. package/dist/piped-command-detector.d.ts +34 -0
  132. package/dist/piped-command-detector.js +64 -0
  133. package/dist/ports/dashboard-renderer.port.d.ts +112 -0
  134. package/dist/ports/dashboard-renderer.port.js +25 -0
  135. package/dist/ports/metrics-collector.port.d.ts +132 -0
  136. package/dist/ports/metrics-collector.port.js +26 -0
  137. package/dist/process-detector.d.ts +84 -0
  138. package/dist/process-detector.js +172 -0
  139. package/dist/prompt-linter.d.ts +72 -0
  140. package/dist/prompt-linter.js +312 -0
  141. package/dist/prompt-monitor.d.ts +15 -0
  142. package/dist/prompt-monitor.js +205 -0
  143. package/dist/rebase-artifact-cleanup.d.ts +145 -0
  144. package/dist/rebase-artifact-cleanup.js +433 -0
  145. package/dist/retry-strategy.d.ts +189 -0
  146. package/dist/retry-strategy.js +283 -0
  147. package/dist/risk-detector.d.ts +108 -0
  148. package/dist/risk-detector.js +252 -0
  149. package/dist/rollback-utils.d.ts +76 -0
  150. package/dist/rollback-utils.js +104 -0
  151. package/dist/section-headings.d.ts +43 -0
  152. package/dist/section-headings.js +49 -0
  153. package/dist/spawn-escalation.d.ts +90 -0
  154. package/dist/spawn-escalation.js +253 -0
  155. package/dist/spawn-monitor.d.ts +229 -0
  156. package/dist/spawn-monitor.js +672 -0
  157. package/dist/spawn-recovery.d.ts +82 -0
  158. package/dist/spawn-recovery.js +298 -0
  159. package/dist/spawn-registry-schema.d.ts +98 -0
  160. package/dist/spawn-registry-schema.js +108 -0
  161. package/dist/spawn-registry-store.d.ts +146 -0
  162. package/dist/spawn-registry-store.js +273 -0
  163. package/dist/spawn-tree.d.ts +121 -0
  164. package/dist/spawn-tree.js +285 -0
  165. package/dist/stamp-status-validator.d.ts +84 -0
  166. package/dist/stamp-status-validator.js +134 -0
  167. package/dist/stamp-utils.d.ts +100 -0
  168. package/dist/stamp-utils.js +229 -0
  169. package/dist/state-machine.d.ts +26 -0
  170. package/dist/state-machine.js +83 -0
  171. package/dist/system-map-validator.d.ts +80 -0
  172. package/dist/system-map-validator.js +272 -0
  173. package/dist/telemetry.d.ts +80 -0
  174. package/dist/telemetry.js +213 -0
  175. package/dist/token-counter.d.ts +51 -0
  176. package/dist/token-counter.js +145 -0
  177. package/dist/usecases/get-dashboard-data.usecase.d.ts +52 -0
  178. package/dist/usecases/get-dashboard-data.usecase.js +61 -0
  179. package/dist/usecases/get-suggestions.usecase.d.ts +100 -0
  180. package/dist/usecases/get-suggestions.usecase.js +153 -0
  181. package/dist/user-normalizer.d.ts +41 -0
  182. package/dist/user-normalizer.js +141 -0
  183. package/dist/validators/phi-constants.d.ts +97 -0
  184. package/dist/validators/phi-constants.js +152 -0
  185. package/dist/validators/phi-scanner.d.ts +58 -0
  186. package/dist/validators/phi-scanner.js +215 -0
  187. package/dist/worktree-ownership.d.ts +50 -0
  188. package/dist/worktree-ownership.js +74 -0
  189. package/dist/worktree-scanner.d.ts +103 -0
  190. package/dist/worktree-scanner.js +168 -0
  191. package/dist/worktree-symlink.d.ts +99 -0
  192. package/dist/worktree-symlink.js +359 -0
  193. package/dist/wu-backlog-updater.d.ts +17 -0
  194. package/dist/wu-backlog-updater.js +37 -0
  195. package/dist/wu-checkpoint.d.ts +124 -0
  196. package/dist/wu-checkpoint.js +233 -0
  197. package/dist/wu-claim-helpers.d.ts +26 -0
  198. package/dist/wu-claim-helpers.js +63 -0
  199. package/dist/wu-claim-resume.d.ts +106 -0
  200. package/dist/wu-claim-resume.js +276 -0
  201. package/dist/wu-consistency-checker.d.ts +95 -0
  202. package/dist/wu-consistency-checker.js +567 -0
  203. package/dist/wu-constants.d.ts +1275 -0
  204. package/dist/wu-constants.js +1382 -0
  205. package/dist/wu-create-validators.d.ts +42 -0
  206. package/dist/wu-create-validators.js +93 -0
  207. package/dist/wu-done-branch-only.d.ts +63 -0
  208. package/dist/wu-done-branch-only.js +191 -0
  209. package/dist/wu-done-messages.d.ts +119 -0
  210. package/dist/wu-done-messages.js +185 -0
  211. package/dist/wu-done-pr.d.ts +72 -0
  212. package/dist/wu-done-pr.js +174 -0
  213. package/dist/wu-done-retry-helpers.d.ts +85 -0
  214. package/dist/wu-done-retry-helpers.js +172 -0
  215. package/dist/wu-done-ui.d.ts +37 -0
  216. package/dist/wu-done-ui.js +69 -0
  217. package/dist/wu-done-validators.d.ts +411 -0
  218. package/dist/wu-done-validators.js +1229 -0
  219. package/dist/wu-done-worktree.d.ts +182 -0
  220. package/dist/wu-done-worktree.js +1097 -0
  221. package/dist/wu-helpers.d.ts +128 -0
  222. package/dist/wu-helpers.js +248 -0
  223. package/dist/wu-lint.d.ts +70 -0
  224. package/dist/wu-lint.js +234 -0
  225. package/dist/wu-paths.d.ts +171 -0
  226. package/dist/wu-paths.js +178 -0
  227. package/dist/wu-preflight-validators.d.ts +86 -0
  228. package/dist/wu-preflight-validators.js +251 -0
  229. package/dist/wu-recovery.d.ts +138 -0
  230. package/dist/wu-recovery.js +341 -0
  231. package/dist/wu-repair-core.d.ts +131 -0
  232. package/dist/wu-repair-core.js +669 -0
  233. package/dist/wu-schema-normalization.d.ts +17 -0
  234. package/dist/wu-schema-normalization.js +82 -0
  235. package/dist/wu-schema.d.ts +793 -0
  236. package/dist/wu-schema.js +881 -0
  237. package/dist/wu-spawn-helpers.d.ts +121 -0
  238. package/dist/wu-spawn-helpers.js +271 -0
  239. package/dist/wu-spawn.d.ts +158 -0
  240. package/dist/wu-spawn.js +1306 -0
  241. package/dist/wu-state-schema.d.ts +213 -0
  242. package/dist/wu-state-schema.js +156 -0
  243. package/dist/wu-state-store.d.ts +264 -0
  244. package/dist/wu-state-store.js +691 -0
  245. package/dist/wu-status-transition.d.ts +63 -0
  246. package/dist/wu-status-transition.js +382 -0
  247. package/dist/wu-status-updater.d.ts +25 -0
  248. package/dist/wu-status-updater.js +116 -0
  249. package/dist/wu-transaction-collectors.d.ts +116 -0
  250. package/dist/wu-transaction-collectors.js +272 -0
  251. package/dist/wu-transaction.d.ts +170 -0
  252. package/dist/wu-transaction.js +273 -0
  253. package/dist/wu-validation-constants.d.ts +60 -0
  254. package/dist/wu-validation-constants.js +66 -0
  255. package/dist/wu-validation.d.ts +118 -0
  256. package/dist/wu-validation.js +243 -0
  257. package/dist/wu-validator.d.ts +62 -0
  258. package/dist/wu-validator.js +325 -0
  259. package/dist/wu-yaml-fixer.d.ts +97 -0
  260. package/dist/wu-yaml-fixer.js +264 -0
  261. package/dist/wu-yaml.d.ts +86 -0
  262. package/dist/wu-yaml.js +222 -0
  263. package/package.json +114 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Spawn Recovery Module (WU-1951)
3
+ *
4
+ * Auto-recovery heuristics for stuck spawns and zombie locks.
5
+ * Used by orchestrate:monitor for automatic spawn health management.
6
+ *
7
+ * Recovery Heuristics:
8
+ * 1. Zombie lock (PID not running) -> auto-release, mark spawn crashed
9
+ * 2. Stale lock (>2h) -> auto-release, mark spawn timeout
10
+ * 3. Active lock + no checkpoint in 1h -> mark stuck, escalate
11
+ *
12
+ * All recovery actions are logged to .beacon/recovery/ for audit.
13
+ *
14
+ * Library-First Note: This is project-specific spawn recovery code for
15
+ * PatientPath's custom spawn-registry.jsonl, lane-lock, and memory-store.
16
+ * No external library exists for this domain-specific agent lifecycle management.
17
+ *
18
+ * @see {@link tools/lib/__tests__/spawn-recovery.test.mjs} - Tests
19
+ * @see {@link tools/lib/spawn-monitor.mjs} - Monitoring logic
20
+ * @see {@link tools/lib/spawn-registry-store.mjs} - Spawn state
21
+ */
22
+ /**
23
+ * Recovery action constants
24
+ */
25
+ export declare const RecoveryAction: Readonly<{
26
+ /** No recovery needed */
27
+ NONE: "none";
28
+ /** Zombie lock released (PID not running) */
29
+ RELEASED_ZOMBIE: "released_zombie";
30
+ /** Stale lock released (>2h old) */
31
+ RELEASED_STALE: "released_stale";
32
+ /** Stuck spawn escalated (active but no checkpoint in 1h) */
33
+ ESCALATED_STUCK: "escalated_stuck";
34
+ }>;
35
+ /**
36
+ * Recovery directory name
37
+ */
38
+ export declare const RECOVERY_DIR_NAME = "recovery";
39
+ /**
40
+ * Threshold for "no checkpoint" detection (1 hour in milliseconds)
41
+ */
42
+ export declare const NO_CHECKPOINT_THRESHOLD_MS: number;
43
+ /**
44
+ * Recovers a stuck spawn by applying appropriate heuristics.
45
+ *
46
+ * Recovery order (first match wins):
47
+ * 1. Zombie lock (PID not running) -> release lock, mark crashed
48
+ * 2. Stale lock (>2h) -> release lock, mark timeout
49
+ * 3. Active lock + no checkpoint in 1h -> escalate (no auto-release)
50
+ * 4. Healthy spawn -> no action
51
+ *
52
+ * @param {string} spawnId - ID of the spawn to recover
53
+ * @param {RecoverStuckSpawnOptions} options - Options
54
+ * @returns {Promise<RecoveryResult>} Recovery result
55
+ *
56
+ * @example
57
+ * const result = await recoverStuckSpawn('spawn-1234', { baseDir: '/path/to/project' });
58
+ * if (result.recovered) {
59
+ * console.log(`Recovered: ${result.action} - ${result.reason}`);
60
+ * }
61
+ */
62
+ export interface RecoverStuckSpawnOptions {
63
+ /** Base directory for .beacon/ */
64
+ baseDir?: string;
65
+ }
66
+ export declare function recoverStuckSpawn(spawnId: any, options?: RecoverStuckSpawnOptions): Promise<{
67
+ recovered: boolean;
68
+ action: "none";
69
+ reason: string;
70
+ } | {
71
+ recovered: boolean;
72
+ action: "released_zombie";
73
+ reason: string;
74
+ } | {
75
+ recovered: boolean;
76
+ action: "released_stale";
77
+ reason: string;
78
+ } | {
79
+ recovered: boolean;
80
+ action: "escalated_stuck";
81
+ reason: string;
82
+ }>;
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Spawn Recovery Module (WU-1951)
3
+ *
4
+ * Auto-recovery heuristics for stuck spawns and zombie locks.
5
+ * Used by orchestrate:monitor for automatic spawn health management.
6
+ *
7
+ * Recovery Heuristics:
8
+ * 1. Zombie lock (PID not running) -> auto-release, mark spawn crashed
9
+ * 2. Stale lock (>2h) -> auto-release, mark spawn timeout
10
+ * 3. Active lock + no checkpoint in 1h -> mark stuck, escalate
11
+ *
12
+ * All recovery actions are logged to .beacon/recovery/ for audit.
13
+ *
14
+ * Library-First Note: This is project-specific spawn recovery code for
15
+ * PatientPath's custom spawn-registry.jsonl, lane-lock, and memory-store.
16
+ * No external library exists for this domain-specific agent lifecycle management.
17
+ *
18
+ * @see {@link tools/lib/__tests__/spawn-recovery.test.mjs} - Tests
19
+ * @see {@link tools/lib/spawn-monitor.mjs} - Monitoring logic
20
+ * @see {@link tools/lib/spawn-registry-store.mjs} - Spawn state
21
+ */
22
+ import fs from 'node:fs/promises';
23
+ import path from 'node:path';
24
+ import { SpawnRegistryStore } from './spawn-registry-store.js';
25
+ import { SpawnStatus } from './spawn-registry-schema.js';
26
+ import { isZombieLock, isLockStale, readLockMetadata, getLockFilePath, releaseLaneLock, } from './lane-lock.js';
27
+ import { toKebab } from './wu-constants.js';
28
+ // Optional import from @lumenflow/memory
29
+ let loadMemory = null;
30
+ try {
31
+ const mod = await import('@lumenflow/memory/store');
32
+ loadMemory = mod.loadMemory;
33
+ }
34
+ catch {
35
+ // @lumenflow/memory not available - memory features disabled
36
+ }
37
+ /**
38
+ * Recovery action constants
39
+ */
40
+ export const RecoveryAction = Object.freeze({
41
+ /** No recovery needed */
42
+ NONE: 'none',
43
+ /** Zombie lock released (PID not running) */
44
+ RELEASED_ZOMBIE: 'released_zombie',
45
+ /** Stale lock released (>2h old) */
46
+ RELEASED_STALE: 'released_stale',
47
+ /** Stuck spawn escalated (active but no checkpoint in 1h) */
48
+ ESCALATED_STUCK: 'escalated_stuck',
49
+ });
50
+ /**
51
+ * Recovery directory name
52
+ */
53
+ export const RECOVERY_DIR_NAME = 'recovery';
54
+ /**
55
+ * Threshold for "no checkpoint" detection (1 hour in milliseconds)
56
+ */
57
+ export const NO_CHECKPOINT_THRESHOLD_MS = 60 * 60 * 1000;
58
+ /**
59
+ * Log prefix for spawn-recovery messages
60
+ */
61
+ const LOG_PREFIX = '[spawn-recovery]';
62
+ /**
63
+ * @typedef {Object} RecoveryResult
64
+ * @property {boolean} recovered - Whether a recovery action was taken
65
+ * @property {string} action - The recovery action taken (from RecoveryAction)
66
+ * @property {string} reason - Human-readable explanation of the result
67
+ */
68
+ /**
69
+ * @typedef {Object} AuditLogEntry
70
+ * @property {string} timestamp - ISO timestamp of recovery action
71
+ * @property {string} spawnId - ID of the spawn being recovered
72
+ * @property {string} action - Recovery action taken
73
+ * @property {string} reason - Explanation of why action was taken
74
+ * @property {Object} context - Additional context
75
+ * @property {string} context.targetWuId - Target WU ID
76
+ * @property {string} context.lane - Lane name
77
+ * @property {Object|null} context.lockMetadata - Lock metadata if present
78
+ * @property {string|null} context.lastCheckpoint - Last checkpoint timestamp
79
+ */
80
+ /**
81
+ * Converts lane name to lock file path (kebab-case)
82
+ *
83
+ * @param {string} lane - Lane name (e.g., "Operations: Tooling")
84
+ * @returns {string} Kebab-case lane name (e.g., "operations-tooling")
85
+ */
86
+ function laneToKebab(lane) {
87
+ return toKebab(lane);
88
+ }
89
+ /**
90
+ * Gets the recovery directory path
91
+ *
92
+ * @param {string} baseDir - Base directory
93
+ * @returns {string} Path to .beacon/recovery/
94
+ */
95
+ function getRecoveryDir(baseDir) {
96
+ return path.join(baseDir, '.beacon', RECOVERY_DIR_NAME);
97
+ }
98
+ /**
99
+ * Creates an audit log entry
100
+ *
101
+ * @param {string} baseDir - Base directory
102
+ * @param {AuditLogEntry} entry - Audit log entry
103
+ * @returns {Promise<void>}
104
+ */
105
+ async function createAuditLog(baseDir, entry) {
106
+ const recoveryDir = getRecoveryDir(baseDir);
107
+ await fs.mkdir(recoveryDir, { recursive: true });
108
+ const timestamp = entry.timestamp.replace(/[:.]/g, '-');
109
+ const fileName = `${entry.spawnId}-${timestamp}.json`;
110
+ const filePath = path.join(recoveryDir, fileName);
111
+ await fs.writeFile(filePath, JSON.stringify(entry, null, 2), 'utf-8');
112
+ console.log(`${LOG_PREFIX} Audit log created: ${fileName}`);
113
+ }
114
+ /**
115
+ * Gets the most recent checkpoint for a WU from the memory store
116
+ *
117
+ * @param {string} baseDir - Base directory
118
+ * @param {string} wuId - WU ID to find checkpoints for
119
+ * @returns {Promise<{timestamp: string, content: string}|null>} Most recent checkpoint or null
120
+ */
121
+ async function getLastCheckpoint(baseDir, wuId) {
122
+ // If memory module not available, return null
123
+ if (!loadMemory) {
124
+ return null;
125
+ }
126
+ const memoryDir = path.join(baseDir, '.beacon', 'state');
127
+ try {
128
+ const memory = await loadMemory(memoryDir, wuId);
129
+ if (!memory) {
130
+ return null;
131
+ }
132
+ const checkpoints = memory.checkpoints ?? [];
133
+ if (checkpoints.length === 0) {
134
+ return null;
135
+ }
136
+ // Sort by timestamp descending, get most recent
137
+ checkpoints.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
138
+ const latest = checkpoints[0];
139
+ return {
140
+ timestamp: latest.timestamp,
141
+ content: '',
142
+ };
143
+ }
144
+ catch {
145
+ // Memory store doesn't exist or is invalid
146
+ return null;
147
+ }
148
+ }
149
+ /**
150
+ * Checks if a checkpoint is recent enough (within 1 hour)
151
+ *
152
+ * @param {string|null} checkpointTimestamp - ISO timestamp of last checkpoint
153
+ * @returns {boolean} True if checkpoint is recent (within 1 hour)
154
+ */
155
+ function isCheckpointRecent(checkpointTimestamp) {
156
+ if (!checkpointTimestamp) {
157
+ return false;
158
+ }
159
+ const checkpointTime = new Date(checkpointTimestamp).getTime();
160
+ const now = Date.now();
161
+ return now - checkpointTime <= NO_CHECKPOINT_THRESHOLD_MS;
162
+ }
163
+ export async function recoverStuckSpawn(spawnId, options = {}) {
164
+ const { baseDir = process.cwd() } = options;
165
+ const registryDir = path.join(baseDir, '.beacon', 'state');
166
+ // Load spawn registry
167
+ const store = new SpawnRegistryStore(registryDir);
168
+ try {
169
+ await store.load();
170
+ }
171
+ catch {
172
+ // Registry doesn't exist or is invalid
173
+ return {
174
+ recovered: false,
175
+ action: RecoveryAction.NONE,
176
+ reason: `Spawn ${spawnId} not found: registry unavailable`,
177
+ };
178
+ }
179
+ // Find the spawn
180
+ const spawn = store.getById(spawnId);
181
+ if (!spawn) {
182
+ return {
183
+ recovered: false,
184
+ action: RecoveryAction.NONE,
185
+ reason: `Spawn ${spawnId} not found in registry`,
186
+ };
187
+ }
188
+ // Check if already completed
189
+ if (spawn.status !== SpawnStatus.PENDING) {
190
+ return {
191
+ recovered: false,
192
+ action: RecoveryAction.NONE,
193
+ reason: `Spawn ${spawnId} already ${spawn.status}`,
194
+ };
195
+ }
196
+ // Get lock for this spawn's lane
197
+ const laneKebab = laneToKebab(spawn.lane);
198
+ const lockPath = getLockFilePath(spawn.lane, baseDir);
199
+ const lockMetadata = readLockMetadata(lockPath);
200
+ // If no lock, nothing to recover
201
+ if (!lockMetadata) {
202
+ return {
203
+ recovered: false,
204
+ action: RecoveryAction.NONE,
205
+ reason: `No lock found for spawn ${spawnId} (lane: ${spawn.lane})`,
206
+ };
207
+ }
208
+ // Check if lock belongs to this WU
209
+ if (lockMetadata.wuId !== spawn.targetWuId) {
210
+ return {
211
+ recovered: false,
212
+ action: RecoveryAction.NONE,
213
+ reason: `Lock belongs to ${lockMetadata.wuId}, not spawn target ${spawn.targetWuId}`,
214
+ };
215
+ }
216
+ // Get last checkpoint for context
217
+ const lastCheckpoint = await getLastCheckpoint(baseDir, spawn.targetWuId);
218
+ const lastCheckpointTs = lastCheckpoint?.timestamp ?? null;
219
+ // Build common audit context
220
+ const auditContext = {
221
+ targetWuId: spawn.targetWuId,
222
+ parentWuId: spawn.parentWuId,
223
+ lane: spawn.lane,
224
+ spawnedAt: spawn.spawnedAt,
225
+ lockMetadata,
226
+ lastCheckpoint: lastCheckpointTs,
227
+ };
228
+ // Heuristic 1: Zombie lock (PID not running)
229
+ if (isZombieLock(lockMetadata)) {
230
+ console.log(`${LOG_PREFIX} Detected zombie lock for ${spawnId} (PID ${lockMetadata.pid} not running)`);
231
+ // Release the lock
232
+ releaseLaneLock(spawn.lane, { baseDir, force: true });
233
+ // Mark spawn as crashed
234
+ await store.updateStatus(spawnId, SpawnStatus.CRASHED);
235
+ const reason = `Zombie lock detected: PID ${lockMetadata.pid} not running`;
236
+ // Create audit log
237
+ await createAuditLog(baseDir, {
238
+ timestamp: new Date().toISOString(),
239
+ spawnId,
240
+ action: RecoveryAction.RELEASED_ZOMBIE,
241
+ reason,
242
+ context: auditContext,
243
+ });
244
+ return {
245
+ recovered: true,
246
+ action: RecoveryAction.RELEASED_ZOMBIE,
247
+ reason,
248
+ };
249
+ }
250
+ // Heuristic 2: Stale lock (>2h old)
251
+ if (isLockStale(lockMetadata)) {
252
+ console.log(`${LOG_PREFIX} Detected stale lock for ${spawnId} (acquired ${lockMetadata.timestamp})`);
253
+ // Release the lock
254
+ releaseLaneLock(spawn.lane, { baseDir, force: true });
255
+ // Mark spawn as timeout
256
+ await store.updateStatus(spawnId, SpawnStatus.TIMEOUT);
257
+ const reason = `Stale lock detected: acquired ${lockMetadata.timestamp} (>2h threshold)`;
258
+ // Create audit log
259
+ await createAuditLog(baseDir, {
260
+ timestamp: new Date().toISOString(),
261
+ spawnId,
262
+ action: RecoveryAction.RELEASED_STALE,
263
+ reason,
264
+ context: auditContext,
265
+ });
266
+ return {
267
+ recovered: true,
268
+ action: RecoveryAction.RELEASED_STALE,
269
+ reason,
270
+ };
271
+ }
272
+ // Heuristic 3: Active lock + no recent checkpoint -> escalate
273
+ if (!isCheckpointRecent(lastCheckpointTs)) {
274
+ const reason = lastCheckpointTs
275
+ ? `No checkpoint in last hour (last: ${lastCheckpointTs})`
276
+ : 'No checkpoints recorded for this spawn';
277
+ console.log(`${LOG_PREFIX} Escalating stuck spawn ${spawnId}: ${reason}`);
278
+ // Create audit log (escalation, not recovery)
279
+ await createAuditLog(baseDir, {
280
+ timestamp: new Date().toISOString(),
281
+ spawnId,
282
+ action: RecoveryAction.ESCALATED_STUCK,
283
+ reason,
284
+ context: auditContext,
285
+ });
286
+ return {
287
+ recovered: false, // No auto-recovery, just escalation
288
+ action: RecoveryAction.ESCALATED_STUCK,
289
+ reason: `Stuck spawn: ${reason}`,
290
+ };
291
+ }
292
+ // Healthy spawn with recent checkpoint
293
+ return {
294
+ recovered: false,
295
+ action: RecoveryAction.NONE,
296
+ reason: `Spawn ${spawnId} healthy (recent checkpoint at ${lastCheckpointTs})`,
297
+ };
298
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Spawn Registry Schema (WU-1944)
3
+ *
4
+ * Zod schemas for spawn event validation.
5
+ * Defines schema for tracking sub-agent spawns by orchestrators.
6
+ *
7
+ * @see {@link tools/lib/__tests__/spawn-registry-store.test.mjs} - Tests
8
+ * @see {@link tools/lib/spawn-registry-store.mjs} - Store implementation
9
+ */
10
+ import { z } from 'zod';
11
+ /**
12
+ * Spawn status values
13
+ */
14
+ export declare const SpawnStatus: {
15
+ readonly PENDING: "pending";
16
+ readonly COMPLETED: "completed";
17
+ readonly TIMEOUT: "timeout";
18
+ readonly CRASHED: "crashed";
19
+ /** WU-1967: Spawn escalated to orchestrator (signal sent, prevents duplicates) */
20
+ readonly ESCALATED: "escalated";
21
+ };
22
+ /** Type for spawn status values */
23
+ export type SpawnStatusValue = (typeof SpawnStatus)[keyof typeof SpawnStatus];
24
+ /**
25
+ * Array of valid spawn statuses
26
+ */
27
+ export declare const SPAWN_STATUSES: readonly ["pending", "completed", "timeout", "crashed", "escalated"];
28
+ /**
29
+ * Regex patterns for spawn validation
30
+ */
31
+ export declare const SPAWN_PATTERNS: {
32
+ /** Spawn ID format: spawn-{4 hex chars} */
33
+ SPAWN_ID: RegExp;
34
+ /** WU ID format: WU-{digits} */
35
+ WU_ID: RegExp;
36
+ };
37
+ /**
38
+ * Spawn Event Schema
39
+ *
40
+ * Defines the structure for spawn registry events.
41
+ * Uses append-only JSONL storage with event replay for state reconstruction.
42
+ */
43
+ export declare const SpawnEventSchema: z.ZodObject<{
44
+ id: z.ZodString;
45
+ parentWuId: z.ZodString;
46
+ targetWuId: z.ZodString;
47
+ lane: z.ZodString;
48
+ spawnedAt: z.ZodString;
49
+ status: z.ZodEnum<{
50
+ completed: "completed";
51
+ timeout: "timeout";
52
+ pending: "pending";
53
+ crashed: "crashed";
54
+ escalated: "escalated";
55
+ }>;
56
+ completedAt: z.ZodNullable<z.ZodString>;
57
+ }, z.core.$strip>;
58
+ /**
59
+ * TypeScript type inferred from schema
60
+ */
61
+ export type SpawnEvent = z.infer<typeof SpawnEventSchema>;
62
+ /**
63
+ * Validates spawn event data against schema
64
+ *
65
+ * @param {unknown} data - Data to validate
66
+ * @returns Validation result
67
+ *
68
+ * @example
69
+ * const result = validateSpawnEvent(eventData);
70
+ * if (!result.success) {
71
+ * result.error.issues.forEach(issue => {
72
+ * console.error(`${issue.path.join('.')}: ${issue.message}`);
73
+ * });
74
+ * }
75
+ */
76
+ export declare function validateSpawnEvent(data: unknown): z.ZodSafeParseResult<{
77
+ id: string;
78
+ parentWuId: string;
79
+ targetWuId: string;
80
+ lane: string;
81
+ spawnedAt: string;
82
+ status: "completed" | "timeout" | "pending" | "crashed" | "escalated";
83
+ completedAt?: string;
84
+ }>;
85
+ /**
86
+ * Generates a unique spawn ID from parent WU, target WU, and timestamp
87
+ *
88
+ * Format: spawn-XXXX (4 hex characters from SHA-256 hash)
89
+ *
90
+ * @param {string} parentWuId - Parent WU ID
91
+ * @param {string} targetWuId - Target WU ID
92
+ * @returns {string} Spawn ID in format spawn-XXXX
93
+ *
94
+ * @example
95
+ * const id = generateSpawnId('WU-1000', 'WU-1001');
96
+ * // Returns: 'spawn-a1b2'
97
+ */
98
+ export declare function generateSpawnId(parentWuId: string, targetWuId: string): string;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Spawn Registry Schema (WU-1944)
3
+ *
4
+ * Zod schemas for spawn event validation.
5
+ * Defines schema for tracking sub-agent spawns by orchestrators.
6
+ *
7
+ * @see {@link tools/lib/__tests__/spawn-registry-store.test.mjs} - Tests
8
+ * @see {@link tools/lib/spawn-registry-store.mjs} - Store implementation
9
+ */
10
+ import { z } from 'zod';
11
+ import crypto from 'node:crypto';
12
+ /**
13
+ * Spawn status values
14
+ */
15
+ export const SpawnStatus = {
16
+ PENDING: 'pending',
17
+ COMPLETED: 'completed',
18
+ TIMEOUT: 'timeout',
19
+ CRASHED: 'crashed',
20
+ /** WU-1967: Spawn escalated to orchestrator (signal sent, prevents duplicates) */
21
+ ESCALATED: 'escalated',
22
+ };
23
+ /**
24
+ * Array of valid spawn statuses
25
+ */
26
+ export const SPAWN_STATUSES = ['pending', 'completed', 'timeout', 'crashed', 'escalated'];
27
+ /**
28
+ * Regex patterns for spawn validation
29
+ */
30
+ export const SPAWN_PATTERNS = {
31
+ /** Spawn ID format: spawn-{4 hex chars} */
32
+ SPAWN_ID: /^spawn-[0-9a-f]{4}$/,
33
+ /** WU ID format: WU-{digits} */
34
+ WU_ID: /^WU-\d+$/,
35
+ };
36
+ /**
37
+ * Error messages for schema validation
38
+ */
39
+ const ERROR_MESSAGES = {
40
+ SPAWN_ID: 'Spawn ID must match pattern spawn-XXXX (e.g., spawn-a1b2)',
41
+ WU_ID: 'WU ID must match pattern WU-XXX (e.g., WU-1000)',
42
+ LANE_REQUIRED: 'Lane is required',
43
+ STATUS: `Status must be one of: ${SPAWN_STATUSES.join(', ')}`,
44
+ TIMESTAMP_REQUIRED: 'Timestamp is required',
45
+ };
46
+ /**
47
+ * Spawn Event Schema
48
+ *
49
+ * Defines the structure for spawn registry events.
50
+ * Uses append-only JSONL storage with event replay for state reconstruction.
51
+ */
52
+ export const SpawnEventSchema = z.object({
53
+ /** Unique spawn ID in format spawn-XXXX (4 hex chars from SHA hash) */
54
+ id: z.string().regex(SPAWN_PATTERNS.SPAWN_ID, { message: ERROR_MESSAGES.SPAWN_ID }),
55
+ /** Parent WU ID (the orchestrator that spawned this agent) */
56
+ parentWuId: z.string().regex(SPAWN_PATTERNS.WU_ID, { message: ERROR_MESSAGES.WU_ID }),
57
+ /** Target WU ID (the WU being executed by the spawned agent) */
58
+ targetWuId: z.string().regex(SPAWN_PATTERNS.WU_ID, { message: ERROR_MESSAGES.WU_ID }),
59
+ /** Lane for the spawned work */
60
+ lane: z.string().min(1, { message: ERROR_MESSAGES.LANE_REQUIRED }),
61
+ /** ISO 8601 timestamp when spawn was recorded */
62
+ spawnedAt: z.string().datetime({ message: ERROR_MESSAGES.TIMESTAMP_REQUIRED }),
63
+ /** Current status of the spawned agent */
64
+ status: z.enum(SPAWN_STATUSES, {
65
+ error: ERROR_MESSAGES.STATUS,
66
+ }),
67
+ /** ISO 8601 timestamp when spawn completed (null if pending) */
68
+ completedAt: z.string().datetime().nullable(),
69
+ });
70
+ /**
71
+ * Validates spawn event data against schema
72
+ *
73
+ * @param {unknown} data - Data to validate
74
+ * @returns Validation result
75
+ *
76
+ * @example
77
+ * const result = validateSpawnEvent(eventData);
78
+ * if (!result.success) {
79
+ * result.error.issues.forEach(issue => {
80
+ * console.error(`${issue.path.join('.')}: ${issue.message}`);
81
+ * });
82
+ * }
83
+ */
84
+ export function validateSpawnEvent(data) {
85
+ return SpawnEventSchema.safeParse(data);
86
+ }
87
+ /**
88
+ * Generates a unique spawn ID from parent WU, target WU, and timestamp
89
+ *
90
+ * Format: spawn-XXXX (4 hex characters from SHA-256 hash)
91
+ *
92
+ * @param {string} parentWuId - Parent WU ID
93
+ * @param {string} targetWuId - Target WU ID
94
+ * @returns {string} Spawn ID in format spawn-XXXX
95
+ *
96
+ * @example
97
+ * const id = generateSpawnId('WU-1000', 'WU-1001');
98
+ * // Returns: 'spawn-a1b2'
99
+ */
100
+ export function generateSpawnId(parentWuId, targetWuId) {
101
+ // Include timestamp and random bytes for uniqueness
102
+ const timestamp = Date.now().toString();
103
+ const randomBytes = crypto.randomBytes(4).toString('hex');
104
+ const input = `${parentWuId}:${targetWuId}:${timestamp}:${randomBytes}`;
105
+ const hash = crypto.createHash('sha256').update(input).digest('hex');
106
+ // Take first 4 hex chars
107
+ return `spawn-${hash.slice(0, 4)}`;
108
+ }