opencastle 0.27.0 → 0.27.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 (242) hide show
  1. package/bin/cli.mjs +6 -0
  2. package/dist/cli/agents.d.ts +3 -0
  3. package/dist/cli/agents.d.ts.map +1 -0
  4. package/dist/cli/agents.js +161 -0
  5. package/dist/cli/agents.js.map +1 -0
  6. package/dist/cli/baselines.d.ts +3 -0
  7. package/dist/cli/baselines.d.ts.map +1 -0
  8. package/dist/cli/baselines.js +128 -0
  9. package/dist/cli/baselines.js.map +1 -0
  10. package/dist/cli/convoy/dashboard-types.d.ts +146 -0
  11. package/dist/cli/convoy/dashboard-types.d.ts.map +1 -0
  12. package/dist/cli/convoy/dashboard-types.js +2 -0
  13. package/dist/cli/convoy/dashboard-types.js.map +1 -0
  14. package/dist/cli/convoy/engine.d.ts +67 -2
  15. package/dist/cli/convoy/engine.d.ts.map +1 -1
  16. package/dist/cli/convoy/engine.js +2036 -28
  17. package/dist/cli/convoy/engine.js.map +1 -1
  18. package/dist/cli/convoy/engine.test.js +1659 -70
  19. package/dist/cli/convoy/engine.test.js.map +1 -1
  20. package/dist/cli/convoy/event-schemas.d.ts +9 -0
  21. package/dist/cli/convoy/event-schemas.d.ts.map +1 -0
  22. package/dist/cli/convoy/event-schemas.js +185 -0
  23. package/dist/cli/convoy/event-schemas.js.map +1 -0
  24. package/dist/cli/convoy/events.d.ts +12 -1
  25. package/dist/cli/convoy/events.d.ts.map +1 -1
  26. package/dist/cli/convoy/events.js +186 -13
  27. package/dist/cli/convoy/events.js.map +1 -1
  28. package/dist/cli/convoy/events.test.js +325 -28
  29. package/dist/cli/convoy/events.test.js.map +1 -1
  30. package/dist/cli/convoy/expertise.d.ts +16 -0
  31. package/dist/cli/convoy/expertise.d.ts.map +1 -0
  32. package/dist/cli/convoy/expertise.js +121 -0
  33. package/dist/cli/convoy/expertise.js.map +1 -0
  34. package/dist/cli/convoy/expertise.test.d.ts +2 -0
  35. package/dist/cli/convoy/expertise.test.d.ts.map +1 -0
  36. package/dist/cli/convoy/expertise.test.js +96 -0
  37. package/dist/cli/convoy/expertise.test.js.map +1 -0
  38. package/dist/cli/convoy/export.test.js +1 -0
  39. package/dist/cli/convoy/export.test.js.map +1 -1
  40. package/dist/cli/convoy/formula.d.ts +19 -0
  41. package/dist/cli/convoy/formula.d.ts.map +1 -0
  42. package/dist/cli/convoy/formula.js +142 -0
  43. package/dist/cli/convoy/formula.js.map +1 -0
  44. package/dist/cli/convoy/formula.test.d.ts +2 -0
  45. package/dist/cli/convoy/formula.test.d.ts.map +1 -0
  46. package/dist/cli/convoy/formula.test.js +342 -0
  47. package/dist/cli/convoy/formula.test.js.map +1 -0
  48. package/dist/cli/convoy/gates.d.ts +128 -0
  49. package/dist/cli/convoy/gates.d.ts.map +1 -0
  50. package/dist/cli/convoy/gates.js +606 -0
  51. package/dist/cli/convoy/gates.js.map +1 -0
  52. package/dist/cli/convoy/gates.test.d.ts +2 -0
  53. package/dist/cli/convoy/gates.test.d.ts.map +1 -0
  54. package/dist/cli/convoy/gates.test.js +976 -0
  55. package/dist/cli/convoy/gates.test.js.map +1 -0
  56. package/dist/cli/convoy/health.d.ts +11 -0
  57. package/dist/cli/convoy/health.d.ts.map +1 -1
  58. package/dist/cli/convoy/health.js +54 -0
  59. package/dist/cli/convoy/health.js.map +1 -1
  60. package/dist/cli/convoy/health.test.js +56 -1
  61. package/dist/cli/convoy/health.test.js.map +1 -1
  62. package/dist/cli/convoy/issues.d.ts +8 -0
  63. package/dist/cli/convoy/issues.d.ts.map +1 -0
  64. package/dist/cli/convoy/issues.js +98 -0
  65. package/dist/cli/convoy/issues.js.map +1 -0
  66. package/dist/cli/convoy/issues.test.d.ts +2 -0
  67. package/dist/cli/convoy/issues.test.d.ts.map +1 -0
  68. package/dist/cli/convoy/issues.test.js +107 -0
  69. package/dist/cli/convoy/issues.test.js.map +1 -0
  70. package/dist/cli/convoy/knowledge.d.ts +5 -0
  71. package/dist/cli/convoy/knowledge.d.ts.map +1 -0
  72. package/dist/cli/convoy/knowledge.js +116 -0
  73. package/dist/cli/convoy/knowledge.js.map +1 -0
  74. package/dist/cli/convoy/knowledge.test.d.ts +2 -0
  75. package/dist/cli/convoy/knowledge.test.d.ts.map +1 -0
  76. package/dist/cli/convoy/knowledge.test.js +87 -0
  77. package/dist/cli/convoy/knowledge.test.js.map +1 -0
  78. package/dist/cli/convoy/lessons.d.ts +17 -0
  79. package/dist/cli/convoy/lessons.d.ts.map +1 -0
  80. package/dist/cli/convoy/lessons.js +149 -0
  81. package/dist/cli/convoy/lessons.js.map +1 -0
  82. package/dist/cli/convoy/lessons.test.d.ts +2 -0
  83. package/dist/cli/convoy/lessons.test.d.ts.map +1 -0
  84. package/dist/cli/convoy/lessons.test.js +135 -0
  85. package/dist/cli/convoy/lessons.test.js.map +1 -0
  86. package/dist/cli/convoy/lock.d.ts +13 -0
  87. package/dist/cli/convoy/lock.d.ts.map +1 -0
  88. package/dist/cli/convoy/lock.js +88 -0
  89. package/dist/cli/convoy/lock.js.map +1 -0
  90. package/dist/cli/convoy/lock.test.d.ts +2 -0
  91. package/dist/cli/convoy/lock.test.d.ts.map +1 -0
  92. package/dist/cli/convoy/lock.test.js +136 -0
  93. package/dist/cli/convoy/lock.test.js.map +1 -0
  94. package/dist/cli/convoy/log-merge.test.d.ts +2 -0
  95. package/dist/cli/convoy/log-merge.test.d.ts.map +1 -0
  96. package/dist/cli/convoy/log-merge.test.js +147 -0
  97. package/dist/cli/convoy/log-merge.test.js.map +1 -0
  98. package/dist/cli/convoy/merge.d.ts +4 -0
  99. package/dist/cli/convoy/merge.d.ts.map +1 -1
  100. package/dist/cli/convoy/merge.js +18 -1
  101. package/dist/cli/convoy/merge.js.map +1 -1
  102. package/dist/cli/convoy/merge.test.js +6 -7
  103. package/dist/cli/convoy/merge.test.js.map +1 -1
  104. package/dist/cli/convoy/partition.d.ts +51 -0
  105. package/dist/cli/convoy/partition.d.ts.map +1 -0
  106. package/dist/cli/convoy/partition.js +186 -0
  107. package/dist/cli/convoy/partition.js.map +1 -0
  108. package/dist/cli/convoy/partition.test.d.ts +2 -0
  109. package/dist/cli/convoy/partition.test.d.ts.map +1 -0
  110. package/dist/cli/convoy/partition.test.js +315 -0
  111. package/dist/cli/convoy/partition.test.js.map +1 -0
  112. package/dist/cli/convoy/pipeline.test.js +6 -0
  113. package/dist/cli/convoy/pipeline.test.js.map +1 -1
  114. package/dist/cli/convoy/store.d.ts +99 -7
  115. package/dist/cli/convoy/store.d.ts.map +1 -1
  116. package/dist/cli/convoy/store.js +764 -31
  117. package/dist/cli/convoy/store.js.map +1 -1
  118. package/dist/cli/convoy/store.test.js +1810 -18
  119. package/dist/cli/convoy/store.test.js.map +1 -1
  120. package/dist/cli/convoy/types.d.ts +427 -5
  121. package/dist/cli/convoy/types.d.ts.map +1 -1
  122. package/dist/cli/convoy/types.js +42 -1
  123. package/dist/cli/convoy/types.js.map +1 -1
  124. package/dist/cli/log.d.ts +11 -0
  125. package/dist/cli/log.d.ts.map +1 -1
  126. package/dist/cli/log.js +114 -2
  127. package/dist/cli/log.js.map +1 -1
  128. package/dist/cli/run/adapters/claude.d.ts +2 -0
  129. package/dist/cli/run/adapters/claude.d.ts.map +1 -1
  130. package/dist/cli/run/adapters/claude.js +89 -49
  131. package/dist/cli/run/adapters/claude.js.map +1 -1
  132. package/dist/cli/run/adapters/claude.test.d.ts +2 -0
  133. package/dist/cli/run/adapters/claude.test.d.ts.map +1 -0
  134. package/dist/cli/run/adapters/claude.test.js +205 -0
  135. package/dist/cli/run/adapters/claude.test.js.map +1 -0
  136. package/dist/cli/run/adapters/copilot.d.ts +1 -0
  137. package/dist/cli/run/adapters/copilot.d.ts.map +1 -1
  138. package/dist/cli/run/adapters/copilot.js +84 -46
  139. package/dist/cli/run/adapters/copilot.js.map +1 -1
  140. package/dist/cli/run/adapters/copilot.test.d.ts +2 -0
  141. package/dist/cli/run/adapters/copilot.test.d.ts.map +1 -0
  142. package/dist/cli/run/adapters/copilot.test.js +195 -0
  143. package/dist/cli/run/adapters/copilot.test.js.map +1 -0
  144. package/dist/cli/run/adapters/cursor.d.ts +1 -0
  145. package/dist/cli/run/adapters/cursor.d.ts.map +1 -1
  146. package/dist/cli/run/adapters/cursor.js +83 -47
  147. package/dist/cli/run/adapters/cursor.js.map +1 -1
  148. package/dist/cli/run/adapters/cursor.test.d.ts +2 -0
  149. package/dist/cli/run/adapters/cursor.test.d.ts.map +1 -0
  150. package/dist/cli/run/adapters/cursor.test.js +129 -0
  151. package/dist/cli/run/adapters/cursor.test.js.map +1 -0
  152. package/dist/cli/run/adapters/opencode.d.ts +1 -0
  153. package/dist/cli/run/adapters/opencode.d.ts.map +1 -1
  154. package/dist/cli/run/adapters/opencode.js +81 -47
  155. package/dist/cli/run/adapters/opencode.js.map +1 -1
  156. package/dist/cli/run/adapters/opencode.test.d.ts +2 -0
  157. package/dist/cli/run/adapters/opencode.test.d.ts.map +1 -0
  158. package/dist/cli/run/adapters/opencode.test.js +119 -0
  159. package/dist/cli/run/adapters/opencode.test.js.map +1 -0
  160. package/dist/cli/run/executor.js +1 -1
  161. package/dist/cli/run/executor.js.map +1 -1
  162. package/dist/cli/run/schema.d.ts.map +1 -1
  163. package/dist/cli/run/schema.js +245 -4
  164. package/dist/cli/run/schema.js.map +1 -1
  165. package/dist/cli/run/schema.test.js +669 -0
  166. package/dist/cli/run/schema.test.js.map +1 -1
  167. package/dist/cli/run.d.ts.map +1 -1
  168. package/dist/cli/run.js +362 -22
  169. package/dist/cli/run.js.map +1 -1
  170. package/dist/cli/types.d.ts +85 -2
  171. package/dist/cli/types.d.ts.map +1 -1
  172. package/dist/cli/types.js.map +1 -1
  173. package/dist/cli/watch.d.ts +15 -0
  174. package/dist/cli/watch.d.ts.map +1 -0
  175. package/dist/cli/watch.js +279 -0
  176. package/dist/cli/watch.js.map +1 -0
  177. package/package.json +5 -1
  178. package/src/cli/agents.ts +177 -0
  179. package/src/cli/baselines.ts +143 -0
  180. package/src/cli/convoy/TELEMETRY.md +203 -0
  181. package/src/cli/convoy/dashboard-types.ts +141 -0
  182. package/src/cli/convoy/engine.test.ts +1937 -70
  183. package/src/cli/convoy/engine.ts +2350 -40
  184. package/src/cli/convoy/event-schemas.ts +195 -0
  185. package/src/cli/convoy/events.test.ts +384 -39
  186. package/src/cli/convoy/events.ts +202 -16
  187. package/src/cli/convoy/expertise.test.ts +128 -0
  188. package/src/cli/convoy/expertise.ts +163 -0
  189. package/src/cli/convoy/export.test.ts +1 -0
  190. package/src/cli/convoy/formula.test.ts +405 -0
  191. package/src/cli/convoy/formula.ts +174 -0
  192. package/src/cli/convoy/gates.test.ts +1169 -0
  193. package/src/cli/convoy/gates.ts +774 -0
  194. package/src/cli/convoy/health.test.ts +64 -2
  195. package/src/cli/convoy/health.ts +80 -2
  196. package/src/cli/convoy/issues.test.ts +143 -0
  197. package/src/cli/convoy/issues.ts +136 -0
  198. package/src/cli/convoy/knowledge.test.ts +101 -0
  199. package/src/cli/convoy/knowledge.ts +132 -0
  200. package/src/cli/convoy/lessons.test.ts +188 -0
  201. package/src/cli/convoy/lessons.ts +164 -0
  202. package/src/cli/convoy/lock.test.ts +181 -0
  203. package/src/cli/convoy/lock.ts +103 -0
  204. package/src/cli/convoy/log-merge.test.ts +179 -0
  205. package/src/cli/convoy/merge.test.ts +6 -7
  206. package/src/cli/convoy/merge.ts +19 -1
  207. package/src/cli/convoy/partition.test.ts +423 -0
  208. package/src/cli/convoy/partition.ts +232 -0
  209. package/src/cli/convoy/pipeline.test.ts +6 -0
  210. package/src/cli/convoy/store.test.ts +2041 -20
  211. package/src/cli/convoy/store.ts +945 -46
  212. package/src/cli/convoy/types.ts +278 -4
  213. package/src/cli/log.ts +120 -2
  214. package/src/cli/run/adapters/claude.test.ts +234 -0
  215. package/src/cli/run/adapters/claude.ts +45 -5
  216. package/src/cli/run/adapters/copilot.test.ts +224 -0
  217. package/src/cli/run/adapters/copilot.ts +34 -4
  218. package/src/cli/run/adapters/cursor.test.ts +144 -0
  219. package/src/cli/run/adapters/cursor.ts +33 -2
  220. package/src/cli/run/adapters/opencode.test.ts +135 -0
  221. package/src/cli/run/adapters/opencode.ts +30 -2
  222. package/src/cli/run/executor.ts +1 -1
  223. package/src/cli/run/schema.test.ts +758 -0
  224. package/src/cli/run/schema.ts +300 -25
  225. package/src/cli/run.ts +341 -21
  226. package/src/cli/types.ts +86 -1
  227. package/src/cli/watch.ts +298 -0
  228. package/src/dashboard/dist/_astro/{index.DtnyD8a5.css → index.6L3_HsPT.css} +1 -1
  229. package/src/dashboard/dist/data/.gitkeep +0 -0
  230. package/src/dashboard/dist/data/convoy-list.json +1 -0
  231. package/src/dashboard/dist/data/overall-stats.json +24 -0
  232. package/src/dashboard/dist/index.html +701 -3
  233. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  234. package/src/dashboard/public/data/.gitkeep +0 -0
  235. package/src/dashboard/public/data/convoy-list.json +1 -0
  236. package/src/dashboard/public/data/overall-stats.json +24 -0
  237. package/src/dashboard/scripts/etl.test.ts +210 -0
  238. package/src/dashboard/scripts/etl.ts +108 -0
  239. package/src/dashboard/scripts/integration-test.ts +504 -0
  240. package/src/dashboard/src/pages/index.astro +854 -15
  241. package/src/dashboard/src/styles/dashboard.css +557 -1
  242. package/src/orchestrator/prompts/generate-convoy.prompt.md +212 -13
