autohand-cli 0.6.11 → 0.7.2

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 (121) hide show
  1. package/README.md +60 -48
  2. package/dist/AutomodeManager-FQQPBHAD.js +1422 -0
  3. package/dist/AutomodeManager-PUNJS7IZ.cjs +1422 -0
  4. package/dist/{CommunitySkillsCache-FXWTOBSZ.js → CommunitySkillsCache-AY3ZYDTN.js} +1 -1
  5. package/dist/{CommunitySkillsCache-MQOTKCVS.cjs → CommunitySkillsCache-KO4DSKMA.cjs} +1 -1
  6. package/dist/{GitHubRegistryFetcher-S7QFUEKV.cjs → GitHubRegistryFetcher-S4JREWUQ.cjs} +1 -1
  7. package/dist/{GitHubRegistryFetcher-6JQ5JEDZ.js → GitHubRegistryFetcher-V23KTTLM.js} +1 -1
  8. package/dist/HookManager-OGINWAJN.cjs +7 -0
  9. package/dist/HookManager-VG46FXSZ.js +7 -0
  10. package/dist/MemoryManager-AFS5EZJ2.js +8 -0
  11. package/dist/MemoryManager-KE6EKW7Y.cjs +8 -0
  12. package/dist/{PermissionManager-RY3K6CVU.cjs → PermissionManager-MAPKIJMD.cjs} +1 -1
  13. package/dist/{PermissionManager-XIMDETMX.js → PermissionManager-V2Q2OAS2.js} +1 -1
  14. package/dist/SessionManager-MKLGLZWC.cjs +10 -0
  15. package/dist/SessionManager-V25OJDDY.js +10 -0
  16. package/dist/{SkillsRegistry-LXDK73BL.cjs → SkillsRegistry-4RU2OAEQ.cjs} +1 -1
  17. package/dist/{SkillsRegistry-SP5MX7OA.js → SkillsRegistry-FTLVJZMA.js} +1 -1
  18. package/dist/{agents-FH47ZMOI.cjs → agents-CHNQ7LQ4.cjs} +1 -1
  19. package/dist/{agents-NB5VQN6H.js → agents-E4NEH2PN.js} +1 -1
  20. package/dist/{agents-new-XLEU26YI.cjs → agents-new-GHVWXW7T.cjs} +1 -1
  21. package/dist/{agents-new-M325HGWT.js → agents-new-W6HMQ7V5.js} +1 -1
  22. package/dist/automode-BJYGRMEQ.cjs +9 -0
  23. package/dist/automode-XCNP6HP4.js +9 -0
  24. package/dist/chunk-2FLBGPE3.js +199 -0
  25. package/dist/{chunk-5RX6NVQO.cjs → chunk-3L76MLO5.cjs} +20 -2
  26. package/dist/chunk-3RG5ZIWI.js +10 -0
  27. package/dist/chunk-4L5WYXHN.js +246 -0
  28. package/dist/{chunk-SM4I2535.js → chunk-52HPEJ4P.js} +1 -1
  29. package/dist/{chunk-O2GOSS3V.js → chunk-6XITU2RU.js} +1 -1
  30. package/dist/{chunk-GNKSRX4R.js → chunk-B2BPL6IL.js} +216 -27
  31. package/dist/{chunk-SFT6BEYU.js → chunk-BAFJQUWR.js} +20 -2
  32. package/dist/chunk-CXZEPTRI.js +172 -0
  33. package/dist/{chunk-O3UH2TS6.cjs → chunk-E6U4UE3B.cjs} +1 -1
  34. package/dist/{chunk-CJ7JHMK7.cjs → chunk-EBM5QDJ4.cjs} +2 -2
  35. package/dist/{chunk-TF4ROETV.cjs → chunk-FEI2GPAW.cjs} +2 -2
  36. package/dist/{chunk-V6EL2YTY.js → chunk-HUE3GT5M.js} +1 -1
  37. package/dist/{chunk-DGVYSTC6.cjs → chunk-HXAAED4W.cjs} +10 -12
  38. package/dist/{chunk-QF2P64F7.js → chunk-LCCJ26QR.js} +2 -2
  39. package/dist/chunk-OBGZSXTJ.cjs +10 -0
  40. package/dist/chunk-OTS4YFSZ.cjs +172 -0
  41. package/dist/{chunk-6SAX2G75.cjs → chunk-SXUZ3CX3.cjs} +215 -26
  42. package/dist/chunk-SXYYH3VD.cjs +497 -0
  43. package/dist/{chunk-NLJMRNGL.cjs → chunk-U43RFUBR.cjs} +2 -2
  44. package/dist/chunk-UOLJZFPJ.js +497 -0
  45. package/dist/chunk-UPR5PKX4.cjs +199 -0
  46. package/dist/chunk-URY4AS4L.cjs +246 -0
  47. package/dist/{chunk-ERBSCRG6.js → chunk-ZYHQ652D.js} +3 -5
  48. package/dist/{completion-HR3ZT55J.js → completion-2XTEWNMC.js} +1 -1
  49. package/dist/{completion-AMEZDTFT.cjs → completion-BTDPVWVB.cjs} +1 -1
  50. package/dist/{constants-N3I2FHCM.js → constants-RVCOJL3L.js} +1 -1
  51. package/dist/{constants-ICQLSGZN.cjs → constants-V4V43DZQ.cjs} +1 -1
  52. package/dist/{defaultHooks-A7TTVVRI.js → defaultHooks-3GRSZNKA.js} +1 -1
  53. package/dist/{defaultHooks-XGIBEP3Z.cjs → defaultHooks-7EAUU6MN.cjs} +1 -1
  54. package/dist/{export-ULYYSO5V.cjs → export-CB34FSEH.cjs} +1 -1
  55. package/dist/{export-MYBJZD3H.js → export-LNPP3XXH.js} +1 -1
  56. package/dist/{feedback-YGSYBQEW.js → feedback-IZKDNGHP.js} +1 -1
  57. package/dist/{feedback-TOGESBX7.cjs → feedback-OKGBXZZ4.cjs} +1 -1
  58. package/dist/{formatters-3POW3KMP.js → formatters-JJK6RS55.js} +1 -1
  59. package/dist/{formatters-UMUJYWV5.cjs → formatters-OIGPANHJ.cjs} +1 -1
  60. package/dist/{help-AW4QPGIW.js → help-MU553D6W.js} +1 -1
  61. package/dist/{help-2PR7P4UJ.cjs → help-QAAUDLFS.cjs} +1 -1
  62. package/dist/{hooks-2CLU2GWV.js → hooks-4N5VRVOF.js} +2 -2
  63. package/dist/hooks-7DZGBMXP.cjs +10 -0
  64. package/dist/index.cjs +990 -1447
  65. package/dist/index.js +871 -1328
  66. package/dist/{init-OLSCW7ZC.cjs → init-2QEB7GL5.cjs} +1 -1
  67. package/dist/{init-HAQKREMF.js → init-WW4RITLV.js} +1 -1
  68. package/dist/{lint-NJPZWVN2.js → lint-L2TD6NY6.js} +1 -1
  69. package/dist/{lint-D5UOJWIK.cjs → lint-ZLRBEAFF.cjs} +1 -1
  70. package/dist/{localProjectPermissions-5RX7NHAH.cjs → localProjectPermissions-2DP6X55S.cjs} +1 -1
  71. package/dist/{localProjectPermissions-W2CMGIFT.js → localProjectPermissions-5CXHD4DO.js} +1 -1
  72. package/dist/{login-YNFMV67B.js → login-LH62FYMH.js} +3 -4
  73. package/dist/login-TWWYJKX6.cjs +13 -0
  74. package/dist/logout-35XNU6Q2.cjs +13 -0
  75. package/dist/{logout-TSQ4O4GS.js → logout-KPHUXO23.js} +3 -4
  76. package/dist/{memory-F3V5FW6W.js → memory-2SGSO4MW.js} +1 -1
  77. package/dist/{memory-77SWEZYB.cjs → memory-ALXCFFQY.cjs} +1 -1
  78. package/dist/{model-B2PE6XFS.cjs → model-JC43B3KM.cjs} +1 -1
  79. package/dist/{model-JC53RT7A.js → model-U3BWIWVH.js} +1 -1
  80. package/dist/{new-J3ABPMW4.cjs → new-24YT5KVI.cjs} +1 -1
  81. package/dist/{new-356ITOM7.js → new-PCOF6OLV.js} +1 -1
  82. package/dist/{patch-MOD7QC3D.cjs → patch-ETANEGRW.cjs} +1 -1
  83. package/dist/{patch-5F6VBIT3.js → patch-RPK3BZQR.js} +1 -1
  84. package/dist/{permissions-3GS4ZWVA.js → permissions-5W5JOVM7.js} +1 -1
  85. package/dist/{permissions-E3MTIE7D.cjs → permissions-NHPJPHDV.cjs} +1 -1
  86. package/dist/{quit-2QWJ75GZ.js → quit-NIDVPHNL.js} +1 -1
  87. package/dist/{quit-DQ57J67A.cjs → quit-TMKMX2YF.cjs} +1 -1
  88. package/dist/{resume-43XMN4CL.cjs → resume-5C44HAAH.cjs} +1 -1
  89. package/dist/{resume-GA7YZJSO.js → resume-ZZ2D2NMB.js} +1 -1
  90. package/dist/{session-BSU2L5UI.cjs → session-6TMBGN7N.cjs} +1 -1
  91. package/dist/{session-SZMRN5KG.js → session-76F55XKA.js} +1 -1
  92. package/dist/{sessions-OJ4DRK3P.js → sessions-ERKBJ7MC.js} +1 -1
  93. package/dist/{sessions-CVOZGTKR.cjs → sessions-NUPCJVCF.cjs} +1 -1
  94. package/dist/{skills-APRIHHHU.js → skills-R25OBHE5.js} +2 -2
  95. package/dist/skills-UCWKIHHG.cjs +13 -0
  96. package/dist/{skills-install-52LS6X6D.js → skills-install-GNTBBL46.js} +1 -1
  97. package/dist/{skills-install-PDWIMVD5.cjs → skills-install-VUSVGSON.cjs} +1 -1
  98. package/dist/{skills-new-XDYS24XW.cjs → skills-new-AGXQNBRA.cjs} +1 -1
  99. package/dist/{skills-new-KIBUN63X.js → skills-new-L36LQXIE.js} +1 -1
  100. package/dist/status-2QV7C3JJ.cjs +9 -0
  101. package/dist/{status-DHA2IL2O.js → status-VJ6FOSRI.js} +2 -2
  102. package/dist/theme-MI3BM56M.cjs +13 -0
  103. package/dist/theme-ZXGSJBZI.js +13 -0
  104. package/dist/{undo-3Q44LSVS.js → undo-3UU5LWQS.js} +1 -1
  105. package/dist/{undo-WF5HB5VU.cjs → undo-W4VN2Y37.cjs} +1 -1
  106. package/package.json +2 -2
  107. package/dist/InkRenderer-4HTMUNIP.js +0 -2160
  108. package/dist/InkRenderer-DSNXNSRQ.cjs +0 -2160
  109. package/dist/chunk-JSBRDJBE.js +0 -30
  110. package/dist/chunk-N254NRHT.cjs +0 -30
  111. package/dist/chunk-QSPEMIKE.js +0 -230
  112. package/dist/chunk-UTNMIGOL.cjs +0 -230
  113. package/dist/filePalette-3JAGHGK5.js +0 -107
  114. package/dist/filePalette-PBE5D2OV.cjs +0 -107
  115. package/dist/hooks-JTRUWAEF.cjs +0 -10
  116. package/dist/login-2JO6W6PP.cjs +0 -14
  117. package/dist/logout-OGJQLG7K.cjs +0 -14
  118. package/dist/skills-UIIM5CKA.cjs +0 -13
  119. package/dist/status-VAGYUU3H.cjs +0 -9
  120. package/dist/theme-GGDFOBKE.js +0 -14
  121. package/dist/theme-PETVTBN7.cjs +0 -14
