elanous 0.2.25 → 0.2.28

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 (337) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/{1454-37fd16f479f5554a.js → 1454-29d1d773d5996a49.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12149c51f7d04b1.js → page-fa46d4588e8442c2.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-256ffd365d32e100.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-dea4b795741126fd.js → page-e0de78ae7b99f01c.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-4c741eb760f2f77c.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-e9ba98f5f1440a21.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-2f6f303fa114ec24.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-ced0bcfc6542b3fc.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-bb1f05cd51c38944.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-f736324afa2117c1.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-6f131b51193a36f9.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-435f5e2faf02bc01.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-6a14333d1b1b3967.js → page-497e0eedf20e7890.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-8c91a9a66c9ec90a.js → page-a8d28fdf995e16ea.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-66a67eb19ebd976b.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-c71267e7681da982.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-cb249e81ed339318.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-781cfc5114e097da.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-478157214db6a023.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-86141f12cf090b7f.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-89cb33bced824e4a.js → page-7256c73bb1c36751.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-979d318a534e92da.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-601fb77340e63c9a.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-05a4b2c9b91034aa.js → page-11bf9e5725a72c22.js} +1 -1
  53. package/apps/pwa/out/approvals/index.html +1 -1
  54. package/apps/pwa/out/approvals/index.txt +13 -13
  55. package/apps/pwa/out/autopilot/index.html +1 -1
  56. package/apps/pwa/out/autopilot/index.txt +13 -13
  57. package/apps/pwa/out/board/index.html +1 -1
  58. package/apps/pwa/out/board/index.txt +13 -13
  59. package/apps/pwa/out/botlab/index.html +1 -1
  60. package/apps/pwa/out/botlab/index.txt +13 -13
  61. package/apps/pwa/out/bots/index.html +1 -1
  62. package/apps/pwa/out/bots/index.txt +13 -13
  63. package/apps/pwa/out/ceo/index.html +1 -1
  64. package/apps/pwa/out/ceo/index.txt +13 -13
  65. package/apps/pwa/out/chat/index.html +1 -1
  66. package/apps/pwa/out/chat/index.txt +13 -13
  67. package/apps/pwa/out/consult/index.html +1 -1
  68. package/apps/pwa/out/consult/index.txt +13 -13
  69. package/apps/pwa/out/control/index.html +1 -1
  70. package/apps/pwa/out/control/index.txt +13 -13
  71. package/apps/pwa/out/decisions/index.html +1 -1
  72. package/apps/pwa/out/decisions/index.txt +13 -13
  73. package/apps/pwa/out/design-check/index.html +1 -1
  74. package/apps/pwa/out/design-check/index.txt +13 -13
  75. package/apps/pwa/out/editor/index.html +1 -1
  76. package/apps/pwa/out/editor/index.txt +13 -13
  77. package/apps/pwa/out/exec/index.html +1 -1
  78. package/apps/pwa/out/exec/index.txt +13 -13
  79. package/apps/pwa/out/field/index.html +1 -1
  80. package/apps/pwa/out/field/index.txt +13 -13
  81. package/apps/pwa/out/healthz +1 -1
  82. package/apps/pwa/out/index.html +1 -1
  83. package/apps/pwa/out/index.txt +13 -13
  84. package/apps/pwa/out/inside/index.html +1 -1
  85. package/apps/pwa/out/inside/index.txt +13 -13
  86. package/apps/pwa/out/intake/index.html +1 -1
  87. package/apps/pwa/out/intake/index.txt +13 -13
  88. package/apps/pwa/out/live/index.html +1 -1
  89. package/apps/pwa/out/live/index.txt +13 -13
  90. package/apps/pwa/out/live-run/index.html +1 -1
  91. package/apps/pwa/out/live-run/index.txt +13 -13
  92. package/apps/pwa/out/loops/index.html +1 -1
  93. package/apps/pwa/out/loops/index.txt +13 -13
  94. package/apps/pwa/out/market/index.html +1 -1
  95. package/apps/pwa/out/market/index.txt +13 -13
  96. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  97. package/apps/pwa/out/missions/placeholder/index.txt +13 -13
  98. package/apps/pwa/out/morning/index.html +1 -1
  99. package/apps/pwa/out/morning/index.txt +13 -13
  100. package/apps/pwa/out/observatory/index.html +1 -1
  101. package/apps/pwa/out/observatory/index.txt +13 -13
  102. package/apps/pwa/out/ops/checklist/index.html +1 -1
  103. package/apps/pwa/out/ops/checklist/index.txt +13 -13
  104. package/apps/pwa/out/ops/release/index.html +1 -1
  105. package/apps/pwa/out/ops/release/index.txt +13 -13
  106. package/apps/pwa/out/ops/seats/index.html +1 -1
  107. package/apps/pwa/out/ops/seats/index.txt +13 -13
  108. package/apps/pwa/out/outputs/index.html +1 -1
  109. package/apps/pwa/out/outputs/index.txt +13 -13
  110. package/apps/pwa/out/reflection/index.html +1 -1
  111. package/apps/pwa/out/reflection/index.txt +14 -14
  112. package/apps/pwa/out/scheduler/index.html +1 -1
  113. package/apps/pwa/out/scheduler/index.txt +13 -13
  114. package/apps/pwa/out/sessions/index.html +1 -1
  115. package/apps/pwa/out/sessions/index.txt +13 -13
  116. package/apps/pwa/out/settings/devices/index.html +1 -1
  117. package/apps/pwa/out/settings/devices/index.txt +13 -13
  118. package/apps/pwa/out/settings/index.html +1 -1
  119. package/apps/pwa/out/settings/index.txt +13 -13
  120. package/apps/pwa/out/setup/done/index.html +1 -1
  121. package/apps/pwa/out/setup/done/index.txt +13 -13
  122. package/apps/pwa/out/setup/index.html +1 -1
  123. package/apps/pwa/out/setup/index.txt +13 -13
  124. package/apps/pwa/out/share/index.html +1 -1
  125. package/apps/pwa/out/share/index.txt +13 -13
  126. package/apps/pwa/out/showroom/index.html +1 -1
  127. package/apps/pwa/out/showroom/index.txt +13 -13
  128. package/apps/pwa/out/tasks/index.html +1 -1
  129. package/apps/pwa/out/tasks/index.txt +13 -13
  130. package/apps/pwa/out/term/index.html +1 -1
  131. package/apps/pwa/out/term/index.txt +13 -13
  132. package/apps/pwa/out/today/index.html +1 -1
  133. package/apps/pwa/out/today/index.txt +13 -13
  134. package/apps/pwa/out/trace/index.html +1 -1
  135. package/apps/pwa/out/trace/index.txt +13 -13
  136. package/apps/pwa/out/vault/index.html +1 -1
  137. package/apps/pwa/out/vault/index.txt +13 -13
  138. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  139. package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
  140. package/apps/pwa/out/workflows/index.html +1 -1
  141. package/apps/pwa/out/workflows/index.txt +13 -13
  142. package/apps/pwa/out/workspace/index.html +1 -1
  143. package/apps/pwa/out/workspace/index.txt +13 -13
  144. package/apps/pwa/out/worktrees/index.html +1 -1
  145. package/apps/pwa/out/worktrees/index.txt +13 -13
  146. package/graphs/implement-loop.yaml +38 -1
  147. package/graphs/steward/recipes.yaml +6 -0
  148. package/graphs/steward/release-day.yaml +23 -0
  149. package/graphs/steward/steward.yaml +9 -1
  150. package/graphs/work/code-pr.yaml +23 -0
  151. package/package.json +1 -1
  152. package/scripts/browser-observe-cycle.ts +76 -2
  153. package/scripts/ci-typecheck-changed.ts +119 -7
  154. package/scripts/coord-msg-bridge.ts +209 -0
  155. package/scripts/coord-post.sh +40 -7
  156. package/scripts/coord-requests-bridge.ts +10 -5
  157. package/scripts/detect-test-interference.ts +31 -1
  158. package/scripts/expose-rubric-suggest.ts +1 -1
  159. package/scripts/measure-tsc-affected-roots.ts +10 -1
  160. package/scripts/release-loop/docs-node.ts +51 -22
  161. package/scripts/release-loop/export-check-node.ts +8 -5
  162. package/scripts/release-loop/gate-node.ts +37 -4
  163. package/scripts/release-loop/node-verdict.ts +10 -3
  164. package/scripts/release-loop/publish-node.ts +3 -0
  165. package/scripts/release-loop/tui-node-checks.ts +5 -2
  166. package/scripts/skills/public-check.ts +5 -2
  167. package/scripts/test-deterministic.ts +520 -37
  168. package/scripts/ux-sim.ts +7 -1
  169. package/scripts/verify-kpack6-live.ts +230 -0
  170. package/src/acp/codex-plugins.ts +17 -0
  171. package/src/agent/task-notification.ts +32 -2
  172. package/src/agent-mission/in-agent-plugin.ts +8 -3
  173. package/src/agent-substrate/review-observation.ts +9 -2
  174. package/src/agent-substrate/self-review-cli.ts +62 -16
  175. package/src/boot/daemon-tools/self-implement.ts +72 -4
  176. package/src/briefing/brief-items.ts +151 -35
  177. package/src/budget/llm-cost.ts +30 -3
  178. package/src/budget/usage-outcomes.ts +32 -0
  179. package/src/claims/claims-ledger.ts +23 -0
  180. package/src/claims/claims-render.ts +22 -2
  181. package/src/cli/brief-cli.ts +131 -10
  182. package/src/cli/claims-cli.ts +24 -4
  183. package/src/cli/control-cli.ts +29 -0
  184. package/src/cli/doctor-cli.ts +90 -8
  185. package/src/cli/msg-cli.ts +177 -11
  186. package/src/cli/plugin-cli.ts +11 -3
  187. package/src/cli/pr-cli.ts +133 -18
  188. package/src/cli/pr-land-overlap-evidence.ts +18 -10
  189. package/src/cli/pr-land-refusal.ts +20 -0
  190. package/src/cli/release-cli.ts +34 -5
  191. package/src/cli/rfc-cli.ts +24 -0
  192. package/src/cli/seat-cli.ts +121 -0
  193. package/src/cli/self-utterances-cli.ts +140 -0
  194. package/src/cli/tailscale-serve.ts +5 -8
  195. package/src/cli/tasks-cli.ts +528 -31
  196. package/src/cli/usage-cli.ts +9 -4
  197. package/src/context-card/cell.ts +40 -0
  198. package/src/dashboard/boot-timing.ts +35 -0
  199. package/src/dashboard/first-screen-band.ts +63 -13
  200. package/src/dashboard/index.ts +94 -27
  201. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
  202. package/src/dashboard/skill-detection-runtime.ts +24 -4
  203. package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
  204. package/src/debug/log.ts +1 -1
  205. package/src/domains/morning-report.ts +3 -1
  206. package/src/domains/release-tool.ts +29 -12
  207. package/src/domains/surface-events.ts +16 -0
  208. package/src/elanous-config-dir.ts +2 -2
  209. package/src/fleet/host-doctor.ts +73 -2
  210. package/src/github/pr-labels.ts +10 -4
  211. package/src/graph-runner/runner.ts +104 -15
  212. package/src/harness/harness-clean.ts +21 -3
  213. package/src/harness/harness-cli-command.ts +68 -10
  214. package/src/harness/harness-pod-dispatch.ts +11 -4
  215. package/src/harness/harness-stop.ts +11 -7
  216. package/src/hq/seats.ts +17 -3
  217. package/src/index.ts +69 -9
  218. package/src/instance/current.ts +2 -6
  219. package/src/instance/resolve.ts +5 -6
  220. package/src/intelligence-map/model-bench-scores.ts +102 -0
  221. package/src/llm/openai-sampling-fields.ts +89 -0
  222. package/src/llm/runtime-model-family.ts +61 -0
  223. package/src/llm/zai-coding-plan.ts +27 -1
  224. package/src/llm.ts +13 -89
  225. package/src/loops/orchestrator/inbox.ts +73 -0
  226. package/src/loops/orchestrator/resync-shadow.ts +100 -12
  227. package/src/loops/orchestrator/tick.ts +73 -2
  228. package/src/machines/machine-ledger.ts +31 -3
  229. package/src/maturity/cli-maturity.ts +13 -3
  230. package/src/maturity/feature-maturity.ts +1 -1
  231. package/src/msg/msg-sse-server.ts +141 -0
  232. package/src/msg/msg-store.ts +149 -17
  233. package/src/ops/overview-collect.ts +3 -2
  234. package/src/ops/overview-owner.ts +29 -0
  235. package/src/patents/ledger.ts +128 -0
  236. package/src/plugin-hooks/shell-hook.ts +17 -2
  237. package/src/plugin-missions/evaluator-runner.ts +18 -2
  238. package/src/plugins/install/market-fetch.ts +32 -1
  239. package/src/plugins/install/plugin-install.ts +11 -1
  240. package/src/pod-lease/admission.ts +116 -41
  241. package/src/prompt-enhance/enhance.ts +11 -0
  242. package/src/release-loop/checklist.ts +52 -4
  243. package/src/release-loop/feature-store.ts +10 -9
  244. package/src/release-loop/merged-pr-checklist.ts +24 -5
  245. package/src/release-loop/rubric.ts +70 -0
  246. package/src/rfc-registry/registry.ts +88 -0
  247. package/src/seat-dispatch/seat-request-ledger.ts +4 -0
  248. package/src/seat-loop/knowhow-lint.ts +233 -0
  249. package/src/seat-loop/seat-loop.ts +5 -1
  250. package/src/seat-loop/seat-pack.ts +16 -4
  251. package/src/seat-loop/traffic-signal.ts +7 -0
  252. package/src/self-dev/ask-launch-flow.ts +48 -8
  253. package/src/self-dev/ask-path-candidates.ts +69 -14
  254. package/src/self-dev/decompose-proposal.ts +43 -1
  255. package/src/self-dev/draft-sweep.ts +23 -14
  256. package/src/self-dev/graph-journey-nodes.ts +2 -0
  257. package/src/self-dev/launch-preflight-text.ts +5 -1
  258. package/src/self-dev/launch-preflight.ts +143 -10
  259. package/src/self-dev/orchestrate.ts +6 -2
  260. package/src/self-dev/run-supervisor.ts +35 -1
  261. package/src/self-implement/gate-cli.ts +6 -2
  262. package/src/self-implement/goal-author.ts +5 -28
  263. package/src/self-implement/goal-command-execution.ts +26 -0
  264. package/src/self-implement/goal-new-paths.ts +4 -5
  265. package/src/self-implement/graph-authority.ts +17 -3
  266. package/src/self-implement/graph-yaml.ts +15 -7
  267. package/src/self-implement/headless-elanous-driver.ts +16 -1
  268. package/src/self-implement/host-regate.ts +44 -9
  269. package/src/self-implement/orchestrator.ts +5 -11
  270. package/src/self-implement/pwa-import-graph.ts +194 -7
  271. package/src/self-implement/rework-policy.ts +80 -2
  272. package/src/self-implement/self-implement-runtime.ts +126 -21
  273. package/src/self-implement/speed-claims.ts +145 -6
  274. package/src/session/index.ts +14 -4
  275. package/src/session-runtime/index.ts +55 -11
  276. package/src/skills/tools/persistent-grounding.ts +59 -4
  277. package/src/skills/tools/shell.ts +79 -1
  278. package/src/steward/rejudge-tick.ts +12 -1
  279. package/src/steward/release-day.ts +158 -0
  280. package/src/steward/resync-tick.ts +123 -0
  281. package/src/task-agent/actions.ts +1 -0
  282. package/src/task-agent/adopt-pr.ts +140 -0
  283. package/src/task-agent/board.ts +11 -1
  284. package/src/task-agent/card-evidence.ts +37 -5
  285. package/src/task-agent/cover.ts +105 -19
  286. package/src/task-agent/failure-notify.ts +61 -0
  287. package/src/task-agent/green-sweep.ts +170 -0
  288. package/src/task-agent/land-worktree.ts +52 -5
  289. package/src/task-agent/live-move-history.ts +1 -0
  290. package/src/task-agent/live-moves.ts +56 -17
  291. package/src/task-agent/parent-host.ts +1 -1
  292. package/src/task-agent/postland-card.ts +139 -0
  293. package/src/task-agent/rejudge-on-head.ts +321 -25
  294. package/src/task-agent/remote-rejudge-bind.ts +62 -0
  295. package/src/task-agent/repair-branch-cas.ts +182 -0
  296. package/src/task-agent/repair-cycle.ts +275 -0
  297. package/src/task-agent/repair-next-move.ts +31 -0
  298. package/src/task-agent/repair-review.ts +24 -0
  299. package/src/task-agent/repair-worker.ts +87 -0
  300. package/src/task-agent/review-result-capture.ts +66 -2
  301. package/src/task-agent/shadow.ts +150 -29
  302. package/src/task-agent/task-hand.ts +133 -8
  303. package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
  304. package/src/task-orchestrator/surfaces/self-implement-pod.ts +71 -12
  305. package/src/telegram.ts +30 -1
  306. package/src/tool-runtime/tool-search-runtime.ts +25 -0
  307. package/src/tui.ts +20 -1
  308. package/src/user-config.ts +11 -60
  309. package/src/ux-sim/fault-sqlite-busy.ts +50 -0
  310. package/src/version/packed-revision.json +1 -1
  311. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +0 -1
  312. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
  313. package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
  314. package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
  315. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
  316. package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +0 -1
  324. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/outputs/page-b9cc8a10bc6078b4.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +0 -1
  336. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
  337. /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