@@ -0,0 +1,177 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import type { CliContext } from './types.js'
4
+ import { c } from './prompt.js'
5
+
6
+ const HELP = `
7
+ opencastle agents [subcommand] [options]
8
+
9
+ Manage persistent agent identities.
10
+
11
+ Subcommands:
12
+ list List all agent identities (agent, task count, latest date)
13
+ inspect <agent> Show summaries for a specific agent
14
+ purge <agent> Delete all identities for an agent
15
+ compact --older-than <d> Delete identities older than N days
16
+
17
+ Options:
18
+ --older-than <days> Days threshold for compact subcommand
19
+ --yes, -y Skip confirmation prompt
20
+ --help, -h Show this help
21
+ `
22
+
23
+ interface AgentsOptions {
24
+ subcommand: string | null
25
+ agentName: string | null
26
+ olderThan: number | null
27
+ yes: boolean
28
+ help: boolean
29
+ }
30
+
31
+ function parseAgentsArgs(args: string[]): AgentsOptions {
32
+ const opts: AgentsOptions = {
33
+ subcommand: null,
34
+ agentName: null,
35
+ olderThan: null,
36
+ yes: false,
37
+ help: false,
38
+ }
39
+
40
+ for (let i = 0; i < args.length; i++) {
41
+ const arg = args[i]
42
+ switch (arg) {
43
+ case '--help':
44
+ case '-h':
45
+ opts.help = true
46
+ break
47
+ case '--older-than':
48
+ if (i + 1 >= args.length) { console.error(' \u2717 --older-than requires a number'); process.exit(1) }
49
+ opts.olderThan = parseInt(args[++i], 10)
50
+ if (!Number.isFinite(opts.olderThan) || opts.olderThan < 1) {
51
+ console.error(' \u2717 --older-than must be a positive integer')
52
+ process.exit(1)
53
+ }
54
+ break
55
+ case '--yes':
56
+ case '-y':
57
+ opts.yes = true
58
+ break
59
+ default:
60
+ if (arg.startsWith('--')) {
61
+ console.error(` \u2717 Unknown option: ${arg}`)
62
+ console.log(HELP)
63
+ process.exit(1)
64
+ }
65
+ if (!opts.subcommand) {
66
+ opts.subcommand = arg
67
+ } else if (!opts.agentName) {
68
+ opts.agentName = arg
69
+ }
70
+ }
71
+ }
72
+
73
+ return opts
74
+ }
75
+
76
+ export default async function agents({ args }: CliContext): Promise<void> {
77
+ const opts = parseAgentsArgs(args)
78
+
79
+ if (opts.help || !opts.subcommand) {
80
+ console.log(HELP)
81
+ return
82
+ }
83
+
84
+ const dbPath = resolve(process.cwd(), '.opencastle', 'convoy.db')
85
+ if (!existsSync(dbPath)) {
86
+ console.log(' No convoy database found at .opencastle/convoy.db')
87
+ return
88
+ }
89
+
90
+ const { createConvoyStore } = await import('./convoy/store.js')
91
+ const store = createConvoyStore(dbPath)
92
+
93
+ try {
94
+ switch (opts.subcommand) {
95
+ case 'list': {
96
+ const summaries = store.listAgentIdentitySummary()
97
+ if (summaries.length === 0) {
98
+ console.log(' No agent identities found.')
99
+ return
100
+ }
101
+ console.log(`\n Agent Identities (${summaries.length} agents):\n`)
102
+ console.log(` ${'Agent'.padEnd(25)} ${'Tasks'.padEnd(8)} Latest`)
103
+ console.log(` ${'\u2500'.repeat(25)} ${'\u2500'.repeat(8)} ${'\u2500'.repeat(20)}`)
104
+ for (const s of summaries) {
105
+ console.log(` ${s.agent.padEnd(25)} ${String(s.task_count).padEnd(8)} ${s.latest_date}`)
106
+ }
107
+ console.log()
108
+ break
109
+ }
110
+
111
+ case 'inspect': {
112
+ if (!opts.agentName) {
113
+ console.error(' \u2717 inspect requires an agent name: opencastle agents inspect <agent>')
114
+ process.exit(1)
115
+ }
116
+ const identities = store.getAgentIdentities(opts.agentName, 100)
117
+ if (identities.length === 0) {
118
+ console.log(` No identities found for agent "${opts.agentName}".`)
119
+ return
120
+ }
121
+ console.log(`\n Agent: ${opts.agentName} (${identities.length} identities)\n`)
122
+ for (const id of identities) {
123
+ console.log(` ${c.dim('\u2500'.repeat(60))}`)
124
+ console.log(` Task: ${id.task_id} | Convoy: ${id.convoy_id}`)
125
+ console.log(` Date: ${id.created_at} | Retention: ${id.retention_days}d`)
126
+ console.log(` Summary:`)
127
+ const lines = id.summary.split('\n')
128
+ for (const line of lines.slice(0, 10)) {
129
+ console.log(` ${line}`)
130
+ }
131
+ if (lines.length > 10) {
132
+ console.log(` ${c.dim(`... ${lines.length - 10} more lines`)}`)
133
+ }
134
+ console.log()
135
+ }
136
+ break
137
+ }
138
+
139
+ case 'purge': {
140
+ if (!opts.agentName) {
141
+ console.error(' \u2717 purge requires an agent name: opencastle agents purge <agent>')
142
+ process.exit(1)
143
+ }
144
+ if (!opts.yes) {
145
+ const existing = store.getAgentIdentities(opts.agentName, 1000)
146
+ if (existing.length === 0) {
147
+ console.log(` No identities found for agent "${opts.agentName}".`)
148
+ return
149
+ }
150
+ console.log(`\n This will delete ${existing.length} identities for agent "${opts.agentName}".`)
151
+ console.log(` Use --yes or -y to confirm.`)
152
+ return
153
+ }
154
+ const deleted = store.purgeAgentIdentities(opts.agentName)
155
+ console.log(` \u2713 Purged ${deleted} identities for agent "${opts.agentName}".`)
156
+ break
157
+ }
158
+
159
+ case 'compact': {
160
+ if (!opts.olderThan) {
161
+ console.error(' \u2717 compact requires --older-than <days>')
162
+ process.exit(1)
163
+ }
164
+ const deleted = store.deleteAgentIdentitiesOlderThan(opts.olderThan)
165
+ console.log(` \u2713 Deleted ${deleted} identities older than ${opts.olderThan} days.`)
166
+ break
167
+ }
168
+
169
+ default:
170
+ console.error(` \u2717 Unknown subcommand: ${opts.subcommand}`)
171
+ console.log(HELP)
172
+ process.exit(1)
173
+ }
174
+ } finally {
175
+ store.close()
176
+ }
177
+ }
@@ -0,0 +1,143 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs'
2
+ import { join, basename } from 'node:path'
3
+ import type { CliContext } from './types.js'
4
+ import { scanForSecrets } from './convoy/gates.js'
5
+
6
+ const HELP = `
7
+ opencastle baselines <subcommand> [options]
8
+
9
+ Manage visual regression baselines.
10
+
11
+ Subcommands:
12
+ update --slug <name> --from <file> Update a baseline from a PNG file
13
+ list List all baselines
14
+
15
+ Options:
16
+ --slug <name> Baseline name (used as filename)
17
+ --from <file> Source PNG file path
18
+ --dir <path> Baselines directory (default: .opencastle/baselines)
19
+ --help, -h Show this help
20
+ `
21
+
22
+ interface BaselinesOptions {
23
+ subcommand: string | null
24
+ slug: string | null
25
+ from: string | null
26
+ dir: string
27
+ help: boolean
28
+ }
29
+
30
+ function parseBaselinesArgs(args: string[]): BaselinesOptions {
31
+ const opts: BaselinesOptions = {
32
+ subcommand: null,
33
+ slug: null,
34
+ from: null,
35
+ dir: '.opencastle/baselines',
36
+ help: false,
37
+ }
38
+ for (let i = 0; i < args.length; i++) {
39
+ const arg = args[i]
40
+ switch (arg) {
41
+ case '--help':
42
+ case '-h':
43
+ opts.help = true
44
+ break
45
+ case '--slug':
46
+ if (i + 1 >= args.length) {
47
+ console.error(' \u2717 --slug requires a name')
48
+ process.exit(1)
49
+ }
50
+ opts.slug = args[++i]
51
+ break
52
+ case '--from':
53
+ if (i + 1 >= args.length) {
54
+ console.error(' \u2717 --from requires a file path')
55
+ process.exit(1)
56
+ }
57
+ opts.from = args[++i]
58
+ break
59
+ case '--dir':
60
+ if (i + 1 >= args.length) {
61
+ console.error(' \u2717 --dir requires a path')
62
+ process.exit(1)
63
+ }
64
+ opts.dir = args[++i]
65
+ break
66
+ default:
67
+ if (arg.startsWith('--')) {
68
+ console.error(` \u2717 Unknown option: ${arg}`)
69
+ console.log(HELP)
70
+ process.exit(1)
71
+ }
72
+ if (!opts.subcommand) {
73
+ opts.subcommand = arg
74
+ }
75
+ }
76
+ }
77
+ return opts
78
+ }
79
+
80
+ export default async function baselines({ args }: CliContext): Promise<void> {
81
+ const opts = parseBaselinesArgs(args)
82
+
83
+ if (opts.help || !opts.subcommand) {
84
+ console.log(HELP)
85
+ return
86
+ }
87
+
88
+ switch (opts.subcommand) {
89
+ case 'update': {
90
+ if (!opts.slug) {
91
+ console.error(' \u2717 update requires --slug <name>')
92
+ process.exit(1)
93
+ }
94
+ if (!/^[a-zA-Z0-9_-]+$/.test(opts.slug)) {
95
+ console.error(' \u2717 Slug must contain only alphanumeric characters, hyphens, and underscores')
96
+ process.exit(1)
97
+ }
98
+ if (!opts.from) {
99
+ console.error(' \u2717 update requires --from <file>')
100
+ process.exit(1)
101
+ }
102
+ if (!existsSync(opts.from)) {
103
+ console.error(` \u2717 Source file not found: ${opts.from}`)
104
+ process.exit(1)
105
+ }
106
+ const data = readFileSync(opts.from)
107
+ const scan = scanForSecrets(data.toString('base64'), opts.from)
108
+ if (!scan.clean) {
109
+ console.error(' \u2717 Source file contains potential secrets \u2014 baseline not updated')
110
+ process.exit(1)
111
+ }
112
+ mkdirSync(opts.dir, { recursive: true })
113
+ const dest = join(opts.dir, `${opts.slug}.png`)
114
+ writeFileSync(dest, data)
115
+ console.log(` \u2713 Baseline updated: ${dest}`)
116
+ break
117
+ }
118
+
119
+ case 'list': {
120
+ if (!existsSync(opts.dir)) {
121
+ console.log(` No baselines directory found at ${opts.dir}`)
122
+ return
123
+ }
124
+ const files = readdirSync(opts.dir).filter((f) => f.endsWith('.png'))
125
+ if (files.length === 0) {
126
+ console.log(' No baselines found.')
127
+ return
128
+ }
129
+ console.log(`\n Baselines in ${opts.dir}:\n`)
130
+ for (const file of files) {
131
+ const stats = statSync(join(opts.dir, file))
132
+ console.log(` ${basename(file, '.png').padEnd(30)} ${(stats.size / 1024).toFixed(1)} KB`)
133
+ }
134
+ console.log()
135
+ break
136
+ }
137
+
138
+ default:
139
+ console.error(` \u2717 Unknown subcommand: ${opts.subcommand}`)
140
+ console.log(HELP)
141
+ process.exit(1)
142
+ }
143
+ }
@@ -0,0 +1,203 @@
1
+ # Convoy Telemetry Model
2
+
3
+ How Convoy concepts map to [OpenTelemetry](https://opentelemetry.io/) semantics.
4
+
5
+ ## Conceptual Mapping
6
+
7
+ | Convoy Concept | OTel Concept | ID Field | Description |
8
+ |---------------|-------------|----------|-------------|
9
+ | **Convoy** | Trace | `convoy_id` → `trace_id` | A single execution run of a `.convoy.yml` spec |
10
+ | **Task** | Span | `task_id` → `span_id` | One unit of work within a convoy |
11
+ | **TaskStep** | Sub-span | `step_index` | Sequential steps within a multi-step task |
12
+ | **Event** | Log / SpanEvent | `type` | Structured occurrence during execution |
13
+ | **Metrics** | Derived aggregates | — | Computed from events (tokens, cost, duration) |
14
+
15
+ ### ID Correlation
16
+
17
+ ```
18
+ trace_id = convoy_id (globally unique, set at convoy creation)
19
+ span_id = task_id (unique within convoy, from spec)
20
+ worker_id = worker trace (ephemeral, tied to adapter process)
21
+ ```
22
+
23
+ Every event carries `convoy_id`, `task_id`, and `worker_id` (all nullable) to enable correlation across the trace hierarchy.
24
+
25
+ ## Storage
26
+
27
+ - **Primary**: SQLite (`convoy.db`) — durable, queryable, crash-safe
28
+ - **Supplementary**: NDJSON (`convoy-events.ndjson`) — append-only log for streaming/grep
29
+
30
+ SQLite is the source of truth. NDJSON is replayed from SQLite on crash recovery via `recoverNdjson()`.
31
+
32
+ ### Write Strategy (v1)
33
+
34
+ NDJSON writes use synchronous `appendFileSync` + `fsyncSync` per event. This ensures crash-safety — every event is durable before the engine proceeds. Trade-offs:
35
+
36
+ - **Latency**: ~1-2ms per event (sync I/O). For convoys with <10,000 events this is negligible.
37
+ - **Throughput**: Not suitable for >10,000 events/second workloads.
38
+ - **Crash-safety**: Every event is fsynced before the engine continues, so a crash never loses the last event.
39
+
40
+ An async buffered writer is deferred as an optimization for Phase 5 if profiling shows sync writes become a bottleneck.
41
+
42
+ ## Event Type Reference
43
+
44
+ All 39 canonical event types emitted by the convoy engine.
45
+
46
+ ### Convoy Lifecycle
47
+
48
+ | Event Type | Source | Data Fields |
49
+ |-----------|--------|-------------|
50
+ | `convoy_started` | engine.ts | `name?: string` |
51
+ | `convoy_finished` | engine.ts | `status: string` |
52
+ | `convoy_failed` | engine.ts | `status: string; reason?: string` |
53
+ | `convoy_guard` | engine.ts | `checks?: string[]` |
54
+
55
+ ### Task Lifecycle
56
+
57
+ | Event Type | Source | Data Fields |
58
+ |-----------|--------|-------------|
59
+ | `task_started` | engine.ts | `worker_id?: string` |
60
+ | `task_done` | engine.ts | `status?: string; retries?: number; worker_id?: string` |
61
+ | `task_failed` | engine.ts | `reason: string; worker_id?: string; gate?: string; hook?: string` |
62
+ | `task_skipped` | engine.ts | `reason: string` |
63
+ | `task_retried` | engine.ts | `previous_status: string` |
64
+ | `task_waiting_input` | engine.ts | `task_id?: string; reason?: string` |
65
+
66
+ ### Review & Disputes
67
+
68
+ | Event Type | Source | Data Fields |
69
+ |-----------|--------|-------------|
70
+ | `review_started` | engine.ts | `level: string; task_id?: string; model?: string` |
71
+ | `review_verdict` | engine.ts | `level: string; verdict: string; tokens: number; model?: string; feedback_length?: number; budget_exceeded?: boolean; budget_downgrade?: boolean; budget_skip?: boolean; passes?: number; blocks?: number` |
72
+ | `dispute_opened` | engine.ts | `dispute_id: string; task_id: string; agent?: string; reason?: string` |
73
+ | `dlq_entry_created` | engine.ts | `dlq_id: string; task_id: string; agent?: string; attempts?: number` |
74
+
75
+ ### Drift Detection
76
+
77
+ | Event Type | Source | Data Fields |
78
+ |-----------|--------|-------------|
79
+ | `drift_check_result` | engine.ts | `score?: number; threshold?: number; passed?: boolean` |
80
+ | `drift_detected` | engine.ts | `score?: number; files?: string[]` |
81
+
82
+ ### Circuit Breaker
83
+
84
+ | Event Type | Source | Data Fields |
85
+ |-----------|--------|-------------|
86
+ | `circuit_breaker_tripped` | engine.ts | `agent?: string; failure_count?: number; threshold?: number` |
87
+ | `circuit_breaker_fallback` | engine.ts | `original_agent?: string; fallback_agent?: string; task_id?: string` |
88
+ | `circuit_breaker_blocked` | engine.ts | `agent?: string; task_id?: string` |
89
+
90
+ ### Merge & Worktree
91
+
92
+ | Event Type | Source | Data Fields |
93
+ |-----------|--------|-------------|
94
+ | `merge_conflict_detected` | engine.ts | `task_id?: string; files?: string[]` |
95
+ | `merge_conflict_failed` | engine.ts | `task_id?: string; error?: string` |
96
+
97
+ ### Artifacts & Injection
98
+
99
+ | Event Type | Source | Data Fields |
100
+ |-----------|--------|-------------|
101
+ | `file_injection_received` | engine.ts | `task_id?: string; from_task?: string; name?: string` |
102
+ | `artifact_limit_reached` | engine.ts | `task_id?: string; limit?: number; current?: number` |
103
+
104
+ ### Agent Intelligence
105
+
106
+ | Event Type | Source | Data Fields |
107
+ |-----------|--------|-------------|
108
+ | `agent_identity_captured` | engine.ts | `agent?: string; task_id?: string` |
109
+ | `agent_identity_rejected` | engine.ts | `agent?: string; task_id?: string; reason?: string` |
110
+ | `weak_area_skipped` | engine.ts | `agent?: string; weak_areas?: string[]; task_files?: string[]` |
111
+ | `swarm_concurrency_update` | engine.ts | `new_concurrency?: number; reason?: string` |
112
+
113
+ ### Hooks
114
+
115
+ | Event Type | Source | Data Fields |
116
+ |-----------|--------|-------------|
117
+ | `post_convoy_hook_failed` | engine.ts | `hook?: string; error?: string` |
118
+
119
+ ### Observability / Session
120
+
121
+ | Event Type | Source | Data Fields |
122
+ |-----------|--------|-------------|
123
+ | `session` | engine.ts | `agent?: string; model?: string; task?: string; outcome?: string; duration_min?: number` |
124
+ | `delegation` | engine.ts | `agent?: string; model?: string; tier?: string; mechanism?: string; outcome?: string` |
125
+
126
+ ### Security & Reliability
127
+
128
+ | Event Type | Source | Data Fields |
129
+ |-----------|--------|-------------|
130
+ | `secret_leak_prevented` | engine.ts, events.ts | `original_type?: string; patterns?: string[]; task_id?: string; findings_count?: number; context?: string` |
131
+ | `ndjson_write_failed` | events.ts | `original_type?: string` |
132
+
133
+ ### Built-in Gates
134
+
135
+ | Event Type | Source | Data Fields |
136
+ |-----------|--------|-------------|
137
+ | `built_in_gate_result` | engine.ts | `gate: string; passed: boolean; output?: string; level?: string` |
138
+
139
+ ### Watch Mode
140
+
141
+ | Event Type | Source | Data Fields |
142
+ |-----------|--------|-------------|
143
+ | `watch_started` | watch.ts | `trigger_type?: string; pid?: number` |
144
+ | `watch_cycle_start` | watch.ts | `cycle_number?: number; triggered_by?: string` |
145
+ | `watch_cycle_end` | watch.ts | `cycle_number?: number; status?: string` |
146
+ | `watch_stopped` | watch.ts | `reason?: string` |
147
+
148
+ ### Worker Health
149
+
150
+ | Event Type | Source | Data Fields |
151
+ |-----------|--------|-------------|
152
+ | `worker_killed` | health.ts | `reason?: string; worker_id?: string; task_id?: string` |
153
+
154
+ ### Discovered Issues
155
+
156
+ | Event Type | Source | Data Fields |
157
+ |-----------|--------|-------------|
158
+ | `discovered_issue` | issues.ts | `task_id?: string; title?: string; file?: string; description?: string; severity?: string` |
159
+
160
+ ## Derived Metrics
161
+
162
+ These are computed from raw events, not emitted directly.
163
+
164
+ | Metric | Derivation |
165
+ |--------|-----------|
166
+ | Task duration | `task_done.timestamp - task_started.timestamp` |
167
+ | Convoy duration | `convoy_finished.timestamp - convoy_started.timestamp` |
168
+ | Retry rate | `COUNT(task_retried) / COUNT(task_started)` |
169
+ | Gate failure rate | `COUNT(built_in_gate_result WHERE !passed) / COUNT(built_in_gate_result)` |
170
+ | Review pass rate | `COUNT(review_verdict WHERE verdict='pass') / COUNT(review_verdict)` |
171
+ | Token usage | `SUM(review_verdict.tokens)` per convoy |
172
+ | Circuit breaker trips | `COUNT(circuit_breaker_tripped)` per agent |
173
+
174
+ ## Runtime Validation
175
+
176
+ - `validateEventType(type)` — checks membership in `KNOWN_EVENT_TYPES` (a `Set<string>` exported from [`types.ts`](types.ts)). Unknown types trigger a `console.warn` but do not throw, preserving extensibility for custom event types.
177
+ - `validateEventData(type, data)` — validates the `data` payload shape for known event types. Defined in [`event-schemas.ts`](event-schemas.ts). Returns `{ valid: boolean; issues?: string[] }`. Invalid payloads trigger a `console.warn` but do not block emission.
178
+
179
+ Both validators are called at emit time in [`events.ts`](events.ts).
180
+
181
+ ## Dashboard Build Pipeline
182
+
183
+ To build the dashboard with real convoy data:
184
+
185
+ ```sh
186
+ # 1. Run ETL to extract data from SQLite → JSON
187
+ npm run dashboard:etl
188
+
189
+ # 2. Build the Astro dashboard (reads from public/data/*.json)
190
+ npx astro build --root src/dashboard
191
+
192
+ # 3. Serve locally (optional)
193
+ npx astro preview --root src/dashboard
194
+ ```
195
+
196
+ In CI, add these steps after tests pass:
197
+
198
+ ```yaml
199
+ - run: npm run dashboard:etl
200
+ - run: npx astro build --root src/dashboard
201
+ ```
202
+
203
+ The ETL script gracefully handles missing databases — it produces empty JSON files so the dashboard renders an empty state instead of crashing.
@@ -0,0 +1,141 @@
1
+ export interface DashboardOverallStats {
2
+ total_convoys: number
3
+ running_convoys: number
4
+ successful_convoys: number
5
+ failed_convoys: number
6
+ avg_convoy_duration_sec: number | null
7
+ p95_convoy_duration_sec: number | null
8
+ total_tokens: number
9
+ total_cost_usd: number
10
+ top_agents: Array<{ agent: string; task_count: number; total_tokens: number }>
11
+ top_models: Array<{ model: string; task_count: number; total_tokens: number }>
12
+ retry_queue_count: number
13
+ disputed_tasks: number
14
+ }
15
+
16
+ export interface DashboardConvoySummary {
17
+ id: string
18
+ name: string
19
+ status: string
20
+ branch: string | null
21
+ created_at: string
22
+ started_at: string | null
23
+ finished_at: string | null
24
+ duration_sec: number | null
25
+ total_tokens: number | null
26
+ total_cost_usd: number | null
27
+ tasks_total: number
28
+ tasks_done: number
29
+ tasks_running: number
30
+ tasks_waiting: number
31
+ tasks_failed: number
32
+ tasks_retrying: number
33
+ }
34
+
35
+ export interface DashboardTaskSummary {
36
+ id: string
37
+ phase: number
38
+ agent: string
39
+ model: string | null
40
+ status: string
41
+ duration_sec: number | null
42
+ retries: number
43
+ files: string[]
44
+ total_tokens: number | null
45
+ cost_usd: number | null
46
+ review_level: string | null
47
+ review_verdict: string | null
48
+ drift_score: number | null
49
+ }
50
+
51
+ export interface DashboardConvoyDetail {
52
+ convoy: {
53
+ id: string
54
+ name: string
55
+ status: string
56
+ created_at: string
57
+ finished_at: string | null
58
+ branch: string | null
59
+ total_tokens: number | null
60
+ total_cost_usd: number | null
61
+ }
62
+ taskSummary: {
63
+ total: number
64
+ done: number
65
+ running: number
66
+ failed: number
67
+ review_blocked: number
68
+ disputed: number
69
+ reviewed: number
70
+ panel_reviewed: number
71
+ tasks_with_drift: number
72
+ max_drift_score: number | null
73
+ drift_retried: number
74
+ }
75
+ quality: {
76
+ reviewed_tasks: number
77
+ review_blocked_tasks: number
78
+ disputed_tasks: number
79
+ panel_reviews: number
80
+ }
81
+ drift: {
82
+ tasks_with_drift: number
83
+ max_drift_score: number | null
84
+ drift_retried_tasks: number
85
+ }
86
+ dlq_count: number
87
+ dlq_entries: Array<{
88
+ id: string
89
+ task_id: string
90
+ agent: string
91
+ failure_type: string
92
+ attempts: number
93
+ resolved: number
94
+ }>
95
+ artifact_count: number
96
+ artifacts: Array<{
97
+ id: string
98
+ name: string
99
+ type: string
100
+ task_id: string
101
+ created_at: string
102
+ }>
103
+ has_more_events: boolean
104
+ events: Array<{
105
+ type: string
106
+ task_id: string | null
107
+ data: unknown
108
+ created_at: string
109
+ }>
110
+ tasks: Array<{
111
+ id: string
112
+ phase: number
113
+ agent: string
114
+ model: string | null
115
+ status: string
116
+ retries: number
117
+ started_at: string | null
118
+ finished_at: string | null
119
+ total_tokens: number | null
120
+ cost_usd: number | null
121
+ review_level: string | null
122
+ review_verdict: string | null
123
+ review_tokens: number | null
124
+ review_model: string | null
125
+ panel_attempts: number | null
126
+ dispute_id: string | null
127
+ drift_score: number | null
128
+ drift_retried: number | null
129
+ files: string[] | null
130
+ }>
131
+ }
132
+
133
+ export interface DashboardTimelineEvent {
134
+ id: number
135
+ timestamp: string
136
+ type: string
137
+ convoy_id: string | null
138
+ task_id: string | null
139
+ worker_id: string | null
140
+ summary: string
141
+ }