@@ -0,0 +1,1422 @@
1
+ import "./chunk-3RG5ZIWI.js";
2
+
3
+ // src/core/AutomodeManager.ts
4
+ import { EventEmitter } from "events";
5
+ import { execSync } from "child_process";
6
+ import path5 from "path";
7
+ import chalk from "chalk";
8
+ import crypto from "crypto";
9
+
10
+ // src/core/AutomodeState.ts
11
+ import fs from "fs-extra";
12
+ import path from "path";
13
+ var STATE_FILE_PATH = ".autohand/automode.local.md";
14
+ var DEFAULT_THRESHOLDS = {
15
+ noProgress: 3,
16
+ sameError: 5,
17
+ testOnly: 3
18
+ };
19
+ var AutomodeState = class {
20
+ constructor(workspaceRoot) {
21
+ this.state = null;
22
+ this.iterations = [];
23
+ this.circuitBreaker = {
24
+ noProgressCount: 0,
25
+ sameErrorCount: 0,
26
+ testOnlyCount: 0
27
+ };
28
+ this.workspaceRoot = workspaceRoot;
29
+ }
30
+ /**
31
+ * Get the state file path
32
+ */
33
+ getStatePath() {
34
+ return path.join(this.workspaceRoot, STATE_FILE_PATH);
35
+ }
36
+ /**
37
+ * Check if an auto-mode session is active
38
+ */
39
+ async hasActiveSession() {
40
+ try {
41
+ const exists = await fs.pathExists(this.getStatePath());
42
+ if (!exists) return false;
43
+ const state = await this.load();
44
+ return state !== null && state.status === "running";
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+ /**
50
+ * Initialize a new auto-mode session
51
+ */
52
+ async initialize(options) {
53
+ this.state = {
54
+ sessionId: options.sessionId,
55
+ prompt: options.prompt,
56
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
57
+ currentIteration: 0,
58
+ maxIterations: options.maxIterations,
59
+ status: "running",
60
+ branch: options.branch,
61
+ worktreePath: options.worktreePath,
62
+ filesCreated: 0,
63
+ filesModified: 0,
64
+ completionPromise: options.completionPromise
65
+ };
66
+ this.iterations = [];
67
+ this.circuitBreaker = {
68
+ noProgressCount: 0,
69
+ sameErrorCount: 0,
70
+ testOnlyCount: 0
71
+ };
72
+ await this.save();
73
+ return this.state;
74
+ }
75
+ /**
76
+ * Load state from file
77
+ */
78
+ async load() {
79
+ try {
80
+ const filePath = this.getStatePath();
81
+ if (!await fs.pathExists(filePath)) {
82
+ return null;
83
+ }
84
+ const content = await fs.readFile(filePath, "utf-8");
85
+ const state = this.parseStateMarkdown(content);
86
+ this.state = state;
87
+ return state;
88
+ } catch (error) {
89
+ console.error("[automode] Failed to load state:", error);
90
+ return null;
91
+ }
92
+ }
93
+ /**
94
+ * Save state to file
95
+ */
96
+ async save() {
97
+ if (!this.state) return;
98
+ try {
99
+ const filePath = this.getStatePath();
100
+ await fs.ensureDir(path.dirname(filePath));
101
+ const content = this.formatStateMarkdown(this.state);
102
+ await fs.writeFile(filePath, content, "utf-8");
103
+ } catch (error) {
104
+ console.error("[automode] Failed to save state:", error);
105
+ }
106
+ }
107
+ /**
108
+ * Get current state
109
+ */
110
+ getState() {
111
+ return this.state;
112
+ }
113
+ /**
114
+ * Get all iteration logs
115
+ */
116
+ getIterations() {
117
+ return [...this.iterations];
118
+ }
119
+ /**
120
+ * Update iteration count and track actions
121
+ */
122
+ async recordIteration(log) {
123
+ if (!this.state) return;
124
+ this.state.currentIteration += 1;
125
+ const iterationLog = {
126
+ ...log,
127
+ iteration: this.state.currentIteration
128
+ };
129
+ this.iterations.push(iterationLog);
130
+ await this.save();
131
+ }
132
+ /**
133
+ * Record a checkpoint (git commit)
134
+ */
135
+ async recordCheckpoint(commit, message) {
136
+ if (!this.state) return;
137
+ this.state.lastCheckpoint = {
138
+ commit,
139
+ message,
140
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
141
+ };
142
+ if (this.iterations.length > 0) {
143
+ const lastIteration = this.iterations[this.iterations.length - 1];
144
+ lastIteration.checkpoint = { commit, message };
145
+ }
146
+ await this.save();
147
+ }
148
+ /**
149
+ * Update file counts
150
+ */
151
+ async updateFileCounts(created, modified) {
152
+ if (!this.state) return;
153
+ this.state.filesCreated += created;
154
+ this.state.filesModified += modified;
155
+ await this.save();
156
+ }
157
+ /**
158
+ * Update status
159
+ */
160
+ async setStatus(status, reason, errorMessage) {
161
+ if (!this.state) return;
162
+ this.state.status = status;
163
+ if (reason) {
164
+ this.state.cancelReason = reason;
165
+ }
166
+ if (errorMessage) {
167
+ this.state.errorMessage = errorMessage;
168
+ }
169
+ await this.save();
170
+ }
171
+ /**
172
+ * Check and update circuit breaker state
173
+ * Returns true if circuit breaker should trigger
174
+ */
175
+ checkCircuitBreaker(hasFileChanges, errorHash, isTestOnly, thresholds = DEFAULT_THRESHOLDS) {
176
+ if (!hasFileChanges) {
177
+ this.circuitBreaker.noProgressCount += 1;
178
+ if (this.circuitBreaker.noProgressCount >= thresholds.noProgress) {
179
+ return {
180
+ triggered: true,
181
+ reason: `No file changes for ${this.circuitBreaker.noProgressCount} consecutive iterations`
182
+ };
183
+ }
184
+ } else {
185
+ this.circuitBreaker.noProgressCount = 0;
186
+ }
187
+ if (errorHash) {
188
+ if (this.circuitBreaker.lastErrorHash === errorHash) {
189
+ this.circuitBreaker.sameErrorCount += 1;
190
+ if (this.circuitBreaker.sameErrorCount >= thresholds.sameError) {
191
+ return {
192
+ triggered: true,
193
+ reason: `Same error for ${this.circuitBreaker.sameErrorCount} consecutive iterations`
194
+ };
195
+ }
196
+ } else {
197
+ this.circuitBreaker.sameErrorCount = 1;
198
+ this.circuitBreaker.lastErrorHash = errorHash;
199
+ }
200
+ } else {
201
+ this.circuitBreaker.sameErrorCount = 0;
202
+ this.circuitBreaker.lastErrorHash = void 0;
203
+ }
204
+ if (isTestOnly) {
205
+ this.circuitBreaker.testOnlyCount += 1;
206
+ if (this.circuitBreaker.testOnlyCount >= thresholds.testOnly) {
207
+ return {
208
+ triggered: true,
209
+ reason: `Only running tests for ${this.circuitBreaker.testOnlyCount} consecutive iterations`
210
+ };
211
+ }
212
+ } else {
213
+ this.circuitBreaker.testOnlyCount = 0;
214
+ }
215
+ return { triggered: false };
216
+ }
217
+ /**
218
+ * Check if completion promise is in the output
219
+ */
220
+ checkCompletionPromise(output) {
221
+ if (!this.state) return false;
222
+ const promise = this.state.completionPromise;
223
+ return output.includes(promise) || output.includes(`<promise>${promise}</promise>`);
224
+ }
225
+ /**
226
+ * Clean up state file after session ends
227
+ */
228
+ async cleanup() {
229
+ try {
230
+ const filePath = this.getStatePath();
231
+ if (await fs.pathExists(filePath)) {
232
+ if (this.state && this.state.status === "running") {
233
+ this.state.status = "cancelled";
234
+ this.state.cancelReason = "error";
235
+ await this.save();
236
+ }
237
+ }
238
+ } catch (error) {
239
+ console.error("[automode] Failed to cleanup state:", error);
240
+ }
241
+ }
242
+ /**
243
+ * Parse state from markdown format
244
+ */
245
+ parseStateMarkdown(content) {
246
+ try {
247
+ const getValue = (key) => {
248
+ const regex = new RegExp(`\\*\\*${key}:\\*\\*\\s*(.+)`, "i");
249
+ const match = content.match(regex);
250
+ return match?.[1]?.trim();
251
+ };
252
+ const sessionId = getValue("Session ID") ?? getValue("sessionId");
253
+ const prompt = getValue("Prompt");
254
+ const startedAt = getValue("Started");
255
+ const currentIteration = parseInt(getValue("Current Iteration") ?? "0", 10);
256
+ const maxIterations = parseInt(getValue("Max Iterations") ?? "50", 10);
257
+ const status = getValue("Status") ?? "running";
258
+ const branch = getValue("Branch");
259
+ const worktreePath = getValue("Worktree");
260
+ const filesCreated = parseInt(getValue("Files Created") ?? "0", 10);
261
+ const filesModified = parseInt(getValue("Files Modified") ?? "0", 10);
262
+ const completionPromise = getValue("Completion Promise") ?? "DONE";
263
+ if (!sessionId || !prompt || !startedAt) {
264
+ return null;
265
+ }
266
+ return {
267
+ sessionId,
268
+ prompt,
269
+ startedAt,
270
+ currentIteration,
271
+ maxIterations,
272
+ status,
273
+ branch,
274
+ worktreePath,
275
+ filesCreated,
276
+ filesModified,
277
+ completionPromise
278
+ };
279
+ } catch {
280
+ return null;
281
+ }
282
+ }
283
+ /**
284
+ * Format state as markdown
285
+ */
286
+ formatStateMarkdown(state) {
287
+ const lines = [
288
+ "# Auto-Mode State",
289
+ "",
290
+ "## Session",
291
+ `- **Session ID:** ${state.sessionId}`,
292
+ `- **Started:** ${state.startedAt}`,
293
+ `- **Prompt:** ${state.prompt}`
294
+ ];
295
+ if (state.branch) {
296
+ lines.push(`- **Branch:** ${state.branch}`);
297
+ }
298
+ if (state.worktreePath) {
299
+ lines.push(`- **Worktree:** ${state.worktreePath}`);
300
+ }
301
+ lines.push(
302
+ "",
303
+ "## Progress",
304
+ `- **Current Iteration:** ${state.currentIteration}`,
305
+ `- **Max Iterations:** ${state.maxIterations}`,
306
+ `- **Status:** ${state.status}`,
307
+ "",
308
+ "## Metrics",
309
+ `- **Files Created:** ${state.filesCreated}`,
310
+ `- **Files Modified:** ${state.filesModified}`
311
+ );
312
+ if (state.lastCheckpoint) {
313
+ lines.push(
314
+ "",
315
+ "## Last Checkpoint",
316
+ `- **Commit:** ${state.lastCheckpoint.commit}`,
317
+ `- **Message:** ${state.lastCheckpoint.message}`,
318
+ `- **Timestamp:** ${state.lastCheckpoint.timestamp}`
319
+ );
320
+ }
321
+ lines.push(
322
+ "",
323
+ "## Settings",
324
+ `- **Completion Promise:** ${state.completionPromise}`
325
+ );
326
+ if (state.cancelReason) {
327
+ lines.push(`- **Cancel Reason:** ${state.cancelReason}`);
328
+ }
329
+ if (state.errorMessage) {
330
+ lines.push(`- **Error:** ${state.errorMessage}`);
331
+ }
332
+ lines.push("");
333
+ return lines.join("\n");
334
+ }
335
+ };
336
+ function hashError(error) {
337
+ return error.slice(0, 100).toLowerCase().replace(/\s+/g, " ").trim();
338
+ }
339
+
340
+ // src/core/AutomodeChangelog.ts
341
+ import fs2 from "fs-extra";
342
+ import path2 from "path";
343
+ var CHANGELOG_FILE = "AUTOMODE_CHANGELOG.md";
344
+ var STATUS_EMOJI = {
345
+ running: "\u{1F504}",
346
+ paused: "\u23F8\uFE0F",
347
+ completed: "\u2705",
348
+ cancelled: "\u26A0\uFE0F",
349
+ failed: "\u274C"
350
+ };
351
+ var STATUS_TEXT = {
352
+ running: "Running",
353
+ paused: "Paused",
354
+ completed: "Completed successfully",
355
+ cancelled: "Cancelled",
356
+ failed: "Failed"
357
+ };
358
+ async function generateChangelog(workspaceRoot, state, iterations, gitCommits = []) {
359
+ const changelogPath = path2.join(workspaceRoot, CHANGELOG_FILE);
360
+ const startTime = new Date(state.startedAt);
361
+ const endTime = /* @__PURE__ */ new Date();
362
+ const durationMs = endTime.getTime() - startTime.getTime();
363
+ const durationMinutes = Math.round(durationMs / 6e4);
364
+ const totalTokens = iterations.reduce((sum, it) => sum + (it.tokensUsed ?? 0), 0);
365
+ const totalCost = iterations.reduce((sum, it) => sum + (it.cost ?? 0), 0);
366
+ const lines = [
367
+ "# Auto-Mode Session Report",
368
+ "",
369
+ "## Summary",
370
+ "",
371
+ `- **Task:** ${state.prompt.slice(0, 100)}${state.prompt.length > 100 ? "..." : ""}`,
372
+ `- **Duration:** ${durationMinutes} minutes (${state.currentIteration} iterations)`,
373
+ `- **Result:** ${STATUS_EMOJI[state.status]} ${STATUS_TEXT[state.status]}`
374
+ ];
375
+ if (state.branch) {
376
+ const merged = state.status === "completed" ? " \u2192 merged to main" : "";
377
+ lines.push(`- **Branch:** ${state.branch}${merged}`);
378
+ }
379
+ if (state.cancelReason && state.status !== "completed") {
380
+ lines.push(`- **Reason:** ${formatCancelReason(state.cancelReason)}`);
381
+ }
382
+ if (state.errorMessage) {
383
+ lines.push(`- **Error:** ${state.errorMessage}`);
384
+ }
385
+ lines.push("", "## Iterations", "");
386
+ for (const iteration of iterations) {
387
+ const time = new Date(iteration.timestamp).toLocaleTimeString();
388
+ lines.push(`### Iteration ${iteration.iteration} (${time})`);
389
+ lines.push("");
390
+ if (iteration.actions.length > 0) {
391
+ for (const action of iteration.actions) {
392
+ lines.push(`- ${action}`);
393
+ }
394
+ } else {
395
+ lines.push("- No actions recorded");
396
+ }
397
+ if (iteration.checkpoint) {
398
+ lines.push("");
399
+ lines.push(`**Checkpoint:** \`${iteration.checkpoint.commit}\` - "${iteration.checkpoint.message}"`);
400
+ }
401
+ if (iteration.tokensUsed) {
402
+ lines.push("");
403
+ lines.push(`*Tokens: ${iteration.tokensUsed.toLocaleString()}*`);
404
+ }
405
+ lines.push("");
406
+ }
407
+ lines.push("## Final State", "");
408
+ lines.push(`- **Files Created:** ${state.filesCreated}`);
409
+ lines.push(`- **Files Modified:** ${state.filesModified}`);
410
+ lines.push(`- **Total Iterations:** ${state.currentIteration}`);
411
+ if (totalTokens > 0) {
412
+ lines.push(`- **Total Tokens:** ${totalTokens.toLocaleString()}`);
413
+ }
414
+ if (totalCost > 0) {
415
+ lines.push(`- **Estimated Cost:** $${totalCost.toFixed(2)}`);
416
+ }
417
+ if (gitCommits.length > 0) {
418
+ lines.push("", "## Commits Made", "");
419
+ gitCommits.forEach((commit, index) => {
420
+ lines.push(`${index + 1}. \`${commit.hash}\` - ${commit.message}`);
421
+ });
422
+ }
423
+ lines.push("", "---", "");
424
+ lines.push(`*Generated by autohand auto-mode on ${endTime.toISOString()}*`);
425
+ lines.push("");
426
+ const content = lines.join("\n");
427
+ await fs2.writeFile(changelogPath, content, "utf-8");
428
+ return changelogPath;
429
+ }
430
+ function formatCancelReason(reason) {
431
+ const reasonMap = {
432
+ user_escape: "User pressed ESC",
433
+ user_cancel: "User cancelled via command",
434
+ hook_cancel: "Cancelled by hook",
435
+ rpc_cancel: "Cancelled via RPC",
436
+ acp_cancel: "Cancelled via ACP",
437
+ max_iterations: "Maximum iterations reached",
438
+ max_runtime: "Maximum runtime exceeded",
439
+ max_cost: "Maximum cost exceeded",
440
+ circuit_breaker: "Circuit breaker triggered",
441
+ completion: "Completion promise detected",
442
+ error: "Error occurred"
443
+ };
444
+ return reasonMap[reason] ?? reason;
445
+ }
446
+
447
+ // src/core/PatternDetector.ts
448
+ import path3 from "path";
449
+ import fs3 from "fs-extra";
450
+ var PatternDetector = class {
451
+ constructor(workspaceRoot) {
452
+ this.workspaceRoot = workspaceRoot;
453
+ }
454
+ /**
455
+ * Detect all project patterns
456
+ */
457
+ async detect() {
458
+ const [techStack, commands, framework, packageManager] = await Promise.all([
459
+ this.detectTechStack(),
460
+ this.detectCommands(),
461
+ this.detectFramework(),
462
+ this.detectPackageManager()
463
+ ]);
464
+ return {
465
+ techStack,
466
+ testCommand: commands.test,
467
+ buildCommand: commands.build,
468
+ lintCommand: commands.lint,
469
+ framework,
470
+ packageManager
471
+ };
472
+ }
473
+ /**
474
+ * Detect tech stack from project files
475
+ */
476
+ async detectTechStack() {
477
+ const stack = [];
478
+ const packageJsonPath = path3.join(this.workspaceRoot, "package.json");
479
+ if (await fs3.pathExists(packageJsonPath)) {
480
+ try {
481
+ const pkg = await fs3.readJSON(packageJsonPath);
482
+ const allDeps = {
483
+ ...pkg.dependencies,
484
+ ...pkg.devDependencies
485
+ };
486
+ if (allDeps.typescript || await fs3.pathExists(path3.join(this.workspaceRoot, "tsconfig.json"))) {
487
+ stack.push("TypeScript");
488
+ } else {
489
+ stack.push("JavaScript");
490
+ }
491
+ if (allDeps.react) {
492
+ stack.push("React");
493
+ }
494
+ if (allDeps.vue) {
495
+ stack.push("Vue");
496
+ }
497
+ if (allDeps.svelte) {
498
+ stack.push("Svelte");
499
+ }
500
+ stack.push("Node.js");
501
+ if (allDeps.vitest) {
502
+ stack.push("Vitest");
503
+ } else if (allDeps.jest) {
504
+ stack.push("Jest");
505
+ } else if (allDeps.mocha) {
506
+ stack.push("Mocha");
507
+ }
508
+ } catch {
509
+ }
510
+ }
511
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "Cargo.toml"))) {
512
+ stack.push("Rust");
513
+ }
514
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "go.mod"))) {
515
+ stack.push("Go");
516
+ }
517
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "pyproject.toml")) || await fs3.pathExists(path3.join(this.workspaceRoot, "setup.py")) || await fs3.pathExists(path3.join(this.workspaceRoot, "requirements.txt"))) {
518
+ stack.push("Python");
519
+ }
520
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "pom.xml")) || await fs3.pathExists(path3.join(this.workspaceRoot, "build.gradle"))) {
521
+ stack.push("Java");
522
+ }
523
+ return stack;
524
+ }
525
+ /**
526
+ * Detect test, build, and lint commands from package.json scripts
527
+ */
528
+ async detectCommands() {
529
+ const commands = {};
530
+ const packageJsonPath = path3.join(this.workspaceRoot, "package.json");
531
+ if (await fs3.pathExists(packageJsonPath)) {
532
+ try {
533
+ const pkg = await fs3.readJSON(packageJsonPath);
534
+ const scripts = pkg.scripts ?? {};
535
+ const pm = await this.detectPackageManager();
536
+ const runCmd = pm === "bun" ? "bun" : pm === "yarn" ? "yarn" : pm === "pnpm" ? "pnpm" : "npm run";
537
+ if (scripts.test) {
538
+ commands.test = `${runCmd} test`;
539
+ }
540
+ if (scripts.build) {
541
+ commands.build = `${runCmd} build`;
542
+ }
543
+ if (scripts.lint) {
544
+ commands.lint = `${runCmd} lint`;
545
+ } else if (scripts.eslint) {
546
+ commands.lint = `${runCmd} eslint`;
547
+ }
548
+ } catch {
549
+ }
550
+ }
551
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "Makefile"))) {
552
+ try {
553
+ const makefile = await fs3.readFile(path3.join(this.workspaceRoot, "Makefile"), "utf-8");
554
+ if (!commands.test && makefile.includes("test:")) {
555
+ commands.test = "make test";
556
+ }
557
+ if (!commands.build && makefile.includes("build:")) {
558
+ commands.build = "make build";
559
+ }
560
+ if (!commands.lint && makefile.includes("lint:")) {
561
+ commands.lint = "make lint";
562
+ }
563
+ } catch {
564
+ }
565
+ }
566
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "Cargo.toml"))) {
567
+ if (!commands.test) commands.test = "cargo test";
568
+ if (!commands.build) commands.build = "cargo build";
569
+ if (!commands.lint) commands.lint = "cargo clippy";
570
+ }
571
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "go.mod"))) {
572
+ if (!commands.test) commands.test = "go test ./...";
573
+ if (!commands.build) commands.build = "go build";
574
+ if (!commands.lint) commands.lint = "golangci-lint run";
575
+ }
576
+ return commands;
577
+ }
578
+ /**
579
+ * Detect the main framework used
580
+ */
581
+ async detectFramework() {
582
+ const packageJsonPath = path3.join(this.workspaceRoot, "package.json");
583
+ if (await fs3.pathExists(packageJsonPath)) {
584
+ try {
585
+ const pkg = await fs3.readJSON(packageJsonPath);
586
+ const allDeps = {
587
+ ...pkg.dependencies,
588
+ ...pkg.devDependencies
589
+ };
590
+ if (allDeps.next) {
591
+ const version = allDeps.next.replace(/[^0-9.]/g, "").split(".")[0];
592
+ return `Next.js ${version}`;
593
+ }
594
+ if (allDeps["@remix-run/node"] || allDeps["@remix-run/react"]) {
595
+ return "Remix";
596
+ }
597
+ if (allDeps.astro) {
598
+ return "Astro";
599
+ }
600
+ if (allDeps.express) {
601
+ return "Express";
602
+ }
603
+ if (allDeps.fastify) {
604
+ return "Fastify";
605
+ }
606
+ if (allDeps["@nestjs/core"]) {
607
+ return "NestJS";
608
+ }
609
+ if (allDeps.hono) {
610
+ return "Hono";
611
+ }
612
+ if (allDeps.elysia) {
613
+ return "Elysia";
614
+ }
615
+ } catch {
616
+ }
617
+ }
618
+ return void 0;
619
+ }
620
+ /**
621
+ * Detect the package manager used
622
+ */
623
+ async detectPackageManager() {
624
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "bun.lockb"))) {
625
+ return "bun";
626
+ }
627
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "pnpm-lock.yaml"))) {
628
+ return "pnpm";
629
+ }
630
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "yarn.lock"))) {
631
+ return "yarn";
632
+ }
633
+ if (await fs3.pathExists(path3.join(this.workspaceRoot, "package-lock.json"))) {
634
+ return "npm";
635
+ }
636
+ return "npm";
637
+ }
638
+ };
639
+
640
+ // src/core/AgentsMdUpdater.ts
641
+ import path4 from "path";
642
+ import fs4 from "fs-extra";
643
+ var AgentsMdUpdater = class {
644
+ constructor(workspaceRoot) {
645
+ this.workspaceRoot = workspaceRoot;
646
+ this.agentsPath = path4.join(workspaceRoot, "AGENTS.md");
647
+ }
648
+ /**
649
+ * Update AGENTS.md with discovered patterns and skills
650
+ */
651
+ async update(options) {
652
+ const { patterns, usedSkills, conventions } = options;
653
+ let content = await this.loadOrCreateAgentsMd();
654
+ if (patterns) {
655
+ content = this.updateTechStackSection(content, patterns);
656
+ content = this.updateCommandsSection(content, patterns);
657
+ }
658
+ if (usedSkills && usedSkills.length > 0) {
659
+ content = this.updateSkillsSection(content, usedSkills);
660
+ }
661
+ if (conventions && conventions.length > 0) {
662
+ content = this.updateConventionsSection(content, conventions);
663
+ }
664
+ await fs4.writeFile(this.agentsPath, content, "utf-8");
665
+ return true;
666
+ }
667
+ /**
668
+ * Load existing AGENTS.md or create a new one
669
+ */
670
+ async loadOrCreateAgentsMd() {
671
+ if (await fs4.pathExists(this.agentsPath)) {
672
+ return await fs4.readFile(this.agentsPath, "utf-8");
673
+ }
674
+ return `# Project Autopilot
675
+
676
+ This file is automatically updated by Autohand to help AI assistants understand your project.
677
+
678
+ `;
679
+ }
680
+ /**
681
+ * Update the Tech Stack section
682
+ */
683
+ updateTechStackSection(content, patterns) {
684
+ if (patterns.techStack.length === 0) return content;
685
+ const techStackContent = patterns.techStack.map((tech) => `- ${tech}`).join("\n");
686
+ const sectionContent = `## Tech Stack
687
+
688
+ ${techStackContent}
689
+ `;
690
+ return this.updateSection(content, "Tech Stack", sectionContent);
691
+ }
692
+ /**
693
+ * Update the Commands section
694
+ */
695
+ updateCommandsSection(content, patterns) {
696
+ const commands = [];
697
+ if (patterns.packageManager) {
698
+ commands.push(`- **Package Manager:** \`${patterns.packageManager}\``);
699
+ }
700
+ if (patterns.testCommand) {
701
+ commands.push(`- **Test:** \`${patterns.testCommand}\``);
702
+ }
703
+ if (patterns.buildCommand) {
704
+ commands.push(`- **Build:** \`${patterns.buildCommand}\``);
705
+ }
706
+ if (patterns.lintCommand) {
707
+ commands.push(`- **Lint:** \`${patterns.lintCommand}\``);
708
+ }
709
+ if (patterns.framework) {
710
+ commands.push(`- **Framework:** ${patterns.framework}`);
711
+ }
712
+ if (commands.length === 0) return content;
713
+ const sectionContent = `## Commands
714
+
715
+ ${commands.join("\n")}
716
+ `;
717
+ return this.updateSection(content, "Commands", sectionContent);
718
+ }
719
+ /**
720
+ * Update the Skills section
721
+ */
722
+ updateSkillsSection(content, usedSkills) {
723
+ if (usedSkills.length === 0) return content;
724
+ const skillCounts = usedSkills.reduce((acc, skill) => {
725
+ acc[skill] = (acc[skill] || 0) + 1;
726
+ return acc;
727
+ }, {});
728
+ const skillsList = Object.entries(skillCounts).sort((a, b) => b[1] - a[1]).map(([skill, count]) => `- ${skill} (${count}x)`).join("\n");
729
+ const sectionContent = `## Skills Used
730
+
731
+ ${skillsList}
732
+ `;
733
+ return this.updateSection(content, "Skills Used", sectionContent);
734
+ }
735
+ /**
736
+ * Update the Conventions section
737
+ */
738
+ updateConventionsSection(content, conventions) {
739
+ if (conventions.length === 0) return content;
740
+ const conventionsList = conventions.map((c) => `- ${c}`).join("\n");
741
+ const sectionContent = `## Conventions
742
+
743
+ ${conventionsList}
744
+ `;
745
+ return this.updateSection(content, "Conventions", sectionContent);
746
+ }
747
+ /**
748
+ * Update or insert a section in the content
749
+ */
750
+ updateSection(content, sectionName, newSectionContent) {
751
+ const sectionRegex = new RegExp(
752
+ `## ${this.escapeRegex(sectionName)}[\\s\\S]*?(?=\\n## |$)`,
753
+ "g"
754
+ );
755
+ if (sectionRegex.test(content)) {
756
+ return content.replace(sectionRegex, newSectionContent.trim() + "\n");
757
+ }
758
+ const conventionsMatch = content.match(/\n## Conventions/);
759
+ if (conventionsMatch && conventionsMatch.index !== void 0) {
760
+ return content.slice(0, conventionsMatch.index) + "\n" + newSectionContent + "\n" + content.slice(conventionsMatch.index);
761
+ }
762
+ return content.trimEnd() + "\n\n" + newSectionContent;
763
+ }
764
+ /**
765
+ * Escape regex special characters
766
+ */
767
+ escapeRegex(str) {
768
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
769
+ }
770
+ /**
771
+ * Check if AGENTS.md exists
772
+ */
773
+ async exists() {
774
+ return fs4.pathExists(this.agentsPath);
775
+ }
776
+ /**
777
+ * Get the detected patterns summary for display
778
+ */
779
+ static formatPatternsSummary(patterns) {
780
+ const parts = [];
781
+ if (patterns.techStack.length > 0) {
782
+ parts.push(`Tech: ${patterns.techStack.join(", ")}`);
783
+ }
784
+ if (patterns.framework) {
785
+ parts.push(`Framework: ${patterns.framework}`);
786
+ }
787
+ if (patterns.packageManager) {
788
+ parts.push(`PM: ${patterns.packageManager}`);
789
+ }
790
+ return parts.join(" | ") || "No patterns detected";
791
+ }
792
+ };
793
+
794
+ // src/core/AutomodeManager.ts
795
+ var DEFAULTS = {
796
+ maxIterations: 50,
797
+ maxRuntime: 120,
798
+ // minutes
799
+ maxCost: 10,
800
+ // dollars
801
+ checkpointInterval: 5,
802
+ completionPromise: "DONE",
803
+ useWorktree: true,
804
+ noProgressThreshold: 3,
805
+ sameErrorThreshold: 5,
806
+ testOnlyThreshold: 3
807
+ };
808
+ var AutomodeManager = class extends EventEmitter {
809
+ constructor(config, workspaceRoot, hookManager, session, memoryManager) {
810
+ super();
811
+ this.abortController = null;
812
+ this.startTime = 0;
813
+ this.totalCost = 0;
814
+ this.worktreePath = null;
815
+ this.originalBranch = null;
816
+ this.branchName = null;
817
+ this.gitCommits = [];
818
+ this.isRunning = false;
819
+ this.isPaused = false;
820
+ this.config = config;
821
+ this.workspaceRoot = workspaceRoot;
822
+ this.hookManager = hookManager;
823
+ this.session = session;
824
+ this.memoryManager = memoryManager;
825
+ this.patternDetector = new PatternDetector(workspaceRoot);
826
+ this.agentsMdUpdater = new AgentsMdUpdater(workspaceRoot);
827
+ this.state = new AutomodeState(workspaceRoot);
828
+ this.settings = {
829
+ ...DEFAULTS,
830
+ ...config.automode
831
+ };
832
+ }
833
+ /**
834
+ * Check if auto-mode is currently running
835
+ */
836
+ isActive() {
837
+ return this.isRunning;
838
+ }
839
+ /**
840
+ * Check if auto-mode is paused
841
+ */
842
+ isPausedState() {
843
+ return this.isPaused;
844
+ }
845
+ /**
846
+ * Get current state
847
+ */
848
+ getState() {
849
+ return this.state.getState();
850
+ }
851
+ /**
852
+ * Start auto-mode loop
853
+ */
854
+ async start(options, runIteration) {
855
+ if (this.isRunning) {
856
+ throw new Error("Auto-mode is already running");
857
+ }
858
+ if (await this.state.hasActiveSession()) {
859
+ const existingState = await this.state.load();
860
+ if (existingState) {
861
+ console.log(chalk.yellow(`
862
+ \u26A0 Existing auto-mode session found: ${existingState.sessionId}`));
863
+ console.log(chalk.gray(` Status: ${existingState.status}, Iteration: ${existingState.currentIteration}`));
864
+ console.log(chalk.gray(` Use /automode resume to continue or /automode cancel to start fresh
865
+ `));
866
+ return;
867
+ }
868
+ }
869
+ this.isRunning = true;
870
+ this.isPaused = false;
871
+ this.abortController = new AbortController();
872
+ this.startTime = Date.now();
873
+ this.totalCost = 0;
874
+ this.gitCommits = [];
875
+ const maxIterations = options.maxIterations ?? this.settings.maxIterations;
876
+ const completionPromise = options.completionPromise ?? this.settings.completionPromise;
877
+ const useWorktree = options.useWorktree ?? this.settings.useWorktree;
878
+ const checkpointInterval = options.checkpointInterval ?? this.settings.checkpointInterval;
879
+ const maxRuntime = options.maxRuntime ?? this.settings.maxRuntime;
880
+ const maxCost = options.maxCost ?? this.settings.maxCost;
881
+ const sessionId = `automode-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
882
+ console.log(chalk.cyan("\n\u{1F504} Starting Auto-Mode"));
883
+ console.log(chalk.gray(` Session: ${sessionId}`));
884
+ console.log(chalk.gray(` Max iterations: ${maxIterations}`));
885
+ console.log(chalk.gray(` Completion promise: "${completionPromise}"`));
886
+ console.log(chalk.gray(` Worktree isolation: ${useWorktree ? "enabled" : "disabled"}`));
887
+ if (useWorktree) {
888
+ try {
889
+ await this.setupWorktree(sessionId);
890
+ console.log(chalk.gray(` Branch: ${this.branchName}`));
891
+ } catch (error) {
892
+ console.log(chalk.yellow(` \u26A0 Worktree setup failed, continuing in current branch`));
893
+ console.log(chalk.gray(` ${error}`));
894
+ }
895
+ }
896
+ await this.state.initialize({
897
+ sessionId,
898
+ prompt: options.prompt,
899
+ maxIterations,
900
+ completionPromise,
901
+ branch: this.branchName ?? void 0,
902
+ worktreePath: this.worktreePath ?? void 0
903
+ });
904
+ console.log(chalk.cyan("\n Press ESC to cancel auto-mode\n"));
905
+ await this.session?.append({
906
+ role: "user",
907
+ content: `[Auto-Mode Start] ${options.prompt}`,
908
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
909
+ _meta: {
910
+ automode: true,
911
+ sessionId,
912
+ maxIterations,
913
+ completionPromise
914
+ }
915
+ });
916
+ await this.emitHookEvent("automode:start", {
917
+ sessionId,
918
+ prompt: options.prompt,
919
+ maxIterations
920
+ });
921
+ try {
922
+ for (let iteration = 1; iteration <= maxIterations; iteration++) {
923
+ if (this.abortController.signal.aborted) {
924
+ break;
925
+ }
926
+ while (this.isPaused && !this.abortController.signal.aborted) {
927
+ await this.delay(500);
928
+ }
929
+ const elapsedMinutes = (Date.now() - this.startTime) / 6e4;
930
+ if (elapsedMinutes >= maxRuntime) {
931
+ await this.cancel("max_runtime");
932
+ break;
933
+ }
934
+ if (this.totalCost >= maxCost) {
935
+ await this.cancel("max_cost");
936
+ break;
937
+ }
938
+ console.log(chalk.cyan(`
939
+ \u{1F4CD} Iteration ${iteration}/${maxIterations}`));
940
+ const result = await runIteration(
941
+ iteration,
942
+ options.prompt,
943
+ this.abortController.signal
944
+ );
945
+ if (result.cost) {
946
+ this.totalCost += result.cost;
947
+ }
948
+ await this.session?.append({
949
+ role: "assistant",
950
+ content: result.output || `[Iteration ${iteration}] Actions: ${result.actions.join(", ")}`,
951
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
952
+ _meta: {
953
+ automode: true,
954
+ iteration,
955
+ actions: result.actions,
956
+ filesCreated: result.filesCreated,
957
+ filesModified: result.filesModified,
958
+ tokensUsed: result.tokensUsed,
959
+ cost: result.cost,
960
+ error: result.error
961
+ }
962
+ });
963
+ const iterationLog = {
964
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
965
+ actions: result.actions,
966
+ tokensUsed: result.tokensUsed,
967
+ cost: result.cost
968
+ };
969
+ if (result.filesCreated || result.filesModified) {
970
+ await this.state.updateFileCounts(
971
+ result.filesCreated ?? 0,
972
+ result.filesModified ?? 0
973
+ );
974
+ }
975
+ await this.emitHookEvent("automode:iteration", {
976
+ sessionId,
977
+ iteration,
978
+ actions: result.actions,
979
+ tokensUsed: result.tokensUsed
980
+ });
981
+ const hasChanges = (result.filesCreated ?? 0) + (result.filesModified ?? 0) > 0;
982
+ const errorHash = result.error ? hashError(result.error) : null;
983
+ const isTestOnly = result.actions.every(
984
+ (a) => a.toLowerCase().includes("test") || a.toLowerCase().includes("spec")
985
+ );
986
+ const circuitResult = this.state.checkCircuitBreaker(
987
+ hasChanges,
988
+ errorHash,
989
+ isTestOnly,
990
+ {
991
+ noProgress: this.settings.noProgressThreshold,
992
+ sameError: this.settings.sameErrorThreshold,
993
+ testOnly: this.settings.testOnlyThreshold
994
+ }
995
+ );
996
+ if (circuitResult.triggered) {
997
+ console.log(chalk.yellow(`
998
+ \u26A1 Circuit breaker triggered: ${circuitResult.reason}`));
999
+ await this.cancel("circuit_breaker");
1000
+ break;
1001
+ }
1002
+ if (result.output && this.state.checkCompletionPromise(result.output)) {
1003
+ console.log(chalk.green(`
1004
+ \u2705 Completion promise detected!`));
1005
+ await this.complete();
1006
+ break;
1007
+ }
1008
+ if (iteration % checkpointInterval === 0) {
1009
+ await this.createCheckpoint(iteration);
1010
+ }
1011
+ await this.state.recordIteration(iterationLog);
1012
+ }
1013
+ const currentState = this.state.getState();
1014
+ if (currentState && currentState.status === "running") {
1015
+ if (currentState.currentIteration >= maxIterations) {
1016
+ console.log(chalk.yellow(`
1017
+ \u26A0 Maximum iterations (${maxIterations}) reached`));
1018
+ await this.cancel("max_iterations");
1019
+ }
1020
+ }
1021
+ } catch (error) {
1022
+ console.error(chalk.red(`
1023
+ \u274C Auto-mode error: ${error}`));
1024
+ await this.state.setStatus("failed", "error", String(error));
1025
+ await this.emitHookEvent("automode:error", {
1026
+ sessionId,
1027
+ error: String(error)
1028
+ });
1029
+ } finally {
1030
+ await this.cleanup();
1031
+ }
1032
+ }
1033
+ /**
1034
+ * Cancel the auto-mode loop
1035
+ */
1036
+ async cancel(reason = "user_cancel") {
1037
+ if (!this.isRunning) return;
1038
+ console.log(chalk.yellow(`
1039
+ \u26A0 Auto-mode cancelled: ${reason}`));
1040
+ this.abortController?.abort();
1041
+ await this.state.setStatus("cancelled", reason);
1042
+ const currentState = this.state.getState();
1043
+ if (currentState) {
1044
+ await this.session?.append({
1045
+ role: "assistant",
1046
+ content: `[Auto-Mode Cancelled] Reason: ${reason}. Stopped at iteration ${currentState.currentIteration}. Files created: ${currentState.filesCreated}, Files modified: ${currentState.filesModified}`,
1047
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1048
+ _meta: {
1049
+ automode: true,
1050
+ status: "cancelled",
1051
+ reason,
1052
+ iterations: currentState.currentIteration,
1053
+ filesCreated: currentState.filesCreated,
1054
+ filesModified: currentState.filesModified,
1055
+ totalCost: this.totalCost
1056
+ }
1057
+ });
1058
+ await this.emitHookEvent("automode:cancel", {
1059
+ sessionId: currentState.sessionId,
1060
+ reason,
1061
+ iteration: currentState.currentIteration
1062
+ });
1063
+ }
1064
+ }
1065
+ /**
1066
+ * Pause the auto-mode loop
1067
+ */
1068
+ async pause() {
1069
+ if (!this.isRunning || this.isPaused) return;
1070
+ this.isPaused = true;
1071
+ await this.state.setStatus("paused");
1072
+ const currentState = this.state.getState();
1073
+ if (currentState) {
1074
+ console.log(chalk.yellow(`
1075
+ \u23F8\uFE0F Auto-mode paused at iteration ${currentState.currentIteration}`));
1076
+ await this.emitHookEvent("automode:pause", {
1077
+ sessionId: currentState.sessionId,
1078
+ iteration: currentState.currentIteration
1079
+ });
1080
+ }
1081
+ }
1082
+ /**
1083
+ * Resume the auto-mode loop
1084
+ */
1085
+ async resume() {
1086
+ if (!this.isRunning || !this.isPaused) return;
1087
+ this.isPaused = false;
1088
+ await this.state.setStatus("running");
1089
+ const currentState = this.state.getState();
1090
+ if (currentState) {
1091
+ console.log(chalk.cyan(`
1092
+ \u25B6\uFE0F Auto-mode resumed at iteration ${currentState.currentIteration}`));
1093
+ await this.emitHookEvent("automode:resume", {
1094
+ sessionId: currentState.sessionId,
1095
+ iteration: currentState.currentIteration
1096
+ });
1097
+ }
1098
+ }
1099
+ /**
1100
+ * Mark auto-mode as complete
1101
+ */
1102
+ async complete() {
1103
+ await this.state.setStatus("completed", "completion");
1104
+ const currentState = this.state.getState();
1105
+ if (currentState) {
1106
+ try {
1107
+ this.detectedPatterns = await this.patternDetector.detect();
1108
+ await this.storeDetectedPatterns();
1109
+ console.log(chalk.gray(` \u{1F50D} Detected patterns saved to memory`));
1110
+ await this.agentsMdUpdater.update({ patterns: this.detectedPatterns });
1111
+ console.log(chalk.gray(` \u{1F4DD} AGENTS.md updated with project info`));
1112
+ } catch (error) {
1113
+ console.log(chalk.gray(` \u26A0 Pattern detection failed: ${error}`));
1114
+ }
1115
+ await this.session?.append({
1116
+ role: "assistant",
1117
+ content: `[Auto-Mode Complete] Task completed successfully after ${currentState.currentIteration} iterations. Files created: ${currentState.filesCreated}, Files modified: ${currentState.filesModified}`,
1118
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1119
+ _meta: {
1120
+ automode: true,
1121
+ status: "completed",
1122
+ iterations: currentState.currentIteration,
1123
+ filesCreated: currentState.filesCreated,
1124
+ filesModified: currentState.filesModified,
1125
+ totalCost: this.totalCost,
1126
+ detectedPatterns: this.detectedPatterns
1127
+ }
1128
+ });
1129
+ await this.emitHookEvent("automode:complete", {
1130
+ sessionId: currentState.sessionId,
1131
+ iterations: currentState.currentIteration,
1132
+ filesCreated: currentState.filesCreated,
1133
+ filesModified: currentState.filesModified
1134
+ });
1135
+ if (this.worktreePath && this.branchName && this.originalBranch) {
1136
+ await this.mergeWorktree();
1137
+ }
1138
+ }
1139
+ }
1140
+ /**
1141
+ * Store detected patterns in project memory
1142
+ */
1143
+ async storeDetectedPatterns() {
1144
+ if (!this.memoryManager || !this.detectedPatterns) return;
1145
+ const patterns = this.detectedPatterns;
1146
+ if (patterns.techStack.length > 0) {
1147
+ await this.memoryManager.store(
1148
+ `Tech Stack: ${patterns.techStack.join(", ")}`,
1149
+ "project",
1150
+ ["auto-detected", "tech-stack"],
1151
+ "automode"
1152
+ );
1153
+ }
1154
+ if (patterns.framework) {
1155
+ await this.memoryManager.store(
1156
+ `Framework: ${patterns.framework}`,
1157
+ "project",
1158
+ ["auto-detected", "framework"],
1159
+ "automode"
1160
+ );
1161
+ }
1162
+ if (patterns.packageManager) {
1163
+ await this.memoryManager.store(
1164
+ `Package Manager: ${patterns.packageManager}`,
1165
+ "project",
1166
+ ["auto-detected", "package-manager"],
1167
+ "automode"
1168
+ );
1169
+ }
1170
+ if (patterns.testCommand) {
1171
+ await this.memoryManager.store(
1172
+ `Test Command: ${patterns.testCommand}`,
1173
+ "project",
1174
+ ["auto-detected", "command", "test"],
1175
+ "automode"
1176
+ );
1177
+ }
1178
+ if (patterns.buildCommand) {
1179
+ await this.memoryManager.store(
1180
+ `Build Command: ${patterns.buildCommand}`,
1181
+ "project",
1182
+ ["auto-detected", "command", "build"],
1183
+ "automode"
1184
+ );
1185
+ }
1186
+ if (patterns.lintCommand) {
1187
+ await this.memoryManager.store(
1188
+ `Lint Command: ${patterns.lintCommand}`,
1189
+ "project",
1190
+ ["auto-detected", "command", "lint"],
1191
+ "automode"
1192
+ );
1193
+ }
1194
+ }
1195
+ /**
1196
+ * Set up git worktree for isolation
1197
+ */
1198
+ async setupWorktree(sessionId) {
1199
+ try {
1200
+ this.originalBranch = execSync("git rev-parse --abbrev-ref HEAD", {
1201
+ cwd: this.workspaceRoot,
1202
+ encoding: "utf-8"
1203
+ }).trim();
1204
+ } catch {
1205
+ throw new Error("Not a git repository");
1206
+ }
1207
+ this.branchName = `autohand-automode-${Date.now()}`;
1208
+ const tempDir = path5.join("/tmp", `autohand-worktree-${crypto.randomBytes(4).toString("hex")}`);
1209
+ this.worktreePath = tempDir;
1210
+ try {
1211
+ execSync(`git worktree add -b ${this.branchName} ${tempDir}`, {
1212
+ cwd: this.workspaceRoot,
1213
+ encoding: "utf-8"
1214
+ });
1215
+ } catch (error) {
1216
+ this.worktreePath = null;
1217
+ this.branchName = null;
1218
+ throw error;
1219
+ }
1220
+ }
1221
+ /**
1222
+ * Create a checkpoint (git commit)
1223
+ */
1224
+ async createCheckpoint(iteration) {
1225
+ const workDir = this.worktreePath ?? this.workspaceRoot;
1226
+ try {
1227
+ const status = execSync("git status --porcelain", {
1228
+ cwd: workDir,
1229
+ encoding: "utf-8"
1230
+ }).trim();
1231
+ if (!status) {
1232
+ return;
1233
+ }
1234
+ execSync("git add -A", { cwd: workDir, encoding: "utf-8" });
1235
+ const message = `automode: checkpoint at iteration ${iteration}`;
1236
+ execSync(`git commit -m "${message}"`, { cwd: workDir, encoding: "utf-8" });
1237
+ const hash = execSync("git rev-parse --short HEAD", {
1238
+ cwd: workDir,
1239
+ encoding: "utf-8"
1240
+ }).trim();
1241
+ await this.state.recordCheckpoint(hash, message);
1242
+ this.gitCommits.push({ hash, message });
1243
+ console.log(chalk.gray(` \u{1F4CC} Checkpoint: ${hash}`));
1244
+ const currentState = this.state.getState();
1245
+ if (currentState) {
1246
+ await this.emitHookEvent("automode:checkpoint", {
1247
+ sessionId: currentState.sessionId,
1248
+ iteration,
1249
+ commit: hash
1250
+ });
1251
+ }
1252
+ } catch (error) {
1253
+ console.log(chalk.gray(` \u26A0 Checkpoint failed: ${error}`));
1254
+ }
1255
+ }
1256
+ /**
1257
+ * Merge worktree back to original branch
1258
+ */
1259
+ async mergeWorktree() {
1260
+ if (!this.worktreePath || !this.branchName || !this.originalBranch) {
1261
+ return;
1262
+ }
1263
+ try {
1264
+ console.log(chalk.cyan(`
1265
+ \u{1F500} Merging ${this.branchName} to ${this.originalBranch}...`));
1266
+ execSync(`git checkout ${this.originalBranch}`, {
1267
+ cwd: this.workspaceRoot,
1268
+ encoding: "utf-8"
1269
+ });
1270
+ execSync(`git merge ${this.branchName} --no-edit`, {
1271
+ cwd: this.workspaceRoot,
1272
+ encoding: "utf-8"
1273
+ });
1274
+ console.log(chalk.green(` \u2705 Successfully merged to ${this.originalBranch}`));
1275
+ await this.cleanupWorktree();
1276
+ } catch (error) {
1277
+ console.log(chalk.yellow(` \u26A0 Merge failed: ${error}`));
1278
+ console.log(chalk.gray(` Worktree preserved at: ${this.worktreePath}`));
1279
+ }
1280
+ }
1281
+ /**
1282
+ * Clean up worktree
1283
+ */
1284
+ async cleanupWorktree() {
1285
+ if (!this.worktreePath || !this.branchName) return;
1286
+ try {
1287
+ execSync(`git worktree remove ${this.worktreePath} --force`, {
1288
+ cwd: this.workspaceRoot,
1289
+ encoding: "utf-8"
1290
+ });
1291
+ execSync(`git branch -d ${this.branchName}`, {
1292
+ cwd: this.workspaceRoot,
1293
+ encoding: "utf-8"
1294
+ });
1295
+ } catch {
1296
+ }
1297
+ }
1298
+ /**
1299
+ * Clean up after auto-mode ends
1300
+ */
1301
+ async cleanup() {
1302
+ this.isRunning = false;
1303
+ this.isPaused = false;
1304
+ this.abortController = null;
1305
+ const currentState = this.state.getState();
1306
+ if (currentState) {
1307
+ try {
1308
+ const changelogPath = await generateChangelog(
1309
+ this.workspaceRoot,
1310
+ currentState,
1311
+ this.state.getIterations(),
1312
+ this.gitCommits
1313
+ );
1314
+ console.log(chalk.gray(`
1315
+ \u{1F4DD} Changelog saved: ${changelogPath}`));
1316
+ } catch (error) {
1317
+ console.log(chalk.gray(` \u26A0 Changelog generation failed: ${error}`));
1318
+ }
1319
+ }
1320
+ this.printSummary();
1321
+ }
1322
+ /**
1323
+ * Print session summary
1324
+ */
1325
+ printSummary() {
1326
+ const state = this.state.getState();
1327
+ if (!state) return;
1328
+ const durationMs = Date.now() - this.startTime;
1329
+ const durationMinutes = Math.round(durationMs / 6e4);
1330
+ console.log(chalk.cyan("\n\u{1F4CA} Auto-Mode Summary"));
1331
+ console.log(chalk.gray(` Status: ${state.status}`));
1332
+ console.log(chalk.gray(` Iterations: ${state.currentIteration}`));
1333
+ console.log(chalk.gray(` Duration: ${durationMinutes} minutes`));
1334
+ console.log(chalk.gray(` Files created: ${state.filesCreated}`));
1335
+ console.log(chalk.gray(` Files modified: ${state.filesModified}`));
1336
+ if (this.totalCost > 0) {
1337
+ console.log(chalk.gray(` Estimated cost: $${this.totalCost.toFixed(2)}`));
1338
+ }
1339
+ if (state.branch) {
1340
+ console.log(chalk.gray(` Branch: ${state.branch}`));
1341
+ }
1342
+ console.log("");
1343
+ }
1344
+ /**
1345
+ * Emit a hook event
1346
+ */
1347
+ async emitHookEvent(event, context) {
1348
+ this.emit(event, context);
1349
+ if (this.hookManager) {
1350
+ try {
1351
+ await this.hookManager.executeHooks(event, context);
1352
+ } catch {
1353
+ }
1354
+ }
1355
+ }
1356
+ /**
1357
+ * Delay helper
1358
+ */
1359
+ delay(ms) {
1360
+ return new Promise((resolve) => setTimeout(resolve, ms));
1361
+ }
1362
+ };
1363
+ function getAutomodeOptions(cliOptions, config) {
1364
+ if (!cliOptions.autoMode) {
1365
+ return null;
1366
+ }
1367
+ const configSettings = config.automode ?? {};
1368
+ return {
1369
+ prompt: cliOptions.autoMode,
1370
+ maxIterations: cliOptions.maxIterations ?? configSettings.maxIterations ?? DEFAULTS.maxIterations,
1371
+ completionPromise: cliOptions.completionPromise ?? configSettings.completionPromise ?? DEFAULTS.completionPromise,
1372
+ useWorktree: cliOptions.noWorktree === true ? false : configSettings.useWorktree ?? DEFAULTS.useWorktree,
1373
+ checkpointInterval: cliOptions.checkpointInterval ?? configSettings.checkpointInterval ?? DEFAULTS.checkpointInterval,
1374
+ maxRuntime: cliOptions.maxRuntime ?? configSettings.maxRuntime ?? DEFAULTS.maxRuntime,
1375
+ maxCost: cliOptions.maxCost ?? configSettings.maxCost ?? DEFAULTS.maxCost,
1376
+ dryRun: cliOptions.dryRun
1377
+ };
1378
+ }
1379
+ export {
1380
+ AutomodeManager,
1381
+ getAutomodeOptions
1382
+ };
1383
+ /**
1384
+ * Auto-Mode State Management
1385
+ *
1386
+ * Handles reading/writing the .autohand/automode.local.md state file
1387
+ * that tracks auto-mode session progress.
1388
+ *
1389
+ * @license Apache-2.0
1390
+ */
1391
+ /**
1392
+ * Auto-Mode Changelog Generation
1393
+ *
1394
+ * Generates AUTOMODE_CHANGELOG.md after auto-mode session completes.
1395
+ *
1396
+ * @license Apache-2.0
1397
+ */
1398
+ /**
1399
+ * Pattern Detector
1400
+ *
1401
+ * Automatically detects project patterns like tech stack, test/build/lint commands,
1402
+ * and frameworks from project configuration files.
1403
+ *
1404
+ * @license Apache-2.0
1405
+ */
1406
+ /**
1407
+ * AGENTS.md Updater
1408
+ *
1409
+ * Automatically updates AGENTS.md with discovered project patterns,
1410
+ * tech stack, commands, and skills used during auto-mode sessions.
1411
+ *
1412
+ * @license Apache-2.0
1413
+ */
1414
+ /**
1415
+ * Auto-Mode Manager
1416
+ *
1417
+ * Orchestrates the autonomous loop feature inspired by the Ralph technique.
1418
+ * Manages iteration cycles, git worktree isolation, checkpointing, and
1419
+ * cancellation via ESC, hooks, RPC, and ACP.
1420
+ *
1421
+ * @license Apache-2.0
1422
+ */