spexcode 0.6.1 → 0.6.5

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 (480) hide show
  1. package/README.md +99 -149
  2. package/bin/spex.mjs +4 -0
  3. package/node_modules/@spexcode/spec-cli/bin/spex.mjs +76 -0
  4. package/node_modules/@spexcode/spec-cli/dist/attach.d.ts +2 -0
  5. package/node_modules/@spexcode/spec-cli/dist/attach.js +48 -0
  6. package/node_modules/@spexcode/spec-cli/dist/claude-headless.d.ts +33 -0
  7. package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +307 -0
  8. package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
  9. package/node_modules/@spexcode/spec-cli/dist/cli.js +1859 -0
  10. package/node_modules/@spexcode/spec-cli/dist/client.d.ts +111 -0
  11. package/node_modules/@spexcode/spec-cli/dist/client.js +476 -0
  12. package/node_modules/@spexcode/spec-cli/dist/cockpit.d.ts +7 -0
  13. package/node_modules/@spexcode/spec-cli/dist/cockpit.js +26 -0
  14. package/node_modules/@spexcode/spec-cli/dist/codex-headless.d.ts +1 -0
  15. package/{spec-cli/src/codex-headless.ts → node_modules/@spexcode/spec-cli/dist/codex-headless.js} +3 -9
  16. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +53 -0
  17. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +686 -0
  18. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.d.ts +1 -0
  19. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.js +90 -0
  20. package/node_modules/@spexcode/spec-cli/dist/contract-filter.d.ts +14 -0
  21. package/node_modules/@spexcode/spec-cli/dist/contract-filter.js +219 -0
  22. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.d.ts +6 -0
  23. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.js +50 -0
  24. package/node_modules/@spexcode/spec-cli/dist/delivery-queue.d.ts +23 -0
  25. package/node_modules/@spexcode/spec-cli/dist/delivery-queue.js +179 -0
  26. package/node_modules/@spexcode/spec-cli/dist/doctor.d.ts +9 -0
  27. package/node_modules/@spexcode/spec-cli/dist/doctor.js +547 -0
  28. package/node_modules/@spexcode/spec-cli/dist/eval-host.d.ts +1 -0
  29. package/node_modules/@spexcode/spec-cli/dist/eval-host.js +19 -0
  30. package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +26 -0
  31. package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +475 -0
  32. package/node_modules/@spexcode/spec-cli/dist/file-write.d.ts +2 -0
  33. package/node_modules/@spexcode/spec-cli/dist/file-write.js +26 -0
  34. package/node_modules/@spexcode/spec-cli/dist/flat.d.ts +89 -0
  35. package/node_modules/@spexcode/spec-cli/dist/flat.js +1076 -0
  36. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.d.ts +56 -0
  37. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.js +168 -0
  38. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.d.ts +28 -0
  39. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +361 -0
  40. package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +37 -0
  41. package/node_modules/@spexcode/spec-cli/dist/gateway.js +503 -0
  42. package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
  43. package/node_modules/@spexcode/spec-cli/dist/graphCache.js +716 -0
  44. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
  45. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +35 -0
  46. package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +85 -0
  47. package/node_modules/@spexcode/spec-cli/dist/graphStream.js +1195 -0
  48. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.d.ts +64 -0
  49. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.js +166 -0
  50. package/node_modules/@spexcode/spec-cli/dist/guide.d.ts +8 -0
  51. package/{spec-cli/src/guide.ts → node_modules/@spexcode/spec-cli/dist/guide.js} +115 -22
  52. package/node_modules/@spexcode/spec-cli/dist/harness-select.d.ts +18 -0
  53. package/node_modules/@spexcode/spec-cli/dist/harness-select.js +62 -0
  54. package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +325 -0
  55. package/node_modules/@spexcode/spec-cli/dist/harness.js +3026 -0
  56. package/node_modules/@spexcode/spec-cli/dist/headless-controller.d.ts +9 -0
  57. package/node_modules/@spexcode/spec-cli/dist/headless-controller.js +47 -0
  58. package/node_modules/@spexcode/spec-cli/dist/help.d.ts +10 -0
  59. package/node_modules/@spexcode/spec-cli/dist/help.js +574 -0
  60. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.d.ts +18 -0
  61. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +122 -0
  62. package/node_modules/@spexcode/spec-cli/dist/hooks.d.ts +1 -0
  63. package/{spec-cli/src/hooks.ts → node_modules/@spexcode/spec-cli/dist/hooks.js} +15 -15
  64. package/node_modules/@spexcode/spec-cli/dist/host-resources.d.ts +97 -0
  65. package/node_modules/@spexcode/spec-cli/dist/host-resources.js +795 -0
  66. package/node_modules/@spexcode/spec-cli/dist/host.d.ts +88 -0
  67. package/node_modules/@spexcode/spec-cli/dist/host.js +663 -0
  68. package/node_modules/@spexcode/spec-cli/dist/index.d.ts +1 -0
  69. package/node_modules/@spexcode/spec-cli/dist/index.js +899 -0
  70. package/node_modules/@spexcode/spec-cli/dist/init.d.ts +1 -0
  71. package/node_modules/@spexcode/spec-cli/dist/init.js +284 -0
  72. package/node_modules/@spexcode/spec-cli/dist/issues-cli.d.ts +6 -0
  73. package/node_modules/@spexcode/spec-cli/dist/issues-cli.js +334 -0
  74. package/node_modules/@spexcode/spec-cli/dist/issues.d.ts +69 -0
  75. package/node_modules/@spexcode/spec-cli/dist/issues.js +189 -0
  76. package/node_modules/@spexcode/spec-cli/dist/lint.d.ts +34 -0
  77. package/node_modules/@spexcode/spec-cli/dist/lint.js +476 -0
  78. package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +7 -0
  79. package/{spec-cli/src/listen.ts → node_modules/@spexcode/spec-cli/dist/listen.js} +13 -17
  80. package/node_modules/@spexcode/spec-cli/dist/localIssues.d.ts +65 -0
  81. package/node_modules/@spexcode/spec-cli/dist/localIssues.js +582 -0
  82. package/node_modules/@spexcode/spec-cli/dist/login-page.d.ts +5 -0
  83. package/{spec-cli/src/login-page.ts → node_modules/@spexcode/spec-cli/dist/login-page.js} +7 -7
  84. package/node_modules/@spexcode/spec-cli/dist/loop-in.d.ts +23 -0
  85. package/{spec-cli/src/loop-in.ts → node_modules/@spexcode/spec-cli/dist/loop-in.js} +35 -45
  86. package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +69 -0
  87. package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +603 -0
  88. package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +18 -0
  89. package/node_modules/@spexcode/spec-cli/dist/materialize.js +468 -0
  90. package/node_modules/@spexcode/spec-cli/dist/mentions.d.ts +50 -0
  91. package/node_modules/@spexcode/spec-cli/dist/mentions.js +117 -0
  92. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.d.ts +4 -0
  93. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +208 -0
  94. package/node_modules/@spexcode/spec-cli/dist/opencode.d.ts +3 -0
  95. package/{spec-cli/src/opencode.ts → node_modules/@spexcode/spec-cli/dist/opencode.js} +9 -13
  96. package/node_modules/@spexcode/spec-cli/dist/pi-harness.d.ts +3 -0
  97. package/{spec-cli/src/pi-harness.ts → node_modules/@spexcode/spec-cli/dist/pi-harness.js} +45 -40
  98. package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +25 -0
  99. package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +196 -0
  100. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.d.ts +21 -0
  101. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.js +146 -0
  102. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.d.ts +10 -0
  103. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +404 -0
  104. package/node_modules/@spexcode/spec-cli/dist/pty-helper.d.mts +1 -0
  105. package/node_modules/@spexcode/spec-cli/dist/pty-helper.mjs +101 -0
  106. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.d.mts +2 -0
  107. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.mjs +23 -0
  108. package/node_modules/@spexcode/spec-cli/dist/public-graph.d.ts +46 -0
  109. package/node_modules/@spexcode/spec-cli/dist/public-graph.js +56 -0
  110. package/node_modules/@spexcode/spec-cli/dist/ranker.d.ts +15 -0
  111. package/node_modules/@spexcode/spec-cli/dist/ranker.js +188 -0
  112. package/node_modules/@spexcode/spec-cli/dist/reaper.d.ts +6 -0
  113. package/node_modules/@spexcode/spec-cli/dist/reaper.js +81 -0
  114. package/node_modules/@spexcode/spec-cli/dist/reviews.d.ts +189 -0
  115. package/node_modules/@spexcode/spec-cli/dist/reviews.js +350 -0
  116. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.d.ts +6 -0
  117. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.js +37 -0
  118. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +21 -0
  119. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +84 -0
  120. package/node_modules/@spexcode/spec-cli/dist/search.bench.d.mts +1 -0
  121. package/node_modules/@spexcode/spec-cli/dist/search.bench.mjs +75 -0
  122. package/node_modules/@spexcode/spec-cli/dist/search.d.ts +20 -0
  123. package/node_modules/@spexcode/spec-cli/dist/search.js +61 -0
  124. package/node_modules/@spexcode/spec-cli/dist/session-cursors.d.ts +14 -0
  125. package/node_modules/@spexcode/spec-cli/dist/session-cursors.js +82 -0
  126. package/node_modules/@spexcode/spec-cli/dist/session-declarations.d.ts +13 -0
  127. package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +126 -0
  128. package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +10 -0
  129. package/node_modules/@spexcode/spec-cli/dist/session-execution.js +70 -0
  130. package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +28 -0
  131. package/node_modules/@spexcode/spec-cli/dist/session-files.js +130 -0
  132. package/node_modules/@spexcode/spec-cli/dist/session-follow.d.ts +31 -0
  133. package/node_modules/@spexcode/spec-cli/dist/session-follow.js +179 -0
  134. package/node_modules/@spexcode/spec-cli/dist/session-reparent.d.ts +10 -0
  135. package/node_modules/@spexcode/spec-cli/dist/session-reparent.js +33 -0
  136. package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +51 -0
  137. package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +233 -0
  138. package/node_modules/@spexcode/spec-cli/dist/session-web.d.ts +24 -0
  139. package/node_modules/@spexcode/spec-cli/dist/session-web.js +141 -0
  140. package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +412 -0
  141. package/node_modules/@spexcode/spec-cli/dist/sessions.js +4239 -0
  142. package/node_modules/@spexcode/spec-cli/dist/sh.d.ts +1 -0
  143. package/node_modules/@spexcode/spec-cli/dist/sh.js +3 -0
  144. package/node_modules/@spexcode/spec-cli/dist/shim-runtime.d.ts +1 -0
  145. package/{spec-cli/src/shim-runtime.ts → node_modules/@spexcode/spec-cli/dist/shim-runtime.js} +3 -4
  146. package/node_modules/@spexcode/spec-cli/dist/slash-commands.d.ts +9 -0
  147. package/node_modules/@spexcode/spec-cli/dist/slash-commands.js +307 -0
  148. package/node_modules/@spexcode/spec-cli/dist/source-files.d.ts +9 -0
  149. package/node_modules/@spexcode/spec-cli/dist/source-files.js +113 -0
  150. package/node_modules/@spexcode/spec-cli/dist/supervise.d.ts +1 -0
  151. package/node_modules/@spexcode/spec-cli/dist/supervise.js +308 -0
  152. package/node_modules/@spexcode/spec-cli/dist/tree.d.ts +25 -0
  153. package/node_modules/@spexcode/spec-cli/dist/tree.js +98 -0
  154. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.d.ts +3 -0
  155. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.js +41 -0
  156. package/node_modules/@spexcode/spec-cli/dist/uninstall.d.ts +3 -0
  157. package/node_modules/@spexcode/spec-cli/dist/uninstall.js +177 -0
  158. package/node_modules/@spexcode/spec-cli/dist/uploads.d.ts +36 -0
  159. package/node_modules/@spexcode/spec-cli/dist/uploads.js +243 -0
  160. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.d.ts +1 -0
  161. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.js +47 -0
  162. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/dispatch.sh +5 -5
  163. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/harness.sh +27 -7
  164. package/node_modules/@spexcode/spec-cli/package.json +47 -0
  165. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
  166. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +9 -0
  167. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec-first.sh +1 -1
  168. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +1 -0
  169. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/spec.md +22 -0
  170. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/spec.md +2 -2
  171. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +31 -29
  172. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +15 -0
  173. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +10 -0
  174. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +13 -0
  175. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +12 -0
  176. package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +94 -0
  177. package/node_modules/@spexcode/spec-core/dist/anchors.js +730 -0
  178. package/node_modules/@spexcode/spec-core/dist/git.d.ts +166 -0
  179. package/node_modules/@spexcode/spec-core/dist/git.js +2736 -0
  180. package/node_modules/@spexcode/spec-core/dist/graph-delta.d.ts +44 -0
  181. package/node_modules/@spexcode/spec-core/dist/graph-delta.js +72 -0
  182. package/node_modules/@spexcode/spec-core/dist/graph.d.ts +34 -0
  183. package/node_modules/@spexcode/spec-core/dist/graph.js +237 -0
  184. package/node_modules/@spexcode/spec-core/dist/graphDelta.d.ts +3 -0
  185. package/node_modules/@spexcode/spec-core/dist/graphDelta.js +13 -0
  186. package/node_modules/@spexcode/spec-core/dist/harness-identity.d.ts +31 -0
  187. package/node_modules/@spexcode/spec-core/dist/harness-identity.js +20 -0
  188. package/node_modules/@spexcode/spec-core/dist/identity-presets.d.ts +152 -0
  189. package/node_modules/@spexcode/spec-core/dist/identity-presets.js +132 -0
  190. package/node_modules/@spexcode/spec-core/dist/index.d.ts +45 -0
  191. package/node_modules/@spexcode/spec-core/dist/index.js +19 -0
  192. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +183 -0
  193. package/node_modules/@spexcode/spec-core/dist/layout.js +548 -0
  194. package/node_modules/@spexcode/spec-core/dist/process-identity.d.ts +37 -0
  195. package/node_modules/@spexcode/spec-core/dist/process-identity.js +214 -0
  196. package/node_modules/@spexcode/spec-core/dist/project-identity.d.ts +12 -0
  197. package/node_modules/@spexcode/spec-core/dist/project-identity.js +71 -0
  198. package/node_modules/@spexcode/spec-core/dist/project-store.d.ts +3 -0
  199. package/node_modules/@spexcode/spec-core/dist/project-store.js +14 -0
  200. package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +2 -0
  201. package/node_modules/@spexcode/spec-core/dist/resilience.js +40 -0
  202. package/node_modules/@spexcode/spec-core/dist/review/index.d.ts +3 -0
  203. package/node_modules/@spexcode/spec-core/dist/review/index.js +5 -0
  204. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +77 -0
  205. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +308 -0
  206. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +66 -0
  207. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +180 -0
  208. package/node_modules/@spexcode/spec-core/dist/review/session.d.ts +4 -0
  209. package/node_modules/@spexcode/spec-core/dist/review/session.js +8 -0
  210. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.d.ts +15 -0
  211. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.js +12 -0
  212. package/node_modules/@spexcode/spec-core/dist/root-lru.d.ts +4 -0
  213. package/{spec-cli/src/root-lru.ts → node_modules/@spexcode/spec-core/dist/root-lru.js} +26 -30
  214. package/node_modules/@spexcode/spec-core/dist/specs.d.ts +117 -0
  215. package/node_modules/@spexcode/spec-core/dist/specs.js +489 -0
  216. package/node_modules/@spexcode/spec-core/package.json +28 -0
  217. package/node_modules/@spexcode/spec-eval/dist/cache.d.ts +10 -0
  218. package/node_modules/@spexcode/spec-eval/dist/cache.js +50 -0
  219. package/node_modules/@spexcode/spec-eval/dist/cli.d.ts +22 -0
  220. package/node_modules/@spexcode/spec-eval/dist/cli.js +981 -0
  221. package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +98 -0
  222. package/node_modules/@spexcode/spec-eval/dist/evaltab.js +176 -0
  223. package/node_modules/@spexcode/spec-eval/dist/filing.d.ts +16 -0
  224. package/node_modules/@spexcode/spec-eval/dist/filing.js +36 -0
  225. package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +48 -0
  226. package/node_modules/@spexcode/spec-eval/dist/freshness.js +799 -0
  227. package/node_modules/@spexcode/spec-eval/dist/host.d.ts +46 -0
  228. package/node_modules/@spexcode/spec-eval/dist/host.js +53 -0
  229. package/node_modules/@spexcode/spec-eval/dist/humanok.d.ts +11 -0
  230. package/node_modules/@spexcode/spec-eval/dist/humanok.js +27 -0
  231. package/node_modules/@spexcode/spec-eval/dist/index.d.ts +4 -0
  232. package/node_modules/@spexcode/spec-eval/dist/index.js +4 -0
  233. package/node_modules/@spexcode/spec-eval/dist/remarks.d.ts +31 -0
  234. package/node_modules/@spexcode/spec-eval/dist/remarks.js +1 -0
  235. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.d.ts +13 -0
  236. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.js +303 -0
  237. package/node_modules/@spexcode/spec-eval/dist/scenarios.d.ts +99 -0
  238. package/node_modules/@spexcode/spec-eval/dist/scenarios.js +650 -0
  239. package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +307 -0
  240. package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +1883 -0
  241. package/node_modules/@spexcode/spec-eval/dist/sidecar.d.ts +55 -0
  242. package/node_modules/@spexcode/spec-eval/dist/sidecar.js +82 -0
  243. package/node_modules/@spexcode/spec-eval/dist/timeline.d.ts +25 -0
  244. package/node_modules/@spexcode/spec-eval/dist/timeline.js +65 -0
  245. package/node_modules/@spexcode/spec-eval/dist/ui-path.d.ts +1 -0
  246. package/node_modules/@spexcode/spec-eval/dist/ui-path.js +2 -0
  247. package/node_modules/@spexcode/spec-eval/package.json +36 -0
  248. package/node_modules/@spexcode/spec-forge/dist/cache.d.ts +29 -0
  249. package/node_modules/@spexcode/spec-forge/dist/cache.js +59 -0
  250. package/node_modules/@spexcode/spec-forge/dist/cli.d.ts +1 -0
  251. package/node_modules/@spexcode/spec-forge/dist/cli.js +100 -0
  252. package/node_modules/@spexcode/spec-forge/dist/drivers/github.d.ts +2 -0
  253. package/node_modules/@spexcode/spec-forge/dist/drivers/github.js +116 -0
  254. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.d.ts +7 -0
  255. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.js +144 -0
  256. package/node_modules/@spexcode/spec-forge/dist/drivers.d.ts +10 -0
  257. package/node_modules/@spexcode/spec-forge/dist/drivers.js +83 -0
  258. package/node_modules/@spexcode/spec-forge/dist/index.d.ts +4 -0
  259. package/node_modules/@spexcode/spec-forge/dist/index.js +4 -0
  260. package/node_modules/@spexcode/spec-forge/dist/links.d.ts +15 -0
  261. package/node_modules/@spexcode/spec-forge/dist/links.js +65 -0
  262. package/node_modules/@spexcode/spec-forge/dist/needs-eval.d.ts +10 -0
  263. package/node_modules/@spexcode/spec-forge/dist/needs-eval.js +20 -0
  264. package/node_modules/@spexcode/spec-forge/dist/port.d.ts +53 -0
  265. package/node_modules/@spexcode/spec-forge/dist/port.js +1 -0
  266. package/node_modules/@spexcode/spec-forge/dist/resident.d.ts +7 -0
  267. package/node_modules/@spexcode/spec-forge/dist/resident.js +43 -0
  268. package/node_modules/@spexcode/spec-forge/package.json +32 -0
  269. package/package.json +33 -19
  270. package/spec-cli/bin/spex.mjs +0 -69
  271. package/spec-cli/src/anchors.ts +0 -706
  272. package/spec-cli/src/attach.ts +0 -43
  273. package/spec-cli/src/claude-headless.ts +0 -321
  274. package/spec-cli/src/cli.ts +0 -1115
  275. package/spec-cli/src/client.ts +0 -315
  276. package/spec-cli/src/cockpit.ts +0 -43
  277. package/spec-cli/src/codex-runtime-generations.ts +0 -646
  278. package/spec-cli/src/commit-surgery.ts +0 -68
  279. package/spec-cli/src/contract-filter.ts +0 -202
  280. package/spec-cli/src/delivery-queue.ts +0 -107
  281. package/spec-cli/src/doctor.ts +0 -516
  282. package/spec-cli/src/file-write.ts +0 -22
  283. package/spec-cli/src/gateway-auth.ts +0 -181
  284. package/spec-cli/src/gateway-hub.ts +0 -356
  285. package/spec-cli/src/gateway.ts +0 -368
  286. package/spec-cli/src/git.ts +0 -2180
  287. package/spec-cli/src/graph.ts +0 -221
  288. package/spec-cli/src/graphCache.ts +0 -641
  289. package/spec-cli/src/graphDelta.ts +0 -77
  290. package/spec-cli/src/graphStream.ts +0 -1019
  291. package/spec-cli/src/harness-select.ts +0 -77
  292. package/spec-cli/src/harness.ts +0 -2747
  293. package/spec-cli/src/headless-controller.ts +0 -63
  294. package/spec-cli/src/help.ts +0 -480
  295. package/spec-cli/src/host-resources.ts +0 -707
  296. package/spec-cli/src/host.ts +0 -611
  297. package/spec-cli/src/identity-presets.d.ts +0 -13
  298. package/spec-cli/src/identity-presets.js +0 -128
  299. package/spec-cli/src/index.ts +0 -730
  300. package/spec-cli/src/init.ts +0 -258
  301. package/spec-cli/src/issues-cli.ts +0 -276
  302. package/spec-cli/src/issues.ts +0 -237
  303. package/spec-cli/src/layout.ts +0 -557
  304. package/spec-cli/src/lint.ts +0 -458
  305. package/spec-cli/src/localIssues.ts +0 -538
  306. package/spec-cli/src/materialize.ts +0 -412
  307. package/spec-cli/src/mentions.ts +0 -81
  308. package/spec-cli/src/opencode-headless.ts +0 -205
  309. package/spec-cli/src/pi-headless.ts +0 -153
  310. package/spec-cli/src/plugin-harness.ts +0 -157
  311. package/spec-cli/src/process-identity.ts +0 -207
  312. package/spec-cli/src/project-identity.ts +0 -73
  313. package/spec-cli/src/project-store.ts +0 -17
  314. package/spec-cli/src/pty-bridge.ts +0 -396
  315. package/spec-cli/src/pty-helper.mjs +0 -96
  316. package/spec-cli/src/pty-native-helper.mjs +0 -22
  317. package/spec-cli/src/ranker.ts +0 -191
  318. package/spec-cli/src/reaper.ts +0 -125
  319. package/spec-cli/src/resilience.ts +0 -41
  320. package/spec-cli/src/reviewSnapshot.ts +0 -28
  321. package/spec-cli/src/reviews.ts +0 -405
  322. package/spec-cli/src/runtime-guard.ts +0 -35
  323. package/spec-cli/src/runtime-ownership.ts +0 -88
  324. package/spec-cli/src/search.bench.mjs +0 -80
  325. package/spec-cli/src/search.ts +0 -63
  326. package/spec-cli/src/session-cursors.ts +0 -91
  327. package/spec-cli/src/session-follow.ts +0 -182
  328. package/spec-cli/src/session-timeline.ts +0 -98
  329. package/spec-cli/src/sessions.ts +0 -3403
  330. package/spec-cli/src/sh.ts +0 -3
  331. package/spec-cli/src/slash-commands.ts +0 -318
  332. package/spec-cli/src/source-files.ts +0 -104
  333. package/spec-cli/src/specs.ts +0 -498
  334. package/spec-cli/src/supervise.ts +0 -245
  335. package/spec-cli/src/tree.ts +0 -118
  336. package/spec-cli/src/tsx-bin.ts +0 -18
  337. package/spec-cli/src/uninstall.ts +0 -171
  338. package/spec-cli/src/uploads.ts +0 -253
  339. package/spec-cli/src/worktree-sources.ts +0 -42
  340. package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +0 -8
  341. package/spec-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -28
  342. package/spec-cli/templates/spec/project/.plugins/core/spec.md +0 -23
  343. package/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -32
  344. package/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -9
  345. package/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -15
  346. package/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -18
  347. package/spec-dashboard/dist/assets/App-F9uaAVcH.js +0 -2
  348. package/spec-dashboard/dist/assets/Dashboard-Ba_jhxp1.js +0 -27
  349. package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
  350. package/spec-dashboard/dist/assets/EvalsPage-FixoOg_n.js +0 -2
  351. package/spec-dashboard/dist/assets/IssuesPage-CuKLFhH3.js +0 -1
  352. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  353. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  354. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  355. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  356. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  357. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  358. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  359. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  360. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  361. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  362. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  363. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  364. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  365. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  366. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  367. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  368. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  369. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  370. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  371. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  372. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  373. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  374. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  375. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  376. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  377. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  378. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  379. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  380. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  381. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  382. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  383. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  384. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  385. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  386. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  387. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  388. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  389. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  390. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  391. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  392. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  393. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  394. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  395. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  396. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  397. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  398. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  399. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  400. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  401. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  402. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  403. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  404. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  405. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  406. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  407. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  408. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  409. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  410. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  411. package/spec-dashboard/dist/assets/MobileApp-CHgEHORJ.js +0 -2
  412. package/spec-dashboard/dist/assets/Modal-CQgYymmr.js +0 -1
  413. package/spec-dashboard/dist/assets/PageScroll-hT7UTLvD.js +0 -1
  414. package/spec-dashboard/dist/assets/ProjectsPage-CtXxakF9.js +0 -1
  415. package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
  416. package/spec-dashboard/dist/assets/SessionInterface-Bpie-9fs.js +0 -39
  417. package/spec-dashboard/dist/assets/SessionWindow-CixDi4PI.js +0 -1
  418. package/spec-dashboard/dist/assets/Settings-C2MsucfE.js +0 -1
  419. package/spec-dashboard/dist/assets/Thread-C6Go8HRh.js +0 -13
  420. package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
  421. package/spec-dashboard/dist/assets/data-B-RQmit6.js +0 -1
  422. package/spec-dashboard/dist/assets/index-CsI8DElI.css +0 -1
  423. package/spec-dashboard/dist/assets/index-DrVao0Ep.js +0 -41
  424. package/spec-dashboard/dist/assets/launch-BBH02b1v.js +0 -276
  425. package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
  426. package/spec-dashboard/dist/assets/terminalFont-C6oGNKrf.js +0 -1
  427. package/spec-dashboard/dist/index.html +0 -29
  428. package/spec-dashboard/src/reviewFilters.js +0 -322
  429. package/spec-dashboard/src/reviewQuery.js +0 -174
  430. package/spec-dashboard/src/session.js +0 -199
  431. package/spec-eval/src/cache.ts +0 -57
  432. package/spec-eval/src/cli.ts +0 -889
  433. package/spec-eval/src/evaltab.ts +0 -262
  434. package/spec-eval/src/filing.ts +0 -39
  435. package/spec-eval/src/freshness.ts +0 -557
  436. package/spec-eval/src/humanok.ts +0 -28
  437. package/spec-eval/src/scenariofresh.ts +0 -244
  438. package/spec-eval/src/scenarios.ts +0 -672
  439. package/spec-eval/src/sessioneval.ts +0 -2226
  440. package/spec-eval/src/sidecar.ts +0 -93
  441. package/spec-eval/src/timeline.ts +0 -54
  442. package/spec-forge/src/__fixtures__/github-forge.json +0 -63
  443. package/spec-forge/src/cache.ts +0 -66
  444. package/spec-forge/src/cli.ts +0 -99
  445. package/spec-forge/src/drivers/github.ts +0 -132
  446. package/spec-forge/src/drivers/gitlab.ts +0 -153
  447. package/spec-forge/src/drivers.ts +0 -73
  448. package/spec-forge/src/links.ts +0 -72
  449. package/spec-forge/src/needs-eval.ts +0 -29
  450. package/spec-forge/src/port.ts +0 -36
  451. package/spec-forge/src/resident.ts +0 -46
  452. /package/{spec-cli → node_modules/@spexcode/spec-cli}/README.md +0 -0
  453. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -0
  454. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-sed-v0.fixture +0 -0
  455. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/commit-msg +0 -0
  456. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-checkout +0 -0
  457. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-merge +0 -0
  458. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/pre-commit +0 -0
  459. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/prepare-commit-msg +0 -0
  460. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/reference-transaction +0 -0
  461. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/extract/spec.md +0 -0
  462. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/regroup/spec.md +0 -0
  463. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/rename/spec.md +0 -0
  464. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/spec.md +0 -0
  465. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/tidy/spec.md +0 -0
  466. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/idle.sh +0 -0
  467. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/spec.md +0 -0
  468. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/mark-active.sh +0 -0
  469. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/spec.md +0 -0
  470. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/fail.sh +0 -0
  471. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/spec.md +0 -0
  472. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec.md +0 -0
  473. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec.md +0 -0
  474. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/spec.md +0 -0
  475. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/digest.mjs +0 -0
  476. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/spec.md +0 -0
  477. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/spec.md +0 -0
  478. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/spec.md +0 -0
  479. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/spec.md +0 -0
  480. /package/{spec-cli → node_modules/@spexcode/spec-core}/templates/spexcode.json +0 -0
