spexcode 0.6.7 → 0.7.0-next.0

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 (247) hide show
  1. package/README.md +12 -7
  2. package/node_modules/@spexcode/session-application/dist/index.d.ts +17 -0
  3. package/node_modules/@spexcode/session-application/dist/index.js +35 -0
  4. package/node_modules/@spexcode/session-application/dist/migration.d.ts +51 -0
  5. package/node_modules/@spexcode/session-application/dist/migration.js +694 -0
  6. package/node_modules/@spexcode/session-application/dist/production.d.ts +109 -0
  7. package/node_modules/@spexcode/session-application/dist/production.js +438 -0
  8. package/node_modules/@spexcode/session-application/dist/schema.d.ts +2 -0
  9. package/node_modules/@spexcode/session-application/dist/schema.js +39 -0
  10. package/node_modules/@spexcode/session-application/package.json +29 -0
  11. package/node_modules/@spexcode/session-events/dist/errors.d.ts +6 -0
  12. package/node_modules/@spexcode/session-events/dist/errors.js +11 -0
  13. package/node_modules/@spexcode/session-events/dist/index.d.ts +46 -0
  14. package/node_modules/@spexcode/session-events/dist/index.js +245 -0
  15. package/node_modules/@spexcode/session-events/dist/schema.d.ts +3 -0
  16. package/node_modules/@spexcode/session-events/dist/schema.js +44 -0
  17. package/node_modules/@spexcode/session-events/package.json +24 -0
  18. package/node_modules/@spexcode/session-protocol/dist/canonical.d.ts +36 -0
  19. package/node_modules/@spexcode/session-protocol/dist/canonical.js +139 -0
  20. package/node_modules/@spexcode/session-protocol/dist/engine.d.ts +22 -0
  21. package/node_modules/@spexcode/session-protocol/dist/engine.js +478 -0
  22. package/node_modules/@spexcode/session-protocol/dist/errors.d.ts +8 -0
  23. package/node_modules/@spexcode/session-protocol/dist/errors.js +39 -0
  24. package/node_modules/@spexcode/session-protocol/dist/index.d.ts +60 -0
  25. package/node_modules/@spexcode/session-protocol/dist/index.js +4 -0
  26. package/node_modules/@spexcode/session-protocol/dist/schema.d.ts +36 -0
  27. package/node_modules/@spexcode/session-protocol/dist/schema.js +192 -0
  28. package/node_modules/@spexcode/{session-core → session-protocol}/package.json +4 -8
  29. package/node_modules/@spexcode/session-runtime/dist/errors.d.ts +6 -0
  30. package/node_modules/@spexcode/session-runtime/dist/errors.js +11 -0
  31. package/node_modules/@spexcode/session-runtime/dist/index.d.ts +33 -0
  32. package/node_modules/@spexcode/session-runtime/dist/index.js +193 -0
  33. package/node_modules/@spexcode/session-runtime/dist/schema.d.ts +2 -0
  34. package/node_modules/@spexcode/session-runtime/dist/schema.js +37 -0
  35. package/node_modules/@spexcode/session-runtime/package.json +24 -0
  36. package/node_modules/@spexcode/session-selflaunch/bin/spex-session.mjs +4 -0
  37. package/node_modules/@spexcode/session-selflaunch/dist/cli.d.ts +25 -0
  38. package/node_modules/@spexcode/session-selflaunch/dist/cli.js +156 -0
  39. package/node_modules/@spexcode/session-selflaunch/dist/index.d.ts +28 -0
  40. package/node_modules/@spexcode/session-selflaunch/dist/index.js +25 -0
  41. package/node_modules/@spexcode/session-selflaunch/dist/locality.d.ts +31 -0
  42. package/node_modules/@spexcode/session-selflaunch/dist/locality.js +135 -0
  43. package/node_modules/@spexcode/session-selflaunch/dist/path.d.ts +13 -0
  44. package/node_modules/@spexcode/session-selflaunch/dist/path.js +45 -0
  45. package/node_modules/@spexcode/session-selflaunch/package.json +35 -0
  46. package/node_modules/@spexcode/session-topology/dist/errors.d.ts +6 -0
  47. package/node_modules/@spexcode/session-topology/dist/errors.js +11 -0
  48. package/node_modules/@spexcode/session-topology/dist/index.d.ts +24 -0
  49. package/node_modules/@spexcode/session-topology/dist/index.js +173 -0
  50. package/node_modules/@spexcode/session-topology/dist/schema.d.ts +3 -0
  51. package/node_modules/@spexcode/session-topology/dist/schema.js +27 -0
  52. package/node_modules/@spexcode/session-topology/package.json +24 -0
  53. package/node_modules/@spexcode/spec-cli/bin/spex.mjs +175 -21
  54. package/node_modules/@spexcode/spec-cli/dist/cli.js +121 -59
  55. package/node_modules/@spexcode/spec-cli/dist/client.d.ts +1 -3
  56. package/node_modules/@spexcode/spec-cli/dist/client.js +49 -30
  57. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +11 -0
  58. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +46 -9
  59. package/node_modules/@spexcode/spec-cli/dist/delivery-lock.d.ts +2 -0
  60. package/node_modules/@spexcode/spec-cli/dist/delivery-lock.js +58 -0
  61. package/node_modules/@spexcode/spec-cli/dist/doctor.js +53 -11
  62. package/node_modules/@spexcode/spec-cli/dist/file-write.js +14 -2
  63. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +2 -1
  64. package/node_modules/@spexcode/spec-cli/dist/gateway.js +6 -3
  65. package/node_modules/@spexcode/spec-cli/dist/graphCache.js +32 -2
  66. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +57 -2
  67. package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +2 -0
  68. package/node_modules/@spexcode/spec-cli/dist/graphStream.js +83 -3
  69. package/node_modules/@spexcode/spec-cli/dist/guide.js +29 -18
  70. package/node_modules/@spexcode/spec-cli/dist/harness-select.js +16 -3
  71. package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +24 -6
  72. package/node_modules/@spexcode/spec-cli/dist/harness.js +510 -72
  73. package/node_modules/@spexcode/spec-cli/dist/help.js +11 -8
  74. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +8 -0
  75. package/node_modules/@spexcode/spec-cli/dist/host-resources.js +29 -8
  76. package/node_modules/@spexcode/spec-cli/dist/host.d.ts +7 -0
  77. package/node_modules/@spexcode/spec-cli/dist/host.js +98 -1
  78. package/node_modules/@spexcode/spec-cli/dist/index.js +294 -36
  79. package/node_modules/@spexcode/spec-cli/dist/init.js +1 -1
  80. package/node_modules/@spexcode/spec-cli/dist/lint.js +70 -35
  81. package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +3 -2
  82. package/node_modules/@spexcode/spec-cli/dist/listen.js +14 -2
  83. package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +2 -1
  84. package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +46 -15
  85. package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +2 -2
  86. package/node_modules/@spexcode/spec-cli/dist/materialize.js +176 -35
  87. package/node_modules/@spexcode/spec-cli/dist/opencode.js +10 -2
  88. package/node_modules/@spexcode/spec-cli/dist/pi-harness.js +18 -5
  89. package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +2 -0
  90. package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +27 -0
  91. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +14 -14
  92. package/node_modules/@spexcode/spec-cli/dist/reviews.js +12 -7
  93. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +11 -0
  94. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +79 -1
  95. package/node_modules/@spexcode/spec-cli/dist/session-application.d.ts +23 -0
  96. package/node_modules/@spexcode/spec-cli/dist/session-application.js +189 -0
  97. package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +13 -1
  98. package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +6 -0
  99. package/node_modules/@spexcode/spec-cli/dist/session-files.js +13 -1
  100. package/node_modules/@spexcode/spec-cli/dist/session-follow.js +39 -22
  101. package/node_modules/@spexcode/{session-core/dist/record-lock.d.ts → spec-cli/dist/session-record-lock.d.ts} +0 -4
  102. package/node_modules/@spexcode/spec-cli/dist/session-record-lock.js +94 -0
  103. package/node_modules/@spexcode/spec-cli/dist/session-runtime-adapter.d.ts +44 -0
  104. package/node_modules/@spexcode/spec-cli/dist/session-runtime-adapter.js +37 -0
  105. package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +25 -2
  106. package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +68 -11
  107. package/node_modules/@spexcode/spec-cli/dist/session-transcript.d.ts +57 -0
  108. package/node_modules/@spexcode/spec-cli/dist/session-transcript.js +137 -0
  109. package/node_modules/@spexcode/spec-cli/dist/session-web.js +4 -4
  110. package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +112 -15
  111. package/node_modules/@spexcode/spec-cli/dist/sessions.js +1553 -764
  112. package/node_modules/@spexcode/spec-cli/dist/shim-runtime.js +14 -0
  113. package/node_modules/@spexcode/spec-cli/dist/source-list.d.ts +13 -0
  114. package/node_modules/@spexcode/spec-cli/dist/source-list.js +99 -0
  115. package/node_modules/@spexcode/spec-cli/dist/source-read.d.ts +16 -0
  116. package/node_modules/@spexcode/spec-cli/dist/source-read.js +84 -0
  117. package/node_modules/@spexcode/spec-cli/dist/spec-attachments.d.ts +7 -0
  118. package/node_modules/@spexcode/spec-cli/dist/spec-attachments.js +89 -0
  119. package/node_modules/@spexcode/spec-cli/dist/spec-body-edit.d.ts +23 -0
  120. package/node_modules/@spexcode/spec-cli/dist/spec-body-edit.js +138 -0
  121. package/node_modules/@spexcode/spec-cli/dist/supervise.js +15 -6
  122. package/node_modules/@spexcode/spec-cli/hooks/dispatch.sh +19 -31
  123. package/node_modules/@spexcode/spec-cli/hooks/harness.sh +6 -6
  124. package/node_modules/@spexcode/spec-cli/package.json +8 -6
  125. package/node_modules/@spexcode/spec-cli/templates/hooks/post-checkout +4 -2
  126. package/node_modules/@spexcode/spec-cli/templates/hooks/post-merge +2 -1
  127. package/node_modules/@spexcode/spec-cli/templates/hooks/pre-commit +5 -3
  128. package/node_modules/@spexcode/spec-cli/templates/hooks/reference-transaction +5 -3
  129. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/spec.md +2 -7
  130. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/idle/idle.sh +4 -10
  131. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/idle/spec.md +1 -1
  132. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/mark-active.sh +22 -24
  133. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md +10 -2
  134. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/fail.sh +8 -7
  135. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/spec.md +3 -1
  136. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/session-listen.sh +133 -0
  137. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/spec.md +36 -0
  138. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/spec.md +2 -0
  139. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md +1 -1
  140. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +17 -20
  141. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/skills/merge/spec.md +33 -0
  142. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/skills/spec.md +2 -6
  143. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/spec.md +7 -0
  144. package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +13 -2
  145. package/node_modules/@spexcode/spec-core/dist/anchors.js +311 -15
  146. package/node_modules/@spexcode/spec-core/dist/git.d.ts +4 -0
  147. package/node_modules/@spexcode/spec-core/dist/git.js +9 -9
  148. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +12 -1
  149. package/node_modules/@spexcode/spec-core/dist/layout.js +4 -2
  150. package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +1 -0
  151. package/node_modules/@spexcode/spec-core/dist/resilience.js +8 -0
  152. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +1 -0
  153. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +7 -3
  154. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +1 -0
  155. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +4 -0
  156. package/node_modules/@spexcode/spec-core/dist/specs.d.ts +2 -0
  157. package/node_modules/@spexcode/spec-core/dist/specs.js +30 -0
  158. package/node_modules/@spexcode/spec-core/package.json +4 -1
  159. package/node_modules/@spexcode/spec-core/templates/spexcode.json +36 -13
  160. package/node_modules/@spexcode/spec-eval/dist/cli.js +1 -1
  161. package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +1 -0
  162. package/node_modules/@spexcode/spec-eval/dist/evaltab.js +6 -1
  163. package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +1 -1
  164. package/node_modules/@spexcode/spec-eval/dist/freshness.js +12 -12
  165. package/node_modules/@spexcode/spec-eval/dist/host.d.ts +2 -1
  166. package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +2 -1
  167. package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +20 -3
  168. package/node_modules/@spexcode/spec-eval/package.json +2 -2
  169. package/node_modules/@spexcode/spec-forge/package.json +2 -2
  170. package/node_modules/@spexcode/transcript/dist/frames.d.ts +44 -0
  171. package/node_modules/@spexcode/transcript/dist/frames.js +85 -0
  172. package/node_modules/@spexcode/transcript/dist/index.d.ts +5 -0
  173. package/node_modules/@spexcode/transcript/dist/index.js +5 -0
  174. package/node_modules/@spexcode/transcript/dist/live.d.ts +16 -0
  175. package/node_modules/@spexcode/transcript/dist/live.js +65 -0
  176. package/node_modules/@spexcode/transcript/dist/parsers.d.ts +47 -0
  177. package/node_modules/@spexcode/transcript/dist/parsers.js +285 -0
  178. package/node_modules/@spexcode/transcript/dist/readers.d.ts +10 -0
  179. package/node_modules/@spexcode/transcript/dist/readers.js +296 -0
  180. package/node_modules/@spexcode/transcript/dist/turns.d.ts +42 -0
  181. package/node_modules/@spexcode/transcript/dist/turns.js +11 -0
  182. package/node_modules/@spexcode/transcript/package.json +30 -0
  183. package/node_modules/@vscode/tree-sitter-wasm/LICENSE +21 -0
  184. package/node_modules/@vscode/tree-sitter-wasm/README.md +36 -0
  185. package/node_modules/@vscode/tree-sitter-wasm/SECURITY.md +41 -0
  186. package/node_modules/@vscode/tree-sitter-wasm/cgmanifest.json +16 -0
  187. package/node_modules/@vscode/tree-sitter-wasm/package.json +42 -0
  188. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-bash.wasm +0 -0
  189. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-c-sharp.wasm +0 -0
  190. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-cpp.wasm +0 -0
  191. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-css.wasm +0 -0
  192. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-go.wasm +0 -0
  193. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ini.wasm +0 -0
  194. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-java.wasm +0 -0
  195. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-javascript.wasm +0 -0
  196. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-php.wasm +0 -0
  197. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-powershell.wasm +0 -0
  198. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-python.wasm +0 -0
  199. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-regex.wasm +0 -0
  200. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ruby.wasm +0 -0
  201. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-rust.wasm +0 -0
  202. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-tsx.wasm +0 -0
  203. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-typescript.wasm +0 -0
  204. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.js +4075 -0
  205. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.wasm +0 -0
  206. package/node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts +1027 -0
  207. package/node_modules/smol-toml/LICENSE +24 -0
  208. package/node_modules/smol-toml/README.md +418 -0
  209. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  210. package/node_modules/smol-toml/dist/date.js +127 -0
  211. package/node_modules/smol-toml/dist/error.d.ts +38 -0
  212. package/node_modules/smol-toml/dist/error.js +63 -0
  213. package/node_modules/smol-toml/dist/extract.js +69 -0
  214. package/node_modules/smol-toml/dist/index.cjs +734 -0
  215. package/node_modules/smol-toml/dist/index.d.ts +43 -0
  216. package/node_modules/smol-toml/dist/index.js +33 -0
  217. package/node_modules/smol-toml/dist/parse.d.ts +36 -0
  218. package/node_modules/smol-toml/dist/parse.js +149 -0
  219. package/node_modules/smol-toml/dist/primitive.js +238 -0
  220. package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
  221. package/node_modules/smol-toml/dist/stringify.js +181 -0
  222. package/node_modules/smol-toml/dist/struct.js +179 -0
  223. package/node_modules/smol-toml/dist/util.d.ts +38 -0
  224. package/node_modules/smol-toml/dist/util.js +89 -0
  225. package/node_modules/smol-toml/package.json +68 -0
  226. package/package.json +9 -3
  227. package/node_modules/@spexcode/session-core/dist/delivery-queue.d.ts +0 -24
  228. package/node_modules/@spexcode/session-core/dist/delivery-queue.js +0 -206
  229. package/node_modules/@spexcode/session-core/dist/index.d.ts +0 -5
  230. package/node_modules/@spexcode/session-core/dist/index.js +0 -5
  231. package/node_modules/@spexcode/session-core/dist/internal.d.ts +0 -3
  232. package/node_modules/@spexcode/session-core/dist/internal.js +0 -3
  233. package/node_modules/@spexcode/session-core/dist/message.d.ts +0 -22
  234. package/node_modules/@spexcode/session-core/dist/message.js +0 -53
  235. package/node_modules/@spexcode/session-core/dist/record-lock.js +0 -152
  236. package/node_modules/@spexcode/session-core/dist/runtime-session.d.ts +0 -62
  237. package/node_modules/@spexcode/session-core/dist/runtime-session.js +0 -326
  238. package/node_modules/@spexcode/session-core/dist/session-cursors.d.ts +0 -14
  239. package/node_modules/@spexcode/session-core/dist/session-cursors.js +0 -82
  240. package/node_modules/@spexcode/session-core/dist/session-timeline.d.ts +0 -47
  241. package/node_modules/@spexcode/session-core/dist/session-timeline.js +0 -216
  242. package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +0 -26
  243. package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +0 -475
  244. package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +0 -10
  245. package/node_modules/@spexcode/spec-cli/dist/session-execution.js +0 -70
  246. package/node_modules/@spexcode/spec-cli/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -53
  247. package/node_modules/@spexcode/spec-cli/hooks/compat/mark-active-sed-v0.fixture +0 -46
