spexcode 0.5.1 → 0.5.3

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 (155) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/spec-cli/bin/spex.mjs +25 -1
  4. package/spec-cli/hooks/dispatch.sh +24 -1
  5. package/spec-cli/src/anchors.ts +192 -58
  6. package/spec-cli/src/attach.ts +11 -8
  7. package/spec-cli/src/claude-headless.ts +116 -66
  8. package/spec-cli/src/cli.ts +221 -72
  9. package/spec-cli/src/client.ts +143 -5
  10. package/spec-cli/src/gateway-hub.ts +2 -6
  11. package/spec-cli/src/gateway.ts +96 -11
  12. package/spec-cli/src/git.ts +1395 -96
  13. package/spec-cli/src/graph.ts +21 -17
  14. package/spec-cli/src/graphCache.ts +405 -61
  15. package/spec-cli/src/graphStream.ts +581 -182
  16. package/spec-cli/src/guide.ts +13 -3
  17. package/spec-cli/src/harness.ts +965 -70
  18. package/spec-cli/src/headless-controller.ts +63 -0
  19. package/spec-cli/src/help.ts +120 -64
  20. package/spec-cli/src/host-resources.ts +706 -0
  21. package/spec-cli/src/host.ts +1 -0
  22. package/spec-cli/src/index.ts +95 -21
  23. package/spec-cli/src/init.ts +47 -8
  24. package/spec-cli/src/issues.ts +36 -3
  25. package/spec-cli/src/layout.ts +193 -37
  26. package/spec-cli/src/lint.ts +152 -27
  27. package/spec-cli/src/localIssues.ts +5 -1
  28. package/spec-cli/src/maintenance-wrapper.ts +276 -0
  29. package/spec-cli/src/materialize.ts +6 -7
  30. package/spec-cli/src/opencode-headless.ts +117 -7
  31. package/spec-cli/src/opencode.ts +8 -4
  32. package/spec-cli/src/pi-headless.ts +18 -60
  33. package/spec-cli/src/process-identity.ts +49 -0
  34. package/spec-cli/src/project-store.ts +17 -0
  35. package/spec-cli/src/pty-bridge.ts +37 -7
  36. package/spec-cli/src/pty-helper.mjs +16 -6
  37. package/spec-cli/src/pty-native-helper.mjs +22 -0
  38. package/spec-cli/src/reviews.ts +17 -13
  39. package/spec-cli/src/runtime-ownership.ts +99 -0
  40. package/spec-cli/src/session-maintenance.ts +750 -0
  41. package/spec-cli/src/session-timeline.ts +29 -23
  42. package/spec-cli/src/sessions.ts +1262 -200
  43. package/spec-cli/src/sh.ts +3 -0
  44. package/spec-cli/src/source-files.ts +27 -2
  45. package/spec-cli/src/specs.ts +58 -25
  46. package/spec-cli/src/supervise.ts +31 -11
  47. package/spec-cli/src/uninstall.ts +7 -6
  48. package/spec-cli/templates/hooks/commit-msg +14 -0
  49. package/spec-cli/templates/hooks/pre-commit +26 -6
  50. package/spec-cli/templates/hooks/prepare-commit-msg +20 -48
  51. package/spec-cli/templates/hooks/reference-transaction +105 -0
  52. package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +1 -1
  53. package/spec-cli/templates/spec/project/.plugins/core/mark-active/mark-active.sh +23 -21
  54. package/spec-cli/templates/spec/project/.plugins/core/spec.md +12 -10
  55. package/spec-cli/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +15 -6
  56. package/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +32 -0
  57. package/spec-dashboard/dist/assets/App-C5vbTw8Q.js +2 -0
  58. package/spec-dashboard/dist/assets/Dashboard-u8RIS3NY.js +27 -0
  59. package/spec-dashboard/dist/assets/EvalsPage-BS7ITcNo.js +2 -0
  60. package/spec-dashboard/dist/assets/IssuesPage-DXbqQFW_.js +1 -0
  61. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  62. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  63. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  64. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  65. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  66. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  67. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  68. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  69. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  70. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  71. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  72. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  73. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  74. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  75. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  76. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  77. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  78. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  79. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  80. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  81. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  82. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  83. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  84. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  85. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  86. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  87. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  88. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  89. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  90. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  91. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  92. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  93. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  94. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  95. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  96. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  97. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  98. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  99. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  100. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  101. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  102. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  103. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  104. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  105. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  106. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  107. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  108. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  109. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  110. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  111. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  112. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  113. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  114. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  115. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  116. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  117. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  118. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  119. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  120. package/spec-dashboard/dist/assets/MobileApp-DVLnk9hz.js +2 -0
  121. package/spec-dashboard/dist/assets/Modal-6mHq6fbZ.js +1 -0
  122. package/spec-dashboard/dist/assets/{PageScroll-B_dKCuXx.js → PageScroll-CAY4S4g4.js} +1 -1
  123. package/spec-dashboard/dist/assets/ProjectsPage-UQyzsTWN.js +1 -0
  124. package/spec-dashboard/dist/assets/SessionInterface-DKU4c1Z-.js +39 -0
  125. package/spec-dashboard/dist/assets/SessionWindow-zGwJaGbR.js +1 -0
  126. package/spec-dashboard/dist/assets/Settings-igR17pns.js +1 -0
  127. package/spec-dashboard/dist/assets/Thread-B-ZUarN1.js +13 -0
  128. package/spec-dashboard/dist/assets/TimelineChat-Cp579UoJ.css +1 -0
  129. package/spec-dashboard/dist/assets/TimelineChat-sc49Qj5d.js +276 -0
  130. package/spec-dashboard/dist/assets/data-B1ot4PF0.js +1 -0
  131. package/spec-dashboard/dist/assets/index-BqBNCa1V.js +41 -0
  132. package/spec-dashboard/dist/assets/index-CzutlTDf.css +1 -0
  133. package/spec-dashboard/dist/assets/{terminalFont-DmTPNdbU.js → terminalFont-C6oGNKrf.js} +1 -1
  134. package/spec-dashboard/dist/index.html +5 -4
  135. package/spec-dashboard/src/reviewFilters.js +46 -8
  136. package/spec-dashboard/src/session.js +64 -9
  137. package/spec-eval/src/cli.ts +144 -32
  138. package/spec-eval/src/evaltab.ts +16 -9
  139. package/spec-eval/src/freshness.ts +336 -54
  140. package/spec-eval/src/scenariofresh.ts +51 -1
  141. package/spec-eval/src/scenarios.ts +324 -25
  142. package/spec-eval/src/sessioneval.ts +969 -173
  143. package/spec-cli/src/message-stream.ts +0 -147
  144. package/spec-dashboard/dist/assets/Dashboard-C_w_wdk5.js +0 -27
  145. package/spec-dashboard/dist/assets/EvalsPage-5_nfIYll.js +0 -2
  146. package/spec-dashboard/dist/assets/IssuesPage-By-u--95.js +0 -1
  147. package/spec-dashboard/dist/assets/MobileApp-CVEwjHr9.js +0 -2
  148. package/spec-dashboard/dist/assets/Modal-BqgvzMJD.js +0 -1
  149. package/spec-dashboard/dist/assets/ProjectsPage-RVP8AqK4.js +0 -1
  150. package/spec-dashboard/dist/assets/SessionInterface-Bh3vq8SU.js +0 -39
  151. package/spec-dashboard/dist/assets/SessionWindow-BuJ5mzjC.js +0 -13
  152. package/spec-dashboard/dist/assets/Settings-B8KFocsz.js +0 -1
  153. package/spec-dashboard/dist/assets/TimelineChat-K0wdlweB.js +0 -1
  154. package/spec-dashboard/dist/assets/index-BKaTHjmU.js +0 -41
  155. package/spec-dashboard/dist/assets/index-DcnCaBAC.css +0 -1
