@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,116 @@
1
+ /**
2
+ * Task fingerprinting utilities.
3
+ *
4
+ * Task fingerprint is used to detect identical re-dispatch loops.
5
+ * If orchestrator 'retries' without changing the plan, fingerprint stays same and runner stops.
6
+ */
7
+ import { createHash } from 'node:crypto';
8
+ /**
9
+ * Top-level fields to include in the fingerprint computation.
10
+ */
11
+ const TOP_LEVEL_FIELDS = [
12
+ 'goal',
13
+ 'subtasks',
14
+ 'acceptance',
15
+ 'verify',
16
+ 'implementation',
17
+ 'risk',
18
+ 'notes',
19
+ ];
20
+ /**
21
+ * Scope fields to include in the fingerprint computation.
22
+ */
23
+ const SCOPE_FIELDS = [
24
+ 'write',
25
+ 'create_under',
26
+ 'forbidden',
27
+ 'read_forbidden',
28
+ ];
29
+ /**
30
+ * Fields to exclude from fingerprint computation.
31
+ */
32
+ const EXCLUDED_FIELDS = new Set(['task_id', 'id', 'v', 'milestone', 'context']);
33
+ /**
34
+ * Recursively processes an object to trim strings and ensure canonical form.
35
+ */
36
+ function processValue(value) {
37
+ if (value === null || value === undefined) {
38
+ return value;
39
+ }
40
+ if (typeof value === 'string') {
41
+ return value.trim();
42
+ }
43
+ if (Array.isArray(value)) {
44
+ return value.map(processValue);
45
+ }
46
+ if (typeof value === 'object') {
47
+ const processed = {};
48
+ const sortedKeys = Object.keys(value).sort();
49
+ for (const key of sortedKeys) {
50
+ processed[key] = processValue(value[key]);
51
+ }
52
+ return processed;
53
+ }
54
+ return value;
55
+ }
56
+ /**
57
+ * Extracts fingerprint-relevant fields from a task object.
58
+ */
59
+ function extractFingerprintFields(task) {
60
+ const result = {};
61
+ // Extract top-level fields
62
+ for (const field of TOP_LEVEL_FIELDS) {
63
+ if (field in task && !EXCLUDED_FIELDS.has(field)) {
64
+ result[field] = task[field];
65
+ }
66
+ }
67
+ // Extract scope fields if scope exists
68
+ if (task.scope && typeof task.scope === 'object') {
69
+ const scope = task.scope;
70
+ const scopeResult = {};
71
+ for (const field of SCOPE_FIELDS) {
72
+ if (field in scope) {
73
+ scopeResult[field] = scope[field];
74
+ }
75
+ }
76
+ // Only add scope if it has at least one field
77
+ if (Object.keys(scopeResult).length > 0) {
78
+ result.scope = scopeResult;
79
+ }
80
+ }
81
+ return result;
82
+ }
83
+ /**
84
+ * Canonicalizes a task object to a stable JSON string.
85
+ *
86
+ * The canonical form:
87
+ * - Has sorted keys (alphabetical order)
88
+ * - Has trimmed strings
89
+ * - Excludes task_id, id, v, milestone, and context fields
90
+ * - Only includes fingerprint-relevant fields
91
+ *
92
+ * @param task - The task object to canonicalize
93
+ * @returns A canonical JSON string representation
94
+ */
95
+ export function canonicalizeTask(task) {
96
+ // Extract only fingerprint-relevant fields
97
+ const fingerprintData = extractFingerprintFields(task);
98
+ // Process the data (trim strings, sort keys recursively)
99
+ const processed = processValue(fingerprintData);
100
+ // Convert to JSON with stable key ordering
101
+ // JSON.stringify already produces stable output for objects with sorted keys
102
+ return JSON.stringify(processed);
103
+ }
104
+ /**
105
+ * Computes a SHA-256 fingerprint of a task.
106
+ *
107
+ * @param task - The task object to fingerprint
108
+ * @returns A 64-character hexadecimal SHA-256 hash string
109
+ */
110
+ export function computeFingerprint(task) {
111
+ const canonical = canonicalizeTask(task);
112
+ const hash = createHash('sha256');
113
+ hash.update(canonical, 'utf8');
114
+ return hash.digest('hex');
115
+ }
116
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/lib/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;GAEG;AACH,MAAM,gBAAgB,GAAG;IACvB,MAAM;IACN,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,gBAAgB;IAChB,MAAM;IACN,OAAO;CACC,CAAC;AAEX;;GAEG;AACH,MAAM,YAAY,GAAG;IACnB,OAAO;IACP,cAAc;IACd,WAAW;IACX,gBAAgB;CACR,CAAC;AAEX;;GAEG;AACH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;AAEhF;;GAEG;AACH,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,SAAS,GAA4B,EAAE,CAAC;QAC9C,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC7B,SAAS,CAAC,GAAG,CAAC,GAAG,YAAY,CAAE,KAAiC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,wBAAwB,CAAC,IAA6B;IAC7D,MAAM,MAAM,GAA4B,EAAE,CAAC;IAE3C,2BAA2B;IAC3B,KAAK,MAAM,KAAK,IAAI,gBAAgB,EAAE,CAAC;QACrC,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACjD,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,uCAAuC;IACvC,IAAI,IAAI,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAgC,CAAC;QACpD,MAAM,WAAW,GAA4B,EAAE,CAAC;QAChD,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;YACjC,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;gBACnB,WAAW,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QACD,8CAA8C;QAC9C,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,KAAK,GAAG,WAAW,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAA6B;IAC5D,2CAA2C;IAC3C,MAAM,eAAe,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAEvD,yDAAyD;IACzD,MAAM,SAAS,GAAG,YAAY,CAAC,eAAe,CAAC,CAAC;IAEhD,2CAA2C;IAC3C,6EAA6E;IAC7E,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAA6B;IAC9D,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Atomic file system utilities for crash-safe JSON operations.
3
+ *
4
+ * These utilities implement the write-tmp-fsync-rename pattern to ensure
5
+ * atomic file writes that survive crashes and power failures on POSIX systems.
6
+ */
7
+ /**
8
+ * Result of glob pattern safety check.
9
+ */
10
+ export type GlobSafetyResult = {
11
+ safe: true;
12
+ } | {
13
+ safe: false;
14
+ reason: string;
15
+ };
16
+ /**
17
+ * Checks if a glob pattern is safe for file deletion operations.
18
+ *
19
+ * Safe patterns:
20
+ * - Relative paths like `envoi/*.tmp`, `envoi/**\/*.tmp`, `*.tmp`
21
+ *
22
+ * Unsafe patterns:
23
+ * - Empty or whitespace-only patterns
24
+ * - Patterns containing path traversal (`..`)
25
+ * - Unix absolute paths (starting with `/`)
26
+ * - Windows absolute paths (e.g., `C:\`)
27
+ * - UNC paths (`\\server\share` or `//server/share`)
28
+ *
29
+ * @param pattern - The glob pattern to check
30
+ * @returns Result indicating if the pattern is safe
31
+ */
32
+ export declare function isGlobPatternSafe(pattern: string): GlobSafetyResult;
33
+ /**
34
+ * Error thrown when atomic file operations fail.
35
+ */
36
+ export declare class AtomicFsError extends Error {
37
+ readonly filePath: string;
38
+ readonly cause?: Error | undefined;
39
+ constructor(message: string, filePath: string, cause?: Error | undefined);
40
+ }
41
+ /**
42
+ * Atomically writes JSON data to a file using the write-tmp-fsync-rename pattern.
43
+ *
44
+ * This ensures that the file is never in a partially-written state, even if the
45
+ * process crashes or the system loses power during the write operation.
46
+ *
47
+ * @param filePath - The path to write the JSON file to
48
+ * @param data - The data to serialize and write
49
+ * @throws {AtomicFsError} If the write operation fails
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * await atomicWriteJson('/path/to/config.json', { version: 1, enabled: true });
54
+ * ```
55
+ */
56
+ export declare function atomicWriteJson<T>(filePath: string, data: T): Promise<void>;
57
+ /**
58
+ * Reads and parses a JSON file with proper typing.
59
+ *
60
+ * @param filePath - The path to the JSON file to read
61
+ * @returns The parsed JSON data
62
+ * @throws {AtomicFsError} If the file cannot be read or parsed
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * interface Config {
67
+ * version: number;
68
+ * enabled: boolean;
69
+ * }
70
+ * const config = await atomicReadJson<Config>('/path/to/config.json');
71
+ * ```
72
+ */
73
+ export declare function atomicReadJson<T>(filePath: string): Promise<T>;
74
+ /**
75
+ * Cleans up stale .tmp files in a directory.
76
+ *
77
+ * This should be called during startup preflight to remove any .tmp files
78
+ * left behind by interrupted write operations.
79
+ *
80
+ * @param dir - The directory to scan for .tmp files
81
+ * @param pattern - Optional pattern to match (defaults to '*.tmp')
82
+ * @returns List of deleted file paths
83
+ * @throws {AtomicFsError} If the cleanup operation fails
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * // Clean up all .tmp files in /envoi directory
88
+ * const deleted = await cleanupTmpFiles('/envoi');
89
+ * console.log(`Cleaned up ${deleted.length} stale tmp files`);
90
+ * ```
91
+ */
92
+ export declare function cleanupTmpFiles(dir: string, pattern?: string): Promise<string[]>;
93
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/lib/fs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CA2BnE;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;aAGpB,QAAQ,EAAE,MAAM;aAChB,KAAK,CAAC,EAAE,KAAK;gBAF7B,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CjF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAWpE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA2BtF"}
package/dist/lib/fs.js ADDED
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Atomic file system utilities for crash-safe JSON operations.
3
+ *
4
+ * These utilities implement the write-tmp-fsync-rename pattern to ensure
5
+ * atomic file writes that survive crashes and power failures on POSIX systems.
6
+ */
7
+ import { open, rename, unlink, readFile, readdir } from 'node:fs/promises';
8
+ import { join } from 'node:path';
9
+ /**
10
+ * Checks if a glob pattern is safe for file deletion operations.
11
+ *
12
+ * Safe patterns:
13
+ * - Relative paths like `envoi/*.tmp`, `envoi/**\/*.tmp`, `*.tmp`
14
+ *
15
+ * Unsafe patterns:
16
+ * - Empty or whitespace-only patterns
17
+ * - Patterns containing path traversal (`..`)
18
+ * - Unix absolute paths (starting with `/`)
19
+ * - Windows absolute paths (e.g., `C:\`)
20
+ * - UNC paths (`\\server\share` or `//server/share`)
21
+ *
22
+ * @param pattern - The glob pattern to check
23
+ * @returns Result indicating if the pattern is safe
24
+ */
25
+ export function isGlobPatternSafe(pattern) {
26
+ // Check for empty or whitespace-only patterns
27
+ if (!pattern || pattern.trim() === '') {
28
+ return { safe: false, reason: 'Empty or whitespace-only pattern' };
29
+ }
30
+ // Check for path traversal
31
+ if (pattern.includes('..')) {
32
+ return { safe: false, reason: 'Pattern contains path traversal (..)' };
33
+ }
34
+ // Check for Unix absolute paths
35
+ if (pattern.startsWith('/')) {
36
+ return { safe: false, reason: 'Pattern is an absolute Unix path' };
37
+ }
38
+ // Check for Windows absolute paths (e.g., C:\, D:\)
39
+ if (/^[A-Za-z]:[\\\/]/.test(pattern)) {
40
+ return { safe: false, reason: 'Pattern is an absolute Windows path' };
41
+ }
42
+ // Check for UNC paths (\\server\share or //server/share)
43
+ if (pattern.startsWith('\\\\') || pattern.startsWith('//')) {
44
+ return { safe: false, reason: 'Pattern is a UNC path' };
45
+ }
46
+ return { safe: true };
47
+ }
48
+ /**
49
+ * Error thrown when atomic file operations fail.
50
+ */
51
+ export class AtomicFsError extends Error {
52
+ filePath;
53
+ cause;
54
+ constructor(message, filePath, cause) {
55
+ super(message);
56
+ this.filePath = filePath;
57
+ this.cause = cause;
58
+ this.name = 'AtomicFsError';
59
+ }
60
+ }
61
+ /**
62
+ * Atomically writes JSON data to a file using the write-tmp-fsync-rename pattern.
63
+ *
64
+ * This ensures that the file is never in a partially-written state, even if the
65
+ * process crashes or the system loses power during the write operation.
66
+ *
67
+ * @param filePath - The path to write the JSON file to
68
+ * @param data - The data to serialize and write
69
+ * @throws {AtomicFsError} If the write operation fails
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * await atomicWriteJson('/path/to/config.json', { version: 1, enabled: true });
74
+ * ```
75
+ */
76
+ export async function atomicWriteJson(filePath, data) {
77
+ const tmpPath = `${filePath}.tmp`;
78
+ let fileHandle = null;
79
+ try {
80
+ // Serialize with 2-space indent for readability
81
+ const content = JSON.stringify(data, null, 2) + '\n';
82
+ // Open file for writing, create if doesn't exist, truncate if exists
83
+ fileHandle = await open(tmpPath, 'w');
84
+ // Write the content
85
+ await fileHandle.writeFile(content, 'utf-8');
86
+ // fsync to ensure data is flushed to disk
87
+ await fileHandle.sync();
88
+ // Close before rename
89
+ await fileHandle.close();
90
+ fileHandle = null;
91
+ // Atomic rename (POSIX guarantees atomicity)
92
+ await rename(tmpPath, filePath);
93
+ }
94
+ catch (error) {
95
+ // Attempt to clean up the tmp file on error
96
+ if (fileHandle) {
97
+ try {
98
+ await fileHandle.close();
99
+ }
100
+ catch {
101
+ // Ignore close errors during cleanup
102
+ }
103
+ }
104
+ try {
105
+ await unlink(tmpPath);
106
+ }
107
+ catch {
108
+ // Ignore unlink errors - file may not exist
109
+ }
110
+ throw new AtomicFsError(`Failed to atomically write JSON to ${filePath}: ${error instanceof Error ? error.message : String(error)}`, filePath, error instanceof Error ? error : undefined);
111
+ }
112
+ }
113
+ /**
114
+ * Reads and parses a JSON file with proper typing.
115
+ *
116
+ * @param filePath - The path to the JSON file to read
117
+ * @returns The parsed JSON data
118
+ * @throws {AtomicFsError} If the file cannot be read or parsed
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * interface Config {
123
+ * version: number;
124
+ * enabled: boolean;
125
+ * }
126
+ * const config = await atomicReadJson<Config>('/path/to/config.json');
127
+ * ```
128
+ */
129
+ export async function atomicReadJson(filePath) {
130
+ try {
131
+ const content = await readFile(filePath, 'utf-8');
132
+ return JSON.parse(content);
133
+ }
134
+ catch (error) {
135
+ throw new AtomicFsError(`Failed to read JSON from ${filePath}: ${error instanceof Error ? error.message : String(error)}`, filePath, error instanceof Error ? error : undefined);
136
+ }
137
+ }
138
+ /**
139
+ * Cleans up stale .tmp files in a directory.
140
+ *
141
+ * This should be called during startup preflight to remove any .tmp files
142
+ * left behind by interrupted write operations.
143
+ *
144
+ * @param dir - The directory to scan for .tmp files
145
+ * @param pattern - Optional pattern to match (defaults to '*.tmp')
146
+ * @returns List of deleted file paths
147
+ * @throws {AtomicFsError} If the cleanup operation fails
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * // Clean up all .tmp files in /envoi directory
152
+ * const deleted = await cleanupTmpFiles('/envoi');
153
+ * console.log(`Cleaned up ${deleted.length} stale tmp files`);
154
+ * ```
155
+ */
156
+ export async function cleanupTmpFiles(dir, pattern = '.tmp') {
157
+ const deleted = [];
158
+ try {
159
+ const entries = await readdir(dir, { withFileTypes: true });
160
+ for (const entry of entries) {
161
+ if (entry.isFile() && entry.name.endsWith(pattern)) {
162
+ const filePath = join(dir, entry.name);
163
+ try {
164
+ await unlink(filePath);
165
+ deleted.push(filePath);
166
+ }
167
+ catch (error) {
168
+ // Log but continue - we want to clean up as many as possible
169
+ console.warn(`Failed to delete tmp file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
170
+ }
171
+ }
172
+ }
173
+ return deleted;
174
+ }
175
+ catch (error) {
176
+ throw new AtomicFsError(`Failed to cleanup tmp files in ${dir}: ${error instanceof Error ? error.message : String(error)}`, dir, error instanceof Error ? error : undefined);
177
+ }
178
+ }
179
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/lib/fs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,IAAI,EAAuB,MAAM,WAAW,CAAC;AAOtD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,8CAA8C;IAC9C,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACtC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;IACrE,CAAC;IAED,2BAA2B;IAC3B,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,sCAAsC,EAAE,CAAC;IACzE,CAAC;IAED,gCAAgC;IAChC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;IACrE,CAAC;IAED,oDAAoD;IACpD,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,qCAAqC,EAAE,CAAC;IACxE,CAAC;IAED,yDAAyD;IACzD,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;IAC1D,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAGpB;IACA;IAHlB,YACE,OAAe,EACC,QAAgB,EAChB,KAAa;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,aAAQ,GAAR,QAAQ,CAAQ;QAChB,UAAK,GAAL,KAAK,CAAQ;QAG7B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAI,QAAgB,EAAE,IAAO;IAChE,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;IAClC,IAAI,UAAU,GAA4C,IAAI,CAAC;IAE/D,IAAI,CAAC;QACH,gDAAgD;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;QAErD,qEAAqE;QACrE,UAAU,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAEtC,oBAAoB;QACpB,MAAM,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAE7C,0CAA0C;QAC1C,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;QAExB,sBAAsB;QACtB,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;QACzB,UAAU,GAAG,IAAI,CAAC;QAElB,6CAA6C;QAC7C,MAAM,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,4CAA4C;QAC5C,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC;gBACH,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,qCAAqC;YACvC,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,4CAA4C;QAC9C,CAAC;QAED,MAAM,IAAI,aAAa,CACrB,sCAAsC,QAAQ,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC3G,QAAQ,EACR,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAC3C,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAI,QAAgB;IACtD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAM,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,aAAa,CACrB,4BAA4B,QAAQ,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACjG,QAAQ,EACR,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAC3C,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW,EAAE,OAAO,GAAG,MAAM;IACjE,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAE5D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBACvC,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;oBACvB,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACzB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,6DAA6D;oBAC7D,OAAO,CAAC,IAAI,CAAC,6BAA6B,QAAQ,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACnH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,aAAa,CACrB,kCAAkC,GAAG,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAClG,GAAG,EACH,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAC3C,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Git helper functions for preflight checks and diff tracking.
3
+ *
4
+ * Uses execSync for git operations to ensure synchronous, blocking behavior.
5
+ */
6
+ /**
7
+ * Checks if the current directory is inside a git repository.
8
+ *
9
+ * @returns true if in a git repo, false otherwise
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * if (!isGitRepo()) {
14
+ * console.error('Not in a git repository');
15
+ * }
16
+ * ```
17
+ */
18
+ export declare function isGitRepo(): boolean;
19
+ /**
20
+ * Checks if the git worktree is clean (no uncommitted tracked changes
21
+ * AND no untracked files).
22
+ *
23
+ * A clean worktree means:
24
+ * - No staged changes
25
+ * - No unstaged changes to tracked files
26
+ * - No untracked files
27
+ *
28
+ * @returns true if worktree is clean, false otherwise
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * if (!isWorktreeClean()) {
33
+ * console.error('Worktree has uncommitted changes');
34
+ * }
35
+ * ```
36
+ */
37
+ export declare function isWorktreeClean(): boolean;
38
+ /**
39
+ * Parses git status porcelain output and filters files based on exclusion globs.
40
+ *
41
+ * This is a pure function that can be easily tested.
42
+ *
43
+ * @param statusOutput - Raw output from `git status --porcelain`
44
+ * @param excludeGlobs - Glob patterns for files to exclude from the dirty check
45
+ * @returns Object with `clean` (boolean), `dirtyFiles`, and `excludedFiles`
46
+ */
47
+ export declare function parseGitStatusWithExclusions(statusOutput: string, excludeGlobs: string[]): {
48
+ clean: boolean;
49
+ dirtyFiles: string[];
50
+ excludedFiles: string[];
51
+ };
52
+ /**
53
+ * Checks if the git worktree is clean, excluding files matching specified globs.
54
+ *
55
+ * This is useful for ignoring runner-owned files (like REPORT.json, STATE.json)
56
+ * that are expected to change every tick.
57
+ *
58
+ * @param excludeGlobs - Glob patterns for files to exclude from the dirty check
59
+ * @returns Object with `clean` (boolean) and `dirtyFiles` (non-excluded dirty files)
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const result = isWorktreeCleanExcluding(['envoi/**', 'pilot/**']);
64
+ * if (!result.clean) {
65
+ * console.error('Dirty files:', result.dirtyFiles);
66
+ * }
67
+ * ```
68
+ */
69
+ export declare function isWorktreeCleanExcluding(excludeGlobs: string[]): {
70
+ clean: boolean;
71
+ dirtyFiles: string[];
72
+ excludedFiles: string[];
73
+ };
74
+ /**
75
+ * Gets the current HEAD commit SHA.
76
+ *
77
+ * @returns The full 40-character SHA of HEAD
78
+ * @throws {Error} If not in a git repo or HEAD doesn't exist
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const sha = getHeadCommit();
83
+ * console.log(`Current commit: ${sha}`);
84
+ * ```
85
+ */
86
+ export declare function getHeadCommit(): string;
87
+ /**
88
+ * Gets the list of files that have changed since a base commit.
89
+ *
90
+ * Returns both modified and added files in the diff.
91
+ *
92
+ * @param base - The base commit SHA to diff against
93
+ * @returns Array of file paths that have changed
94
+ * @throws {Error} If the diff operation fails
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * const changedFiles = getDiffFiles('abc123');
99
+ * console.log(`${changedFiles.length} files changed`);
100
+ * ```
101
+ */
102
+ export declare function getDiffFiles(base: string): string[];
103
+ /**
104
+ * Gets the list of untracked files in the repository.
105
+ *
106
+ * @returns Array of untracked file paths
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const untracked = getUntrackedFiles();
111
+ * if (untracked.length > 0) {
112
+ * console.log(`Found ${untracked.length} untracked files`);
113
+ * }
114
+ * ```
115
+ */
116
+ export declare function getUntrackedFiles(): string[];
117
+ /**
118
+ * Gets the current git branch name.
119
+ *
120
+ * @returns The current branch name
121
+ * @throws {Error} If not in a git repo or branch cannot be determined
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * const branch = getCurrentBranch();
126
+ * console.log(`Current branch: ${branch}`);
127
+ * ```
128
+ */
129
+ export declare function getCurrentBranch(): string;
130
+ /**
131
+ * Gets the top-level directory of the git repository.
132
+ *
133
+ * @returns The absolute path to the git repository root, or null if not in a git repo
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const root = getGitTopLevel();
138
+ * if (root) {
139
+ * console.log(`Git root: ${root}`);
140
+ * }
141
+ * ```
142
+ */
143
+ export declare function getGitTopLevel(): string | null;
144
+ /**
145
+ * Stashes pilot/ directory files to prevent merge conflicts.
146
+ *
147
+ * Creates a stash containing only files in the pilot/ directory.
148
+ * This allows safe merging without losing pilot state.
149
+ *
150
+ * @returns The stash reference (e.g., "stash@{0}")
151
+ * @throws {Error} If the stash operation fails
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const stashRef = stashPilotFiles();
156
+ * // Perform merge...
157
+ * popPilotStash(stashRef);
158
+ * ```
159
+ */
160
+ export declare function stashPilotFiles(): string;
161
+ /**
162
+ * Pops a previously created stash to restore pilot files.
163
+ *
164
+ * Restores the stashed pilot/ directory files back to the working tree.
165
+ *
166
+ * @param stashRef - The stash reference returned by stashPilotFiles (e.g., "stash@{0}")
167
+ * @throws {Error} If the stash pop operation fails
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const stashRef = stashPilotFiles();
172
+ * // Perform merge...
173
+ * popPilotStash(stashRef);
174
+ * ```
175
+ */
176
+ export declare function popPilotStash(stashRef: string): void;
177
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/lib/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAUnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAazC;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EAAE,GACrB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CA0CnE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,EAAE,GACrB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAYnE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAYtC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAenD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAiB5C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAYzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAU9C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAmCxC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAWpD"}