@@ -14,7 +14,6 @@ export type Config = {
14
14
  apiUrl?: string;
15
15
  title?: string;
16
16
  icon?: string;
17
- showHeadlessLaunchers?: boolean;
18
17
  };
19
18
  uploads?: {
20
19
  maxBytes?: number;
@@ -111,8 +110,10 @@ export type RawRecord = {
111
110
  createdAt: number;
112
111
  harness?: string;
113
112
  harness_session_id?: string;
113
+ runtime_start_token?: string;
114
114
  stopped?: boolean;
115
115
  archived?: boolean;
116
+ closed_at?: string;
116
117
  cold_proof?: string;
117
118
  adapter_recovery?: string;
118
119
  launcher?: string;
@@ -125,6 +126,15 @@ export type RawRecord = {
125
126
  runtime_revision?: string;
126
127
  runtime_metadata?: Record<string, string>;
127
128
  base?: string;
129
+ diff_comments?: Array<{
130
+ id: string;
131
+ file_path: string;
132
+ line_start: number;
133
+ line_end: number;
134
+ body: string;
135
+ diff_identity: string;
136
+ sent_at: string | null;
137
+ }>;
128
138
  launch_readiness_pending?: '' | RawLaunchReadinessPending;
129
139
  };
130
140
  export declare const SESSION_LIFECYCLES: readonly ["active", "idle", "awaiting", "parked", "error", "asking", "queued"];
@@ -139,6 +149,7 @@ export type RawLaunchReadinessOriginal = {
139
149
  note: string | null;
140
150
  stopped: boolean;
141
151
  archived: boolean;
152
+ closed_at?: string | null;
142
153
  cold_proof: string | null;
143
154
  adapter_recovery: string | null;
144
155
  };
@@ -136,7 +136,7 @@ export function mainRoot(proj) {
136
136
  }
137
137
  // @@@ global per-session store - Fork A: NO SpexCode files live in the worktree any more, so the worktree's
138
138
  // spec/code tree is pristine (zero per-session pollution). Every per-session runtime artifact — the
139
- // structured record (session.json) AND the launcher products (prompt, launch, launch.sh) AND the recorded comms AND
139
+ // runtime envelope (runtime.json) AND the launcher products (prompt, launch, launch.sh) AND the recorded comms AND
140
140
  // the spec-discipline sentinels — lives in a per-USER GLOBAL store, keyed by the harness `session_id` so two
141
141
  // agents in one folder never clobber, and grouped PER PROJECT (mirroring Claude's ~/.claude/projects/<enc>/)
142
142
  // so the board enumerates ONE directory. This is the single seam that knows where the store sits; sessions.ts
@@ -169,7 +169,7 @@ export function treeSlotDir(wt) {
169
169
  // all keyed by session_id under <home>/projects/<enc>/sessions/.
170
170
  export function sessionsRoot() { return join(runtimeRoot(), 'sessions'); }
171
171
  export function sessionStoreDir(id) { return join(sessionsRoot(), id); }
172
- export function sessionRecordPath(id) { return join(sessionStoreDir(id), 'session.json'); }
172
+ export function sessionRecordPath(id) { return join(sessionStoreDir(id), 'runtime.json'); }
173
173
  export function sessionArtifactPath(id, name) { return join(sessionStoreDir(id), name); }
174
174
  export const SESSION_LIFECYCLES = ['active', 'idle', 'awaiting', 'parked', 'error', 'asking', 'queued'];
175
175
  export const SESSION_PROPOSALS = ['merge', 'nothing', 'close'];
@@ -190,6 +190,7 @@ export function rawLaunchReadinessOriginal(raw) {
190
190
  || !(original.proposal === null || original.proposal === '' || isSessionProposal(original.proposal))
191
191
  || !(typeof original.note === 'string' || original.note === null)
192
192
  || typeof original.stopped !== 'boolean' || typeof original.archived !== 'boolean'
193
+ || !(typeof original.closed_at === 'string' || original.closed_at === null || original.closed_at === undefined)
193
194
  || !(typeof original.cold_proof === 'string' || original.cold_proof === null)
194
195
  || !(typeof original.adapter_recovery === 'string' || original.adapter_recovery === null)) {
195
196
  throw new Error(`session '${raw.session_id}' has an invalid launch_readiness_pending fence`);
@@ -281,6 +282,7 @@ export function projectPublicRecordEntry(id, entry) {
281
282
  note: original.note || null,
282
283
  stopped: original.stopped,
283
284
  archived: original.archived,
285
+ closed_at: original.closed_at ?? undefined,
284
286
  cold_proof: original.cold_proof ?? undefined,
285
287
  adapter_recovery: original.adapter_recovery ?? undefined,
286
288
  launch_readiness_pending: '',
@@ -1,2 +1,3 @@
1
+ export declare function isTrashWorktreePath(path: string): boolean;
1
2
  export declare function guardWorktree<T>(dir: string, fn: () => T | Promise<T>, degraded: () => T): Promise<T | null>;
2
3
  export declare function installProcessGuards(): void;
@@ -1,7 +1,15 @@
1
1
  import { existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
2
3
  function describe(e) {
3
4
  return e instanceof Error ? e.message : String(e);
4
5
  }
6
+ // Delayed close deletion lives below this directory and is never a live worktree input.
7
+ export function isTrashWorktreePath(path) {
8
+ const normalized = resolve(path);
9
+ const parts = normalized.split(/[\\/]+/);
10
+ const worktrees = parts.lastIndexOf('.worktrees');
11
+ return worktrees >= 0 && parts[worktrees + 1] === '.trash';
12
+ }
5
13
  // run a per-worktree DETAIL read; on a throw, branch on whether the worktree DIRECTORY still exists.
6
14
  // dir gone → the worktree was genuinely removed mid-read → return null so the caller omits it.
7
15
  // dir present → a flaky detail read (ENOENT race on a sibling file, or a git index/ref lock under a
@@ -68,6 +68,7 @@ export const EVAL_FILTER_KIND: Readonly<{
68
68
  RESULT: "result";
69
69
  BLIND: "blind";
70
70
  UNMEASURED: "unmeasured";
71
+ DEFERRED: "deferred";
71
72
  DANGLING: "dangling";
72
73
  }>;
73
74
  export function evidenceList(reading: any): any;
@@ -11,6 +11,7 @@ export const EVAL_FILTER_KIND = Object.freeze({
11
11
  RESULT: 'result',
12
12
  BLIND: 'blind',
13
13
  UNMEASURED: 'unmeasured',
14
+ DEFERRED: 'deferred',
14
15
  DANGLING: 'dangling',
15
16
  });
16
17
  export const evidenceList = (reading) => reading?.evidence?.length ? reading.evidence
@@ -146,9 +147,11 @@ export function issueFilterModel(items, raw = {}, context = {}) {
146
147
  const evalIsResult = (entry) => entry.filterKind === EVAL_FILTER_KIND.RESULT;
147
148
  const verdictOf = (entry) => evalIsResult(entry)
148
149
  ? (entry.verdict?.status || 'unscored')
149
- : entry.filterKind === EVAL_FILTER_KIND.BLIND || entry.filterKind === EVAL_FILTER_KIND.UNMEASURED
150
- ? 'unmeasured'
151
- : 'unscored';
150
+ : entry.filterKind === EVAL_FILTER_KIND.DEFERRED
151
+ ? 'deferred'
152
+ : entry.filterKind === EVAL_FILTER_KIND.BLIND || entry.filterKind === EVAL_FILTER_KIND.UNMEASURED
153
+ ? 'unmeasured'
154
+ : 'unscored';
152
155
  const reviewStateOf = (entry) => (evalIsResult(entry) && entry.fresh && entry.humanOk ? 'reviewed' : 'current');
153
156
  // the ONE freshness axis of the Eval adapter — the facet's option values and the verdict sections' split
154
157
  // read it, so a chip and its Freshness menu can never disagree about what "stale" counts.
@@ -194,6 +197,7 @@ const EVAL_CONFIG = {
194
197
  { value: 'fail', label: 'reviewList.verdict.fail', split: true },
195
198
  { value: 'pass', label: 'reviewList.verdict.pass', split: true },
196
199
  { value: 'unmeasured', label: 'reviewList.verdict.unmeasured' },
200
+ { value: 'deferred', label: 'reviewList.verdict.deferred' },
197
201
  ],
198
202
  },
199
203
  facets: [
@@ -31,6 +31,7 @@ export const ISSUE_QUERY_DEFAULT: "is:issue state:open";
31
31
  export const EVAL_QUERY_DEFAULT: "is:eval";
32
32
  export function scopedEvalQuery(sessionId: any): any;
33
33
  export function nodeEvalQuery(nodeId: any): any;
34
+ export function nodeIssueQuery(nodeId: any): any;
34
35
  export function quoteValue(v: any): string;
35
36
  export function tokenize(text: any): ({
36
37
  ws: boolean;
@@ -8,6 +8,10 @@ export const EVAL_QUERY_DEFAULT = 'is:eval';
8
8
  export const scopedEvalQuery = (sessionId) => setToken(EVAL_QUERY_DEFAULT, 'scope', sessionId);
9
9
  // the aggregate score/count doors' address ([[eval-score-badge]]): the default view, node-filtered.
10
10
  export const nodeEvalQuery = (nodeId) => setToken(EVAL_QUERY_DEFAULT, 'node', nodeId);
11
+ // a node's OPEN issues ([[context-dock]]): the issue list's default view, node-filtered. Same shape and
12
+ // same reason as the eval twin — a surface that wants "this node's issues" asks for a text, never for a
13
+ // second filter path, so the panel and the list it links to are literally the same query.
14
+ export const nodeIssueQuery = (nodeId) => setToken(ISSUE_QUERY_DEFAULT, 'node', nodeId);
11
15
  const KEY_RE = /^([A-Za-z][A-Za-z0-9-]*):(.*)$/s;
12
16
  const unquote = (v) => (v.length >= 2 && v.startsWith('"') && v.endsWith('"') ? v.slice(1, -1) : v);
13
17
  export const quoteValue = (v) => (/\s/.test(String(v)) ? `"${v}"` : String(v));
@@ -9,6 +9,7 @@ export type SpecParts = {
9
9
  expandedSpec: string;
10
10
  };
11
11
  declare function parseParts(body: string): SpecParts | null;
12
+ export declare function bodyMentions(body: string): string[];
12
13
  export type DerivedStatus = 'pending' | 'active' | 'merged' | 'drift';
13
14
  export declare function deriveStatus(d: {
14
15
  version: number;
@@ -43,6 +44,7 @@ export declare function specContent(id: string): {
43
44
  body: string;
44
45
  parts: ReturnType<typeof parseParts>;
45
46
  } | null;
47
+ export declare function specDir(id: string): string | null;
46
48
  export type LoadSpecsOptions = {
47
49
  tip?: string;
48
50
  history?: HistoryIndex | null;
@@ -68,6 +68,29 @@ function parseParts(body) {
68
68
  const t = (a) => a.join('\n').trim();
69
69
  return { rawSource: t(acc.rawSource), expandedSpec: t(acc.expandedSpec) };
70
70
  }
71
+ // the `[[id]]` reference grammar as a spec BODY writes it. Its consumer is the lint rule that rejects a
72
+ // dangling reference — the one place a `[[name]]` has to be judged against the node universe. It is a
73
+ // parser and not a projection: the loader once shipped the surviving ids as a `mentions` edge on every
74
+ // node, and that edge is gone, because a prose mention is a fact about the GRAPH and never was a fact
75
+ // about the node the reader has open ([[context-dock]]). Prose only: a fenced block or an inline
76
+ // `code span` is sample text (`[[node]]`, `[[<id>]]` placeholders live there), not a reference. Distinct,
77
+ // in first-appearance order; whether a name resolves to a real node is the caller's judgement.
78
+ const MENTION_RE = /\[\[(\.?[\p{L}\p{N}_-]+)\]\]/gu;
79
+ export function bodyMentions(body) {
80
+ const out = new Set();
81
+ let inFence = false;
82
+ for (const rawLine of body.split('\n')) {
83
+ if (/^\s*```/.test(rawLine)) {
84
+ inFence = !inFence;
85
+ continue;
86
+ }
87
+ if (inFence)
88
+ continue;
89
+ for (const m of rawLine.replace(/`[^`]*`/g, '').matchAll(MENTION_RE))
90
+ out.add(m[1]);
91
+ }
92
+ return [...out];
93
+ }
71
94
  export function deriveStatus(d) {
72
95
  if (d.fmStatus === 'pending' && !d.hasCode && d.drift === 0)
73
96
  return 'pending';
@@ -228,6 +251,13 @@ export function specContent(id) {
228
251
  const r = raws().find((x) => x.id === id);
229
252
  return r ? { body: r.body.trim(), parts: parseParts(r.body) } : null;
230
253
  }
254
+ // Where a node LIVES, repo-relative — its own folder, not its spec.md. A node's folder is the unit (the
255
+ // same rule the plugin instances are built on), so anything that wants to see what a node carries besides
256
+ // its body asks the spec tree's own reader rather than re-deriving a path from an id.
257
+ export function specDir(id) {
258
+ const r = raws().find((x) => x.id === id);
259
+ return r ? r.relPath.replace(/\/spec\.md$/, '') : null;
260
+ }
231
261
  export async function loadSpecs(root = ROOT, options = {}) {
232
262
  // The default pair shares one immutable-event snapshot; explicit sides let callers skip or supply either
233
263
  // projection. Every node below is then a pure in-memory lookup.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spexcode/spec-core",
3
- "version": "0.6.7",
3
+ "version": "0.7.0-next.0",
4
4
  "type": "module",
5
5
  "description": "SpexCode's dependency-minimal spec graph core.",
6
6
  "files": [
@@ -17,6 +17,9 @@
17
17
  "engines": {
18
18
  "node": ">=22"
19
19
  },
20
+ "dependencies": {
21
+ "@vscode/tree-sitter-wasm": "0.3.1"
22
+ },
20
23
  "publishConfig": {
21
24
  "access": "public"
22
25
  },
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "lint": {
3
- "governedRoots": ["."]
4
- },
5
- "dashboard": {
6
- "showHeadlessLaunchers": false
3
+ "governedRoots": [
4
+ "."
5
+ ]
7
6
  },
8
7
  "uploads": {
9
8
  "maxBytes": 2147483648,
@@ -19,15 +18,39 @@
19
18
  },
20
19
  "sessions": {
21
20
  "launchers": {
22
- "claude": { "harness": "claude", "cmd": "claude" },
23
- "claude-headless": { "harness": "claude-headless", "cmd": "claude" },
24
- "codex": { "harness": "codex", "cmd": "codex" },
25
- "codex-headless": { "harness": "codex-headless", "cmd": "codex --yolo" },
26
- "opencode": { "harness": "opencode", "cmd": "opencode" },
27
- "opencode-headless": { "harness": "opencode-headless", "cmd": "opencode --auto" },
28
- "pi": { "harness": "pi", "cmd": "pi" },
29
- "pi-headless": { "harness": "pi-headless", "cmd": "pi" }
21
+ "claude-headless": {
22
+ "harness": "claude-headless",
23
+ "cmd": "claude"
24
+ },
25
+ "claude": {
26
+ "harness": "claude",
27
+ "cmd": "claude"
28
+ },
29
+ "codex-headless": {
30
+ "harness": "codex-headless",
31
+ "cmd": "codex --yolo"
32
+ },
33
+ "codex": {
34
+ "harness": "codex",
35
+ "cmd": "codex"
36
+ },
37
+ "opencode-headless": {
38
+ "harness": "opencode-headless",
39
+ "cmd": "opencode --auto"
40
+ },
41
+ "opencode": {
42
+ "harness": "opencode",
43
+ "cmd": "opencode"
44
+ },
45
+ "pi-headless": {
46
+ "harness": "pi-headless",
47
+ "cmd": "pi"
48
+ },
49
+ "pi": {
50
+ "harness": "pi",
51
+ "cmd": "pi"
52
+ }
30
53
  },
31
- "defaultLauncher": "claude"
54
+ "defaultLauncher": "claude-headless"
32
55
  }
33
56
  }
@@ -203,7 +203,7 @@ async function scan(args = []) {
203
203
  }
204
204
  // an anchor is a claim a named unit exists — dead/ambiguous/unextractable is LOUD, and until it is
205
205
  // repaired the probe issues no verdict, so the reading stays conservatively stale.
206
- for (const p of [...axis.problems, ...anchorProblems(root, axis.entries)]) {
206
+ for (const p of [...axis.problems, ...(await anchorProblems(root, axis.entries))]) {
207
207
  malformed++;
208
208
  findings.push(` • eval-schema: '${s.id}' scenario '${sc.name}' ${p} — fix ${y.evalPath}`);
209
209
  }
@@ -83,6 +83,7 @@ export type EvalContext = {
83
83
  export declare function evalContext(root: string, specs: Awaited<ReturnType<typeof loadSpecs>>, idx: DriftIndex, hidx: HistoryIndex, remarks?: Map<string, RemarkTrack>, ynodes?: EvalNode[]): Promise<EvalContext>;
84
84
  export declare function evalTimelines(ids: readonly string[], ctx?: EvalContext, opts?: {
85
85
  order?: boolean;
86
+ latestOnly?: boolean;
86
87
  }): Promise<EvalTimeline[]>;
87
88
  export declare function evalTimeline(id: string, ctx?: EvalContext): Promise<EvalTimeline>;
88
89
  export type BlobResult = {
@@ -50,9 +50,14 @@ export async function evalTimelines(ids, ctx, opts = {}) {
50
50
  const codeEntries = specs.find((s) => dirname(s.path) === relative(root, ynode.dir))?.codeEntries ?? [];
51
51
  const byName = new Map(ynode.scenarios.map((s) => [s.name, s]));
52
52
  const { readings, retractions, oks } = readSidecar(ynode.sidecarPath);
53
- const rows = applyRetractions(readings, retractions).map((reading) => ({
53
+ const allRows = applyRetractions(readings, retractions).map((reading) => ({
54
54
  reading, axis: scenarioCodeAxis(byName.get(reading.scenario)?.code, codeEntries),
55
55
  }));
56
+ // The board only renders the latest state per scenario. Historical rows remain available to the
57
+ // session/eval detail paths, but sending them through freshness here multiplies immutable Git probes.
58
+ const rows = opts.latestOnly
59
+ ? [...allRows.reduce((latest, row) => latest.set(row.reading.scenario, row), new Map()).values()]
60
+ : allRows;
56
61
  return { id, ynode, codeEntries, rows, retractions, oks };
57
62
  });
58
63
  // an order-only read stops HERE, before a single probe: the rows above are already the whole answer.
@@ -30,7 +30,7 @@ export type AnchorDemand = {
30
30
  entries: readonly RelationEntry[];
31
31
  };
32
32
  export declare function anchorProbeFor(root: string, idx: DriftIndex): AnchorProbe;
33
- export declare function anchorProblems(root: string, entries: readonly RelationEntry[]): string[];
33
+ export declare function anchorProblems(root: string, entries: readonly RelationEntry[]): Promise<string[]>;
34
34
  export type RemarkSignal = {
35
35
  resolved: boolean;
36
36
  resolvedAt?: string;
@@ -497,7 +497,7 @@ const currentTreeUnitMemo = new Map();
497
497
  // caller retaining a verdict derived from these units names it by the same identity that decides the parse
498
498
  // ([[selector-anchor-scope]]). Returning the key rather than re-deriving it elsewhere is what keeps the two
499
499
  // from drifting apart.
500
- function currentTreeImage(root, x, path) {
500
+ async function currentTreeImage(root, x, path) {
501
501
  const source = readFileSync(join(root, path), 'utf8');
502
502
  const key = `${x.memoKey(path)}\0${createHash('sha1').update(source).digest('hex')}`;
503
503
  const hit = currentTreeUnitMemo.get(key);
@@ -508,7 +508,7 @@ function currentTreeImage(root, x, path) {
508
508
  }
509
509
  let entry;
510
510
  try {
511
- entry = { units: x.extract(source, path) };
511
+ entry = { units: await x.extract(source, path) };
512
512
  }
513
513
  catch (err) {
514
514
  entry = { failed: err?.message ?? String(err) };
@@ -520,15 +520,15 @@ function currentTreeImage(root, x, path) {
520
520
  throw new Error(entry.failed);
521
521
  return { key, units: entry.units };
522
522
  }
523
- function entryImage(root, regs, path, selector) {
523
+ async function entryImage(root, regs, path, selector) {
524
524
  const x = extractorFor(regs, extOf(path));
525
525
  if (!x)
526
526
  return { problem: `\`code\` selector \`${path}#${selector}\` — no designated extractor for that language; drop the #anchor or add a language row` };
527
- const ready = x.ready();
527
+ const ready = await x.ready();
528
528
  if (ready !== true)
529
529
  return { problem: `\`code\` anchors on ${path} are unverified: ${ready}` };
530
530
  try {
531
- return currentTreeImage(root, x, path);
531
+ return await currentTreeImage(root, x, path);
532
532
  }
533
533
  catch (err) {
534
534
  return { problem: `\`code\` anchors on ${path} are unverified: ${err?.message ?? String(err)}` };
@@ -550,8 +550,8 @@ function selectorProblem(units, path, selectors) {
550
550
  }
551
551
  return null;
552
552
  }
553
- function entryUnverifiable(root, regs, entry) {
554
- const image = entryImage(root, regs, entry.path, entry.selectors[0]);
553
+ async function entryUnverifiable(root, regs, entry) {
554
+ const image = await entryImage(root, regs, entry.path, entry.selectors[0]);
555
555
  return 'problem' in image ? image.problem : selectorProblem(image.units, entry.path, entry.selectors);
556
556
  }
557
557
  // @@@ the verify sweep must reach the MACROTASK queue - resolving every reading's `code:` selectors against
@@ -603,10 +603,10 @@ export function anchorProbeFor(root, idx) {
603
603
  // one coherent read of each source per sweep: every entry on a path resolves against the same bytes,
604
604
  // and the read that names the verdict is the read the verdict was derived from.
605
605
  const images = new Map();
606
- const imageOf = (path, selector) => {
606
+ const imageOf = async (path, selector) => {
607
607
  let hit = images.get(path);
608
608
  if (hit === undefined) {
609
- hit = entryImage(root, regs, path, selector);
609
+ hit = await entryImage(root, regs, path, selector);
610
610
  images.set(path, hit);
611
611
  }
612
612
  return hit;
@@ -630,7 +630,7 @@ export function anchorProbeFor(root, idx) {
630
630
  const key = anchorKey(sinceSha, e.path, e.selectors);
631
631
  if (verdicts.has(key) || queued.has(key))
632
632
  continue;
633
- const image = imageOf(e.path, e.selectors[0]);
633
+ const image = await imageOf(e.path, e.selectors[0]);
634
634
  const name = 'problem' in image ? null : `${registry}\x1f${image.key}\x1f${key}`;
635
635
  const remembered = scope && name !== null ? scope.anchorVerdicts.get(name) : undefined;
636
636
  if (remembered !== undefined) {
@@ -666,13 +666,13 @@ export function anchorProbeFor(root, idx) {
666
666
  // the LOUD half: a selector is a claim that a named unit EXISTS, held to the same standard as a ghost path.
667
667
  // Dead, ambiguous, unparseable, or no designated extractor — each names itself and its repair, and until
668
668
  // repaired the probe issues no verdict, so the reading stays stale. Over-warn, never a silent pass.
669
- export function anchorProblems(root, entries) {
669
+ export async function anchorProblems(root, entries) {
670
670
  const regs = extractors(root);
671
671
  const out = [];
672
672
  for (const e of entries) {
673
673
  if (!e.selectors.length)
674
674
  continue;
675
- const problem = entryUnverifiable(root, regs, e);
675
+ const problem = await entryUnverifiable(root, regs, e);
676
676
  if (problem)
677
677
  out.push(problem);
678
678
  }
@@ -1,4 +1,5 @@
1
1
  import type { RemarkTrack } from './remarks.js';
2
+ import type { ReviewDiffFile } from '@spexcode/spec-core';
2
3
  export type EvalHost = {
3
4
  loadConfig?: (root: string) => any;
4
5
  trackedSourceFiles?: (root: string, roots: string[], policy: any) => string[];
@@ -19,7 +20,7 @@ export type ReviewPayload = {
19
20
  label: string;
20
21
  ahead: number;
21
22
  dirtyNonRuntime: number;
22
- diff: import('@spexcode/spec-core').ReviewDiffFile[];
23
+ diff: ReviewDiffFile[];
23
24
  gates: {
24
25
  conflictsWithMain: boolean;
25
26
  lint: {
@@ -218,7 +218,7 @@ export type SessionEvalRevision = {
218
218
  export type SessionEvalProjection = {
219
219
  epoch: string;
220
220
  generation: number;
221
- phase: 'loading' | 'updating' | 'ready' | 'error';
221
+ phase: 'loading' | 'updating' | 'ready' | 'error' | 'dormant';
222
222
  revision?: string;
223
223
  value?: SessionEvalSummary;
224
224
  lastKnown?: {
@@ -279,6 +279,7 @@ export declare class SessionEvalProjectionCache {
279
279
  demand<T>(id: string, path: string, run: () => Promise<T>): Promise<T>;
280
280
  idle(): Promise<void>;
281
281
  accept(id: string, generation: number, revision: string, value: SessionEvalSummary): boolean;
282
+ private dormantEntry;
282
283
  private project;
283
284
  private matches;
284
285
  private publish;
@@ -116,7 +116,7 @@ async function validateSelectorEntry(context, revision, entry, sourceView) {
116
116
  if (!x) {
117
117
  throw new SessionImpactUnavailableError(`session impact selector '${entry.path}#${entry.selectors[0]}' is unavailable: no designated extractor for '.${extOf(entry.path)}'`);
118
118
  }
119
- const ready = x.ready();
119
+ const ready = await x.ready();
120
120
  if (ready !== true) {
121
121
  throw new SessionImpactUnavailableError(`session impact selectors on '${entry.path}' are unavailable: ${ready}`);
122
122
  }
@@ -132,7 +132,7 @@ async function validateSelectorEntry(context, revision, entry, sourceView) {
132
132
  throw new SessionImpactUnavailableError(`session impact selector '${entry.path}#${entry.selectors[0]}' is dead at ${revision.slice(0, 8)}: the file does not exist`);
133
133
  }
134
134
  try {
135
- return { extractor: x, units: x.extract(source, entry.path) };
135
+ return { extractor: x, units: await x.extract(source, entry.path) };
136
136
  }
137
137
  catch (error) {
138
138
  throw new SessionImpactUnavailableError(`session impact selectors on '${entry.path}' are unextractable at ${revision.slice(0, 8)}: ${error?.message ?? String(error)}`);
@@ -1636,6 +1636,16 @@ export class SessionEvalProjectionCache {
1636
1636
  promise.finally(() => {
1637
1637
  if (this.demands.get(id) === promise)
1638
1638
  this.demands.delete(id);
1639
+ // The cancel above exists ONLY so the warmup does not duplicate the build this demand is about to run.
1640
+ // Once the demand has settled that reason is spent — and a demand that REJECTED published nothing, so
1641
+ // leaving the mark set kept `authorize` refusing this generation forever and the entry stuck at
1642
+ // `loading`/`updating` with no build ever scheduled. A demand that published is already `ready` and
1643
+ // needs no second build.
1644
+ if (this.entries.get(id) !== entry || entry.demandCancelledGeneration !== entry.generation)
1645
+ return;
1646
+ entry.demandCancelledGeneration = null;
1647
+ this.authorize(entry);
1648
+ queueMicrotask(() => this.startScheduled());
1639
1649
  }).catch(() => { });
1640
1650
  return promise;
1641
1651
  }
@@ -1660,12 +1670,19 @@ export class SessionEvalProjectionCache {
1660
1670
  this.notify();
1661
1671
  return true;
1662
1672
  }
1673
+ // Dormant offline history is demand-only BY POLICY: `authorize` refuses to schedule an offline entry, so
1674
+ // nothing will move it until a demand builds it. `loading`/`updating` are arrival phases — a reader that
1675
+ // renders them as progress waits forever. This names the difference at the one place that knows it.
1676
+ dormantEntry(entry) {
1677
+ return entry.liveness === 'offline' && entry.running == null && entry.scheduled == null
1678
+ && entry.observerHolds.size === 0 && entry.phase !== 'ready' && entry.phase !== 'error';
1679
+ }
1663
1680
  project(entry) {
1664
1681
  const stable = entry.current;
1665
1682
  return {
1666
1683
  epoch: this.epoch,
1667
1684
  generation: entry.generation,
1668
- phase: entry.phase,
1685
+ phase: this.dormantEntry(entry) ? 'dormant' : entry.phase,
1669
1686
  ...(entry.phase === 'ready' && stable
1670
1687
  ? { revision: stable.revision, value: stable.value }
1671
1688
  : stable ? { lastKnown: stable } : {}),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spexcode/spec-eval",
3
- "version": "0.6.7",
3
+ "version": "0.7.0-next.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
@@ -26,7 +26,7 @@
26
26
  "test": "tsx --import ../scripts/test-home.mjs --test src/*.test.ts"
27
27
  },
28
28
  "dependencies": {
29
- "@spexcode/spec-core": "0.6.7"
29
+ "@spexcode/spec-core": "0.7.0-next.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^20.16.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spexcode/spec-forge",
3
- "version": "0.6.7",
3
+ "version": "0.7.0-next.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "test": "tsx --test src/*.test.ts"
23
23
  },
24
24
  "dependencies": {
25
- "@spexcode/spec-core": "0.6.7"
25
+ "@spexcode/spec-core": "0.7.0-next.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^20.16.0",
@@ -0,0 +1,44 @@
1
+ import { type TranscriptRead, type TranscriptReader, type TranscriptTool, type TranscriptTurn } from './turns.js';
2
+ export type StreamTool = Readonly<Omit<TranscriptTool, 'output'> & {
3
+ output?: null;
4
+ }>;
5
+ export type StreamTurn = Readonly<Omit<TranscriptTurn, 'tools'> & {
6
+ tools?: readonly StreamTool[];
7
+ }>;
8
+ export type StreamFrame = Readonly<Omit<TranscriptRead, 'turns'> & {
9
+ kind: 'full' | 'delta';
10
+ turns: readonly StreamTurn[];
11
+ removed?: readonly string[];
12
+ }>;
13
+ export type TranscriptErrorFrame = Readonly<{
14
+ revision: string;
15
+ from: number;
16
+ to: number;
17
+ error: string;
18
+ reason: string;
19
+ }>;
20
+ export type TranscriptFrame = StreamFrame | TranscriptErrorFrame;
21
+ export declare const isErrorFrame: (frame: TranscriptFrame) => frame is TranscriptErrorFrame;
22
+ export declare const ABSENT_REVISION = "absent";
23
+ export declare const withheld: (read: TranscriptRead) => Omit<StreamFrame, "kind">;
24
+ export declare class FrameProducer {
25
+ private primed;
26
+ private sent;
27
+ private counters;
28
+ next(read: TranscriptRead): StreamFrame | null;
29
+ reset(): void;
30
+ }
31
+ export declare const absentFrame: (from: number, to: number) => StreamFrame;
32
+ export type FrameStream = Readonly<{
33
+ publish(now?: number): Promise<TranscriptFrame | null>;
34
+ close(): void;
35
+ }>;
36
+ export declare function openFrameStream(reader: TranscriptReader, threadId: string, from: number, clock?: () => number): FrameStream;
37
+ export type HeldTranscript = Readonly<{
38
+ turns: readonly StreamTurn[];
39
+ }>;
40
+ export type MergedFrame = Readonly<{
41
+ state: HeldTranscript;
42
+ payload: TranscriptErrorFrame | (Omit<StreamFrame, 'kind' | 'removed'>);
43
+ }>;
44
+ export declare function mergeTranscriptFrame(state: HeldTranscript, frame: TranscriptFrame): MergedFrame;