opencastle 0.27.1 → 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 (66) hide show
  1. package/dist/cli/convoy/dashboard-types.d.ts +146 -0
  2. package/dist/cli/convoy/dashboard-types.d.ts.map +1 -0
  3. package/dist/cli/convoy/dashboard-types.js +2 -0
  4. package/dist/cli/convoy/dashboard-types.js.map +1 -0
  5. package/dist/cli/convoy/engine.d.ts +0 -1
  6. package/dist/cli/convoy/engine.d.ts.map +1 -1
  7. package/dist/cli/convoy/engine.js +31 -99
  8. package/dist/cli/convoy/engine.js.map +1 -1
  9. package/dist/cli/convoy/engine.test.js +88 -1
  10. package/dist/cli/convoy/engine.test.js.map +1 -1
  11. package/dist/cli/convoy/event-schemas.d.ts +9 -0
  12. package/dist/cli/convoy/event-schemas.d.ts.map +1 -0
  13. package/dist/cli/convoy/event-schemas.js +185 -0
  14. package/dist/cli/convoy/event-schemas.js.map +1 -0
  15. package/dist/cli/convoy/events.d.ts +8 -0
  16. package/dist/cli/convoy/events.d.ts.map +1 -1
  17. package/dist/cli/convoy/events.js +117 -5
  18. package/dist/cli/convoy/events.js.map +1 -1
  19. package/dist/cli/convoy/events.test.js +173 -3
  20. package/dist/cli/convoy/events.test.js.map +1 -1
  21. package/dist/cli/convoy/log-merge.test.d.ts +2 -0
  22. package/dist/cli/convoy/log-merge.test.d.ts.map +1 -0
  23. package/dist/cli/convoy/log-merge.test.js +147 -0
  24. package/dist/cli/convoy/log-merge.test.js.map +1 -0
  25. package/dist/cli/convoy/store.d.ts +52 -2
  26. package/dist/cli/convoy/store.d.ts.map +1 -1
  27. package/dist/cli/convoy/store.js +244 -17
  28. package/dist/cli/convoy/store.js.map +1 -1
  29. package/dist/cli/convoy/store.test.js +481 -22
  30. package/dist/cli/convoy/store.test.js.map +1 -1
  31. package/dist/cli/convoy/types.d.ts +271 -3
  32. package/dist/cli/convoy/types.d.ts.map +1 -1
  33. package/dist/cli/convoy/types.js +42 -1
  34. package/dist/cli/convoy/types.js.map +1 -1
  35. package/dist/cli/log.d.ts +11 -0
  36. package/dist/cli/log.d.ts.map +1 -1
  37. package/dist/cli/log.js +114 -2
  38. package/dist/cli/log.js.map +1 -1
  39. package/package.json +5 -1
  40. package/src/cli/convoy/TELEMETRY.md +203 -0
  41. package/src/cli/convoy/dashboard-types.ts +141 -0
  42. package/src/cli/convoy/engine.test.ts +99 -1
  43. package/src/cli/convoy/engine.ts +27 -96
  44. package/src/cli/convoy/event-schemas.ts +195 -0
  45. package/src/cli/convoy/events.test.ts +207 -3
  46. package/src/cli/convoy/events.ts +119 -5
  47. package/src/cli/convoy/log-merge.test.ts +179 -0
  48. package/src/cli/convoy/store.test.ts +545 -22
  49. package/src/cli/convoy/store.ts +274 -21
  50. package/src/cli/convoy/types.ts +108 -3
  51. package/src/cli/log.ts +120 -2
  52. package/src/dashboard/dist/_astro/{index.DtnyD8a5.css → index.6L3_HsPT.css} +1 -1
  53. package/src/dashboard/dist/data/.gitkeep +0 -0
  54. package/src/dashboard/dist/data/convoy-list.json +1 -0
  55. package/src/dashboard/dist/data/overall-stats.json +24 -0
  56. package/src/dashboard/dist/index.html +701 -3
  57. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  58. package/src/dashboard/public/data/.gitkeep +0 -0
  59. package/src/dashboard/public/data/convoy-list.json +1 -0
  60. package/src/dashboard/public/data/overall-stats.json +24 -0
  61. package/src/dashboard/scripts/etl.test.ts +210 -0
  62. package/src/dashboard/scripts/etl.ts +108 -0
  63. package/src/dashboard/scripts/integration-test.ts +504 -0
  64. package/src/dashboard/src/pages/index.astro +854 -15
  65. package/src/dashboard/src/styles/dashboard.css +557 -1
  66. package/src/orchestrator/prompts/generate-convoy.prompt.md +212 -13