@@ -0,0 +1,88 @@
1
+ import { chmodSync, mkdirSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { Database } from 'bun:sqlite';
4
+ import { releaseLedgerRoot } from '../instance/resolve.js';
5
+ import { assertNotTestWritingOps } from '../instance/test-write-guard.js';
6
+
7
+ /** The RFC ledger is independent of the release checklist ledger. Callers own and close the handle. */
8
+ export function openRfcRegistry(root = releaseLedgerRoot()): Database {
9
+ const path = join(root, 'rfc', 'registry.sqlite');
10
+ assertNotTestWritingOps(path, 'open the RFC registry');
11
+ mkdirSync(dirname(path), { recursive: true });
12
+ const db = new Database(path, { create: true, strict: true });
13
+ try {
14
+ chmodSync(path, 0o600);
15
+ db.exec('PRAGMA busy_timeout = 10000; PRAGMA foreign_keys = ON');
16
+ db.exec(`CREATE TABLE IF NOT EXISTS rfc (
17
+ id TEXT PRIMARY KEY CHECK(length(id) = 8 AND id GLOB 'RFC-[0-9][0-9][0-9][0-9]'),
18
+ title TEXT NOT NULL CHECK(length(trim(title)) > 0),
19
+ status TEXT NOT NULL,
20
+ doc_path TEXT,
21
+ created_at TEXT NOT NULL
22
+ );
23
+ CREATE TABLE IF NOT EXISTS rfc_item (
24
+ id TEXT PRIMARY KEY CHECK(length(id) = 12 AND id GLOB 'RFC-[0-9][0-9][0-9][0-9].I[0-9][0-9]'),
25
+ rfc_id TEXT NOT NULL REFERENCES rfc(id),
26
+ title TEXT NOT NULL CHECK(length(trim(title)) > 0),
27
+ status TEXT NOT NULL,
28
+ created_at TEXT NOT NULL,
29
+ CHECK(substr(id, 1, 8) = rfc_id)
30
+ );
31
+ CREATE TABLE IF NOT EXISTS rfc_item_link (
32
+ item_id TEXT NOT NULL REFERENCES rfc_item(id),
33
+ target_item_id TEXT NOT NULL REFERENCES rfc_item(id),
34
+ PRIMARY KEY(item_id, target_item_id),
35
+ CHECK(item_id <> target_item_id)
36
+ );
37
+ CREATE TABLE IF NOT EXISTS rfc_event (
38
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
39
+ rfc_id TEXT NOT NULL REFERENCES rfc(id),
40
+ item_id TEXT REFERENCES rfc_item(id),
41
+ at TEXT NOT NULL,
42
+ actor TEXT NOT NULL,
43
+ event TEXT NOT NULL,
44
+ payload TEXT
45
+ );
46
+ CREATE TRIGGER IF NOT EXISTS rfc_id_immutable BEFORE UPDATE OF id ON rfc
47
+ BEGIN SELECT RAISE(ABORT, 'RFC identifier is immutable'); END;
48
+ CREATE TRIGGER IF NOT EXISTS rfc_no_delete BEFORE DELETE ON rfc
49
+ BEGIN SELECT RAISE(ABORT, 'RFC identifier is immutable'); END;
50
+ CREATE TRIGGER IF NOT EXISTS rfc_item_id_immutable BEFORE UPDATE OF id, rfc_id ON rfc_item
51
+ BEGIN SELECT RAISE(ABORT, 'RFC item identifier is immutable'); END;
52
+ CREATE TRIGGER IF NOT EXISTS rfc_item_no_delete BEFORE DELETE ON rfc_item
53
+ BEGIN SELECT RAISE(ABORT, 'RFC item identifier is immutable'); END;
54
+ CREATE TRIGGER IF NOT EXISTS rfc_event_item_parent BEFORE INSERT ON rfc_event
55
+ WHEN NEW.item_id IS NOT NULL AND NOT EXISTS
56
+ (SELECT 1 FROM rfc_item WHERE id = NEW.item_id AND rfc_id = NEW.rfc_id)
57
+ BEGIN SELECT RAISE(ABORT, 'RFC event item belongs to another RFC'); END;
58
+ CREATE TRIGGER IF NOT EXISTS rfc_event_no_update BEFORE UPDATE ON rfc_event
59
+ BEGIN SELECT RAISE(ABORT, 'RFC event history is append-only'); END;
60
+ CREATE TRIGGER IF NOT EXISTS rfc_event_no_delete BEFORE DELETE ON rfc_event
61
+ BEGIN SELECT RAISE(ABORT, 'RFC event history is append-only'); END;`);
62
+ return db;
63
+ } catch (error) { db.close(); throw error; }
64
+ }
65
+
66
+ export interface RfcRow { id: string; title: string; status: string; doc_path: string | null; created_at: string }
67
+ export interface RfcItemRow { id: string; rfc_id: string; title: string; status: string; created_at: string }
68
+ export interface RfcItemLinkRow { item_id: string; target_item_id: string }
69
+ export interface RfcDetail extends RfcRow { items: Array<RfcItemRow & { links: RfcItemLinkRow[] }> }
70
+
71
+ export function listRfcs(root?: string): RfcRow[] {
72
+ const db = openRfcRegistry(root);
73
+ try { return db.query('SELECT id, title, status, doc_path, created_at FROM rfc ORDER BY id').all() as RfcRow[]; }
74
+ finally { db.close(); }
75
+ }
76
+
77
+ export function showRfc(id: string, root?: string): RfcDetail | null {
78
+ const db = openRfcRegistry(root);
79
+ try {
80
+ const read = db.transaction(() => {
81
+ const row = db.query('SELECT id, title, status, doc_path, created_at FROM rfc WHERE id = ?').get(id) as RfcRow | null;
82
+ if (!row) return null;
83
+ const items = db.query('SELECT id, rfc_id, title, status, created_at FROM rfc_item WHERE rfc_id = ? ORDER BY id').all(id) as RfcItemRow[];
84
+ return { ...row, items: items.map((item) => ({ ...item, links: db.query('SELECT item_id, target_item_id FROM rfc_item_link WHERE item_id = ? ORDER BY target_item_id').all(item.id) as RfcItemLinkRow[] })) };
85
+ });
86
+ return read();
87
+ } finally { db.close(); }
88
+ }
@@ -11,6 +11,10 @@ export type SeatRequestRow = {
11
11
  text: string;
12
12
  queuedAt: string;
13
13
  reason?: string;
14
+ /** One-line explanation of the automated action recorded on this row. */
15
+ why?: string;
16
+ /** Pointer to the evidence supporting the action (log line, run id, ledger path, …). */
17
+ evidenceRef?: string;
14
18
  closedAt?: string;
15
19
  [field: string]: unknown;
16
20
  };
@@ -0,0 +1,233 @@
1
+ import { seatPackLeakPatterns } from './seat-pack.js';
2
+
3
+ /** 필수 칸 — 형식 v2 선언(각 자리 KNOWHOW 머리글)의 공통 아홉. 괄호 수식어(신호(관측) 등)는 정규화해서 비교한다. */
4
+ const REQUIRED_FIELDS = ['상황', '신호', '원인', '처방', '재는 명령', '기대', '유효 조건', '검증', '출처'] as const;
5
+
6
+ /** Only commands whose executable and subcommand are known to be observational are accepted. */
7
+ const READ_COMMANDS: Record<string, ReadonlySet<string> | null> = {
8
+ cat: null, echo: null, rg: null, grep: null, head: null, tail: null,
9
+ wc: null, sort: null, uniq: null, cut: null, jq: null, ls: null, stat: null,
10
+ readlink: null, pwd: null, test: null,
11
+ git: new Set(['rev-parse', 'status', 'log', 'show', 'merge-base']),
12
+ eln: new Set(['logs', 'where', 'tasks', 'config', 'release']),
13
+ 'bun bin/elanous.mjs': new Set(['logs', 'where', 'release']),
14
+ };
15
+
16
+ /** Git's options are commands too: e.g. show --textconv runs a configured external driver. */
17
+ const READ_GIT_OPTIONS: Record<string, ReadonlySet<string>> = {
18
+ 'rev-parse': new Set(['--git-dir', '--show-toplevel', '--is-inside-work-tree', '--verify', '--short', '--abbrev-ref', '--show-prefix']),
19
+ status: new Set(['--short', '--porcelain', '--branch', '--untracked-files=all', '--untracked-files=no']),
20
+ log: new Set(['--oneline', '--no-patch', '--stat', '--name-only', '--name-status', '--all', '--decorate']),
21
+ show: new Set(['--no-patch', '--stat', '--name-only', '--name-status', '--oneline']),
22
+ 'merge-base': new Set(['--is-ancestor', '--fork-point', '--all']),
23
+ };
24
+
25
+ /** `logs level <value>` changes daemon state. Accept only the top-level query options. */
26
+ const LOGS_QUERY_FLAGS = new Set([
27
+ '--follow', '-f', '--list-categories', '--list-events', '--explain', '--top-failures',
28
+ '--json', '--json-data', '--test', '--all', '--include-test', '-r', '--help', '-h',
29
+ ]);
30
+ const LOGS_QUERY_VALUES = new Set([
31
+ '--level', '--surface', '--space', '--category', '--exact-category', '--axis', '--event',
32
+ '--grep', '--rework-recurrence-disagreement', '--since', '--until', '--before',
33
+ '--session', '--limit', '--threshold', '--instance', '--remote',
34
+ ]);
35
+
36
+ function readOnlyLogs(args: string[]): boolean {
37
+ for (let i = 0; i < args.length; i++) {
38
+ const arg = args[i]!;
39
+ if (LOGS_QUERY_FLAGS.has(arg)) continue;
40
+ if (LOGS_QUERY_VALUES.has(arg) && args[i + 1] && !args[i + 1]!.startsWith('-')) { i++; continue; }
41
+ return false;
42
+ }
43
+ return true;
44
+ }
45
+
46
+ function readOnlyGit(rest: string[]): boolean {
47
+ const [subcommand, ...args] = rest;
48
+ const options = READ_GIT_OPTIONS[subcommand ?? ''];
49
+ if (!options) return false;
50
+ return args.every((arg) => !arg.startsWith('-') || options.has(arg)
51
+ || ((subcommand === 'log' || subcommand === 'show')
52
+ && (/^--(?:format|pretty|grep|since|until|max-count)=.+$/u.test(arg) || /^-[1-9][0-9]*$/u.test(arg))));
53
+ }
54
+
55
+ /** Do not execute the measurement here: parse only a small, fail-closed shell subset. */
56
+ function readOnlyMeasure(command: string): boolean {
57
+ const source = command.trim().replace(/^`|`$/gu, '');
58
+ const tokens: string[] = [];
59
+ let word = '';
60
+ let quote: '"' | "'" | null = null;
61
+ for (let i = 0; i < source.length; i++) {
62
+ const char = source[i]!;
63
+ if (char === '\\' || char === '`' || char === '$' || char === '\n') return false;
64
+ if (quote) {
65
+ if (char === quote) quote = null;
66
+ else word += char;
67
+ } else if (char === '"' || char === "'") {
68
+ quote = char;
69
+ } else if (/\s/u.test(char)) {
70
+ if (word) { tokens.push(word); word = ''; }
71
+ } else if ('|&;<>()[{}]'.includes(char)) {
72
+ if (word) { tokens.push(word); word = ''; }
73
+ if (char === '|' || char === '&') {
74
+ if (source[i + 1] === char) { tokens.push(char + char); i++; }
75
+ else if (char === '|') tokens.push('|');
76
+ else return false;
77
+ } else if (char === '>') {
78
+ if (source[i + 1] === '>') return false;
79
+ if (source.slice(i + 1, i + 3) === '&1') { tokens.push('>&1'); i += 2; }
80
+ else tokens.push('>');
81
+ } else return false;
82
+ } else word += char;
83
+ }
84
+ if (quote) return false;
85
+ if (word) tokens.push(word);
86
+ if (!tokens.length) return false;
87
+ const segments: string[][] = [[]];
88
+ for (let i = 0; i < tokens.length; i++) {
89
+ const token = tokens[i]!;
90
+ if (token === '>' && tokens[i + 1] === '/dev/null') { i++; continue; }
91
+ if (token === '2' && tokens[i + 1] === '>&1') { i++; continue; }
92
+ if (token === '&&' || token === '||' || token === '|') {
93
+ if (!segments.at(-1)!.length) return false;
94
+ segments.push([]);
95
+ } else if (token === '>' || token === '>&1') return false;
96
+ else segments.at(-1)!.push(token);
97
+ }
98
+ return segments.every((args) => {
99
+ if (!args.length || args.some((arg) => /^(?:--pre|--output|--ext-diff|--exec|--config|--config-env|--write)(?:=|$)/u.test(arg))) return false;
100
+ if (args.some((arg) => /^(?:--output(?:=\S*)?|--replace|--in-place)$/u.test(arg))) return false;
101
+ if (args[0] === 'sort' && args.some((arg) => /^-o\S*|^--compress-program(?:=|$)/u.test(arg))) return false;
102
+ let executable = args[0]!;
103
+ let rest = args.slice(1);
104
+ if (executable === 'bun' && rest[0] === 'bin/elanous.mjs') { executable = 'bun bin/elanous.mjs'; rest = rest.slice(1); }
105
+ if (!Object.hasOwn(READ_COMMANDS, executable)) return false;
106
+ const allowed = READ_COMMANDS[executable]!;
107
+ if (allowed === null) return true;
108
+ if (executable === 'bun bin/elanous.mjs' && rest[0] === '--test') rest = rest.slice(1);
109
+ if (!allowed.has(rest[0] ?? '')) return false;
110
+ if (executable === 'git') return readOnlyGit(rest);
111
+ if ((executable === 'eln' || executable === 'bun bin/elanous.mjs') && rest[0] === 'logs') return readOnlyLogs(rest.slice(1));
112
+ if ((executable === 'eln' || executable === 'bun bin/elanous.mjs') && rest[0] === 'tasks') return rest[1] === 'close' && rest[2] === '--help';
113
+ if ((executable === 'eln' || executable === 'bun bin/elanous.mjs') && rest[0] === 'config') return rest[1] === 'get';
114
+ if ((executable === 'eln' || executable === 'bun bin/elanous.mjs') && rest[0] === 'release') return rest[1] === 'checklist' && rest[2] === 'status';
115
+ return true;
116
+ });
117
+ }
118
+
119
+ /** 비밀 표식 — 팩 스캔 표식(seatPackLeakPatterns)에 실제 자격 모양을 더한 집합. */
120
+ const SECRET_SHAPE_PATTERNS: Array<[string, RegExp]> = [
121
+ ['api key', /\b(?:sk|rk)-(?:proj-)?[A-Za-z0-9_-]{16,}\b/u],
122
+ ['github token', /\b(?:gh[pousr]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{16,})\b/u],
123
+ ['slack token', /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/u],
124
+ ['aws key id', /\bAKIA[0-9A-Z]{12,}\b/u],
125
+ ['private key block', /-----BEGIN [A-Z ]*PRIVATE KEY-----/u],
126
+ ['credential assignment', /\b(?:api[_-]?key|secret|token|password)\s*[:=]\s*["']?[A-Za-z0-9][A-Za-z0-9._/+~-]{11,}/iu],
127
+ ];
128
+
129
+ export class KnowhowLintError extends Error {
130
+ constructor(file: string, card: string, reason: string) {
131
+ super(`knowhow lint: ${file} ${card} — ${reason}`);
132
+ this.name = 'KnowhowLintError';
133
+ }
134
+ }
135
+
136
+ interface KnowhowCard {
137
+ id: string;
138
+ lines: Array<[number, string]>;
139
+ }
140
+
141
+ /** 카드 제목 — `## K-<자리>-<번호>` 뒤에는 줄 끝·공백·콜론·em 대시만 온다. */
142
+ const CARD_HEADING = /^[ \t]{0,3}##\s+(K-[A-Za-z]+-\d+)(?=$|\s|:|—)/u;
143
+ /** 카드처럼 보이는 `##` 제목(예: `## K-OP-7a`) — CARD_HEADING 을 벗어나면 인식 실패로 명시 거부한다. */
144
+ const CARD_HEADING_LIKE = /^[ \t]{0,3}##\s*(K-[^\s:]*)/iu;
145
+
146
+ function parseCards(body: string, file: string): KnowhowCard[] {
147
+ const cards: KnowhowCard[] = [];
148
+ let current: KnowhowCard | null = null;
149
+ let fence: { marker: string; length: number } | null = null;
150
+ for (const [index, line] of body.split(/\r?\n/u).entries()) {
151
+ const fenceLine = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/u);
152
+ if (fence) {
153
+ if (fenceLine && fenceLine[1]![0] === fence.marker && fenceLine[1]!.length >= fence.length
154
+ && /^[ \t]*$/u.test(fenceLine[2]!)) fence = null;
155
+ continue;
156
+ }
157
+ if (fenceLine) {
158
+ fence = { marker: fenceLine[1]![0]!, length: fenceLine[1]!.length };
159
+ continue;
160
+ }
161
+ const heading = line.match(CARD_HEADING);
162
+ if (heading) {
163
+ current = { id: heading[1]!, lines: [] };
164
+ cards.push(current);
165
+ continue;
166
+ }
167
+ if (/^[ \t]{0,3}#/u.test(line)) {
168
+ const like = line.match(CARD_HEADING_LIKE);
169
+ if (like) throw new KnowhowLintError(file, like[1]!, '카드 제목 형식이 K-<자리>-<번호>를 벗어남');
170
+ current = null;
171
+ } else if (current) current.lines.push([index + 1, line]);
172
+ }
173
+ return cards;
174
+ }
175
+
176
+ function parseField(line: string): { label: string; value: string } | null {
177
+ const match = line.match(/^-\s*([^:—]+?)\s*[—:]\s*(.*)$/u);
178
+ if (!match) return null;
179
+ return { label: match[1]!.replace(/\s*\([^)]*\)/gu, '').trim(), value: match[2]!.trim() };
180
+ }
181
+
182
+ /** 자리 KNOWHOW.md 카드를 내보내기 전에 검증한다 — 카드 제목 형식 · 필수 칸 · 읽기 전용 재는 명령(이어 적은 여러 줄 포함) · 비밀 표식. */
183
+ export function lintKnowhow(body: string, file: string): void {
184
+ const secrets = [...seatPackLeakPatterns(), ...SECRET_SHAPE_PATTERNS];
185
+ for (const [index, line] of body.split(/\r?\n/u).entries()) {
186
+ for (const [label, pattern] of secrets) {
187
+ if (pattern.test(line)) throw new KnowhowLintError(file, 'file', `비밀 표식 (${label}) — ${file}:${index + 1}`);
188
+ }
189
+ }
190
+ const cards = parseCards(body, file);
191
+ if (!cards.length) throw new KnowhowLintError(file, 'file', '카드가 0개 — 최소 한 개의 K-<자리>-<번호> 카드가 필요함');
192
+ for (const card of cards) {
193
+ const fields = new Map<string, string>();
194
+ const fieldEntries: Array<{ label: string; value: string }> = [];
195
+ let current: { label: string; value: string } | null = null;
196
+ for (const [, line] of card.lines) {
197
+ const field = parseField(line);
198
+ if (field) {
199
+ if (current) fieldEntries.push(current);
200
+ current = { label: field.label, value: field.value };
201
+ } else if (current && /^\s+\S/u.test(line)) {
202
+ current.value += `\n${line.trim()}`;
203
+ } else if (current) {
204
+ fieldEntries.push(current);
205
+ current = null;
206
+ }
207
+ }
208
+ if (current) fieldEntries.push(current);
209
+ for (const field of fieldEntries) {
210
+ if (fields.has(field.label)) throw new KnowhowLintError(file, card.id, `중복 칸: ${field.label}`);
211
+ if ((REQUIRED_FIELDS as readonly string[]).includes(field.label) || field.label === '상태') {
212
+ if (!field.value) throw new KnowhowLintError(file, card.id, `필수 칸 누락: ${field.label}`);
213
+ }
214
+ if (field.label === '재는 명령'
215
+ && !field.value.split('\n').every((command) => readOnlyMeasure(command))) {
216
+ throw new KnowhowLintError(file, card.id, '재는 명령이 읽기 전용이 아님');
217
+ }
218
+ fields.set(field.label, field.value);
219
+ }
220
+ for (const required of REQUIRED_FIELDS) {
221
+ if (!fields.get(required)) throw new KnowhowLintError(file, card.id, `필수 칸 누락: ${required}`);
222
+ }
223
+ const source = fields.get('출처') ?? '';
224
+ const statusInSource = source.match(/(?:^|[· \t])상태[ \t]*[:—][ \t]*([^·\n]*)/u);
225
+ if (!source.slice(0, statusInSource?.index ?? source.length).replace(/[·\s]+$/u, '').trim()) {
226
+ throw new KnowhowLintError(file, card.id, '필수 칸 누락: 출처');
227
+ }
228
+ const combinedStatus = statusInSource?.[1]?.trim();
229
+ if (!fields.get('상태') && !combinedStatus) {
230
+ throw new KnowhowLintError(file, card.id, '필수 칸 누락: 상태');
231
+ }
232
+ }
233
+ }
@@ -85,7 +85,7 @@ export type SeatDeps = {
85
85
  /** Release schedules (version · cutAt) — a version is open while its cut is still ahead. Default reads the release ledger; unreadable ⇒ no checklist candidates (fail closed). */
86
86
  schedules?: () => Array<{ version: string; cutAt: string; landBy?: string | null }>;
87
87
  /** Checklist items of one version — default reads the release ledger DB (checklist.json is only a legacy import source since REL5b). */
88
- checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null; disposition?: string; evidence?: string }>;
88
+ checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null; disposition?: string; evidence?: string; dispatchKind?: string }>;
89
89
  /** Optional injected traffic/handoff snapshots; defaults read the OP files. */