@@ -1,147 +0,0 @@
1
- import { closeSync, fstatSync, openSync, readSync, watch, type FSWatcher } from 'node:fs'
2
- import { streamSSE } from 'hono/streaming'
3
- import type { Context } from 'hono'
4
-
5
- import { readAliasedRawRecord, sessionArtifactPath, sessionStoreDir } from './layout.js'
6
-
7
- export type NativeMessageEvent = Record<string, unknown>
8
- export type MessageEnvelope = { cursor: number; event: NativeMessageEvent }
9
- export type MessageBatch = { messages: MessageEnvelope[]; cursor: number }
10
-
11
- function fileBytes(path: string, cursor: number): Buffer {
12
- let fd: number
13
- try { fd = openSync(path, 'r') }
14
- catch (error) {
15
- if ((error as NodeJS.ErrnoException).code === 'ENOENT' && cursor === 0) return Buffer.alloc(0)
16
- throw error
17
- }
18
- try {
19
- const size = fstatSync(fd).size
20
- if (cursor > size) throw new Error(`messages.ndjson cursor ${cursor} is past file size ${size}`)
21
- const bytes = Buffer.alloc(size - cursor)
22
- let read = 0
23
- while (read < bytes.length) {
24
- const count = readSync(fd, bytes, read, bytes.length - read, cursor + read)
25
- if (count === 0) break
26
- read += count
27
- }
28
- return read === bytes.length ? bytes : bytes.subarray(0, read)
29
- } finally { closeSync(fd) }
30
- }
31
-
32
- // Parse only newline-terminated records. The cursor is a byte offset, not a character count, so an SSE
33
- // reconnect resumes correctly even when a native event contains non-ASCII text.
34
- export function readMessageBatchFile(path: string, requestedCursor = 0): MessageBatch {
35
- const cursor = Number.isSafeInteger(requestedCursor) && requestedCursor >= 0 ? requestedCursor : 0
36
- const bytes = fileBytes(path, cursor)
37
-
38
- const messages: MessageEnvelope[] = []
39
- let lineStart = 0
40
- while (lineStart < bytes.length) {
41
- const newline = bytes.indexOf(0x0a, lineStart)
42
- if (newline < 0) break
43
- const nextCursor = cursor + newline + 1
44
- const text = bytes.subarray(lineStart, newline).toString('utf8')
45
- let event: unknown
46
- try { event = JSON.parse(text) }
47
- catch (error) {
48
- throw new Error(`invalid messages.ndjson event at byte ${cursor + lineStart}: ${(error as Error).message}`)
49
- }
50
- if (!event || typeof event !== 'object' || Array.isArray(event)) {
51
- throw new Error(`invalid messages.ndjson event at byte ${cursor + lineStart}: expected a JSON object`)
52
- }
53
- messages.push({ cursor: nextCursor, event: event as NativeMessageEvent })
54
- lineStart = newline + 1
55
- }
56
- return { messages, cursor: cursor + lineStart }
57
- }
58
-
59
- // null means the id is unknown or is not a governed session. A known session with no adapter output yet is
60
- // an ordinary empty stream: the adapter may create messages.ndjson after the browser has connected.
61
- export function readSessionMessages(id: string, cursor = 0): MessageBatch | null {
62
- const record = readAliasedRawRecord(id)
63
- if (!record?.governed) return null
64
- return readMessageBatchFile(sessionArtifactPath(record.session_id, 'messages.ndjson'), cursor)
65
- }
66
-
67
- type StreamSignal = 'append' | 'ping' | 'abort' | 'watch-error'
68
-
69
- function signalQueue() {
70
- const queued: StreamSignal[] = []
71
- let waiter: ((signal: StreamSignal) => void) | null = null
72
- const push = (signal: StreamSignal) => {
73
- if (signal === 'append' && queued.includes('append')) return
74
- if (waiter) {
75
- const resolve = waiter
76
- waiter = null
77
- resolve(signal)
78
- } else queued.push(signal)
79
- }
80
- const next = (): Promise<StreamSignal> => {
81
- const ready = queued.shift()
82
- return ready ? Promise.resolve(ready) : new Promise((resolve) => { waiter = resolve })
83
- }
84
- return { push, next }
85
- }
86
-
87
- function cursorParam(value: string | undefined): number | null {
88
- if (!value || !/^\d+$/.test(value)) return null
89
- const cursor = Number(value)
90
- return Number.isSafeInteger(cursor) ? cursor : null
91
- }
92
-
93
- // GET /api/sessions/:id/messages/stream — the append-follow half of [[message-stream]]. REST establishes a
94
- // complete snapshot + cursor; this stream starts there. Standard SSE ids let EventSource reconnect through
95
- // Last-Event-ID even though its original URL still carries the older cursor.
96
- export function sessionMessageStream(c: Context): Response {
97
- const record = readAliasedRawRecord(c.req.param('id') as string)
98
- if (!record?.governed) return c.json({ error: 'no such session' }, 404)
99
-
100
- const lastEventCursor = cursorParam(c.req.header('Last-Event-ID'))
101
- const queryCursor = cursorParam(c.req.query('cursor'))
102
- const startCursor = lastEventCursor ?? queryCursor ?? 0
103
- const id = record.session_id
104
-
105
- return streamSSE(c, async (stream) => {
106
- const signals = signalQueue()
107
- let aborted = false
108
- let watcher: FSWatcher | null = null
109
- const ping = setInterval(() => signals.push('ping'), 10_000)
110
- ping.unref()
111
- stream.onAbort(() => { aborted = true; signals.push('abort') })
112
-
113
- try {
114
- watcher = watch(sessionStoreDir(id), { persistent: false }, (_event, filename) => {
115
- if (filename == null || String(filename) === 'messages.ndjson') signals.push('append')
116
- })
117
- watcher.on('error', () => signals.push('watch-error'))
118
-
119
- let cursor = startCursor
120
- await stream.writeSSE({ event: 'ready', data: JSON.stringify({ cursor }) })
121
- const flush = async () => {
122
- const batch = readSessionMessages(id, cursor)
123
- if (!batch) throw new Error('session closed')
124
- cursor = batch.cursor
125
- for (const message of batch.messages) {
126
- await stream.writeSSE({ event: 'message', id: String(message.cursor), data: JSON.stringify(message) })
127
- }
128
- }
129
- await flush()
130
-
131
- while (!aborted) {
132
- const signal = await signals.next()
133
- if (signal === 'abort') break
134
- if (signal === 'watch-error') throw new Error('messages.ndjson watcher failed')
135
- if (signal === 'ping') await stream.writeSSE({ event: 'ping', data: 'x' })
136
- else await flush()
137
- }
138
- } catch (error) {
139
- if (!aborted) {
140
- await stream.writeSSE({ event: 'stream-error', data: JSON.stringify({ error: (error as Error).message }) }).catch(() => {})
141
- }
142
- } finally {
143
- clearInterval(ping)
144
- watcher?.close()
145
- }
146
- })
147
- }