opencastle 0.26.1 → 0.27.1

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 (226) hide show
  1. package/README.md +7 -1
  2. package/bin/cli.mjs +10 -0
  3. package/dist/cli/agents.d.ts +3 -0
  4. package/dist/cli/agents.d.ts.map +1 -0
  5. package/dist/cli/agents.js +161 -0
  6. package/dist/cli/agents.js.map +1 -0
  7. package/dist/cli/baselines.d.ts +3 -0
  8. package/dist/cli/baselines.d.ts.map +1 -0
  9. package/dist/cli/baselines.js +128 -0
  10. package/dist/cli/baselines.js.map +1 -0
  11. package/dist/cli/convoy/engine.d.ts +68 -2
  12. package/dist/cli/convoy/engine.d.ts.map +1 -1
  13. package/dist/cli/convoy/engine.js +2102 -26
  14. package/dist/cli/convoy/engine.js.map +1 -1
  15. package/dist/cli/convoy/engine.test.js +1572 -70
  16. package/dist/cli/convoy/engine.test.js.map +1 -1
  17. package/dist/cli/convoy/events.d.ts +4 -1
  18. package/dist/cli/convoy/events.d.ts.map +1 -1
  19. package/dist/cli/convoy/events.js +74 -13
  20. package/dist/cli/convoy/events.js.map +1 -1
  21. package/dist/cli/convoy/events.test.js +154 -27
  22. package/dist/cli/convoy/events.test.js.map +1 -1
  23. package/dist/cli/convoy/expertise.d.ts +16 -0
  24. package/dist/cli/convoy/expertise.d.ts.map +1 -0
  25. package/dist/cli/convoy/expertise.js +121 -0
  26. package/dist/cli/convoy/expertise.js.map +1 -0
  27. package/dist/cli/convoy/expertise.test.d.ts +2 -0
  28. package/dist/cli/convoy/expertise.test.d.ts.map +1 -0
  29. package/dist/cli/convoy/expertise.test.js +96 -0
  30. package/dist/cli/convoy/expertise.test.js.map +1 -0
  31. package/dist/cli/convoy/export.test.js +1 -0
  32. package/dist/cli/convoy/export.test.js.map +1 -1
  33. package/dist/cli/convoy/formula.d.ts +19 -0
  34. package/dist/cli/convoy/formula.d.ts.map +1 -0
  35. package/dist/cli/convoy/formula.js +142 -0
  36. package/dist/cli/convoy/formula.js.map +1 -0
  37. package/dist/cli/convoy/formula.test.d.ts +2 -0
  38. package/dist/cli/convoy/formula.test.d.ts.map +1 -0
  39. package/dist/cli/convoy/formula.test.js +342 -0
  40. package/dist/cli/convoy/formula.test.js.map +1 -0
  41. package/dist/cli/convoy/gates.d.ts +128 -0
  42. package/dist/cli/convoy/gates.d.ts.map +1 -0
  43. package/dist/cli/convoy/gates.js +606 -0
  44. package/dist/cli/convoy/gates.js.map +1 -0
  45. package/dist/cli/convoy/gates.test.d.ts +2 -0
  46. package/dist/cli/convoy/gates.test.d.ts.map +1 -0
  47. package/dist/cli/convoy/gates.test.js +976 -0
  48. package/dist/cli/convoy/gates.test.js.map +1 -0
  49. package/dist/cli/convoy/health.d.ts +11 -0
  50. package/dist/cli/convoy/health.d.ts.map +1 -1
  51. package/dist/cli/convoy/health.js +54 -0
  52. package/dist/cli/convoy/health.js.map +1 -1
  53. package/dist/cli/convoy/health.test.js +56 -1
  54. package/dist/cli/convoy/health.test.js.map +1 -1
  55. package/dist/cli/convoy/issues.d.ts +8 -0
  56. package/dist/cli/convoy/issues.d.ts.map +1 -0
  57. package/dist/cli/convoy/issues.js +98 -0
  58. package/dist/cli/convoy/issues.js.map +1 -0
  59. package/dist/cli/convoy/issues.test.d.ts +2 -0
  60. package/dist/cli/convoy/issues.test.d.ts.map +1 -0
  61. package/dist/cli/convoy/issues.test.js +107 -0
  62. package/dist/cli/convoy/issues.test.js.map +1 -0
  63. package/dist/cli/convoy/knowledge.d.ts +5 -0
  64. package/dist/cli/convoy/knowledge.d.ts.map +1 -0
  65. package/dist/cli/convoy/knowledge.js +116 -0
  66. package/dist/cli/convoy/knowledge.js.map +1 -0
  67. package/dist/cli/convoy/knowledge.test.d.ts +2 -0
  68. package/dist/cli/convoy/knowledge.test.d.ts.map +1 -0
  69. package/dist/cli/convoy/knowledge.test.js +87 -0
  70. package/dist/cli/convoy/knowledge.test.js.map +1 -0
  71. package/dist/cli/convoy/lessons.d.ts +17 -0
  72. package/dist/cli/convoy/lessons.d.ts.map +1 -0
  73. package/dist/cli/convoy/lessons.js +149 -0
  74. package/dist/cli/convoy/lessons.js.map +1 -0
  75. package/dist/cli/convoy/lessons.test.d.ts +2 -0
  76. package/dist/cli/convoy/lessons.test.d.ts.map +1 -0
  77. package/dist/cli/convoy/lessons.test.js +135 -0
  78. package/dist/cli/convoy/lessons.test.js.map +1 -0
  79. package/dist/cli/convoy/lock.d.ts +13 -0
  80. package/dist/cli/convoy/lock.d.ts.map +1 -0
  81. package/dist/cli/convoy/lock.js +88 -0
  82. package/dist/cli/convoy/lock.js.map +1 -0
  83. package/dist/cli/convoy/lock.test.d.ts +2 -0
  84. package/dist/cli/convoy/lock.test.d.ts.map +1 -0
  85. package/dist/cli/convoy/lock.test.js +136 -0
  86. package/dist/cli/convoy/lock.test.js.map +1 -0
  87. package/dist/cli/convoy/merge.d.ts +4 -0
  88. package/dist/cli/convoy/merge.d.ts.map +1 -1
  89. package/dist/cli/convoy/merge.js +18 -1
  90. package/dist/cli/convoy/merge.js.map +1 -1
  91. package/dist/cli/convoy/merge.test.js +6 -7
  92. package/dist/cli/convoy/merge.test.js.map +1 -1
  93. package/dist/cli/convoy/partition.d.ts +51 -0
  94. package/dist/cli/convoy/partition.d.ts.map +1 -0
  95. package/dist/cli/convoy/partition.js +186 -0
  96. package/dist/cli/convoy/partition.js.map +1 -0
  97. package/dist/cli/convoy/partition.test.d.ts +2 -0
  98. package/dist/cli/convoy/partition.test.d.ts.map +1 -0
  99. package/dist/cli/convoy/partition.test.js +315 -0
  100. package/dist/cli/convoy/partition.test.js.map +1 -0
  101. package/dist/cli/convoy/pipeline.test.js +6 -0
  102. package/dist/cli/convoy/pipeline.test.js.map +1 -1
  103. package/dist/cli/convoy/store.d.ts +47 -5
  104. package/dist/cli/convoy/store.d.ts.map +1 -1
  105. package/dist/cli/convoy/store.js +525 -19
  106. package/dist/cli/convoy/store.js.map +1 -1
  107. package/dist/cli/convoy/store.test.js +1345 -12
  108. package/dist/cli/convoy/store.test.js.map +1 -1
  109. package/dist/cli/convoy/types.d.ts +156 -2
  110. package/dist/cli/convoy/types.d.ts.map +1 -1
  111. package/dist/cli/destroy.d.ts +3 -0
  112. package/dist/cli/destroy.d.ts.map +1 -0
  113. package/dist/cli/destroy.js +69 -0
  114. package/dist/cli/destroy.js.map +1 -0
  115. package/dist/cli/destroy.test.d.ts +2 -0
  116. package/dist/cli/destroy.test.d.ts.map +1 -0
  117. package/dist/cli/destroy.test.js +116 -0
  118. package/dist/cli/destroy.test.js.map +1 -0
  119. package/dist/cli/gitignore.d.ts +9 -0
  120. package/dist/cli/gitignore.d.ts.map +1 -1
  121. package/dist/cli/gitignore.js +29 -0
  122. package/dist/cli/gitignore.js.map +1 -1
  123. package/dist/cli/plan.d.ts +3 -0
  124. package/dist/cli/plan.d.ts.map +1 -0
  125. package/dist/cli/plan.js +288 -0
  126. package/dist/cli/plan.js.map +1 -0
  127. package/dist/cli/run/adapters/claude.d.ts +2 -0
  128. package/dist/cli/run/adapters/claude.d.ts.map +1 -1
  129. package/dist/cli/run/adapters/claude.js +89 -49
  130. package/dist/cli/run/adapters/claude.js.map +1 -1
  131. package/dist/cli/run/adapters/claude.test.d.ts +2 -0
  132. package/dist/cli/run/adapters/claude.test.d.ts.map +1 -0
  133. package/dist/cli/run/adapters/claude.test.js +205 -0
  134. package/dist/cli/run/adapters/claude.test.js.map +1 -0
  135. package/dist/cli/run/adapters/copilot.d.ts +1 -0
  136. package/dist/cli/run/adapters/copilot.d.ts.map +1 -1
  137. package/dist/cli/run/adapters/copilot.js +84 -46
  138. package/dist/cli/run/adapters/copilot.js.map +1 -1
  139. package/dist/cli/run/adapters/copilot.test.d.ts +2 -0
  140. package/dist/cli/run/adapters/copilot.test.d.ts.map +1 -0
  141. package/dist/cli/run/adapters/copilot.test.js +195 -0
  142. package/dist/cli/run/adapters/copilot.test.js.map +1 -0
  143. package/dist/cli/run/adapters/cursor.d.ts +1 -0
  144. package/dist/cli/run/adapters/cursor.d.ts.map +1 -1
  145. package/dist/cli/run/adapters/cursor.js +83 -47
  146. package/dist/cli/run/adapters/cursor.js.map +1 -1
  147. package/dist/cli/run/adapters/cursor.test.d.ts +2 -0
  148. package/dist/cli/run/adapters/cursor.test.d.ts.map +1 -0
  149. package/dist/cli/run/adapters/cursor.test.js +129 -0
  150. package/dist/cli/run/adapters/cursor.test.js.map +1 -0
  151. package/dist/cli/run/adapters/opencode.d.ts +1 -0
  152. package/dist/cli/run/adapters/opencode.d.ts.map +1 -1
  153. package/dist/cli/run/adapters/opencode.js +81 -47
  154. package/dist/cli/run/adapters/opencode.js.map +1 -1
  155. package/dist/cli/run/adapters/opencode.test.d.ts +2 -0
  156. package/dist/cli/run/adapters/opencode.test.d.ts.map +1 -0
  157. package/dist/cli/run/adapters/opencode.test.js +119 -0
  158. package/dist/cli/run/adapters/opencode.test.js.map +1 -0
  159. package/dist/cli/run/executor.js +1 -1
  160. package/dist/cli/run/executor.js.map +1 -1
  161. package/dist/cli/run/schema.d.ts.map +1 -1
  162. package/dist/cli/run/schema.js +245 -4
  163. package/dist/cli/run/schema.js.map +1 -1
  164. package/dist/cli/run/schema.test.js +669 -0
  165. package/dist/cli/run/schema.test.js.map +1 -1
  166. package/dist/cli/run.d.ts.map +1 -1
  167. package/dist/cli/run.js +362 -22
  168. package/dist/cli/run.js.map +1 -1
  169. package/dist/cli/types.d.ts +85 -2
  170. package/dist/cli/types.d.ts.map +1 -1
  171. package/dist/cli/types.js.map +1 -1
  172. package/dist/cli/watch.d.ts +15 -0
  173. package/dist/cli/watch.d.ts.map +1 -0
  174. package/dist/cli/watch.js +279 -0
  175. package/dist/cli/watch.js.map +1 -0
  176. package/package.json +1 -1
  177. package/src/cli/agents.ts +177 -0
  178. package/src/cli/baselines.ts +143 -0
  179. package/src/cli/convoy/engine.test.ts +1839 -70
  180. package/src/cli/convoy/engine.ts +2417 -38
  181. package/src/cli/convoy/events.test.ts +179 -38
  182. package/src/cli/convoy/events.ts +88 -16
  183. package/src/cli/convoy/expertise.test.ts +128 -0
  184. package/src/cli/convoy/expertise.ts +163 -0
  185. package/src/cli/convoy/export.test.ts +1 -0
  186. package/src/cli/convoy/formula.test.ts +405 -0
  187. package/src/cli/convoy/formula.ts +174 -0
  188. package/src/cli/convoy/gates.test.ts +1169 -0
  189. package/src/cli/convoy/gates.ts +774 -0
  190. package/src/cli/convoy/health.test.ts +64 -2
  191. package/src/cli/convoy/health.ts +80 -2
  192. package/src/cli/convoy/issues.test.ts +143 -0
  193. package/src/cli/convoy/issues.ts +136 -0
  194. package/src/cli/convoy/knowledge.test.ts +101 -0
  195. package/src/cli/convoy/knowledge.ts +132 -0
  196. package/src/cli/convoy/lessons.test.ts +188 -0
  197. package/src/cli/convoy/lessons.ts +164 -0
  198. package/src/cli/convoy/lock.test.ts +181 -0
  199. package/src/cli/convoy/lock.ts +103 -0
  200. package/src/cli/convoy/merge.test.ts +6 -7
  201. package/src/cli/convoy/merge.ts +19 -1
  202. package/src/cli/convoy/partition.test.ts +423 -0
  203. package/src/cli/convoy/partition.ts +232 -0
  204. package/src/cli/convoy/pipeline.test.ts +6 -0
  205. package/src/cli/convoy/store.test.ts +1512 -14
  206. package/src/cli/convoy/store.ts +676 -30
  207. package/src/cli/convoy/types.ts +170 -1
  208. package/src/cli/destroy.test.ts +141 -0
  209. package/src/cli/destroy.ts +88 -0
  210. package/src/cli/gitignore.ts +36 -0
  211. package/src/cli/plan.ts +316 -0
  212. package/src/cli/run/adapters/claude.test.ts +234 -0
  213. package/src/cli/run/adapters/claude.ts +45 -5
  214. package/src/cli/run/adapters/copilot.test.ts +224 -0
  215. package/src/cli/run/adapters/copilot.ts +34 -4
  216. package/src/cli/run/adapters/cursor.test.ts +144 -0
  217. package/src/cli/run/adapters/cursor.ts +33 -2
  218. package/src/cli/run/adapters/opencode.test.ts +135 -0
  219. package/src/cli/run/adapters/opencode.ts +30 -2
  220. package/src/cli/run/executor.ts +1 -1
  221. package/src/cli/run/schema.test.ts +758 -0
  222. package/src/cli/run/schema.ts +300 -25
  223. package/src/cli/run.ts +341 -21
  224. package/src/cli/types.ts +86 -1
  225. package/src/cli/watch.ts +298 -0
  226. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