@@ -0,0 +1,179 @@
1
+ import { mkdtempSync, rmSync, realpathSync, mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
5
+ import { mergeConvoyLogs } from '../log.js'
6
+
7
+ const CONVOYS_REL = '.opencastle/logs/convoys'
8
+ const OUTPUT_REL = '.opencastle/logs/convoy-events.ndjson'
9
+
10
+ function makeBase(): string {
11
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), 'log-merge-test-')))
12
+ mkdirSync(join(dir, CONVOYS_REL), { recursive: true })
13
+ return dir
14
+ }
15
+
16
+ function writeConvoyFile(base: string, convoyId: string, records: object[]): void {
17
+ const path = join(base, CONVOYS_REL, `${convoyId}.ndjson`)
18
+ writeFileSync(path, records.map(r => JSON.stringify(r)).join('\n') + '\n', 'utf8')
19
+ }
20
+
21
+ let tmpDir: string
22
+
23
+ beforeEach(() => {
24
+ tmpDir = makeBase()
25
+ })
26
+
27
+ afterEach(() => {
28
+ rmSync(tmpDir, { recursive: true, force: true })
29
+ })
30
+
31
+ describe('mergeConvoyLogs', () => {
32
+ it('returns zeros when convoys directory is missing', async () => {
33
+ rmSync(join(tmpDir, '.opencastle'), { recursive: true, force: true })
34
+ const result = await mergeConvoyLogs({ basePath: tmpDir })
35
+ expect(result).toEqual({ merged: 0, deduplicated: 0, written: 0 })
36
+ })
37
+
38
+ it('returns zeros when convoys directory is empty', async () => {
39
+ const result = await mergeConvoyLogs({ basePath: tmpDir })
40
+ expect(result).toEqual({ merged: 0, deduplicated: 0, written: 0 })
41
+ })
42
+
43
+ it('merges records from 3 convoy files', async () => {
44
+ writeConvoyFile(tmpDir, 'convoy-a', [
45
+ { _event_id: 1, timestamp: '2026-01-01T10:00:00.000Z', type: 'task_started' },
46
+ ])
47
+ writeConvoyFile(tmpDir, 'convoy-b', [
48
+ { _event_id: 2, timestamp: '2026-01-02T10:00:00.000Z', type: 'task_done' },
49
+ ])
50
+ writeConvoyFile(tmpDir, 'convoy-c', [
51
+ { _event_id: 3, timestamp: '2026-01-03T10:00:00.000Z', type: 'session' },
52
+ ])
53
+
54
+ const result = await mergeConvoyLogs({ basePath: tmpDir })
55
+ expect(result.merged).toBe(3)
56
+ expect(result.written).toBe(3)
57
+ })
58
+
59
+ it('output is sorted by timestamp ascending', async () => {
60
+ writeConvoyFile(tmpDir, 'convoy-z', [
61
+ { _event_id: 10, timestamp: '2026-03-01T00:00:00.000Z', type: 'task_done' },
62
+ { _event_id: 11, timestamp: '2026-01-01T00:00:00.000Z', type: 'task_started' },
63
+ ])
64
+ writeConvoyFile(tmpDir, 'convoy-a', [
65
+ { _event_id: 12, timestamp: '2026-02-01T00:00:00.000Z', type: 'session' },
66
+ ])
67
+
68
+ const outputPath = join(tmpDir, 'merged.ndjson')
69
+ await mergeConvoyLogs({ basePath: tmpDir, output: outputPath })
70
+
71
+ const lines = readFileSync(outputPath, 'utf8').split('\n').filter(l => l.trim())
72
+ const timestamps = lines.map(l => (JSON.parse(l) as { timestamp: string }).timestamp)
73
+ expect(timestamps).toEqual([
74
+ '2026-01-01T00:00:00.000Z',
75
+ '2026-02-01T00:00:00.000Z',
76
+ '2026-03-01T00:00:00.000Z',
77
+ ])
78
+ })
79
+
80
+ it('deduplicates records by _event_id (keeps first occurrence)', async () => {
81
+ writeConvoyFile(tmpDir, 'convoy-a', [
82
+ { _event_id: 5, timestamp: '2026-01-01T00:00:00.000Z', type: 'task_started', note: 'first' },
83
+ ])
84
+ writeConvoyFile(tmpDir, 'convoy-b', [
85
+ { _event_id: 5, timestamp: '2026-01-01T00:00:00.000Z', type: 'task_started', note: 'duplicate' },
86
+ { _event_id: 6, timestamp: '2026-01-02T00:00:00.000Z', type: 'task_done' },
87
+ ])
88
+
89
+ const outputPath = join(tmpDir, 'merged.ndjson')
90
+ const result = await mergeConvoyLogs({ basePath: tmpDir, output: outputPath })
91
+
92
+ expect(result.merged).toBe(3)
93
+ expect(result.deduplicated).toBe(1)
94
+ expect(result.written).toBe(2)
95
+
96
+ const lines = readFileSync(outputPath, 'utf8').split('\n').filter(l => l.trim())
97
+ expect(lines).toHaveLength(2)
98
+ const first = JSON.parse(lines[0]) as { note: string }
99
+ expect(first.note).toBe('first')
100
+ })
101
+
102
+ it('filters by --since (inclusive)', async () => {
103
+ writeConvoyFile(tmpDir, 'convoy-a', [
104
+ { _event_id: 1, timestamp: '2026-01-01T00:00:00.000Z', type: 'session' },
105
+ { _event_id: 2, timestamp: '2026-02-01T00:00:00.000Z', type: 'session' },
106
+ { _event_id: 3, timestamp: '2026-03-01T00:00:00.000Z', type: 'session' },
107
+ ])
108
+
109
+ const outputPath = join(tmpDir, 'merged.ndjson')
110
+ const result = await mergeConvoyLogs({ basePath: tmpDir, since: '2026-02-01T00:00:00.000Z', output: outputPath })
111
+
112
+ expect(result.written).toBe(2)
113
+ const lines = readFileSync(outputPath, 'utf8').split('\n').filter(l => l.trim())
114
+ expect(lines).toHaveLength(2)
115
+ })
116
+
117
+ it('filters by --until (inclusive)', async () => {
118
+ writeConvoyFile(tmpDir, 'convoy-a', [
119
+ { _event_id: 1, timestamp: '2026-01-01T00:00:00.000Z', type: 'session' },
120
+ { _event_id: 2, timestamp: '2026-02-01T00:00:00.000Z', type: 'session' },
121
+ { _event_id: 3, timestamp: '2026-03-01T00:00:00.000Z', type: 'session' },
122
+ ])
123
+
124
+ const outputPath = join(tmpDir, 'merged.ndjson')
125
+ const result = await mergeConvoyLogs({ basePath: tmpDir, until: '2026-02-01T00:00:00.000Z', output: outputPath })
126
+
127
+ expect(result.written).toBe(2)
128
+ })
129
+
130
+ it('filters by --since and --until together', async () => {
131
+ writeConvoyFile(tmpDir, 'convoy-a', [
132
+ { _event_id: 1, timestamp: '2026-01-01T00:00:00.000Z', type: 'session' },
133
+ { _event_id: 2, timestamp: '2026-02-15T00:00:00.000Z', type: 'session' },
134
+ { _event_id: 3, timestamp: '2026-03-01T00:00:00.000Z', type: 'session' },
135
+ ])
136
+
137
+ const outputPath = join(tmpDir, 'merged.ndjson')
138
+ const result = await mergeConvoyLogs({
139
+ basePath: tmpDir,
140
+ since: '2026-02-01T00:00:00.000Z',
141
+ until: '2026-02-28T23:59:59.999Z',
142
+ output: outputPath,
143
+ })
144
+
145
+ expect(result.written).toBe(1)
146
+ const lines = readFileSync(outputPath, 'utf8').split('\n').filter(l => l.trim())
147
+ const record = JSON.parse(lines[0]) as { timestamp: string }
148
+ expect(record.timestamp).toBe('2026-02-15T00:00:00.000Z')
149
+ })
150
+
151
+ it('writes to default output path when --output not specified', async () => {
152
+ writeConvoyFile(tmpDir, 'convoy-a', [
153
+ { _event_id: 1, timestamp: '2026-01-01T00:00:00.000Z', type: 'session' },
154
+ ])
155
+
156
+ await mergeConvoyLogs({ basePath: tmpDir })
157
+
158
+ const defaultPath = join(tmpDir, '.opencastle', 'logs', 'convoy-events.ndjson')
159
+ expect(existsSync(defaultPath)).toBe(true)
160
+ })
161
+
162
+ it('returns written: 0 when all records filtered out', async () => {
163
+ writeConvoyFile(tmpDir, 'convoy-a', [
164
+ { _event_id: 1, timestamp: '2026-01-01T00:00:00.000Z', type: 'session' },
165
+ ])
166
+
167
+ const result = await mergeConvoyLogs({ basePath: tmpDir, since: '2027-01-01T00:00:00.000Z' })
168
+ expect(result.written).toBe(0)
169
+ expect(result.merged).toBe(1)
170
+ })
171
+
172
+ it('skips malformed JSON lines gracefully', async () => {
173
+ const path = join(tmpDir, CONVOYS_REL, 'convoy-bad.ndjson')
174
+ writeFileSync(path, '{"_event_id":1,"timestamp":"2026-01-01T00:00:00.000Z","type":"session"}\nnot-valid-json\n{"_event_id":2,"timestamp":"2026-01-02T00:00:00.000Z","type":"task_done"}\n', 'utf8')
175
+
176
+ const result = await mergeConvoyLogs({ basePath: tmpDir })
177
+ expect(result.written).toBe(2)
178
+ })
179
+ })