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,5 +1,5 @@
1
1
  import { watch, existsSync, readFileSync, appendFileSync, mkdirSync, type FSWatcher } from 'node:fs'
2
- import { sessionsRoot, sessionStoreDir, sessionArtifactPath, listSessionIds, readAliasedRawRecord } from './layout.js'
2
+ import { sessionsRoot, sessionStoreDir, sessionArtifactPath, listSessionIds, readAliasedRawRecord, readPublicRecordEntry } from './layout.js'
3
3
  import type { Lifecycle, Proposal } from './sessions.js'
4
4
 
5
5
  // @@@ session-timeline - the PERSISTED interaction history of a session: every authored-lifecycle
@@ -8,20 +8,19 @@ import type { Lifecycle, Proposal } from './sessions.js'
8
8
  // [[mobile-ui]]) renders instead of a live pane: without the terminal, the declaration notes ARE the agent's
9
9
  // replies, and the timeline is the whole conversation.
10
10
  //
11
- // WHY an observer, not writer instrumentation: the lifecycle has a writer the TS layer never sees — the
12
- // mark-active hook value-replaces status/proposal/note in session.json with pure-shell sed ([[state]]).
13
- // Instrumenting every writer would always miss that one, so the recorder OBSERVES the store instead: one
14
- // fs.watch on the sessions root (debounced) plus a slow reconcile tick (the fs.watch is best-effort, same
15
- // stance as [[graph-stream]]'s source 1), and on each tick it diffs every governed record's
16
- // (status, proposal, note) against the last seen and appends what moved. One mechanism covers every writer
17
- // by construction. Granularity is the debounce window — a flap faster than ~100ms can collapse, exactly like
18
- // the board itself.
11
+ // A declaration note is conversation content, so TS lifecycle writes append moved state at the same write
12
+ // boundary instead of asking a later sample of mutable session.json to reconstruct it. The observer remains
13
+ // because the lifecycle also has a writer the TS layer never sees: the mark-active hook value-replaces
14
+ // status/proposal/note with pure-shell sed ([[state]]). One fs.watch on the sessions root (debounced) plus a
15
+ // slow reconcile tick repairs those external writes. Direct append + observation may duplicate one move;
16
+ // readTimeline folds adjacent duplicates without making history mutable.
19
17
  //
20
- // The recorder runs ONLY in the serve process (superviseTimeline is called from index.ts) so exactly one
21
- // process appends; timestamps are observation times, honest to within the debounce. Only the AUTHORED axis
22
- // is recorded liveness (offline/starting/unknown) is a present-tense derivation ([[state]]), re-derived
23
- // per probe and never history, so it stays off the durable log; a surface shows the CURRENT liveness from
24
- // the board row. The timeline lives and dies with the session record (close sweeps the store dir), like
18
+ // The observer runs ONLY in the serve process (superviseTimeline is called from index.ts); lifecycle writers
19
+ // and confirmed senders append from whichever process owns that write. Direct events use the write time;
20
+ // observed shell events use an observation time honest to within the debounce. Only the AUTHORED axis is
21
+ // recorded liveness (offline/starting/unknown) is a present-tense derivation ([[state]]), re-derived per
22
+ // probe and never history, so it stays off the durable log; a surface shows the CURRENT liveness from the
23
+ // board row. The timeline lives and dies with the session record (close sweeps the store dir), like
25
24
  // comms.ndjson. `sent` events are appended by sendText on a CONFIRMED post-launch delivery (dashboard/phone
26
25
  // input, `spex session send`, merge and issue dispatch); the initial launch prompt passes through the same
27
26
  // composition seam but has no adapter confirmation to record here. `from` is the sending session's id,
@@ -40,6 +39,13 @@ function append(id: string, ev: TimelineEvent): void {
40
39
  } catch { /* best-effort: a failed history append must never break the state machine or a delivery */ }
41
40
  }
42
41
 
42
+ // Record a lifecycle value that has already landed in session.json. TypeScript state writers call this
43
+ // synchronously before returning, so a later write cannot erase an intermediate declaration note from the
44
+ // conversation. The serve observer calls the same sink for shell-authored state.
45
+ export function recordStatus(id: string, status: Lifecycle, proposal: Proposal | null, note: string | null): void {
46
+ append(id, { ts: new Date().toISOString(), kind: 'status', status, proposal, note })
47
+ }
48
+
43
49
  function readEvents(id: string): TimelineEvent[] {
44
50
  try {
45
51
  const p = timelinePath(id)
@@ -78,11 +84,11 @@ function scan(): void {
78
84
  try { ids = listSessionIds() } catch { return }
79
85
  for (const id of ids) {
80
86
  try {
81
- const raw = readAliasedRawRecord(id)
82
- if (!raw || !raw.governed) continue
83
- const status = (raw.status || 'active') as Lifecycle
84
- const proposal = (raw.proposal || null) as Proposal | null
85
- const note = raw.note || null
87
+ const entry = readPublicRecordEntry(id)
88
+ if (entry.kind !== 'ok' || !entry.raw.governed) continue
89
+ const status = (entry.raw.status || 'active') as Lifecycle
90
+ const proposal = (entry.raw.proposal || null) as Proposal | null
91
+ const note = entry.raw.note || null
86
92
  const fp = fpOf(status, proposal, note)
87
93
  if (lastSeen.get(id) === fp) continue
88
94
  if (!lastSeen.has(id)) {
@@ -90,7 +96,7 @@ function scan(): void {
90
96
  if (last && fpOf(last.status, last.proposal ?? null, last.note ?? null) === fp) { lastSeen.set(id, fp); continue }
91
97
  }
92
98
  lastSeen.set(id, fp)
93
- append(id, { ts: new Date().toISOString(), kind: 'status', status, proposal, note })
99
+ recordStatus(id, status, proposal, note)
94
100
  } catch { /* one bad record must not stall the sweep */ }
95
101
  }
96
102
  const live = new Set(ids)
@@ -144,9 +150,9 @@ export function recordSent(id: string, text: string, from: string | null, replyV
144
150
 
145
151
  // the read surface behind GET /api/sessions/:id/timeline: the last `limit` events, oldest first, each
146
152
  // status event carrying its composed display word. null = no such session (the route 404s).
147
- // Adjacent status lines with identical (status, proposal, note) fold into their first: TWO serve processes
148
- // observing one store (a throwaway worktree/eval serve beside the live one) each keep their own lastSeen,
149
- // so a single record move can append twice — cross-process write locking isn't worth buying, so the log
153
+ // Adjacent status lines with identical (status, proposal, note) fold into their first: a direct writer and
154
+ // observer, or TWO serve processes observing one store (a throwaway worktree/eval serve beside the live
155
+ // one), can append a single record move twice. Cross-process write locking isn't worth buying, so the log
150
156
  // stays best-effort append-only and the read is where duplicates die, same stance as the board.
151
157
  export function readTimeline(id: string, limit = 500): { events: TimelineEvent[] } | null {
152
158
  let raw: ReturnType<typeof readAliasedRawRecord>