@@ -0,0 +1,298 @@
1
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
2
+ import { dirname, resolve } from 'node:path'
3
+ import type { TaskSpec, AgentAdapter } from './types.js'
4
+ import type { ConvoyResult } from './convoy/engine.js'
5
+ import type { WatchConfig } from './convoy/types.js'
6
+ import { parseTaskSpecText, parseYaml } from './run/schema.js'
7
+ import { c } from './prompt.js'
8
+
9
+ export interface WatchLoopOptions {
10
+ spec: TaskSpec
11
+ specText: string
12
+ specPath: string
13
+ adapter: AgentAdapter
14
+ verbose: boolean
15
+ pidPath: string
16
+ clearScratchpad: boolean
17
+ watchConfigPath: string | null
18
+ printResult: (result: ConvoyResult) => void
19
+ }
20
+
21
+ function parseCronField(field: string, min: number, max: number): number[] {
22
+ if (field === '*') {
23
+ const all: number[] = []
24
+ for (let i = min; i <= max; i++) all.push(i)
25
+ return all
26
+ }
27
+ const values: number[] = []
28
+ for (const part of field.split(',')) {
29
+ if (part.includes('/')) {
30
+ const [rangeStr, stepStr] = part.split('/')
31
+ const step = parseInt(stepStr, 10)
32
+ if (!Number.isFinite(step) || step < 1) continue
33
+ let start = min
34
+ let end = max
35
+ if (rangeStr !== '*') {
36
+ const [s, e] = rangeStr.split('-').map(Number)
37
+ start = s
38
+ end = e ?? s
39
+ }
40
+ for (let i = start; i <= end; i += step) values.push(i)
41
+ } else if (part.includes('-')) {
42
+ const [s, e] = part.split('-').map(Number)
43
+ for (let i = s; i <= e; i++) values.push(i)
44
+ } else {
45
+ const n = parseInt(part, 10)
46
+ if (Number.isFinite(n)) values.push(n)
47
+ }
48
+ }
49
+ return values
50
+ }
51
+
52
+ function cronMatches(schedule: string, date: Date): boolean {
53
+ const parts = schedule.trim().split(/\s+/)
54
+ if (parts.length !== 5) return false
55
+ const [minF, hourF, domF, monF, dowF] = parts
56
+ const minute = parseCronField(minF, 0, 59)
57
+ const hour = parseCronField(hourF, 0, 23)
58
+ const dom = parseCronField(domF, 1, 31)
59
+ const mon = parseCronField(monF, 1, 12)
60
+ const dow = parseCronField(dowF, 0, 6)
61
+ return (
62
+ minute.includes(date.getMinutes()) &&
63
+ hour.includes(date.getHours()) &&
64
+ dom.includes(date.getDate()) &&
65
+ mon.includes(date.getMonth() + 1) &&
66
+ dow.includes(date.getDay())
67
+ )
68
+ }
69
+
70
+ function sleep(ms: number): Promise<void> {
71
+ return new Promise(resolve => setTimeout(resolve, ms))
72
+ }
73
+
74
+ export async function watchLoop(options: WatchLoopOptions): Promise<void> {
75
+ const {
76
+ spec,
77
+ specText,
78
+ specPath,
79
+ adapter,
80
+ verbose,
81
+ pidPath,
82
+ clearScratchpad,
83
+ watchConfigPath,
84
+ printResult,
85
+ } = options
86
+
87
+ // Resolve watch config — from --watch-config file, spec.watch, or default file-change
88
+ let watchConfig: WatchConfig
89
+ if (watchConfigPath) {
90
+ const raw = readFileSync(watchConfigPath, 'utf8')
91
+ const parsed = parseYaml(raw) as Record<string, unknown>
92
+ watchConfig = parsed as unknown as WatchConfig
93
+ } else if (spec.watch) {
94
+ watchConfig = spec.watch
95
+ } else {
96
+ // Default: file-change trigger on the spec file itself
97
+ watchConfig = {
98
+ triggers: [{ type: 'file-change', glob: specPath, debounce_ms: 500 }],
99
+ }
100
+ }
101
+
102
+ // Write PID file
103
+ mkdirSync(dirname(pidPath), { recursive: true })
104
+ writeFileSync(pidPath, String(process.pid), 'utf8')
105
+
106
+ let shuttingDown = false
107
+ let currentRun: Promise<ConvoyResult> | null = null
108
+ let cycleNumber = 0
109
+
110
+ // Graceful shutdown handlers
111
+ function onShutdownSignal(): void {
112
+ if (shuttingDown) return
113
+ shuttingDown = true
114
+ console.log(`\n ${c.yellow('\u26a0')} Watch mode shutting down...`)
115
+ // If a run is in progress, we let it finish
116
+ if (!currentRun) {
117
+ cleanup()
118
+ process.exit(0)
119
+ }
120
+ }
121
+
122
+ process.on('SIGTERM', onShutdownSignal)
123
+ process.on('SIGINT', onShutdownSignal)
124
+
125
+ function cleanup(): void {
126
+ try { unlinkSync(pidPath) } catch { /* ignore */ }
127
+ process.removeListener('SIGTERM', onShutdownSignal)
128
+ process.removeListener('SIGINT', onShutdownSignal)
129
+ }
130
+
131
+ // Emit watch_started
132
+ const { createConvoyStore } = await import('./convoy/store.js')
133
+ const { createEventEmitter } = await import('./convoy/events.js')
134
+ const dbPath = resolve(process.cwd(), '.opencastle', 'convoy.db')
135
+ mkdirSync(dirname(dbPath), { recursive: true })
136
+ const evtStore = createConvoyStore(dbPath)
137
+ const ndjsonPath = resolve(process.cwd(), '.opencastle', 'logs', 'convoy-events.ndjson')
138
+ mkdirSync(dirname(ndjsonPath), { recursive: true })
139
+ const watchEvents = createEventEmitter(evtStore, { ndjsonPath })
140
+
141
+ const triggerTypes = watchConfig.triggers.map(t => t.type).join(',')
142
+ watchEvents.emit('watch_started', { trigger_type: triggerTypes, pid: process.pid })
143
+
144
+ // Clear scratchpad if requested
145
+ if (clearScratchpad || watchConfig.clear_scratchpad) {
146
+ evtStore.clearScratchpad()
147
+ }
148
+
149
+ console.log(`\n ${c.cyan('\ud83d\udc41')} Watch mode active (PID: ${process.pid})`)
150
+ console.log(` Triggers: ${watchConfig.triggers.map(t => t.type).join(', ')}`)
151
+
152
+ // Set up trigger watchers
153
+ let triggerFired = false
154
+ let triggerSource = ''
155
+ const triggerCleanups: Array<() => void> = []
156
+
157
+ for (const trigger of watchConfig.triggers) {
158
+ if (trigger.type === 'file-change') {
159
+ const { watch } = await import('node:fs')
160
+ const debounceMs = trigger.debounce_ms ?? 500
161
+ let debounceTimer: ReturnType<typeof setTimeout> | undefined
162
+ const globPattern = trigger.glob ?? '**/*'
163
+ const watcher = watch(resolve(process.cwd()), { recursive: true }, (_event, filename) => {
164
+ if (!filename) return
165
+ const globStr = globPattern
166
+ let matches = false
167
+ if (globStr.includes('*')) {
168
+ // Convert simple glob to regex
169
+ const re = new RegExp('^' + globStr.replace(/\./g, '\\.').replace(/\*\*/g, '\u29bf').replace(/\*/g, '[^/]*').replace(/\u29bf/g, '.*') + '$')
170
+ matches = re.test(filename)
171
+ } else {
172
+ matches = filename === globStr || filename.endsWith('/' + globStr)
173
+ }
174
+ if (matches) {
175
+ if (debounceTimer) clearTimeout(debounceTimer)
176
+ debounceTimer = setTimeout(() => {
177
+ triggerFired = true
178
+ triggerSource = `file-change:${filename}`
179
+ }, debounceMs)
180
+ }
181
+ })
182
+ triggerCleanups.push(() => { watcher.close(); if (debounceTimer) clearTimeout(debounceTimer) })
183
+ } else if (trigger.type === 'cron') {
184
+ // Poll every 30s to check if cron matches
185
+ let lastFired = -1
186
+ const interval = setInterval(() => {
187
+ const now = new Date()
188
+ const minuteKey = now.getFullYear() * 525600 + now.getMonth() * 43800 + now.getDate() * 1440 + now.getHours() * 60 + now.getMinutes()
189
+ if (minuteKey !== lastFired && cronMatches(trigger.schedule!, now)) {
190
+ lastFired = minuteKey
191
+ triggerFired = true
192
+ triggerSource = `cron:${trigger.schedule}`
193
+ }
194
+ }, 30_000)
195
+ triggerCleanups.push(() => clearInterval(interval))
196
+ } else if (trigger.type === 'git-push') {
197
+ // Poll git remote every 60s
198
+ let lastRef = ''
199
+ const { execFile: execFileCb } = await import('node:child_process')
200
+ const { promisify } = await import('node:util')
201
+ const execFileAsync = promisify(execFileCb)
202
+ const interval = setInterval(async () => {
203
+ try {
204
+ await execFileAsync('git', ['fetch', '--quiet'], { cwd: process.cwd() })
205
+ const { stdout } = await execFileAsync('git', ['rev-parse', 'FETCH_HEAD'], { cwd: process.cwd() })
206
+ const ref = stdout.trim()
207
+ if (lastRef && ref !== lastRef) {
208
+ triggerFired = true
209
+ triggerSource = `git-push:${ref.slice(0, 8)}`
210
+ }
211
+ lastRef = ref
212
+ } catch { /* ignore fetch errors */ }
213
+ }, 60_000)
214
+ triggerCleanups.push(() => clearInterval(interval))
215
+ }
216
+ }
217
+
218
+ // Main watch loop
219
+ try {
220
+ while (!shuttingDown) {
221
+ // Wait for a trigger
222
+ while (!triggerFired && !shuttingDown) {
223
+ await sleep(500)
224
+ }
225
+
226
+ if (shuttingDown) break
227
+ triggerFired = false
228
+
229
+ cycleNumber++
230
+ console.log(`\n ${c.cyan('\u27f3')} Watch cycle ${cycleNumber} triggered by: ${triggerSource}`)
231
+
232
+ // Retention cleanup of agent identities
233
+ try {
234
+ evtStore.deleteAgentIdentitiesOlderThan(90)
235
+ } catch { /* non-critical */ }
236
+
237
+ // Scratchpad retention cleanup
238
+ if (watchConfig.scratchpad_retention_days) {
239
+ try {
240
+ evtStore.clearScratchpadOlderThan(watchConfig.scratchpad_retention_days)
241
+ } catch { /* non-critical */ }
242
+ }
243
+
244
+ watchEvents.emit('watch_cycle_start', { cycle_number: cycleNumber, triggered_by: triggerSource })
245
+
246
+ // Re-read spec from disk (may have changed since last cycle)
247
+ let cycleSpec: TaskSpec
248
+ let cycleSpecText: string
249
+ try {
250
+ cycleSpecText = readFileSync(specPath, 'utf8')
251
+ cycleSpec = parseTaskSpecText(cycleSpecText)
252
+ // Apply overrides from original spec
253
+ if (spec.concurrency !== 1) cycleSpec.concurrency = spec.concurrency
254
+ if (spec.adapter) cycleSpec.adapter = spec.adapter
255
+ if (verbose) cycleSpec._verbose = true
256
+ } catch (err) {
257
+ console.error(` ${c.red('\u2717')} Failed to re-read spec: ${(err as Error).message}`)
258
+ watchEvents.emit('watch_cycle_end', { cycle_number: cycleNumber, status: 'error' })
259
+ continue
260
+ }
261
+
262
+ // Run convoy
263
+ const { createConvoyEngine } = await import('./convoy/engine.js')
264
+ const cycleEngine = createConvoyEngine({
265
+ spec: cycleSpec,
266
+ specYaml: cycleSpecText,
267
+ adapter,
268
+ verbose,
269
+ })
270
+
271
+ let cycleResult: ConvoyResult
272
+ try {
273
+ currentRun = cycleEngine.run()
274
+ cycleResult = await currentRun
275
+ currentRun = null
276
+ } catch (err) {
277
+ currentRun = null
278
+ console.error(` ${c.red('\u2717')} Cycle ${cycleNumber} failed: ${(err as Error).message}`)
279
+ watchEvents.emit('watch_cycle_end', { cycle_number: cycleNumber, status: 'error' })
280
+ continue
281
+ }
282
+
283
+ printResult(cycleResult)
284
+ watchEvents.emit('watch_cycle_end', { cycle_number: cycleNumber, status: cycleResult.status })
285
+
286
+ if (shuttingDown) break
287
+ }
288
+ } finally {
289
+ // Clean up
290
+ watchEvents.emit('watch_stopped', { reason: shuttingDown ? 'signal' : 'exit' })
291
+ watchEvents.close()
292
+ evtStore.close()
293
+ for (const fn of triggerCleanups) {
294
+ try { fn() } catch { /* ignore */ }
295
+ }
296
+ cleanup()
297
+ }
298
+ }
@@ -1,25 +1,25 @@
1
1
  {
2
- "hash": "0f52f4b3",
2
+ "hash": "d3546064",
3
3
  "configHash": "30f8ea04",
4
- "lockfileHash": "cf370ff3",
5
- "browserHash": "cd4e9a91",
4
+ "lockfileHash": "2237f08d",
5
+ "browserHash": "25793c93",
6
6
  "optimized": {
7
7
  "astro > cssesc": {
8
8
  "src": "../../../../../node_modules/cssesc/cssesc.js",
9
9
  "file": "astro___cssesc.js",
10
- "fileHash": "cfa5042b",
10
+ "fileHash": "e7dc1213",
11
11
  "needsInterop": true
12
12
  },
13
13
  "astro > aria-query": {
14
14
  "src": "../../../../../node_modules/aria-query/lib/index.js",
15
15
  "file": "astro___aria-query.js",
16
- "fileHash": "80fa9785",
16
+ "fileHash": "f017ff62",
17
17
  "needsInterop": true
18
18
  },
19
19
  "astro > axobject-query": {
20
20
  "src": "../../../../../node_modules/axobject-query/lib/index.js",
21
21
  "file": "astro___axobject-query.js",
22
- "fileHash": "73e4f752",
22
+ "fileHash": "2b8684e9",
23
23
  "needsInterop": true
24
24
  }
25
25
  },