@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,501 @@
1
+ /**
2
+ * Judge phase: Compute touched files from git diff.
3
+ *
4
+ * Provides functions to analyze git changes and categorize files
5
+ * for scope guardrail enforcement.
6
+ */
7
+ import { execSync } from 'node:child_process';
8
+ import micromatch from 'micromatch';
9
+ /**
10
+ * Parses git diff --name-status output into categorized file lists.
11
+ *
12
+ * Git diff --name-status format:
13
+ * - M <path> - Modified
14
+ * - A <path> - Added
15
+ * - D <path> - Deleted
16
+ * - R<score> <old> <new> - Renamed (tab-separated, e.g., R100\told\tnew)
17
+ *
18
+ * This is a pure function that can be easily tested.
19
+ *
20
+ * @param output - Raw output from `git diff --name-status <base>...HEAD`
21
+ * @returns Object with categorized file lists
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const diffOutput = 'M\tfile1.ts\nA\tfile2.ts\nR100\told.ts\tnew.ts\n';
26
+ * const result = parseGitDiffNameStatus(diffOutput);
27
+ * // result.modified = ['file1.ts']
28
+ * // result.added = ['file2.ts']
29
+ * // result.renamed = [{ from: 'old.ts', to: 'new.ts' }]
30
+ * ```
31
+ */
32
+ export function parseGitDiffNameStatus(output) {
33
+ const modified = [];
34
+ const added = [];
35
+ const deleted = [];
36
+ const renamed = [];
37
+ const trimmed = output.trim();
38
+ if (trimmed === '') {
39
+ return { modified, added, deleted, renamed };
40
+ }
41
+ const lines = trimmed.split('\n').filter((line) => line.length > 0);
42
+ for (const line of lines) {
43
+ // Handle renamed files: R<score>\t<old>\t<new>
44
+ // The similarity score can be 0-100, so we check if line starts with R
45
+ if (line.startsWith('R')) {
46
+ // Split by tab to get: [R<score>, old, new]
47
+ const parts = line.split('\t');
48
+ if (parts.length >= 3) {
49
+ const from = parts[1];
50
+ const to = parts[2];
51
+ renamed.push({ from, to });
52
+ }
53
+ continue;
54
+ }
55
+ // For other status codes, format is: <code>\t<path>
56
+ const parts = line.split('\t');
57
+ if (parts.length < 2) {
58
+ continue;
59
+ }
60
+ const status = parts[0];
61
+ const path = parts[1];
62
+ switch (status) {
63
+ case 'M':
64
+ modified.push(path);
65
+ break;
66
+ case 'A':
67
+ added.push(path);
68
+ break;
69
+ case 'D':
70
+ deleted.push(path);
71
+ break;
72
+ // Ignore other status codes (C for copy, etc.)
73
+ }
74
+ }
75
+ return { modified, added, deleted, renamed };
76
+ }
77
+ /**
78
+ * Gets all files touched since a base commit using git diff --name-status
79
+ * and git status --porcelain.
80
+ *
81
+ * Combines:
82
+ * - Tracked file changes (modified, added, deleted, renamed) from git diff
83
+ * - Untracked files from git status
84
+ *
85
+ * @param baseCommit - The base commit SHA to diff against (e.g., 'main' or commit hash)
86
+ * @returns TouchedFiles object with categorized file lists
87
+ * @throws {Error} If git commands fail
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const touched = getTouchedFiles('main');
92
+ * console.log(`Modified: ${touched.modified.length}`);
93
+ * console.log(`All files: ${touched.all.join(', ')}`);
94
+ * ```
95
+ */
96
+ export function getTouchedFiles(baseCommit) {
97
+ // Get tracked file changes from git diff
98
+ let diffStatus;
99
+ try {
100
+ const diffOutput = execSync(`git diff --name-status ${baseCommit}...HEAD`, {
101
+ encoding: 'utf-8',
102
+ stdio: ['pipe', 'pipe', 'pipe'],
103
+ });
104
+ diffStatus = parseGitDiffNameStatus(diffOutput);
105
+ }
106
+ catch (error) {
107
+ throw new Error(`Failed to get git diff from ${baseCommit}: ${error instanceof Error ? error.message : String(error)}`);
108
+ }
109
+ // Get untracked files from git status
110
+ let untracked = [];
111
+ try {
112
+ const statusOutput = execSync('git status --porcelain', {
113
+ encoding: 'utf-8',
114
+ stdio: ['pipe', 'pipe', 'pipe'],
115
+ });
116
+ // Untracked files have ?? prefix
117
+ untracked = statusOutput
118
+ .trim()
119
+ .split('\n')
120
+ .filter((line) => line.startsWith('??'))
121
+ .map((line) => line.substring(3).trim()); // Remove "?? " prefix
122
+ }
123
+ catch (error) {
124
+ // If git status fails, continue without untracked files
125
+ // (this is less critical than diff failure)
126
+ }
127
+ // Compute union of all file paths (excluding deleted files)
128
+ const all = [
129
+ ...diffStatus.modified,
130
+ ...diffStatus.added,
131
+ ...diffStatus.renamed.map((r) => r.to),
132
+ ...untracked,
133
+ ];
134
+ return {
135
+ modified: diffStatus.modified,
136
+ added: diffStatus.added,
137
+ deleted: diffStatus.deleted,
138
+ renamed: diffStatus.renamed,
139
+ untracked,
140
+ all,
141
+ };
142
+ }
143
+ /**
144
+ * Checks if a path matches any of the given glob patterns.
145
+ *
146
+ * @param path - The file path to check
147
+ * @param patterns - Array of glob patterns to match against
148
+ * @returns True if path matches any pattern, false otherwise
149
+ */
150
+ function matchesGlob(path, patterns) {
151
+ if (patterns.length === 0) {
152
+ return false;
153
+ }
154
+ return micromatch.isMatch(path, patterns);
155
+ }
156
+ /**
157
+ * Checks if a path is a lockfile based on scopeConfig.lockfiles list.
158
+ *
159
+ * @param path - The file path to check
160
+ * @param lockfiles - Array of lockfile names/patterns
161
+ * @returns True if path matches any lockfile pattern
162
+ */
163
+ function isLockfile(path, lockfiles) {
164
+ if (lockfiles.length === 0) {
165
+ return false;
166
+ }
167
+ // Check if path ends with any lockfile name, or matches lockfile globs
168
+ return lockfiles.some((lockfile) => {
169
+ // If lockfile is a simple name (e.g., "package-lock.json"), check if path ends with it
170
+ if (!lockfile.includes('/') && !lockfile.includes('*')) {
171
+ return path.endsWith(lockfile);
172
+ }
173
+ // Otherwise, treat as glob pattern
174
+ return micromatch.isMatch(path, [lockfile]);
175
+ });
176
+ }
177
+ /**
178
+ * Checks touched files against scope rules and returns first violation or success.
179
+ *
180
+ * Checks are performed in priority order:
181
+ * 1. Runner-owned mutation (highest priority)
182
+ * 2. Forbidden globs
183
+ * 3. Outside allowed globs
184
+ * 4. New file when not allowed
185
+ * 5. Lockfile change when not allowed
186
+ *
187
+ * Returns the first violation found, or success if no violations.
188
+ *
189
+ * @param touched - TouchedFiles object with categorized file lists
190
+ * @param taskScope - Task scope configuration with allowed/forbidden globs and permissions
191
+ * @param scopeConfig - Scope configuration with lockfiles list
192
+ * @param runnerOwnedGlobs - Glob patterns for files owned by the runner
193
+ * @returns ScopeCheckResult with stopCode and violatingFiles
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * const result = checkScopeViolations(
198
+ * { modified: ['src/utils.ts'], added: ['src/new.ts'], deleted: [], renamed: [], untracked: ['src/new.ts'], all: ['src/utils.ts', 'src/new.ts'] },
199
+ * { allowed_globs: ['src/**'], forbidden_globs: ['*.key'], allow_new_files: false, allow_lockfile_changes: true },
200
+ * { lockfiles: ['package-lock.json'], default_allowed_globs: [], default_forbidden_globs: [], default_allow_new_files: true, default_allow_lockfile_changes: false },
201
+ * ['pilot/**']
202
+ * );
203
+ * if (!result.ok) {
204
+ * console.error(`Violation: ${result.stopCode}`, result.violatingFiles);
205
+ * }
206
+ * ```
207
+ */
208
+ export function checkScopeViolations(touched, taskScope, scopeConfig, runnerOwnedGlobs) {
209
+ // Get all touched paths (excluding deleted files)
210
+ const allPaths = touched.all;
211
+ const untrackedSet = new Set(touched.untracked);
212
+ const newFilesSet = new Set([...touched.added, ...touched.untracked, ...touched.renamed.map((r) => r.to)]);
213
+ // Check 1: Runner-owned mutation (highest priority)
214
+ const runnerOwnedViolations = [];
215
+ for (const path of allPaths) {
216
+ if (matchesGlob(path, runnerOwnedGlobs)) {
217
+ runnerOwnedViolations.push(path);
218
+ }
219
+ }
220
+ if (runnerOwnedViolations.length > 0) {
221
+ return {
222
+ ok: false,
223
+ stopCode: 'STOP_RUNNER_OWNED_MUTATION',
224
+ violatingFiles: runnerOwnedViolations,
225
+ reason: `Files match runner-owned globs: ${runnerOwnedViolations.join(', ')}`,
226
+ };
227
+ }
228
+ // Check 2: Forbidden globs
229
+ const forbiddenViolations = [];
230
+ for (const path of allPaths) {
231
+ if (matchesGlob(path, taskScope.forbidden_globs)) {
232
+ forbiddenViolations.push(path);
233
+ }
234
+ }
235
+ if (forbiddenViolations.length > 0) {
236
+ return {
237
+ ok: false,
238
+ stopCode: 'STOP_SCOPE_VIOLATION_FORBIDDEN',
239
+ violatingFiles: forbiddenViolations,
240
+ reason: `Files match forbidden glob patterns: ${forbiddenViolations.join(', ')}`,
241
+ };
242
+ }
243
+ // Check 3: Outside allowed globs
244
+ if (taskScope.allowed_globs.length > 0) {
245
+ const outsideAllowedViolations = [];
246
+ for (const path of allPaths) {
247
+ if (!matchesGlob(path, taskScope.allowed_globs)) {
248
+ outsideAllowedViolations.push(path);
249
+ }
250
+ }
251
+ if (outsideAllowedViolations.length > 0) {
252
+ return {
253
+ ok: false,
254
+ stopCode: 'STOP_SCOPE_VIOLATION_OUTSIDE_ALLOWED',
255
+ violatingFiles: outsideAllowedViolations,
256
+ reason: `Files do not match any allowed glob pattern: ${outsideAllowedViolations.join(', ')}`,
257
+ };
258
+ }
259
+ }
260
+ // Check 4: New file when not allowed
261
+ if (!taskScope.allow_new_files) {
262
+ const newFileViolations = [];
263
+ for (const path of allPaths) {
264
+ if (newFilesSet.has(path)) {
265
+ newFileViolations.push(path);
266
+ }
267
+ }
268
+ if (newFileViolations.length > 0) {
269
+ return {
270
+ ok: false,
271
+ stopCode: 'STOP_SCOPE_VIOLATION_NEW_FILE',
272
+ violatingFiles: newFileViolations,
273
+ reason: `New files created but allow_new_files is false: ${newFileViolations.join(', ')}`,
274
+ };
275
+ }
276
+ }
277
+ // Check 5: Lockfile change when not allowed
278
+ if (!taskScope.allow_lockfile_changes) {
279
+ const lockfileViolations = [];
280
+ for (const path of allPaths) {
281
+ if (isLockfile(path, scopeConfig.lockfiles)) {
282
+ lockfileViolations.push(path);
283
+ }
284
+ }
285
+ if (lockfileViolations.length > 0) {
286
+ return {
287
+ ok: false,
288
+ stopCode: 'STOP_LOCKFILE_CHANGE_FORBIDDEN',
289
+ violatingFiles: lockfileViolations,
290
+ reason: `Lockfiles modified but allow_lockfile_changes is false: ${lockfileViolations.join(', ')}`,
291
+ };
292
+ }
293
+ }
294
+ // No violations found
295
+ return {
296
+ ok: true,
297
+ stopCode: null,
298
+ violatingFiles: [],
299
+ reason: null,
300
+ };
301
+ }
302
+ /**
303
+ * Parses git diff --stat output to extract line counts.
304
+ *
305
+ * Git diff --stat format:
306
+ * - Individual file lines: " file.ts | N +++++" or " file.ts | N +++---"
307
+ * - Summary line: "N files changed, M insertions(+), K deletions(-)"
308
+ *
309
+ * This function extracts the summary line totals.
310
+ *
311
+ * @param output - Raw output from `git diff --stat <base>...HEAD`
312
+ * @returns Object with linesAdded and linesDeleted totals
313
+ *
314
+ * @example
315
+ * ```typescript
316
+ * const diffStat = ' file1.ts | 5 +++++\n file2.ts | 3 ---\n 2 files changed, 5 insertions(+), 3 deletions(-)';
317
+ * const result = parseGitDiffStat(diffStat);
318
+ * // result.linesAdded = 5
319
+ * // result.linesDeleted = 3
320
+ * ```
321
+ */
322
+ export function parseGitDiffStat(output) {
323
+ const trimmed = output.trim();
324
+ if (trimmed === '') {
325
+ return { linesAdded: 0, linesDeleted: 0 };
326
+ }
327
+ const lines = trimmed.split('\n');
328
+ // The summary line is typically the last line
329
+ // Format: "N files changed, M insertions(+), K deletions(-)"
330
+ const summaryLine = lines[lines.length - 1];
331
+ // Match pattern: "N files changed, M insertions(+), K deletions(-)"
332
+ // Or: "N files changed, M insertions(+)" (no deletions)
333
+ // Or: "N files changed, K deletions(-)" (no insertions)
334
+ const filesChangedMatch = summaryLine.match(/(\d+)\s+files?\s+changed/);
335
+ if (!filesChangedMatch) {
336
+ // If no summary line found, return zeros
337
+ return { linesAdded: 0, linesDeleted: 0 };
338
+ }
339
+ // Extract insertions
340
+ const insertionsMatch = summaryLine.match(/(\d+)\s+insertions?\(\+\)/);
341
+ const linesAdded = insertionsMatch ? parseInt(insertionsMatch[1], 10) : 0;
342
+ // Extract deletions
343
+ const deletionsMatch = summaryLine.match(/(\d+)\s+deletions?\(-\)/);
344
+ const linesDeleted = deletionsMatch ? parseInt(deletionsMatch[1], 10) : 0;
345
+ return { linesAdded, linesDeleted };
346
+ }
347
+ /**
348
+ * Computes blast radius metrics from git diff.
349
+ *
350
+ * Uses git diff --stat to get line counts and TouchedFiles to get file counts.
351
+ *
352
+ * @param baseCommit - The base commit SHA to diff against (e.g., 'main' or commit hash)
353
+ * @param touched - TouchedFiles object with categorized file lists
354
+ * @returns BlastRadius object with file and line counts
355
+ * @throws {Error} If git diff --stat command fails
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * const touched = getTouchedFiles('main');
360
+ * const blastRadius = computeBlastRadius('main', touched);
361
+ * console.log(`Files touched: ${blastRadius.files_touched}`);
362
+ * console.log(`Lines added: ${blastRadius.lines_added}`);
363
+ * ```
364
+ */
365
+ export function computeBlastRadius(baseCommit, touched) {
366
+ // Get line counts from git diff --stat
367
+ let linesAdded = 0;
368
+ let linesDeleted = 0;
369
+ try {
370
+ const diffStatOutput = execSync(`git diff --stat ${baseCommit}...HEAD`, {
371
+ encoding: 'utf-8',
372
+ stdio: ['pipe', 'pipe', 'pipe'],
373
+ });
374
+ const parsed = parseGitDiffStat(diffStatOutput);
375
+ linesAdded = parsed.linesAdded;
376
+ linesDeleted = parsed.linesDeleted;
377
+ }
378
+ catch (error) {
379
+ throw new Error(`Failed to get git diff --stat from ${baseCommit}: ${error instanceof Error ? error.message : String(error)}`);
380
+ }
381
+ // Compute file counts from TouchedFiles
382
+ // files_touched = all unique files (modified + added + renamed.to + untracked)
383
+ const filesTouched = touched.all.length;
384
+ // new_files_count = added + untracked + renamed.to
385
+ const newFilesSet = new Set([
386
+ ...touched.added,
387
+ ...touched.untracked,
388
+ ...touched.renamed.map((r) => r.to),
389
+ ]);
390
+ const newFilesCount = newFilesSet.size;
391
+ return {
392
+ files_touched: filesTouched,
393
+ lines_added: linesAdded,
394
+ lines_deleted: linesDeleted,
395
+ new_files: newFilesCount,
396
+ };
397
+ }
398
+ /**
399
+ * Checks blast radius against configured diff limits.
400
+ *
401
+ * Returns STOP_DIFF_TOO_LARGE if either limit is exceeded:
402
+ * - files_touched > max_files_touched
403
+ * - lines_changed (added + deleted) > max_lines_changed
404
+ *
405
+ * @param blastRadius - Blast radius metrics from computeBlastRadius
406
+ * @param limits - Diff limits configuration
407
+ * @returns DiffCheckResult with ok status and stopCode
408
+ *
409
+ * @example
410
+ * ```typescript
411
+ * const blastRadius = computeBlastRadius('main', touched);
412
+ * const result = checkDiffLimits(blastRadius, { max_files_touched: 10, max_lines_changed: 100 });
413
+ * if (!result.ok) {
414
+ * console.error(`Violation: ${result.stopCode}`, result.reason);
415
+ * }
416
+ * ```
417
+ */
418
+ export function checkDiffLimits(blastRadius, limits) {
419
+ const linesChanged = blastRadius.lines_added + blastRadius.lines_deleted;
420
+ const violations = [];
421
+ if (blastRadius.files_touched > limits.max_files_touched) {
422
+ violations.push(`files_touched (${blastRadius.files_touched}) exceeds max_files_touched (${limits.max_files_touched})`);
423
+ }
424
+ if (linesChanged > limits.max_lines_changed) {
425
+ violations.push(`lines_changed (${linesChanged}) exceeds max_lines_changed (${limits.max_lines_changed})`);
426
+ }
427
+ if (violations.length > 0) {
428
+ return {
429
+ ok: false,
430
+ stopCode: 'STOP_DIFF_TOO_LARGE',
431
+ blastRadius,
432
+ reason: violations.join('; '),
433
+ };
434
+ }
435
+ return {
436
+ ok: true,
437
+ stopCode: null,
438
+ blastRadius,
439
+ reason: null,
440
+ };
441
+ }
442
+ /**
443
+ * Checks if HEAD is still at the expected commit (or a descendant of it).
444
+ *
445
+ * - If HEAD === expectedBaseCommit → ok (no changes yet).
446
+ * - If expectedBaseCommit is ancestor of HEAD → ok (builder made commits).
447
+ * - Otherwise → HEAD was moved externally (force push, external commit, branch switch) → STOP_HEAD_MOVED.
448
+ *
449
+ * @param expectedBaseCommit - The base commit SHA stored at tick start
450
+ * @returns HeadCheckResult with ok, stopCode, expectedHead, actualHead, reason
451
+ */
452
+ export function checkHeadMoved(expectedBaseCommit) {
453
+ let actualHead;
454
+ try {
455
+ actualHead = execSync('git rev-parse HEAD', {
456
+ encoding: 'utf-8',
457
+ stdio: ['pipe', 'pipe', 'pipe'],
458
+ }).trim();
459
+ }
460
+ catch (error) {
461
+ return {
462
+ ok: false,
463
+ stopCode: 'STOP_HEAD_MOVED',
464
+ expectedHead: expectedBaseCommit,
465
+ actualHead: '',
466
+ reason: `Failed to get current HEAD: ${error instanceof Error ? error.message : String(error)}`,
467
+ };
468
+ }
469
+ if (actualHead === expectedBaseCommit) {
470
+ return {
471
+ ok: true,
472
+ stopCode: null,
473
+ expectedHead: expectedBaseCommit,
474
+ actualHead,
475
+ reason: null,
476
+ };
477
+ }
478
+ try {
479
+ execSync(`git merge-base --is-ancestor ${expectedBaseCommit} HEAD`, {
480
+ encoding: 'utf-8',
481
+ stdio: ['pipe', 'pipe', 'pipe'],
482
+ });
483
+ return {
484
+ ok: true,
485
+ stopCode: null,
486
+ expectedHead: expectedBaseCommit,
487
+ actualHead,
488
+ reason: null,
489
+ };
490
+ }
491
+ catch {
492
+ return {
493
+ ok: false,
494
+ stopCode: 'STOP_HEAD_MOVED',
495
+ expectedHead: expectedBaseCommit,
496
+ actualHead,
497
+ reason: `HEAD moved externally: expected ${expectedBaseCommit}, got ${actualHead}`,
498
+ };
499
+ }
500
+ }
501
+ //# sourceMappingURL=judge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"judge.js","sourceRoot":"","sources":["../../src/lib/judge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
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 type { LockInfo } from '../types/lock.js';
8
+ /**
9
+ * Error thrown when a lock is already held by another process.
10
+ */
11
+ export declare class LockHeldError extends Error {
12
+ readonly lockPath: string;
13
+ readonly holder: LockInfo;
14
+ constructor(message: string, lockPath: string, holder: LockInfo);
15
+ }
16
+ /**
17
+ * Error thrown when a lock file is corrupt or malformed.
18
+ */
19
+ export declare class LockCorruptError extends Error {
20
+ readonly lockPath: string;
21
+ constructor(message: string, lockPath: string);
22
+ }
23
+ /**
24
+ * Gets a unique identifier for the current boot session.
25
+ *
26
+ * On Linux, reads /proc/sys/kernel/random/boot_id.
27
+ * On macOS, uses system uptime + hostname as a fingerprint since macOS
28
+ * doesn't have boot_id but uptime.boot_time is stable per boot.
29
+ *
30
+ * The result is cached for the lifetime of the process.
31
+ *
32
+ * @returns A string uniquely identifying the current boot session
33
+ */
34
+ export declare function getBootId(): string;
35
+ /**
36
+ * Checks if a process with the given PID is currently running.
37
+ *
38
+ * Uses process.kill(pid, 0) which checks for process existence
39
+ * without actually sending a signal.
40
+ *
41
+ * @param pid - The process ID to check
42
+ * @returns true if the process is running, false otherwise
43
+ */
44
+ export declare function isPidRunning(pid: number): boolean;
45
+ /**
46
+ * Determines if a lock is stale and can be reclaimed.
47
+ *
48
+ * A lock is considered stale if:
49
+ * - The holding process is no longer running, OR
50
+ * - The boot_id differs from the current boot (system has rebooted)
51
+ *
52
+ * @param lock - The lock information to check
53
+ * @returns true if the lock is stale and can be reclaimed
54
+ */
55
+ export declare function isLockStale(lock: LockInfo): boolean;
56
+ /**
57
+ * Acquires a lock by creating a lock file atomically.
58
+ *
59
+ * If a lock already exists:
60
+ * - If stale (process dead or different boot), reclaims it with a warning
61
+ * - If held by an active process, throws LockHeldError
62
+ *
63
+ * @param lockPath - Path to the lock file
64
+ * @returns The lock information that was written
65
+ * @throws {LockHeldError} If the lock is held by another active process
66
+ * @throws {LockCorruptError} If the lock file is corrupt or malformed
67
+ * @throws {AtomicFsError} If the lock file cannot be written
68
+ */
69
+ export declare function acquireLock(lockPath: string): Promise<LockInfo>;
70
+ /**
71
+ * Releases a lock by deleting the lock file.
72
+ *
73
+ * Only releases the lock if it belongs to the current process.
74
+ * Silently succeeds if the lock doesn't exist or belongs to another process.
75
+ *
76
+ * @param lockPath - Path to the lock file
77
+ */
78
+ export declare function releaseLock(lockPath: string): Promise<void>;
79
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/lib/lock.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGjD;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;aAGpB,QAAQ,EAAE,MAAM;aAChB,MAAM,EAAE,QAAQ;gBAFhC,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,QAAQ;CAKnC;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;aAGvB,QAAQ,EAAE,MAAM;gBADhC,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM;CAKnC;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAwClC;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAgBjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAQnD;AA+CD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4DrE;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBjE"}