@@ -1,458 +0,0 @@
1
- import { readFileSync, existsSync, statSync } from 'node:fs'
2
- import { join } from 'node:path'
3
- import { repoRoot, git, sourceIndexes, rowsFor, treeFilePaths, treeFileText, type DriftPathEvent } from './git.js'
4
- import { loadSpecs, parseFrontmatter } from './specs.js'
5
- import { readJsonConfig } from './layout.js'
6
- import { extractors, extractorFor, extOf, parseCodeEntry, relationClaimsPath, resolveAnchor, resolveSelectors, windowEvents, anchorHitQueries } from './anchors.js'
7
- import { DEFAULT_TEST_GLOBS, sourcePolicyDescription, trackedSourceFiles } from './source-files.js'
8
-
9
- export type Finding = { level: 'error' | 'warn'; rule: string; spec?: string; file?: string; msg: string }
10
-
11
- export type LintConfig = {
12
- governedRoots: string[] // dirs whose tracked source files must each be governed by a spec. '.' = whole project.
13
- sourceIncludeGlobs: string[] | null // null includes every tracked regular text file; [] intentionally includes none
14
- sourceExcludeGlobs: string[] // explicit source-policy subtraction
15
- sourceExtensions: string[] | null // compatibility shorthand compiled into sourceIncludeGlobs
16
- testGlobs: string[] // globs EXCLUDED from coverage; set [] to govern tests too
17
- maxOwners: number // warn when a file is governed (code:) by > this many nodes
18
- scenarioTags: string[] // the closed vocabulary an eval scenario's `tags:` must draw from; extend it to mint a new tag
19
- scopedCodeMiss: 'warn' | 'ignore' // the file-level drift ADVISORY on a selector-scoped code: file whose window has no
20
- // selector hit ([[code-anchor]]). 'warn' (default) keeps today's drift warning; 'ignore'
21
- // silences ONLY that advisory — hit blocks, bare code drift, integrity, acks, related
22
- // semantics, and eval freshness are all untouched by this knob.
23
- }
24
- const DEFAULT_CONFIG: LintConfig = {
25
- governedRoots: ['spec-dashboard/src', 'spec-cli/src'],
26
- sourceIncludeGlobs: null,
27
- sourceExcludeGlobs: [],
28
- sourceExtensions: null,
29
- testGlobs: DEFAULT_TEST_GLOBS,
30
- maxOwners: 3,
31
- scenarioTags: ['frontend-e2e', 'backend-api', 'cli', 'desktop', 'mobile'],
32
- scopedCodeMiss: 'warn',
33
- }
34
- export function loadConfig(root: string, pendingSource?: string | null): LintConfig {
35
- // Absent spexcode.json → tuned defaults; a MALFORMED one throws LOUD (readJsonConfig) rather than
36
- // silently reverting the author's budgets to defaults and green-washing the very warnings they tuned.
37
- let parsed: any
38
- if (pendingSource === undefined) parsed = readJsonConfig(join(root, 'spexcode.json'))
39
- else if (pendingSource === null) parsed = {}
40
- else {
41
- try { parsed = JSON.parse(pendingSource) }
42
- catch (e: any) { throw new Error(`invalid JSON in candidate spexcode.json: ${e?.message ?? e}`) }
43
- }
44
- const c = parsed?.lint ?? {}
45
- const merged = { ...DEFAULT_CONFIG, ...c }
46
- return normalizeConfig(merged)
47
- }
48
-
49
- // Compile every author-facing source selector into one include-minus-exclude/test policy. In particular,
50
- // sourceExtensions is compatibility syntax only: it contributes include globs and never reaches discovery.
51
- export function normalizeConfig(cfg: LintConfig): LintConfig {
52
- // a mistyped enum silently reverting to the default would green-wash (or over-warn) exactly the
53
- // advisory the author meant to tune — same fail-loud rule as a malformed spexcode.json.
54
- if (cfg.scopedCodeMiss !== 'warn' && cfg.scopedCodeMiss !== 'ignore')
55
- throw new Error(`spexcode.json lint.scopedCodeMiss must be "warn" or "ignore", got ${JSON.stringify(cfg.scopedCodeMiss)}`)
56
- const dedot = (xs: string[]) => xs.map((x) => x.replace(/^\.+/, ''))
57
- const anyDepth = (xs: string[]) => xs.map((g) => (g.includes('/') ? g : `**/${g}`))
58
- const extensions = cfg.sourceExtensions === null ? null : dedot(cfg.sourceExtensions)
59
- const includes = cfg.sourceIncludeGlobs === null && extensions === null
60
- ? null
61
- : [...new Set([...anyDepth(cfg.sourceIncludeGlobs ?? []), ...(extensions ?? []).map((ext) => `**/*.${ext}`)])]
62
- return {
63
- ...cfg,
64
- sourceIncludeGlobs: includes,
65
- sourceExcludeGlobs: anyDepth(cfg.sourceExcludeGlobs),
66
- sourceExtensions: extensions,
67
- testGlobs: anyDepth(cfg.testGlobs),
68
- }
69
- }
70
-
71
- export type SpecLintOptions = { tip?: string }
72
-
73
- function untrackedAdoptionFiles(root: string): string[] {
74
- if (git(['-C', root, 'rev-parse', '--is-inside-work-tree']).trim() !== 'true') return []
75
- const status = git([
76
- '-C', root,
77
- '-c', 'core.quotePath=false',
78
- 'status', '--porcelain=v1', '-z', '--untracked-files=all',
79
- '--', '.spec', 'spexcode.json',
80
- ])
81
- return status.split('\0')
82
- .filter((entry) => entry.startsWith('?? '))
83
- .map((entry) => entry.slice(3))
84
- .filter(Boolean)
85
- }
86
-
87
- function pendingChangedPaths(root: string, tip: string): string[] {
88
- try {
89
- git(['-C', root, 'rev-parse', `${tip}^{commit}`])
90
- // `-m` compares a merge with every parent. An `ours` merge can leave the result tree
91
- // identical to its first parent while still making an unacknowledged side-branch commit
92
- // reachable; first-parent-only paths would filter that debt out of the pending anchor window.
93
- const fields = git(['-C', root, '-c', 'core.quotePath=false', 'diff-tree', '--no-commit-id', '--name-status', '-z', '-r', '-m', '-M', tip])
94
- .split('\0').filter(Boolean)
95
- const changed: string[] = []
96
- for (let i = 0; i < fields.length;) {
97
- const status = fields[i++]
98
- if (/^[RC]/.test(status)) {
99
- changed.push(fields[i++], fields[i++])
100
- } else {
101
- changed.push(fields[i++])
102
- }
103
- }
104
- return [...new Set(changed.filter(Boolean))]
105
- } catch { return [] }
106
- }
107
- // Cheap pending classification for the reference hook. It reads only the candidate tree and claims; it
108
- // never constructs either history index. A normal lint call deliberately does not use this
109
- // shortcut so its full findings/oracle contract remains unchanged.
110
- export async function pendingTouchesGoverned(root: string, tip: string): Promise<boolean> {
111
- // A merge can introduce reachable side-branch debt without changing the result tree. The first-parent
112
- // diff is insufficient for a scope proof, so all multi-parent candidates stay on the full lint path.
113
- const parentCount = git(['-C', root, 'rev-list', '--parents', '-n1', tip]).trim().split(/\s+/).length - 1
114
- if (parentCount > 1) return true
115
- const changed = pendingChangedPaths(root, tip)
116
- if (!changed.length) return true
117
- // `governedRoots` is source discovery policy, not the set of actual code claims: a spec may deliberately
118
- // govern a path outside those roots. Read only the candidate spec tree (no history/drift indexes) so the
119
- // scope proof follows the same code:/related: declarations that lint later enforces.
120
- const specs = await loadSpecs(root, { tip, history: null, drift: null })
121
- const claims = specs.flatMap((spec) => [...spec.code, ...spec.related])
122
- return changed.some((path) => claims.some((claim) => relationClaimsPath(claim, path))
123
- || path === 'spexcode.json' || path === 'spexcode.local.json'
124
- || (path.startsWith('.spec/') && !path.startsWith('.spec/.issues/'))
125
- || path === '.spec')
126
- }
127
-
128
- export async function specLint(root = repoRoot(), regs = extractors(root), options: SpecLintOptions = {}): Promise<Finding[]> {
129
- const tip = options.tip ?? 'HEAD'
130
- const pending = tip !== 'HEAD'
131
- const changed = pending ? pendingChangedPaths(root, tip) : []
132
- const files = pending ? treeFilePaths(root, tip) : null
133
- const directories = new Set<string>()
134
- for (const file of files ?? []) {
135
- const parts = file.split('/')
136
- for (let i = 1; i < parts.length; i++) directories.add(parts.slice(0, i).join('/'))
137
- }
138
- const existsAtTip = (path: string) => files ? files.has(path) || directories.has(path.replace(/\/+$/, '')) : existsSync(join(root, path))
139
- const isDirectoryAtTip = (path: string) => files
140
- ? !files.has(path) && directories.has(path.replace(/\/+$/, ''))
141
- : statSync(join(root, path)).isDirectory()
142
- const textAtTip = (path: string) => pending ? treeFileText(root, tip, path) : readFileSync(join(root, path), 'utf8')
143
- const cfg = loadConfig(root, pending ? treeFileText(root, tip, 'spexcode.json') : undefined)
144
- const untracked = untrackedAdoptionFiles(root)
145
- if (untracked.length) {
146
- const shown = untracked.slice(0, 6)
147
- const suffix = untracked.length > shown.length ? ` (+${untracked.length - shown.length} more)` : ''
148
- return [{
149
- level: 'error',
150
- rule: 'integrity',
151
- msg: `project source of truth is untracked: ${shown.join(', ')}${suffix} — add it with \`git add .spec spexcode.json\` and commit it; generated harness files such as .codex/, .claude/, and AGENTS.md are machine-local`,
152
- }]
153
- }
154
- const governed = trackedSourceFiles(root, cfg.governedRoots, cfg, tip)
155
- const [hidx, didx] = await sourceIndexes(root, tip)
156
- const specs = await loadSpecs(root, { tip, history: hidx, drift: didx })
157
- const out: Finding[] = []
158
-
159
- // integrity + build the file -> owners map. A relation's STRUCTURAL problems (a duplicate entry,
160
- // bare/scoped mixing on one base path, a selector on a glob — all from parseRelation,
161
- // [[code-anchor]]) are integrity errors: malformed edges block like broken ones.
162
- const owners = new Map<string, string[]>()
163
- const claimed = new Set<string>()
164
- const governedClaims = new Set<string>()
165
- for (const s of specs) {
166
- for (const p of s.relationProblems)
167
- out.push({ level: 'error', rule: 'integrity', spec: s.id, msg: `'${s.id}' ${p}` })
168
- const scopedPaths = new Set(s.codeScoped.map((e) => e.path))
169
- for (const f of s.code) {
170
- if (!existsAtTip(f))
171
- out.push({ level: 'error', rule: 'integrity', spec: s.id, file: f, msg: `spec '${s.id}' lists a missing file: ${f}` })
172
- claimed.add(f)
173
- governedClaims.add(f)
174
- // a selector-SCOPED entry claims named units, not the whole file, so it stays out of the owners
175
- // bound below ([[code-anchor]]) — `spex spec owner` still displays it as a (scoped) governor.
176
- if (!scopedPaths.has(f)) owners.set(f, [...(owners.get(f) ?? []), s.id])
177
- }
178
- // one-govern: a node is source of truth for at most ONE file — DISTINCT base paths; several
179
- // `path#symbol` selectors on the same file are one subject — so drift/eval/ack have a single
180
- // unambiguous subject (see [[governed-related]]). >1 is a defect — pick the true subject, demote the
181
- // rest to related. ERROR (the node-side twin of too-many-owners' file-side bound). 0 is fine.
182
- if (s.code.length > 1)
183
- out.push({ level: 'error', rule: 'one-govern', spec: s.id, msg: `'${s.id}' governs ${s.code.length} files [${s.code.join(', ')}] — a node is source of truth for at most ONE. Keep the true subject in code:, move the rest to related:` })
184
- }
185
- // a file is COVERED if any node GOVERNS (code:) or merely REFERENCES (related:) it; integrity covers both.
186
- // `related:` is the coverage net: govern is a sharp ideally-one-file pointer, so most files are reached by
187
- // related, not govern (see [[governed-related]]). It carries coverage but never drift, never eval freshness.
188
- for (const s of specs) for (const f of s.related) {
189
- if (!existsAtTip(f))
190
- out.push({ level: 'error', rule: 'integrity', spec: s.id, file: f, msg: `spec '${s.id}' lists a missing related file: ${f}` })
191
- claimed.add(f)
192
- }
193
-
194
- // A candidate may retire a node together with the implementation it owned, or transfer that path to a
195
- // new node. It may not delete the governor while leaving the governed subject behind: ordinary coverage
196
- // is advisory, so without this transition check a candidate could erase the very contract that would
197
- // reject its code change. Read only deleted spec blobs from HEAD; no second history index is needed.
198
- if (pending) {
199
- let deleted: string[] = []
200
- try {
201
- deleted = git(['-C', root, '-c', 'core.quotePath=false', 'diff', '--diff-filter=D', '--name-only', 'HEAD', tip, '--', '.spec'])
202
- .split('\n').map((path) => path.trim()).filter((path) => path.endsWith('/spec.md'))
203
- } catch { /* an unreadable comparison is already surfaced by the candidate history reads */ }
204
- for (const specPath of deleted) {
205
- const oldSource = treeFileText(root, 'HEAD', specPath)
206
- if (oldSource === null) continue
207
- const value = parseFrontmatter(oldSource).fm.code
208
- const rows = Array.isArray(value) ? value : value ? [value] : []
209
- for (const row of rows) {
210
- const path = parseCodeEntry(row).path
211
- if (existsAtTip(path) && !governedClaims.has(path))
212
- out.push({ level: 'error', rule: 'integrity', file: path, msg: `candidate deletes governor '${specPath}' but leaves its governed subject '${path}' without a code: owner — delete the retired implementation too, or transfer it to a real node in this commit` })
213
- }
214
- }
215
- }
216
-
217
- // id-format: a node id (its leaf dir basename) passes an EXACT per-character whitelist — an ascii char
218
- // must be [a-z0-9-]; a non-ascii char must be a unicode letter/number (judged on NFC, the mint's
219
- // canonical form) — and is UNIQUE tree-wide (ERROR). This is THE id vocabulary, defined once (the
220
- // spec-lint node's rule table) and referenced by [[mentions]] / [[id-url-safe]]: CJK and every other
221
- // letter script is first-class, exactly what the script-agnostic resolve machinery already accepts.
222
- // Everything else is forbidden by construction, no heuristics — space, '/', uppercase Latin (lowercase
223
- // is the Latin norm), control chars, and '_' (reserved as the mint's parent-qualification join; a '_'
224
- // inside a basename would make that join ambiguous). One optional leading dot is allowed — the
225
- // reflexive plugin root `.plugins` is dot-prefixed by design. Uniqueness is what keeps the leaf THE id:
226
- // on a collision the mint must parent-qualify with `_`, so every surface suddenly speaks a longer id
227
- // than the dir name — legal to the machinery, illegible to people.
228
- const ID_RE = /^\.?(?:[a-z0-9-]|(?![\x00-\x7F])[\p{L}\p{N}])+$/u
229
- const leafOf = (p: string) => { const segs = p.split('/'); return segs[segs.length - 2] }
230
- const byLeaf = new Map<string, string[]>()
231
- for (const s of specs) {
232
- const leaf = leafOf(s.path)
233
- byLeaf.set(leaf, [...(byLeaf.get(leaf) ?? []), s.path])
234
- if (!ID_RE.test(leaf.normalize('NFC')))
235
- out.push({ level: 'error', rule: 'id-format', spec: s.id, msg: `node dir '${leaf}' is not a valid id — each char is ascii [a-z0-9-] or a non-ascii unicode letter/number (one optional leading dot); space, '/', '_', uppercase Latin and control chars are forbidden; rename the directory` })
236
- }
237
- for (const [leaf, paths] of byLeaf) {
238
- if (paths.length > 1)
239
- out.push({ level: 'error', rule: 'id-format', msg: `leaf id '${leaf}' names ${paths.length} nodes [${paths.map((p) => p.replace(/\/spec\.md$/, '')).join(', ')}] — a leaf id is unique tree-wide; rename all but one` })
240
- }
241
-
242
- // confusable-id: two leaf ids one edit apart read as the same word (WARN — a typo in either reaches a
243
- // real, wrong node). Deliberately conservative: distance exactly 1, so hierarchy naming like
244
- // graph/graph-delivery (a whole suffix apart) and verb pairs like evidence-put/evidence-get (distance 2)
245
- // never warn — better to miss a borderline pair than to nag legitimate siblings. Distance is measured
246
- // in CODE POINTS, script-agnostic: a CJK pair one character apart (节点/结点 — a classic homophone
247
- // IME slip) warns like an ascii pair, an astral char counts as one edit (not two surrogate units), and
248
- // a pure-CJK id can never sit one edit from a pure-ascii one.
249
- const lev1 = (a: string, b: string): boolean => {
250
- const A = [...a], B = [...b]
251
- if (Math.abs(A.length - B.length) > 1 || a === b) return false
252
- let i = 0
253
- while (i < A.length && i < B.length && A[i] === B[i]) i++
254
- const rest = (x: string[], k: number) => x.slice(k).join('')
255
- return rest(A, i + 1) === rest(B, i + 1) || rest(A, i + 1) === rest(B, i) || rest(A, i) === rest(B, i + 1)
256
- }
257
- const leaves = [...byLeaf.keys()]
258
- for (let i = 0; i < leaves.length; i++) for (let j = i + 1; j < leaves.length; j++) {
259
- if (lev1(leaves[i], leaves[j]))
260
- out.push({ level: 'warn', rule: 'confusable-id', msg: `leaf ids '${leaves[i]}' and '${leaves[j]}' are one edit apart — easily confused; if they are distinct concepts, rename one to read apart` })
261
- }
262
-
263
- // mention: a `[[id]]` in a body must name a real node (ERROR — a dangling mention is a broken edge in
264
- // the very graph the tree exists to keep honest). Checked against the SAME minted ids every other
265
- // surface resolves ([[id-url-safe]]). Prose only: a fenced block or inline `code span` is sample text
266
- // (`[[node]]`, `[[<id>]]` placeholders live there), not a reference.
267
- const idSet = new Set(specs.map((s) => s.id))
268
- const MENTION_RE = /\[\[(\.?[\p{L}\p{N}_-]+)\]\]/gu
269
- for (const s of specs) {
270
- let inFence = false
271
- for (const rawLine of s.body.split('\n')) {
272
- if (/^\s*```/.test(rawLine)) { inFence = !inFence; continue }
273
- if (inFence) continue
274
- const line = rawLine.replace(/`[^`]*`/g, '')
275
- for (const m of line.matchAll(MENTION_RE)) {
276
- if (!idSet.has(m[1]))
277
- out.push({ level: 'error', rule: 'mention', spec: s.id, msg: `'${s.id}' mentions [[${m[1]}]] — no such node; retarget or drop it (backtick it if it is sample text)` })
278
- }
279
- }
280
- }
281
-
282
- // living: a spec body describes the node's CURRENT intent — it is not a changelog. Version history
283
- // (every content commit, its reason/session/line-diff) is read from git and shown in the dashboard's
284
- // recent/history tabs, so a `## vN`-style heading in the body is duplicated, drift-prone state.
285
- // Reject it. Fence-aware — a `## v2` inside a ``` block is sample text, not a heading.
286
- const VER_HEADING = /^#{1,6}\s+v\d+\b/
287
- for (const s of specs) {
288
- let inFence = false
289
- for (const line of s.body.split('\n')) {
290
- if (/^\s*```/.test(line)) { inFence = !inFence; continue }
291
- if (!inFence && VER_HEADING.test(line))
292
- out.push({ level: 'error', rule: 'living', spec: s.id, msg: `'${s.id}' has a changelog heading "${line.trim()}" — keep the body current-state; version history lives in git (recent/history tabs)` })
293
- }
294
- }
295
-
296
- // coverage: every governed source file must be claimed by at least one spec.
297
- if (governed.length === 0)
298
- out.push({ level: 'warn', rule: 'coverage', msg: `governing NOTHING — 0 source candidates under governedRoots [${cfg.governedRoots.join(', ')}]; ${sourcePolicyDescription(cfg)}. Repair these knobs under the "lint" key in spexcode.json (top-level keys are ignored): governedRoots, sourceIncludeGlobs, sourceExcludeGlobs, testGlobs; sourceExtensions remains compatibility shorthand for include globs.` })
299
- for (const f of governed)
300
- if (!claimed.has(f)) out.push({ level: 'warn', rule: 'coverage', file: f, msg: `no spec governs: ${f}` })
301
-
302
- // too-many-owners: many nodes governing one file is ordinary composition (drift fans to each — correct,
303
- // every owner has a stake), so this is NOT flagged. Only an OVER-owned file is a smell — governed by more
304
- // than maxOwners nodes, it has accreted more independently-specified functionality than one file should
305
- // hold (see [[governed-related]]). ONE summary line (not one per file — its own wall of noise): the count,
306
- // the worst offenders, and the remedy, which blames the FILE not the ownership — SPLIT it so each governor
307
- // reclaims its own module; or merge the nodes if they're one concern; or give it a single foundation owner.
308
- const over = [...owners].filter(([, ids]) => ids.length > cfg.maxOwners).sort((a, b) => b[1].length - a[1].length)
309
- if (over.length) {
310
- const top = over.slice(0, 5).map(([f, ids]) => `${f.split('/').pop()}(${ids.length})`).join(', ')
311
- out.push({ level: 'warn', rule: 'owners', msg: `${over.length} file(s) are governed by > ${cfg.maxOwners} nodes — each holds more separately-specified functionality than one file should. Worst: ${top}. SPLIT the file so each governor owns its own module (or merge the nodes, or give it a single foundation owner + related:).` })
312
- }
313
-
314
- // code anchors ([[code-anchor]]): a code:/related: entry may pin named units (`path#symbol` — any
315
- // number per base file, OR'd). On code:, the anchor is the BLOCKING tier of drift: a window
316
- // commit (spec's last version..tip, ordinary diff or merge-authored combined diff) whose zero-context hunks
317
- // intersect any pinned unit's line range — extracted from the file AS OF that commit, by the
318
- // extension's ONE designated extractor — is ONE anchor-drift ERROR naming the hit selectors, unless a
319
- // Spec-OK ack covers it. On related:, the SAME engine yields only a soft warn on a hit — a scoped
320
- // related miss is silent (never blocks, no ack, no eval freshness). Resolution failures are never
321
- // silent for either relation: a dead or ambiguous selector, a selector on a directory, and an
322
- // unparseable working-tree file ERROR. An extension with no designated extractor, or a designated
323
- // extractor that cannot run here, also ERRORS but skips those anchors so the remaining checks continue.
324
- type AnchorLintQuery = { id: string; version: number; relation: 'code' | 'related'; path: string; symbols: string[]; win: DriftPathEvent[] }
325
- type AnchorLintStep = { finding: Finding } | { query: AnchorLintQuery }
326
- const readyWarned = new Set<string>()
327
- const anchorSteps: AnchorLintStep[] = []
328
- for (const s of specs) {
329
- for (const { relation, entries } of [{ relation: 'code' as const, entries: s.codeScoped }, { relation: 'related' as const, entries: s.relatedScoped }]) {
330
- for (const { path, selectors } of entries) {
331
- if (pending && !changed.some((file) => relationClaimsPath(path, file))) continue
332
- const x = extractorFor(regs, extOf(path))
333
- if (!x) {
334
- anchorSteps.push({ finding: { level: 'error', rule: 'integrity', spec: s.id, file: path, msg: `'${s.id}' anchors ${path}#${selectors.join(', #')} (${relation}:), but no extractor is designated for '.${extOf(path)}' files — anchor validation was skipped and remains unverified; add a LangSpec row (anchors.ts) or drop the selector(s)` } })
335
- continue
336
- }
337
- const ready = x.ready()
338
- if (ready !== true) {
339
- // once per (extractor, reason), even across several anchored nodes — one repair, one message.
340
- if (!readyWarned.has(x.id + ready)) { readyWarned.add(x.id + ready); anchorSteps.push({ finding: { level: 'error', rule: 'integrity', msg: `anchor extractor '${x.id}' cannot run: ${ready}` } }) }
341
- continue
342
- }
343
- if (!existsAtTip(path)) continue // the missing FILE already errored above
344
- if (isDirectoryAtTip(path)) {
345
- anchorSteps.push({ finding: { level: 'error', rule: 'integrity', spec: s.id, file: path, msg: `'${s.id}' puts a selector on a directory (${relation}: ${path}#${selectors[0]}) — a selector scopes ONE real file` } })
346
- continue
347
- }
348
- let units
349
- try {
350
- const source = textAtTip(path)
351
- if (source === null) throw new Error(`candidate tree has no file '${path}'`)
352
- units = x.extract(source, path)
353
- } catch (e: any) {
354
- anchorSteps.push({ finding: { level: 'error', rule: 'integrity', spec: s.id, file: path, msg: `anchor ${path}#${selectors.join(', #')} ('${s.id}') is unverifiable — the current file does not parse: ${e?.message ?? e}` } })
355
- continue
356
- }
357
- // each selector resolves (or errors) on its own; only the live ones feed the window engine.
358
- // The dead/ambiguous verdict itself comes from the ONE shared classifier ([[code-anchor]]); only the
359
- // wording of the gate's findings lives here.
360
- const live: string[] = []
361
- for (const res of resolveSelectors(units, selectors)) {
362
- const sym = res.selector
363
- if ('dead' in res) {
364
- anchorSteps.push({ finding: { level: 'error', rule: 'integrity', spec: s.id, file: path, msg: `dead anchor: ${path}#${sym} ('${s.id}') names no unit on the current tree — the unit was deleted or renamed; update the spec's ${relation}: entry to follow it` } })
365
- continue
366
- }
367
- if ('ambiguous' in res) {
368
- anchorSteps.push({ finding: { level: 'error', rule: 'integrity', spec: s.id, file: path, msg: `ambiguous anchor: ${path}#${sym} ('${s.id}') names ${res.ambiguous} same-named units in one file — an anchor must be unique; rename one unit` } })
369
- continue
370
- }
371
- if (res.ok.typeOnly)
372
- anchorSteps.push({ finding: { level: 'warn', rule: 'anchor', spec: s.id, file: path, msg: `${path}#${sym} anchors a ${res.ok.kind} — anchoring a type is usually wrong (types reshape with every refactor); anchor the behaviour-bearing unit instead` } })
373
- live.push(sym)
374
- }
375
- if (!live.length) continue
376
- const since = rowsFor(hidx, s.path)[0]?.hash || ''
377
- const win = windowEvents(didx, since, path, s.id)
378
- if (!win.length) continue
379
- anchorSteps.push({ query: { id: s.id, version: s.version, relation, path, symbols: live, win } })
380
- }
381
- }
382
- }
383
- const anchorHits = await anchorHitQueries(root, anchorSteps.flatMap((step) => 'query' in step ? [{ win: step.query.win, symbols: step.query.symbols }] : []), regs)
384
- let hitIndex = 0
385
- for (const step of anchorSteps) {
386
- if ('finding' in step) { out.push(step.finding); continue }
387
- const { id, version, relation, path } = step.query
388
- const hits = anchorHits[hitIndex++]
389
- if (!hits.length) continue
390
- const hitSyms = [...new Set(hits.flatMap((h) => h.selectors))]
391
- const shas = hits.map((h) => h.commit.slice(0, 8)).join(', ')
392
- const unparseable = hits.filter((h) => h.unparseable)
393
- const parseNote = unparseable.length ? ` (${unparseable.length} of these could not be parsed at that commit — counted as hits conservatively)` : ''
394
- if (relation === 'code') {
395
- const current = pending && hits.some((hit) => hit.commit === tip)
396
- const older = pending && hits.some((hit) => hit.commit !== tip)
397
- const remedy = !pending
398
- ? `update the spec, or 'spex spec ack ${id} --reason "…"' if the contract still holds`
399
- : current && older
400
- ? `update the spec in this commit; its own hit can be declared by retrying with 'git commit --trailer "Spec-OK: ${id}" …', but the listed older debt must be cleared first (an in-commit declaration never pardons ancestors)`
401
- : current
402
- ? `update the spec in this commit, or retry with 'git commit --trailer "Spec-OK: ${id}" …'; a later empty ack cannot pre-author this candidate`
403
- : `update the spec in this commit, or clear this older debt with 'spex spec ack ${id} --reason "…"' before retrying the candidate`
404
- out.push({ level: 'error', rule: 'anchor-drift', spec: id, file: path, msg: `${path}#${hitSyms.join(', #')} was changed by ${hits.length} commit(s) since spec '${id}' v${version} [${shas}]${parseNote} — the anchored contract's code moved: ${remedy}` })
405
- } else
406
- out.push({ level: 'warn', rule: 'related-drift', spec: id, file: path, msg: `related ${path}#${hitSyms.join(', #')} ('${id}') was changed by ${hits.length} commit(s) since v${version} [${shas}]${parseNote} — a scoped dependency shifted, worth a glance (SOFT: never blocks, no ack, no eval staleness)` })
407
- }
408
-
409
- // drift: a governed file has commits NOT yet reflected in its spec. Judged by true git ancestry —
410
- // loadSpecs computes `driftFiles` via driftFor() over the one cached driftIndex walk (git.ts): a
411
- // commit to the file counts iff it is NOT reachable from the spec's latest version (in-memory
412
- // parent-edge reachability, the equivalent of `rev-list <version>..HEAD -- <file>`), never a
413
- // log-position or timestamp guess.
414
- // A selector-SCOPED code file keeps this file-level advisory by default (a miss still nudges); the
415
- // committed `lint.scopedCodeMiss: "ignore"` silences ONLY this advisory for scoped entries — the
416
- // anchor engine's verdicts above (hit = block, resolution failures = integrity) are untouched.
417
- for (const s of specs) {
418
- const scopedPaths = new Set(s.codeScoped.map((e) => e.path))
419
- for (const d of s.driftFiles) {
420
- if (cfg.scopedCodeMiss === 'ignore' && scopedPaths.has(d.file)) continue
421
- out.push({ level: 'warn', rule: 'drift', spec: s.id, file: d.file, msg: `${d.file} is ${d.behind} commit(s) ahead of spec '${s.id}' (v${s.version}) — may be stale` })
422
- }
423
- }
424
-
425
- // related drift: the SOFT tier ([[governed-related]]). A referenced file moved ahead of the node's
426
- // version — a nudge that a dependency shifted. Same ancestry basis as govern drift, but WARN-only,
427
- // never reaching the commit gate (driftGate reads govern) or eval freshness. It is COMMON (shared substrate and
428
- // faces change often without re-versioning every referrer), so per-file it is a wall; like
429
- // too-many-owners it collapses to ONE summary line, with the per-file detail riding the board
430
- // (relatedDriftFiles). It stays a soft edge, never a per-file interruption.
431
- const rd = specs.flatMap((s) => s.relatedDriftFiles.map((d) => ({ id: s.id, behind: d.behind })))
432
- if (rd.length) {
433
- const byNode = new Map<string, number>()
434
- for (const d of rd) byNode.set(d.id, (byNode.get(d.id) ?? 0) + 1)
435
- const worst = [...byNode].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([id, n]) => `${id}(${n})`).join(', ')
436
- out.push({ level: 'warn', rule: 'related-drift', msg: `${rd.length} related file(s) across ${byNode.size} node(s) drifted ahead of their spec (SOFT — a dependency shifted, worth a glance; never blocks, no ack, no eval staleness). Most: ${worst}` })
437
- }
438
-
439
- return out
440
- }
441
-
442
- export const DRIFT_GUIDANCE = `DRIFT — a governed file has moved ahead of its spec. A CHECKPOINT, not a chore: find WHERE the truth
443
- broke along raw intent → expanded spec → code: link → code structure → implementation, fix THAT layer.
444
-
445
- Inspect: spex spec lint which files, against which spec
446
- the node's spec.md its raw source + expanded spec
447
- git diff $(git log -1 --format=%H -- <spec.md>)..HEAD -- <file> the code delta since the spec
448
-
449
- Diagnose, then apply the one honest remedy:
450
- • contract changed → rewrite the spec body to the new intent, commit it (re-versions the node)
451
- • only mechanics changed → spex spec ack <node> "checked — spec still valid" (give a real reason)
452
- • implementation is WRONG → the spec is right; fix the CODE back toward it, then ack
453
- • wrong code: link → the node shouldn't own this file (or owns it too broadly); fix frontmatter
454
- • expanded spec ≠ raw → the spec drifted from human intent; fix the expanded spec to serve the raw
455
- • structural mismatch → one file owned by many specs / a feature with no home: refactor so a file
456
- maps to a node, or file an issue and link it (defer honestly)
457
-
458
- Never patch. A reasoned ack or a real fix are recorded and re-judged at review; a blind ack is a lie.`