90
90
  trafficSignal?: TrafficSignalRead;
91
91
  handedCells?: ReadonlySet<string>;
@@ -588,6 +588,10 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}, ledger
588
588
  }
589
589
  for (const item of items) {
590
590
  if (item.owner === seat && (item.status === 'yellow' || item.status === 'red')) {
591
+ if (item.dispatchKind && item.dispatchKind !== 'code') {
592
+ debug.log('seat.loop', 'skip-non-code-cell', { id: item.id, version, dispatchKind: item.dispatchKind });
593
+ continue;
594
+ }
591
595
  if (signal?.hold?.includes(item.id)) {
592
596
  debug.log('seat.loop', 'skip-signal-cell', { id: item.id, why: 'hold' });
593
597
  continue;
@@ -1,8 +1,9 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
3
- import { dirname, join, relative, resolve, sep } from 'node:path';
3
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
4
4
  import { userInfo } from 'node:os';
5
5
  import { DEFAULT_CONCURRENT_PODS, DEFAULT_DAILY_GOALS } from '../org/seat-budget.js';
6
+ import { lintKnowhow } from './knowhow-lint.js';
6
7
 
7
8
  export interface SeatPackManifest {
8
9
  name: string;
@@ -31,13 +32,20 @@ function walk(directory: string, root: string): string[] {
31
32
  });
32
33
  }
33
34
 
35
+ /** Static leak markers shared by the pack scan and the KNOWHOW lint. Account-name patterns stay in `scan` — they depend on the exporting host's user. */
36
+ export function seatPackLeakPatterns(): Array<[string, RegExp]> {
37
+ return [
38
+ ['royal marker', /\u{1F451}/u],
39
+ ['track marker', /(?:🅞|🅣|🅢|🅕|\*\*\[(?:OP|TC|MK|UX|S|T|O|F)\]\*\*|\[(?:OP|TC|MK|UX|S|T|O|F)\])/u],
40
+ ['absolute user path', /\/Users\/[A-Za-z0-9._-]+/u],
41
+ ];
42
+ }
43
+
34
44
  function scan(file: string, body: string): void {
35
45
  const usernames = new Set([userInfo().username, process.env.ELANOUS_LEAK_HOME_USER].filter((name): name is string => Boolean(name)));
36
46
  const accountPatterns = [...usernames].map((name) => new RegExp(`(?<![A-Za-z0-9._-])${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9._-])`, 'iu'));
37
47
  const patterns: Array<[string, RegExp]> = [
38
- ['royal marker', /\u{1F451}/u],
39
- ['track marker', /(?:🅞|🅣|🅢|🅕|\*\*\[(?:OP|TC|MK|UX|S|T|O|F)\]\*\*|\[(?:OP|TC|MK|UX|S|T|O|F)\])/u],
40
- ['absolute user path', /\/Users\/[A-Za-z0-9._-]+/u],
48
+ ...seatPackLeakPatterns(),
41
49
  ...accountPatterns.map((pattern): [string, RegExp] => ['account name', pattern]),
42
50
  ];
43
51
  for (const [index, line] of body.split(/\r?\n/u).entries()) {
@@ -61,6 +69,10 @@ export function exportSeatPack(seat: string, out: string, root = resolve(import.
61
69
  for (const file of roleFiles) {
62
70
  const body = readFileSync(join(repo, file));
63
71
  contents.set(file, body);
72
+ // Public leak markers take precedence over formatting errors: callers need
73
+ // the file:line and exit 2 before an export directory can be created.
74
+ scan(file, body.toString('utf8'));
75
+ if (basename(file) === 'KNOWHOW.md') lintKnowhow(body.toString('utf8'), file);
64
76
  for (const match of body.toString('utf8').matchAll(/graphs\/([A-Za-z0-9_./-]+(?:\.ya?ml|\/\*\*))/gu)) {
65
77
  const ref = `graphs/${match[1]!}`;
66
78
  if (ref.split('/').includes('..')) throw new Error(`invalid seat graph: ${ref}`);
@@ -10,6 +10,13 @@ export type TrafficSignal = {
10
10
  };
11
11
  export type TrafficSignalRead = TrafficSignal | { unreadable: true } | null;
12
12
 
13
+ /** Pure proposal classification; applying a proposal belongs to the downstream seat-message caller. */
14
+ export function classifySeatTrafficProposal(message: string): 'hold' | 'UNHOLD' | null {
15
+ if (message.includes('첫 줄 확정')) return 'UNHOLD';
16
+ if (message.includes('손 구현 착수')) return 'hold';
17
+ return null;
18
+ }
19
+
13
20
  const defaultPath = (name: string): string => join(homedir(), 'elanous-hq', 'seat-state', 'OP', name);
14
21
  const missing = (error: unknown): boolean => (error as NodeJS.ErrnoException).code === 'ENOENT';
15
22
 
@@ -11,6 +11,8 @@
11
11
  // ⛔ 종료(`process.exit`)는 여기가 «안» 한다 — 호출자가 자기 표면에 맞게 끝낸다.
12
12
  // 그래서 결과를 «세 값»으로 낸다: 발사 / 저작 전 중단 / 전제 검사 중단.
13
13
  import { linesOutsideFencedCode } from '../self-implement/goal-author.js';
14
+ import { existsSync } from 'node:fs';
15
+ import { debug } from '../debug/log.js';
14
16
  import { recordAuthoredGoal } from './authored-goal-trace.js';
15
17
  import { findAskPathCandidates, type AskPathCandidateOptions } from './ask-path-candidates.js';
16
18
  import { retiredEntranceNotice, type EntranceDeclaration } from './entrance-registry.js';
@@ -18,6 +20,7 @@ import {
18
20
  classifyConcurrentAuthoring,
19
21
  countRepeatedBlocks,
20
22
  decideAskPreflight,
23
+ discardNonFileTracedPathItems,
21
24
  parseAskTargetPathHintsResult,
22
25
  parseBlockedChoice,
23
26
  planBlockedPrompt,
@@ -797,10 +800,10 @@ async function intakeClarifications(
797
800
  selfResolveClarifications: selfResolveClarifications.value,
798
801
  });
799
802
  const parentDocument = document;
800
- effectiveGoalFile = reauthored.path;
803
+ effectiveGoalFile = isAbsolutePath(reauthored.path) ? reauthored.path : resolvePath(deps.cwd(), reauthored.path);
801
804
  deps.print(`[ask] ⑴b 재저작됨: ${reauthored.path}`);
802
805
  // ⛔ 「재저작이 «났다»」와 「재저작이 «옳았다»」는 다른 물음이다(JDG-T35 3단 ⑶).
803
- const childDocument = reauthored.authored?.document ?? deps.readFile(reauthored.path);
806
+ const childDocument = reauthored.authored?.document ?? deps.readFile(effectiveGoalFile);
804
807
  const askFidelity = classifyReauthoredAsk(parentDocument, childDocument);
805
808
  if (askFidelity !== 'preserved') {
806
809
  deps.print(`[ask] ⑴b ⚠️ 재저작 ask 판정: ${askFidelity} — ⛔ 「preserved」가 아니면 사람의 ask 가 살아남았는지 «확인하라»`);
@@ -939,7 +942,31 @@ export async function runAskLaunchFlow(
939
942
  }, markerCounts !== null && markerCounts.missingMarkers > 0 ? 'warn' : 'info');
940
943
 
941
944
  // ⛔ 되묻기에 답하면 «새 골»이 나온다 — 아래 전제 검사·발사는 그 «최종» 골을 써야 한다.
942
- const goalFile = await intakeClarifications(authored, input, deps, selfResolveClarifications);
945
+ const goalFile = await intakeClarifications({
946
+ ...authored,
947
+ path: isAbsolutePath(authored.path) ? authored.path : resolvePath(deps.cwd(), authored.path),
948
+ }, input, deps, selfResolveClarifications);
949
+ // The child reads this file again. Filtering only preflight.paths would leave the same bogus
950
+ // TRACED PATHS prose in the child and repeat its missing-file failure.
951
+ let sanitizeFailed = false;
952
+ let sanitizeFailure: unknown;
953
+ if (existsSync(goalFile)) {
954
+ try {
955
+ const original = deps.readFile(goalFile);
956
+ const sanitized = discardNonFileTracedPathItems(original, deps.cwd());
957
+ if (sanitized.dropped.length > 0) {
958
+ // The child reads this same file, so a failed rewrite must stop launch.
959
+ (deps.writeGoalDocument ?? deps.writeFile)(goalFile, sanitized.document);
960
+ for (const dropped of sanitized.dropped) {
961
+ try { debug.log('harness.preflight', 'traced-path-discarded', { goalFile, dropped }); }
962
+ catch { /* observation must not change launch */ }
963
+ }
964
+ }
965
+ } catch (error) {
966
+ sanitizeFailed = true;
967
+ sanitizeFailure = error;
968
+ }
969
+ }
943
970
  const preflightDeps = await deps.buildPreflightDeps();
944
971
  observeLaunchingTree(deps, preflightDeps);
945
972
  const askOutsidePathHints = parseAskTargetPathHintsResult(input.askText).paths;
@@ -954,11 +981,11 @@ export async function runAskLaunchFlow(
954
981
  ...(machineSuggestedTargetPaths.length > 0 ? { machineSuggestedTargetPaths } : {}),
955
982
  declaredPathsRoot: deps.cwd(),
956
983
  },
957
- preflightDeps,
984
+ sanitizeFailed ? { ...preflightDeps, readGoalDocument: () => { throw sanitizeFailure; } } : preflightDeps,
958
985
  input.forceRequested,
959
986
  );
960
987
  const preflight = decision.result;
961
- deps.print(renderLaunchPreflight(preflight, input.forceRequested && preflight.blockers.length > 0, 'before-launch'));
988
+ deps.print(renderLaunchPreflight(preflight, input.forceRequested && decision.shouldLaunch && preflight.blockers.length > 0, 'before-launch'));
962
989
  observeLaunchPreflight(deps, 'before-launch', decision);
963
990
  // ⛔ 자식 격리 worktree를 만들기 전, 이 흐름을 호출한 작업 트리에서만 센다.
964
991
  // 이 관측은 발사 판정의 입력이 아니다 — 의도적으로 옛 판에서 쏘는 경우도 있다.
@@ -1001,7 +1028,7 @@ export async function runAskLaunchFlow(
1001
1028
  liveRunWindowMs: preflight.liveRunWindowMs,
1002
1029
  // ⛔ 「플래그를 줬다」와 「실제로 뚫었다」는 다른 값이다(리뷰 should-fix).
1003
1030
  forceRequested: input.forceRequested,
1004
- bypassed: input.forceRequested && preflight.blockers.length > 0,
1031
+ bypassed: input.forceRequested && decision.shouldLaunch && preflight.blockers.length > 0,
1005
1032
  }, preflight.blockers.length > 0 ? 'warn' : 'info');
1006
1033
  return { goalFile, decision };
1007
1034
  };
@@ -1023,14 +1050,27 @@ export async function runAskLaunchFlow(
1023
1050
  } catch (error) {
1024
1051
  candidates = { tokens: [], paths: [], error: error instanceof Error ? error.message : String(error) };
1025
1052
  }
1053
+ const selectedPath = candidates.selection?.path
1054
+ ?? (!candidates.selection && candidates.paths.length === 1 ? candidates.paths[0] : undefined);
1055
+ const selectionReason = candidates.selection?.reason
1056
+ ?? (candidates.error ? 'search-error' : selectedPath ? 'only-candidate'
1057
+ : candidates.paths.length > 0 ? 'ambiguous-without-evidence' : 'no-candidates');
1026
1058
  deps.log('ask-auto-target-candidates', {
1027
1059
  tokens: candidates.tokens, paths: candidates.paths,
1060
+ evidence: candidates.evidence ?? [],
1061
+ selection: { path: selectedPath ?? null, reason: selectionReason },
1028
1062
  ...(candidates.error === undefined ? {} : { error: candidates.error }),
1029
- }, candidates.error === undefined ? 'info' : 'warn');
1063
+ }, candidates.error === undefined && selectedPath ? 'info' : 'warn');
1030
1064
  }
1031
- const selectedPaths = candidates.paths.slice(0, 1);
1065
+ const selectedPaths = candidates.selection?.path
1066
+ ? [candidates.selection.path]
1067
+ : !candidates.selection && candidates.paths.length === 1 ? [candidates.paths[0]!] : [];
1032
1068
  if (selectedPaths.length > 0) {
1033
1069
  deps.print(`[ask] ⑴ 코드에서 후보 경로 ${selectedPaths.length}개를 찾아 넣고 다시 저작한다: ${selectedPaths.join(' · ')}`);
1070
+ } else if (candidates.paths.length > 0) {
1071
+ deps.print(`[ask] ⑴ ⚠️ 후보 경로를 하나로 판정할 근거가 없다. 대상 경로를 확인해야 한다: ${candidates.paths.join(' · ')}`);
1072
+ } else {
1073
+ deps.print('[ask] ⑴ ⚠️ 자동 재저작에 넣을 대상 경로 후보가 없다. 대상 경로를 확인해야 한다');
1034
1074
  }
1035
1075
  authored = await authorAndPreflight(selectedPaths.length > 0
1036
1076
  ? `대상 경로: ${selectedPaths.join(' · ')}\n${input.askText}`
@@ -1,15 +1,31 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { statSync } from 'node:fs';
3
- import { isAbsolute, relative, resolve } from 'node:path';
3
+ import { basename, extname, isAbsolute, relative, resolve } from 'node:path';
4
4
 
5
5
  export interface AskPathCandidateOptions {
6
6
  readonly cwd: string;
7
7
  readonly run?: (command: string, args: readonly string[], options: { cwd: string; encoding: 'utf8' }) => string;
8
8
  }
9
9
 
10
- export type AskPathCandidates =
11
- | { readonly tokens: string[]; readonly paths: string[]; readonly error?: undefined }
12
- | { readonly tokens: string[]; readonly paths: string[]; readonly error: string };
10
+ export interface AskPathCandidates {
11
+ readonly tokens: string[];
12
+ readonly paths: string[];
13
+ readonly evidence?: AskPathEvidence[];
14
+ readonly selection?: AskPathSelection;
15
+ readonly error?: string;
16
+ }
17
+
18
+ export interface AskPathEvidence {
19
+ readonly path: string;
20
+ readonly pathTokens: string[];
21
+ readonly strongestPathMatch: 'explicit-file' | 'filename' | 'directory' | null;
22
+ readonly contentTokens: string[];
23
+ readonly score: number;
24
+ }
25
+
26
+ export type AskPathSelection =
27
+ | { readonly path: string; readonly reason: 'unique-strongest-evidence' }
28
+ | { readonly path: null; readonly reason: 'tied-strongest-evidence' };
13
29
 
14
30
  const STOP_WORDS = new Set(['the', 'and', 'test', 'src', 'scripts', 'file', 'path', 'paths', 'code', 'with', 'from', 'this', 'that', 'into', 'for', 'then', 'when', 'have', 'does', 'should', 'will', 'please', 'target', 'change']);
15
31
  const WORD = /[-/]?[A-Za-z][A-Za-z0-9_.-]*(?:\/[A-Za-z0-9_.-]+)*/g;
@@ -63,7 +79,15 @@ function existingFile(fragment: string, cwd: string): string | null {
63
79
 
64
80
  /** Find existing code paths by lexical evidence from an unmodified ask. */
65
81
  export function findAskPathCandidates(askText: string, { cwd, run = execFileSync }: AskPathCandidateOptions): AskPathCandidates {
66
- const scores = new Map<string, Set<string>>();
82
+ const scores = new Map<string, { explicit: Set<string>; filename: Set<string>; directory: Set<string>; content: Set<string> }>();
83
+ const hitsFor = (path: string) => {
84
+ let hits = scores.get(path);
85
+ if (!hits) {
86
+ hits = { explicit: new Set(), filename: new Set(), directory: new Set(), content: new Set() };
87
+ scores.set(path, hits);
88
+ }
89
+ return hits;
90
+ };
67
91
  const tokens: string[] = [];
68
92
  const seen = new Set<string>();
69
93
  const body = stripAskWrapper(askText);
@@ -73,11 +97,7 @@ export function findAskPathCandidates(askText: string, { cwd, run = execFileSync
73
97
  const raw = match[0];
74
98
  const token = raw.replace(/^\//, '');
75
99
  const file = existingFile(token, cwd);
76
- if (file) {
77
- const hits = scores.get(file) ?? new Set<string>();
78
- hits.add(token);
79
- scores.set(file, hits);
80
- }
100
+ if (file) hitsFor(file).explicit.add(token);
81
101
  if (STOP_WORDS.has(token.toLowerCase()) || isGenericToken(token) || seen.has(token)) continue;
82
102
  if (!file && !token.includes('/') && !token.includes('.') && !token.includes('-') && !token.includes('_')
83
103
  && !/[a-z][A-Z]/.test(token) && token.length < 4) continue;
@@ -85,6 +105,24 @@ export function findAskPathCandidates(askText: string, { cwd, run = execFileSync
85
105
  tokens.push(token);
86
106
  }
87
107
  }
108
+ if (tokens.length > 0) {
109
+ let files: string;
110
+ try {
111
+ files = run('rg', ['--files', '--glob', '!*.test.ts', 'src', 'scripts'], { cwd, encoding: 'utf8' });
112
+ } catch (error) {
113
+ if ((error as { status?: number }).status === 1) files = '';
114
+ else return { tokens, paths: [], error: error instanceof Error ? error.message : String(error) };
115
+ }
116
+ for (const path of files.split(/\r?\n/).filter(Boolean)) {
117
+ const fileStem = basename(path, extname(path)).toLowerCase();
118
+ const directories = path.toLowerCase().split('/').slice(0, -1);
119
+ for (const token of tokens) {
120
+ const lower = token.toLowerCase();
121
+ if (fileStem === lower || basename(path).toLowerCase() === lower) hitsFor(path).filename.add(token);
122
+ else if (directories.includes(lower)) hitsFor(path).directory.add(token);
123
+ }
124
+ }
125
+ }
88
126
  for (const token of tokens) {
89
127
  let matches: string;
90
128
  try {
@@ -94,13 +132,30 @@ export function findAskPathCandidates(askText: string, { cwd, run = execFileSync
94
132
  return { tokens, paths: [], error: error instanceof Error ? error.message : String(error) };
95
133
  }
96
134
  for (const path of matches.split(/\r?\n/).filter(Boolean)) {
97
- const hits = scores.get(path) ?? new Set<string>();
98
- hits.add(token);
99
- scores.set(path, hits);
135
+ hitsFor(path).content.add(token);
100
136
  }
101
137
  }
138
+ const evidence = [...scores].map<AskPathEvidence>(([path, hits]) => {
139
+ const tier = hits.explicit.size > 0 ? 3 : hits.filename.size > 0 ? 2 : hits.directory.size > 0 ? 1 : 0;
140
+ return {
141
+ path,
142
+ pathTokens: [...new Set([...hits.explicit, ...hits.filename, ...hits.directory])],
143
+ strongestPathMatch: tier === 3 ? 'explicit-file' : tier === 2 ? 'filename' : tier === 1 ? 'directory' : null,
144
+ contentTokens: [...hits.content],
145
+ // Lexicographic tiers: even many directory hits cannot outrank one filename hit.
146
+ score: tier * 1000 + Math.min(hits.explicit.size + hits.filename.size + hits.directory.size, 9) * 100
147
+ + Math.min(hits.content.size, 99),
148
+ };
149
+ }).sort((a, b) => b.score - a.score || a.path.localeCompare(b.path, 'en'));
150
+ const top = evidence[0];
102
151
  return {
103
152
  tokens,
104
- paths: [...scores].sort(([a, aHits], [b, bHits]) => bHits.size - aHits.size || a.localeCompare(b, 'en')).slice(0, 5).map(([path]) => path),
153
+ paths: evidence.slice(0, 5).map(({ path }) => path),
154
+ ...(top ? {
155
+ evidence: evidence.slice(0, 5),
156
+ selection: evidence[1]?.score === top.score
157
+ ? { path: null, reason: 'tied-strongest-evidence' as const }
158
+ : { path: top.path, reason: 'unique-strongest-evidence' as const },
159
+ } : {}),
105
160
  };
106
161
  }