@ttfw/envoi 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 (283) hide show
  1. package/README.md +238 -0
  2. package/dist/commands/app.d.ts +2 -0
  3. package/dist/commands/app.d.ts.map +1 -0
  4. package/dist/commands/app.js +31 -0
  5. package/dist/commands/app.js.map +1 -0
  6. package/dist/commands/autonomy.d.ts +6 -0
  7. package/dist/commands/autonomy.d.ts.map +1 -0
  8. package/dist/commands/autonomy.js +89 -0
  9. package/dist/commands/autonomy.js.map +1 -0
  10. package/dist/commands/builder.d.ts +13 -0
  11. package/dist/commands/builder.d.ts.map +1 -0
  12. package/dist/commands/builder.js +142 -0
  13. package/dist/commands/builder.js.map +1 -0
  14. package/dist/commands/idea.d.ts +12 -0
  15. package/dist/commands/idea.d.ts.map +1 -0
  16. package/dist/commands/idea.js +79 -0
  17. package/dist/commands/idea.js.map +1 -0
  18. package/dist/commands/init.d.ts +18 -0
  19. package/dist/commands/init.d.ts.map +1 -0
  20. package/dist/commands/init.js +423 -0
  21. package/dist/commands/init.js.map +1 -0
  22. package/dist/commands/mode.d.ts +13 -0
  23. package/dist/commands/mode.d.ts.map +1 -0
  24. package/dist/commands/mode.js +96 -0
  25. package/dist/commands/mode.js.map +1 -0
  26. package/dist/commands/onboard.d.ts +37 -0
  27. package/dist/commands/onboard.d.ts.map +1 -0
  28. package/dist/commands/onboard.js +743 -0
  29. package/dist/commands/onboard.js.map +1 -0
  30. package/dist/commands/pr-note.d.ts +8 -0
  31. package/dist/commands/pr-note.d.ts.map +1 -0
  32. package/dist/commands/pr-note.js +27 -0
  33. package/dist/commands/pr-note.js.map +1 -0
  34. package/dist/commands/undo.d.ts +7 -0
  35. package/dist/commands/undo.d.ts.map +1 -0
  36. package/dist/commands/undo.js +59 -0
  37. package/dist/commands/undo.js.map +1 -0
  38. package/dist/commands/update.d.ts +24 -0
  39. package/dist/commands/update.d.ts.map +1 -0
  40. package/dist/commands/update.js +248 -0
  41. package/dist/commands/update.js.map +1 -0
  42. package/dist/constants/report_codes.d.ts +29 -0
  43. package/dist/constants/report_codes.d.ts.map +1 -0
  44. package/dist/constants/report_codes.js +69 -0
  45. package/dist/constants/report_codes.js.map +1 -0
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +675 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/lib/autonomy.d.ts +16 -0
  51. package/dist/lib/autonomy.d.ts.map +1 -0
  52. package/dist/lib/autonomy.js +38 -0
  53. package/dist/lib/autonomy.js.map +1 -0
  54. package/dist/lib/blocked.d.ts +87 -0
  55. package/dist/lib/blocked.d.ts.map +1 -0
  56. package/dist/lib/blocked.js +134 -0
  57. package/dist/lib/blocked.js.map +1 -0
  58. package/dist/lib/branding.d.ts +13 -0
  59. package/dist/lib/branding.d.ts.map +1 -0
  60. package/dist/lib/branding.js +19 -0
  61. package/dist/lib/branding.js.map +1 -0
  62. package/dist/lib/claude.d.ts +42 -0
  63. package/dist/lib/claude.d.ts.map +1 -0
  64. package/dist/lib/claude.js +291 -0
  65. package/dist/lib/claude.js.map +1 -0
  66. package/dist/lib/config.d.ts +71 -0
  67. package/dist/lib/config.d.ts.map +1 -0
  68. package/dist/lib/config.js +410 -0
  69. package/dist/lib/config.js.map +1 -0
  70. package/dist/lib/diff.d.ts +150 -0
  71. package/dist/lib/diff.d.ts.map +1 -0
  72. package/dist/lib/diff.js +257 -0
  73. package/dist/lib/diff.js.map +1 -0
  74. package/dist/lib/doctor.d.ts +67 -0
  75. package/dist/lib/doctor.d.ts.map +1 -0
  76. package/dist/lib/doctor.js +211 -0
  77. package/dist/lib/doctor.js.map +1 -0
  78. package/dist/lib/fingerprint.d.ts +27 -0
  79. package/dist/lib/fingerprint.d.ts.map +1 -0
  80. package/dist/lib/fingerprint.js +116 -0
  81. package/dist/lib/fingerprint.js.map +1 -0
  82. package/dist/lib/fs.d.ts +93 -0
  83. package/dist/lib/fs.d.ts.map +1 -0
  84. package/dist/lib/fs.js +179 -0
  85. package/dist/lib/fs.js.map +1 -0
  86. package/dist/lib/git.d.ts +177 -0
  87. package/dist/lib/git.d.ts.map +1 -0
  88. package/dist/lib/git.js +355 -0
  89. package/dist/lib/git.js.map +1 -0
  90. package/dist/lib/git_branching.d.ts +84 -0
  91. package/dist/lib/git_branching.d.ts.map +1 -0
  92. package/dist/lib/git_branching.js +327 -0
  93. package/dist/lib/git_branching.js.map +1 -0
  94. package/dist/lib/gitignore.d.ts +26 -0
  95. package/dist/lib/gitignore.d.ts.map +1 -0
  96. package/dist/lib/gitignore.js +119 -0
  97. package/dist/lib/gitignore.js.map +1 -0
  98. package/dist/lib/guardrails.d.ts +232 -0
  99. package/dist/lib/guardrails.d.ts.map +1 -0
  100. package/dist/lib/guardrails.js +323 -0
  101. package/dist/lib/guardrails.js.map +1 -0
  102. package/dist/lib/history.d.ts +110 -0
  103. package/dist/lib/history.d.ts.map +1 -0
  104. package/dist/lib/history.js +236 -0
  105. package/dist/lib/history.js.map +1 -0
  106. package/dist/lib/index.d.ts +29 -0
  107. package/dist/lib/index.d.ts.map +1 -0
  108. package/dist/lib/index.js +29 -0
  109. package/dist/lib/index.js.map +1 -0
  110. package/dist/lib/json-extract.d.ts +42 -0
  111. package/dist/lib/json-extract.d.ts.map +1 -0
  112. package/dist/lib/json-extract.js +201 -0
  113. package/dist/lib/json-extract.js.map +1 -0
  114. package/dist/lib/judge.d.ts +237 -0
  115. package/dist/lib/judge.d.ts.map +1 -0
  116. package/dist/lib/judge.js +501 -0
  117. package/dist/lib/judge.js.map +1 -0
  118. package/dist/lib/lock.d.ts +79 -0
  119. package/dist/lib/lock.d.ts.map +1 -0
  120. package/dist/lib/lock.js +254 -0
  121. package/dist/lib/lock.js.map +1 -0
  122. package/dist/lib/migration.d.ts +9 -0
  123. package/dist/lib/migration.d.ts.map +1 -0
  124. package/dist/lib/migration.js +74 -0
  125. package/dist/lib/migration.js.map +1 -0
  126. package/dist/lib/paths.d.ts +18 -0
  127. package/dist/lib/paths.d.ts.map +1 -0
  128. package/dist/lib/paths.js +27 -0
  129. package/dist/lib/paths.js.map +1 -0
  130. package/dist/lib/preflight.d.ts +33 -0
  131. package/dist/lib/preflight.d.ts.map +1 -0
  132. package/dist/lib/preflight.js +177 -0
  133. package/dist/lib/preflight.js.map +1 -0
  134. package/dist/lib/prompt_budget.d.ts +18 -0
  135. package/dist/lib/prompt_budget.d.ts.map +1 -0
  136. package/dist/lib/prompt_budget.js +36 -0
  137. package/dist/lib/prompt_budget.js.map +1 -0
  138. package/dist/lib/report.d.ts +102 -0
  139. package/dist/lib/report.d.ts.map +1 -0
  140. package/dist/lib/report.js +347 -0
  141. package/dist/lib/report.js.map +1 -0
  142. package/dist/lib/reviewer-flow.d.ts +80 -0
  143. package/dist/lib/reviewer-flow.d.ts.map +1 -0
  144. package/dist/lib/reviewer-flow.js +138 -0
  145. package/dist/lib/reviewer-flow.js.map +1 -0
  146. package/dist/lib/reviewer.d.ts +53 -0
  147. package/dist/lib/reviewer.d.ts.map +1 -0
  148. package/dist/lib/reviewer.js +199 -0
  149. package/dist/lib/reviewer.js.map +1 -0
  150. package/dist/lib/risk.d.ts +127 -0
  151. package/dist/lib/risk.d.ts.map +1 -0
  152. package/dist/lib/risk.js +192 -0
  153. package/dist/lib/risk.js.map +1 -0
  154. package/dist/lib/rollback.d.ts +143 -0
  155. package/dist/lib/rollback.d.ts.map +1 -0
  156. package/dist/lib/rollback.js +244 -0
  157. package/dist/lib/rollback.js.map +1 -0
  158. package/dist/lib/schema.d.ts +47 -0
  159. package/dist/lib/schema.d.ts.map +1 -0
  160. package/dist/lib/schema.js +91 -0
  161. package/dist/lib/schema.js.map +1 -0
  162. package/dist/lib/scope.d.ts +89 -0
  163. package/dist/lib/scope.d.ts.map +1 -0
  164. package/dist/lib/scope.js +135 -0
  165. package/dist/lib/scope.js.map +1 -0
  166. package/dist/lib/self_update.d.ts +13 -0
  167. package/dist/lib/self_update.d.ts.map +1 -0
  168. package/dist/lib/self_update.js +172 -0
  169. package/dist/lib/self_update.js.map +1 -0
  170. package/dist/lib/state.d.ts +143 -0
  171. package/dist/lib/state.d.ts.map +1 -0
  172. package/dist/lib/state.js +258 -0
  173. package/dist/lib/state.js.map +1 -0
  174. package/dist/lib/tick.d.ts +310 -0
  175. package/dist/lib/tick.d.ts.map +1 -0
  176. package/dist/lib/tick.js +424 -0
  177. package/dist/lib/tick.js.map +1 -0
  178. package/dist/lib/transport.d.ts +145 -0
  179. package/dist/lib/transport.d.ts.map +1 -0
  180. package/dist/lib/transport.js +237 -0
  181. package/dist/lib/transport.js.map +1 -0
  182. package/dist/lib/verdict_labels.d.ts +5 -0
  183. package/dist/lib/verdict_labels.d.ts.map +1 -0
  184. package/dist/lib/verdict_labels.js +25 -0
  185. package/dist/lib/verdict_labels.js.map +1 -0
  186. package/dist/lib/verify-safety.d.ts +63 -0
  187. package/dist/lib/verify-safety.d.ts.map +1 -0
  188. package/dist/lib/verify-safety.js +123 -0
  189. package/dist/lib/verify-safety.js.map +1 -0
  190. package/dist/lib/verify.d.ts +139 -0
  191. package/dist/lib/verify.d.ts.map +1 -0
  192. package/dist/lib/verify.js +311 -0
  193. package/dist/lib/verify.js.map +1 -0
  194. package/dist/lib/workspace_state.d.ts +79 -0
  195. package/dist/lib/workspace_state.d.ts.map +1 -0
  196. package/dist/lib/workspace_state.js +283 -0
  197. package/dist/lib/workspace_state.js.map +1 -0
  198. package/dist/runner/builder.d.ts +58 -0
  199. package/dist/runner/builder.d.ts.map +1 -0
  200. package/dist/runner/builder.js +775 -0
  201. package/dist/runner/builder.js.map +1 -0
  202. package/dist/runner/builder_parse.d.ts +37 -0
  203. package/dist/runner/builder_parse.d.ts.map +1 -0
  204. package/dist/runner/builder_parse.js +76 -0
  205. package/dist/runner/builder_parse.js.map +1 -0
  206. package/dist/runner/index.d.ts +9 -0
  207. package/dist/runner/index.d.ts.map +1 -0
  208. package/dist/runner/index.js +7 -0
  209. package/dist/runner/index.js.map +1 -0
  210. package/dist/runner/loop.d.ts +51 -0
  211. package/dist/runner/loop.d.ts.map +1 -0
  212. package/dist/runner/loop.js +221 -0
  213. package/dist/runner/loop.js.map +1 -0
  214. package/dist/runner/orchestrator.d.ts +67 -0
  215. package/dist/runner/orchestrator.d.ts.map +1 -0
  216. package/dist/runner/orchestrator.js +376 -0
  217. package/dist/runner/orchestrator.js.map +1 -0
  218. package/dist/runner/tick.d.ts +10 -0
  219. package/dist/runner/tick.d.ts.map +1 -0
  220. package/dist/runner/tick.js +1639 -0
  221. package/dist/runner/tick.js.map +1 -0
  222. package/dist/types/blocked.d.ts +52 -0
  223. package/dist/types/blocked.d.ts.map +1 -0
  224. package/dist/types/blocked.js +8 -0
  225. package/dist/types/blocked.js.map +1 -0
  226. package/dist/types/builder.d.ts +25 -0
  227. package/dist/types/builder.d.ts.map +1 -0
  228. package/dist/types/builder.js +7 -0
  229. package/dist/types/builder.js.map +1 -0
  230. package/dist/types/claude.d.ts +86 -0
  231. package/dist/types/claude.d.ts.map +1 -0
  232. package/dist/types/claude.js +48 -0
  233. package/dist/types/claude.js.map +1 -0
  234. package/dist/types/config.d.ts +384 -0
  235. package/dist/types/config.d.ts.map +1 -0
  236. package/dist/types/config.js +7 -0
  237. package/dist/types/config.js.map +1 -0
  238. package/dist/types/index.d.ts +18 -0
  239. package/dist/types/index.d.ts.map +1 -0
  240. package/dist/types/index.js +8 -0
  241. package/dist/types/index.js.map +1 -0
  242. package/dist/types/lock.d.ts +21 -0
  243. package/dist/types/lock.d.ts.map +1 -0
  244. package/dist/types/lock.js +8 -0
  245. package/dist/types/lock.js.map +1 -0
  246. package/dist/types/preflight.d.ts +49 -0
  247. package/dist/types/preflight.d.ts.map +1 -0
  248. package/dist/types/preflight.js +8 -0
  249. package/dist/types/preflight.js.map +1 -0
  250. package/dist/types/report.d.ts +161 -0
  251. package/dist/types/report.d.ts.map +1 -0
  252. package/dist/types/report.js +8 -0
  253. package/dist/types/report.js.map +1 -0
  254. package/dist/types/reviewer.d.ts +66 -0
  255. package/dist/types/reviewer.d.ts.map +1 -0
  256. package/dist/types/reviewer.js +5 -0
  257. package/dist/types/reviewer.js.map +1 -0
  258. package/dist/types/state.d.ts +124 -0
  259. package/dist/types/state.d.ts.map +1 -0
  260. package/dist/types/state.js +20 -0
  261. package/dist/types/state.js.map +1 -0
  262. package/dist/types/task.d.ts +117 -0
  263. package/dist/types/task.d.ts.map +1 -0
  264. package/dist/types/task.js +7 -0
  265. package/dist/types/task.js.map +1 -0
  266. package/dist/types/workspace_state.d.ts +125 -0
  267. package/dist/types/workspace_state.d.ts.map +1 -0
  268. package/dist/types/workspace_state.js +10 -0
  269. package/dist/types/workspace_state.js.map +1 -0
  270. package/envoi.config.json +191 -0
  271. package/package.json +52 -0
  272. package/relais/prompts/.gitkeep +0 -0
  273. package/relais/prompts/builder.system.txt +13 -0
  274. package/relais/prompts/builder.user.txt +15 -0
  275. package/relais/prompts/orchestrator.system.txt +37 -0
  276. package/relais/prompts/orchestrator.user.txt +34 -0
  277. package/relais/prompts/reviewer.system.txt +33 -0
  278. package/relais/prompts/reviewer.user.txt +35 -0
  279. package/relais/schemas/.gitkeep +0 -0
  280. package/relais/schemas/builder_result.schema.json +29 -0
  281. package/relais/schemas/report.schema.json +195 -0
  282. package/relais/schemas/reviewer_result.schema.json +70 -0
  283. package/relais/schemas/task.schema.json +155 -0
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Lock mechanism for preventing concurrent envoi runs.
3
+ *
4
+ * Implements crash-safe lock acquisition with boot_id tracking to enable
5
+ * safe reclaim of stale locks after crashes or reboots.
6
+ */
7
+ import { execSync } from 'node:child_process';
8
+ import { hostname } from 'node:os';
9
+ import { unlink } from 'node:fs/promises';
10
+ import { atomicWriteJson, atomicReadJson, AtomicFsError } from './fs.js';
11
+ /**
12
+ * Error thrown when a lock is already held by another process.
13
+ */
14
+ export class LockHeldError extends Error {
15
+ lockPath;
16
+ holder;
17
+ constructor(message, lockPath, holder) {
18
+ super(message);
19
+ this.lockPath = lockPath;
20
+ this.holder = holder;
21
+ this.name = 'LockHeldError';
22
+ }
23
+ }
24
+ /**
25
+ * Error thrown when a lock file is corrupt or malformed.
26
+ */
27
+ export class LockCorruptError extends Error {
28
+ lockPath;
29
+ constructor(message, lockPath) {
30
+ super(message);
31
+ this.lockPath = lockPath;
32
+ this.name = 'LockCorruptError';
33
+ }
34
+ }
35
+ /** Cached boot ID to avoid repeated system calls */
36
+ let cachedBootId = null;
37
+ /**
38
+ * Gets a unique identifier for the current boot session.
39
+ *
40
+ * On Linux, reads /proc/sys/kernel/random/boot_id.
41
+ * On macOS, uses system uptime + hostname as a fingerprint since macOS
42
+ * doesn't have boot_id but uptime.boot_time is stable per boot.
43
+ *
44
+ * The result is cached for the lifetime of the process.
45
+ *
46
+ * @returns A string uniquely identifying the current boot session
47
+ */
48
+ export function getBootId() {
49
+ if (cachedBootId !== null) {
50
+ return cachedBootId;
51
+ }
52
+ const platform = process.platform;
53
+ if (platform === 'linux') {
54
+ try {
55
+ // Linux provides a unique boot ID
56
+ const { readFileSync } = require('node:fs');
57
+ const bootId = readFileSync('/proc/sys/kernel/random/boot_id', 'utf-8').trim();
58
+ cachedBootId = bootId;
59
+ return bootId;
60
+ }
61
+ catch {
62
+ // Fall through to fallback
63
+ }
64
+ }
65
+ if (platform === 'darwin') {
66
+ try {
67
+ // macOS: use boot time from sysctl
68
+ const output = execSync('sysctl -n kern.boottime', { encoding: 'utf-8' });
69
+ // Output format: { sec = 1234567890, usec = 123456 } ...
70
+ const match = output.match(/sec\s*=\s*(\d+)/);
71
+ if (match) {
72
+ const bootTime = match[1];
73
+ cachedBootId = `${hostname()}-${bootTime}`;
74
+ return cachedBootId;
75
+ }
76
+ }
77
+ catch {
78
+ // Fall through to fallback
79
+ }
80
+ }
81
+ // Fallback: use process start time as a rough approximation
82
+ // This is less reliable but better than nothing
83
+ const startTime = Date.now() - (process.uptime() * 1000);
84
+ cachedBootId = `${hostname()}-${Math.floor(startTime / 1000)}`;
85
+ return cachedBootId;
86
+ }
87
+ /**
88
+ * Checks if a process with the given PID is currently running.
89
+ *
90
+ * Uses process.kill(pid, 0) which checks for process existence
91
+ * without actually sending a signal.
92
+ *
93
+ * @param pid - The process ID to check
94
+ * @returns true if the process is running, false otherwise
95
+ */
96
+ export function isPidRunning(pid) {
97
+ try {
98
+ // Signal 0 doesn't send anything, just checks if process exists
99
+ process.kill(pid, 0);
100
+ return true;
101
+ }
102
+ catch (error) {
103
+ // ESRCH = no such process, EPERM = exists but no permission
104
+ if (error instanceof Error && 'code' in error) {
105
+ const code = error.code;
106
+ if (code === 'EPERM') {
107
+ // Process exists but we don't have permission to signal it
108
+ return true;
109
+ }
110
+ }
111
+ return false;
112
+ }
113
+ }
114
+ /**
115
+ * Determines if a lock is stale and can be reclaimed.
116
+ *
117
+ * A lock is considered stale if:
118
+ * - The holding process is no longer running, OR
119
+ * - The boot_id differs from the current boot (system has rebooted)
120
+ *
121
+ * @param lock - The lock information to check
122
+ * @returns true if the lock is stale and can be reclaimed
123
+ */
124
+ export function isLockStale(lock) {
125
+ // If boot_id differs, the system has rebooted - lock is definitely stale
126
+ if (lock.boot_id !== getBootId()) {
127
+ return true;
128
+ }
129
+ // Same boot session - check if the process is still running
130
+ return !isPidRunning(lock.pid);
131
+ }
132
+ /**
133
+ * Validates that a parsed value is a valid LockInfo object.
134
+ *
135
+ * @param lockPath - Path to the lock file (for error messages)
136
+ * @param value - The parsed JSON value to validate
137
+ * @throws {LockCorruptError} If the value is not a valid LockInfo
138
+ */
139
+ function validateLockShape(lockPath, value) {
140
+ const remediation = `Delete the lock file at ${lockPath} and retry.`;
141
+ // Must be an object (not null, not array)
142
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
143
+ throw new LockCorruptError(`Lock file has invalid structure (expected object, got ${value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value}). ${remediation}`, lockPath);
144
+ }
145
+ const obj = value;
146
+ // pid must be integer > 0
147
+ if (typeof obj.pid !== 'number' || !Number.isInteger(obj.pid) || obj.pid <= 0) {
148
+ throw new LockCorruptError(`Lock file has invalid pid (expected integer > 0, got ${JSON.stringify(obj.pid)}). ${remediation}`, lockPath);
149
+ }
150
+ // started_at must be non-empty string
151
+ if (typeof obj.started_at !== 'string' || obj.started_at === '') {
152
+ throw new LockCorruptError(`Lock file has invalid started_at (expected non-empty string, got ${JSON.stringify(obj.started_at)}). ${remediation}`, lockPath);
153
+ }
154
+ // boot_id must be non-empty string
155
+ if (typeof obj.boot_id !== 'string' || obj.boot_id === '') {
156
+ throw new LockCorruptError(`Lock file has invalid boot_id (expected non-empty string, got ${JSON.stringify(obj.boot_id)}). ${remediation}`, lockPath);
157
+ }
158
+ }
159
+ /**
160
+ * Acquires a lock by creating a lock file atomically.
161
+ *
162
+ * If a lock already exists:
163
+ * - If stale (process dead or different boot), reclaims it with a warning
164
+ * - If held by an active process, throws LockHeldError
165
+ *
166
+ * @param lockPath - Path to the lock file
167
+ * @returns The lock information that was written
168
+ * @throws {LockHeldError} If the lock is held by another active process
169
+ * @throws {LockCorruptError} If the lock file is corrupt or malformed
170
+ * @throws {AtomicFsError} If the lock file cannot be written
171
+ */
172
+ export async function acquireLock(lockPath) {
173
+ // Check if lock file exists
174
+ let existingLock = null;
175
+ try {
176
+ const rawValue = await atomicReadJson(lockPath);
177
+ validateLockShape(lockPath, rawValue);
178
+ existingLock = rawValue;
179
+ }
180
+ catch (error) {
181
+ // Re-throw LockCorruptError as-is
182
+ if (error instanceof LockCorruptError) {
183
+ throw error;
184
+ }
185
+ if (error instanceof AtomicFsError) {
186
+ const cause = error.cause;
187
+ // File doesn't exist - no lock, proceed
188
+ if (cause && 'code' in cause && cause.code === 'ENOENT') {
189
+ // No lock file exists
190
+ }
191
+ else if (cause instanceof SyntaxError) {
192
+ // JSON parse error - corrupt lock file
193
+ const remediation = `Delete the lock file at ${lockPath} and retry.`;
194
+ throw new LockCorruptError(`Lock file contains invalid JSON. ${remediation}`, lockPath);
195
+ }
196
+ else {
197
+ // Real error (I/O issue, permission denied, etc.)
198
+ throw error;
199
+ }
200
+ }
201
+ else {
202
+ // Unknown error, rethrow
203
+ throw error;
204
+ }
205
+ }
206
+ if (existingLock) {
207
+ if (isLockStale(existingLock)) {
208
+ // Lock is stale - log warning and reclaim
209
+ console.warn(`Reclaiming stale lock from PID ${existingLock.pid} (started_at: ${existingLock.started_at}, boot_id: ${existingLock.boot_id})`);
210
+ }
211
+ else {
212
+ // Lock is held by an active process
213
+ throw new LockHeldError(`Lock is held by PID ${existingLock.pid} (started_at: ${existingLock.started_at})`, lockPath, existingLock);
214
+ }
215
+ }
216
+ // Create new lock
217
+ const lockInfo = {
218
+ pid: process.pid,
219
+ started_at: new Date().toISOString(),
220
+ boot_id: getBootId(),
221
+ };
222
+ await atomicWriteJson(lockPath, lockInfo);
223
+ return lockInfo;
224
+ }
225
+ /**
226
+ * Releases a lock by deleting the lock file.
227
+ *
228
+ * Only releases the lock if it belongs to the current process.
229
+ * Silently succeeds if the lock doesn't exist or belongs to another process.
230
+ *
231
+ * @param lockPath - Path to the lock file
232
+ */
233
+ export async function releaseLock(lockPath) {
234
+ try {
235
+ // Read current lock to verify ownership
236
+ const lock = await atomicReadJson(lockPath);
237
+ // Only delete if we own the lock
238
+ if (lock.pid === process.pid && lock.boot_id === getBootId()) {
239
+ await unlink(lockPath);
240
+ }
241
+ }
242
+ catch (error) {
243
+ // Lock doesn't exist or can't be read - nothing to release
244
+ if (error instanceof AtomicFsError) {
245
+ const cause = error.cause;
246
+ if (cause && 'code' in cause && cause.code === 'ENOENT') {
247
+ // File doesn't exist - already released
248
+ return;
249
+ }
250
+ }
251
+ // For other errors, also silently succeed - lock cleanup shouldn't fail the process
252
+ }
253
+ }
254
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/lib/lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { EnvoiConfig } from '../types/config.js';
2
+ interface MigrationResult {
3
+ config: EnvoiConfig;
4
+ configPath: string;
5
+ migrated: boolean;
6
+ }
7
+ export declare function migrateLegacyLayoutIfNeeded(configPath: string, config: EnvoiConfig): Promise<MigrationResult>;
8
+ export {};
9
+ //# sourceMappingURL=migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/lib/migration.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAStD,UAAU,eAAe;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAkDD,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,eAAe,CAAC,CA0B1B"}
@@ -0,0 +1,74 @@
1
+ import { access, rename } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { atomicWriteJson } from './fs.js';
4
+ import { CONFIG_FILE_NAME, LEGACY_WORKSPACE_DIR_NAME, PRODUCT_NAME, WORKSPACE_DIR_NAME, isLegacyConfigPath, } from './branding.js';
5
+ async function exists(path) {
6
+ try {
7
+ await access(path);
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ function rewriteString(value, oldWorkspace, newWorkspace) {
15
+ if (value === oldWorkspace)
16
+ return newWorkspace;
17
+ if (value.startsWith(`${oldWorkspace}/`)) {
18
+ return `${newWorkspace}/${value.slice(oldWorkspace.length + 1)}`;
19
+ }
20
+ if (value.startsWith('relais/')) {
21
+ return `envoi/${value.slice('relais/'.length)}`;
22
+ }
23
+ return value.replace(/\brelais\//g, 'envoi/');
24
+ }
25
+ function rewriteValue(value, oldWorkspace, newWorkspace) {
26
+ if (typeof value === 'string') {
27
+ return rewriteString(value, oldWorkspace, newWorkspace);
28
+ }
29
+ if (Array.isArray(value)) {
30
+ return value.map((entry) => rewriteValue(entry, oldWorkspace, newWorkspace));
31
+ }
32
+ if (value && typeof value === 'object') {
33
+ const output = {};
34
+ for (const [key, entry] of Object.entries(value)) {
35
+ output[key] = rewriteValue(entry, oldWorkspace, newWorkspace);
36
+ }
37
+ return output;
38
+ }
39
+ return value;
40
+ }
41
+ function buildMigratedConfig(config) {
42
+ const oldWorkspace = config.workspace_dir || LEGACY_WORKSPACE_DIR_NAME;
43
+ const newWorkspace = oldWorkspace === LEGACY_WORKSPACE_DIR_NAME ? WORKSPACE_DIR_NAME : oldWorkspace;
44
+ const rewritten = rewriteValue(config, oldWorkspace, newWorkspace);
45
+ return {
46
+ ...rewritten,
47
+ product_name: PRODUCT_NAME.toLowerCase(),
48
+ workspace_dir: newWorkspace,
49
+ };
50
+ }
51
+ export async function migrateLegacyLayoutIfNeeded(configPath, config) {
52
+ if (!isLegacyConfigPath(configPath)) {
53
+ return { config, configPath, migrated: false };
54
+ }
55
+ const repoRoot = dirname(configPath);
56
+ const targetConfigPath = join(repoRoot, CONFIG_FILE_NAME);
57
+ const migratedConfig = buildMigratedConfig(config);
58
+ if (config.workspace_dir === LEGACY_WORKSPACE_DIR_NAME) {
59
+ const legacyWorkspacePath = join(repoRoot, LEGACY_WORKSPACE_DIR_NAME);
60
+ const newWorkspacePath = join(repoRoot, WORKSPACE_DIR_NAME);
61
+ if ((await exists(legacyWorkspacePath)) && !(await exists(newWorkspacePath))) {
62
+ await rename(legacyWorkspacePath, newWorkspacePath);
63
+ }
64
+ }
65
+ if (!(await exists(targetConfigPath))) {
66
+ await atomicWriteJson(targetConfigPath, migratedConfig);
67
+ }
68
+ return {
69
+ config: migratedConfig,
70
+ configPath: targetConfigPath,
71
+ migrated: true,
72
+ };
73
+ }
74
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.js","sourceRoot":"","sources":["../../src/lib/migration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Path helpers for workspace-relative files.
3
+ *
4
+ * Config paths may be expressed either:
5
+ * - relative to `workspace_dir` (e.g. "prompts/orchestrator.user.txt"), OR
6
+ * - already prefixed with `workspace_dir` (e.g. "envoi/prompts/orchestrator.user.txt")
7
+ *
8
+ * We support both forms for backwards compatibility.
9
+ */
10
+ /**
11
+ * Resolves a config path into a filesystem path.
12
+ *
13
+ * If `p` is absolute, returns it as-is.
14
+ * If `p` already starts with `${workspaceDir}/`, returns it as-is.
15
+ * Otherwise returns `join(workspaceDir, p)`.
16
+ */
17
+ export declare function resolveInWorkspace(workspaceDir: string, p: string): string;
18
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAO1E"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Path helpers for workspace-relative files.
3
+ *
4
+ * Config paths may be expressed either:
5
+ * - relative to `workspace_dir` (e.g. "prompts/orchestrator.user.txt"), OR
6
+ * - already prefixed with `workspace_dir` (e.g. "envoi/prompts/orchestrator.user.txt")
7
+ *
8
+ * We support both forms for backwards compatibility.
9
+ */
10
+ import { isAbsolute, join } from 'node:path';
11
+ /**
12
+ * Resolves a config path into a filesystem path.
13
+ *
14
+ * If `p` is absolute, returns it as-is.
15
+ * If `p` already starts with `${workspaceDir}/`, returns it as-is.
16
+ * Otherwise returns `join(workspaceDir, p)`.
17
+ */
18
+ export function resolveInWorkspace(workspaceDir, p) {
19
+ if (isAbsolute(p))
20
+ return p;
21
+ const normalized = p.replace(/^[.][/]/, '');
22
+ if (normalized === workspaceDir || normalized.startsWith(`${workspaceDir}/`)) {
23
+ return normalized;
24
+ }
25
+ return join(workspaceDir, normalized);
26
+ }
27
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,YAAoB,EAAE,CAAS;IAChE,IAAI,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAC5B,MAAM,UAAU,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC5C,IAAI,UAAU,KAAK,YAAY,IAAI,UAAU,CAAC,UAAU,CAAC,GAAG,YAAY,GAAG,CAAC,EAAE,CAAC;QAC7E,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,OAAO,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Preflight checks for determining if a tick can safely start.
3
+ *
4
+ * Runs a series of checks and returns a PreflightResult indicating
5
+ * whether execution can proceed or is blocked.
6
+ */
7
+ import type { EnvoiConfig } from '../types/config.js';
8
+ import type { PreflightResult } from '../types/preflight.js';
9
+ /**
10
+ * Runs all preflight checks to determine if a tick can safely start.
11
+ *
12
+ * Checks are run in order, returning immediately on BLOCKED:
13
+ * 1. Git repo exists (if config.runner.require_git)
14
+ * 2. Worktree is clean (no uncommitted changes)
15
+ * 3. Cleanup .tmp files under config.workspace_dir
16
+ * 4. Check history size (vs config.history.max_mb)
17
+ * 5. Budget check (placeholder - returns warning for now)
18
+ *
19
+ * @param config - The Envoi configuration
20
+ * @returns PreflightResult indicating success or blocked state
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const result = await runPreflight(config);
25
+ * if (!result.ok) {
26
+ * console.error(`Blocked: ${result.blocked_code}: ${result.blocked_reason}`);
27
+ * process.exit(1);
28
+ * }
29
+ * console.log(`Preflight passed, base commit: ${result.base_commit}`);
30
+ * ```
31
+ */
32
+ export declare function runPreflight(config: EnvoiConfig): Promise<PreflightResult>;
33
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/lib/preflight.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,uBAAuB,CAAC;AAuE1E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CA2HhF"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Preflight checks for determining if a tick can safely start.
3
+ *
4
+ * Runs a series of checks and returns a PreflightResult indicating
5
+ * whether execution can proceed or is blocked.
6
+ */
7
+ import { stat, readdir } from 'node:fs/promises';
8
+ import { join } from 'node:path';
9
+ import { cleanupTmpFiles, isGlobPatternSafe } from './fs.js';
10
+ import { isGitRepo, isWorktreeCleanExcluding, getHeadCommit } from './git.js';
11
+ import { readWorkspaceState } from './workspace_state.js';
12
+ import { resolveInWorkspace } from './paths.js';
13
+ /**
14
+ * Creates a blocked PreflightResult with the given code and reason.
15
+ */
16
+ function blocked(code, reason, warnings = []) {
17
+ return {
18
+ ok: false,
19
+ blocked_code: code,
20
+ blocked_reason: reason,
21
+ warnings,
22
+ base_commit: null,
23
+ };
24
+ }
25
+ /**
26
+ * Creates a successful PreflightResult.
27
+ */
28
+ function success(baseCommit, warnings = []) {
29
+ return {
30
+ ok: true,
31
+ blocked_code: null,
32
+ blocked_reason: null,
33
+ warnings,
34
+ base_commit: baseCommit,
35
+ };
36
+ }
37
+ /**
38
+ * Calculates the total size of a directory in bytes.
39
+ *
40
+ * @param dirPath - Path to the directory
41
+ * @returns Total size in bytes, or 0 if directory doesn't exist
42
+ */
43
+ async function getDirectorySize(dirPath) {
44
+ let totalSize = 0;
45
+ try {
46
+ const entries = await readdir(dirPath, { withFileTypes: true });
47
+ for (const entry of entries) {
48
+ const entryPath = join(dirPath, entry.name);
49
+ if (entry.isFile()) {
50
+ try {
51
+ const stats = await stat(entryPath);
52
+ totalSize += stats.size;
53
+ }
54
+ catch {
55
+ // Skip files we can't stat
56
+ }
57
+ }
58
+ else if (entry.isDirectory()) {
59
+ // Recursively calculate subdirectory size
60
+ totalSize += await getDirectorySize(entryPath);
61
+ }
62
+ }
63
+ }
64
+ catch {
65
+ // Directory doesn't exist or can't be read
66
+ return 0;
67
+ }
68
+ return totalSize;
69
+ }
70
+ /**
71
+ * Runs all preflight checks to determine if a tick can safely start.
72
+ *
73
+ * Checks are run in order, returning immediately on BLOCKED:
74
+ * 1. Git repo exists (if config.runner.require_git)
75
+ * 2. Worktree is clean (no uncommitted changes)
76
+ * 3. Cleanup .tmp files under config.workspace_dir
77
+ * 4. Check history size (vs config.history.max_mb)
78
+ * 5. Budget check (placeholder - returns warning for now)
79
+ *
80
+ * @param config - The Envoi configuration
81
+ * @returns PreflightResult indicating success or blocked state
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const result = await runPreflight(config);
86
+ * if (!result.ok) {
87
+ * console.error(`Blocked: ${result.blocked_code}: ${result.blocked_reason}`);
88
+ * process.exit(1);
89
+ * }
90
+ * console.log(`Preflight passed, base commit: ${result.base_commit}`);
91
+ * ```
92
+ */
93
+ export async function runPreflight(config) {
94
+ const warnings = [];
95
+ let baseCommit = null;
96
+ // 1. Check if git repo exists (if required)
97
+ if (config.runner.require_git) {
98
+ if (!isGitRepo()) {
99
+ return blocked('BLOCKED_MISSING_CONFIG', 'Not inside a git repository (require_git is enabled)');
100
+ }
101
+ // 2. Check if worktree is clean (excluding runner-owned files)
102
+ const worktreeStatus = isWorktreeCleanExcluding(config.runner.runner_owned_globs);
103
+ if (!worktreeStatus.clean) {
104
+ return blocked('BLOCKED_DIRTY_WORKTREE', `Git worktree has uncommitted changes: ${worktreeStatus.dirtyFiles.join(', ')}`);
105
+ }
106
+ // Get base commit for tracking changes
107
+ try {
108
+ baseCommit = getHeadCommit();
109
+ }
110
+ catch (error) {
111
+ return blocked('BLOCKED_MISSING_CONFIG', `Failed to get HEAD commit: ${error instanceof Error ? error.message : String(error)}`);
112
+ }
113
+ }
114
+ // 3. Validate delete_tmp_glob pattern if configured
115
+ const deleteGlob = config.runner.crash_cleanup?.delete_tmp_glob;
116
+ if (deleteGlob && deleteGlob.trim() !== '') {
117
+ const globSafety = isGlobPatternSafe(deleteGlob);
118
+ if (!globSafety.safe) {
119
+ return blocked('BLOCKED_CRASH_RECOVERY_REQUIRED', `Unsafe delete_tmp_glob pattern: ${globSafety.reason}`);
120
+ }
121
+ }
122
+ // 4. Cleanup .tmp files (crash artifacts)
123
+ try {
124
+ const deleted = await cleanupTmpFiles(config.workspace_dir);
125
+ if (deleted.length > 0) {
126
+ warnings.push(`Cleaned up ${deleted.length} stale .tmp file(s): ${deleted.join(', ')}`);
127
+ }
128
+ }
129
+ catch (error) {
130
+ // If cleanup fails, it might indicate a crash recovery issue
131
+ // For now, just add a warning - the directory might not exist yet
132
+ warnings.push(`Could not cleanup tmp files in ${config.workspace_dir}: ${error instanceof Error ? error.message : String(error)}`);
133
+ }
134
+ // 4. Check history size vs cap
135
+ if (config.history.enabled) {
136
+ const historyPath = resolveInWorkspace(config.workspace_dir, config.history.dir);
137
+ const historySizeBytes = await getDirectorySize(historyPath);
138
+ const historySizeMb = historySizeBytes / (1024 * 1024);
139
+ const maxMb = config.history.max_mb;
140
+ if (historySizeMb >= maxMb) {
141
+ return blocked('BLOCKED_HISTORY_CAP_CLEANUP_REQUIRED', `History directory (${historySizeMb.toFixed(2)} MB) exceeds cap (${maxMb} MB). Manual cleanup required.`);
142
+ }
143
+ // Warn if approaching limit (>80%)
144
+ const warnThreshold = maxMb * 0.8;
145
+ if (historySizeMb >= warnThreshold) {
146
+ warnings.push(`History size (${historySizeMb.toFixed(2)} MB) is approaching cap (${maxMb} MB)`);
147
+ }
148
+ }
149
+ // 5. Budget hard-cap check
150
+ // Read workspace state and check if any budget exceeds its per-milestone cap
151
+ try {
152
+ const state = await readWorkspaceState(config.workspace_dir);
153
+ const caps = config.budgets.per_milestone;
154
+ if (state.budgets.ticks >= caps.max_ticks) {
155
+ return blocked('BLOCKED_BUDGET_CAP', `Tick budget exceeded: ${state.budgets.ticks} >= ${caps.max_ticks}`);
156
+ }
157
+ if (state.budgets.orchestrator_calls >= caps.max_orchestrator_calls) {
158
+ return blocked('BLOCKED_BUDGET_CAP', `Orchestrator call budget exceeded: ${state.budgets.orchestrator_calls} >= ${caps.max_orchestrator_calls}`);
159
+ }
160
+ if (state.budgets.builder_calls >= caps.max_builder_calls) {
161
+ return blocked('BLOCKED_BUDGET_CAP', `Builder call budget exceeded: ${state.budgets.builder_calls} >= ${caps.max_builder_calls}`);
162
+ }
163
+ if (state.budgets.verify_runs >= caps.max_verify_runs) {
164
+ return blocked('BLOCKED_BUDGET_CAP', `Verify run budget exceeded: ${state.budgets.verify_runs} >= ${caps.max_verify_runs}`);
165
+ }
166
+ // Add warning if approaching any limit
167
+ if (state.budget_warning) {
168
+ warnings.push('Budget warning: approaching limit on one or more budget categories');
169
+ }
170
+ }
171
+ catch (error) {
172
+ // If we can't read state, add warning but don't block
173
+ warnings.push(`Could not read workspace state for budget check: ${error instanceof Error ? error.message : String(error)}`);
174
+ }
175
+ return success(baseCommit, warnings);
176
+ }
177
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/lib/preflight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Prompt budgeting helpers to keep context payloads bounded and deterministic.
3
+ */
4
+ export interface TruncatePromptSectionResult {
5
+ text: string;
6
+ truncated: boolean;
7
+ originalChars: number;
8
+ }
9
+ /**
10
+ * Truncates a prompt section to a max size with a deterministic marker.
11
+ *
12
+ * Strategy:
13
+ * - keep the beginning (usually most structural context)
14
+ * - keep the tail (usually latest status/error detail)
15
+ * - insert a deterministic marker between both
16
+ */
17
+ export declare function truncatePromptSection(sectionName: string, value: string, maxChars: number): TruncatePromptSectionResult;
18
+ //# sourceMappingURL=prompt_budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt_budget.d.ts","sourceRoot":"","sources":["../../src/lib/prompt_budget.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,2BAA2B,CA0B7B"}