elanous 0.2.8 → 0.2.10

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 (385) 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/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
  14. package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-d4b6df905f185055.js → page-54b32bd27726588d.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/{page-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/market/{page-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.js → page-6cb0e1bbefe5fb3d.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7464c1987dd76e6d.js → page-e168fa1030af0548.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
  61. package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
  62. package/apps/pwa/out/approvals/index.html +1 -1
  63. package/apps/pwa/out/approvals/index.txt +30 -29
  64. package/apps/pwa/out/autopilot/index.html +1 -1
  65. package/apps/pwa/out/autopilot/index.txt +30 -29
  66. package/apps/pwa/out/board/index.html +1 -1
  67. package/apps/pwa/out/board/index.txt +30 -29
  68. package/apps/pwa/out/botlab/index.html +1 -1
  69. package/apps/pwa/out/botlab/index.txt +27 -26
  70. package/apps/pwa/out/bots/index.html +1 -1
  71. package/apps/pwa/out/bots/index.txt +27 -26
  72. package/apps/pwa/out/chat/index.html +1 -1
  73. package/apps/pwa/out/chat/index.txt +27 -26
  74. package/apps/pwa/out/consult/index.html +1 -1
  75. package/apps/pwa/out/consult/index.txt +30 -29
  76. package/apps/pwa/out/control/index.html +1 -1
  77. package/apps/pwa/out/control/index.txt +30 -29
  78. package/apps/pwa/out/design-check/index.html +1 -1
  79. package/apps/pwa/out/design-check/index.txt +30 -29
  80. package/apps/pwa/out/editor/index.html +1 -1
  81. package/apps/pwa/out/editor/index.txt +30 -29
  82. package/apps/pwa/out/exec/index.html +1 -1
  83. package/apps/pwa/out/exec/index.txt +30 -29
  84. package/apps/pwa/out/field/index.html +1 -1
  85. package/apps/pwa/out/field/index.txt +30 -29
  86. package/apps/pwa/out/healthz +1 -1
  87. package/apps/pwa/out/index.html +1 -1
  88. package/apps/pwa/out/index.txt +27 -26
  89. package/apps/pwa/out/intake/index.html +1 -1
  90. package/apps/pwa/out/intake/index.txt +30 -29
  91. package/apps/pwa/out/live/index.html +1 -1
  92. package/apps/pwa/out/live/index.txt +30 -29
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +27 -26
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +27 -26
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +30 -29
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +30 -29
  101. package/apps/pwa/out/ops/checklist/index.html +1 -0
  102. package/apps/pwa/out/ops/checklist/index.txt +30 -0
  103. package/apps/pwa/out/ops/release/index.html +1 -0
  104. package/apps/pwa/out/ops/release/index.txt +30 -0
  105. package/apps/pwa/out/ops/seats/index.html +1 -0
  106. package/apps/pwa/out/ops/seats/index.txt +30 -0
  107. package/apps/pwa/out/reflection/index.html +1 -1
  108. package/apps/pwa/out/reflection/index.txt +28 -27
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +30 -29
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +27 -26
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +30 -29
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +30 -29
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +30 -29
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +30 -29
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +30 -29
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +27 -26
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +30 -29
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +30 -29
  129. package/apps/pwa/out/today/index.html +1 -0
  130. package/apps/pwa/out/today/index.txt +33 -0
  131. package/apps/pwa/out/trace/index.html +1 -1
  132. package/apps/pwa/out/trace/index.txt +30 -29
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +30 -29
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +30 -29
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +30 -29
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +30 -29
  143. package/graphs/field/field-feed.yaml +2 -0
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/graphs/release/recipes.yaml +3 -0
  146. package/graphs/release/release-loop.yaml +3 -1
  147. package/graphs/test-diet/nightly-audit.yaml +18 -0
  148. package/graphs/test-diet/recipes.yaml +3 -0
  149. package/package.json +1 -1
  150. package/plugins/card-followup/graphs/run-step.ts +48 -13
  151. package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
  152. package/plugins/doc-draft/graphs/run-step.ts +14 -7
  153. package/scripts/ci-pwa-build-gate.ts +160 -0
  154. package/scripts/coord-channel-watch.sh +42 -8
  155. package/scripts/coord-digest.ts +199 -0
  156. package/scripts/coord-post.sh +60 -3
  157. package/scripts/install.ps1 +68 -9
  158. package/scripts/install.sh +39 -12
  159. package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
  160. package/scripts/lib/tui-regress-checks.ts +146 -0
  161. package/scripts/memory-lifecycle-cycle.ts +2 -2
  162. package/scripts/ops-now-probe.ts +327 -0
  163. package/scripts/publish-internal-dist.ts +11 -1
  164. package/scripts/release-loop/cutoff.ts +31 -2
  165. package/scripts/release-loop/export-check-node.ts +85 -0
  166. package/scripts/release-loop/gate-node.ts +18 -4
  167. package/scripts/release-loop/node-verdict.ts +8 -1
  168. package/scripts/release-loop/ops-upgrade-node.ts +72 -16
  169. package/scripts/release-loop/release-readiness.ts +9 -0
  170. package/scripts/release-loop/version-node.ts +58 -10
  171. package/scripts/repo-watch-cycle.ts +1 -1
  172. package/scripts/retro-cycle.ts +2 -2
  173. package/scripts/schedule-health-report.ts +1 -1
  174. package/scripts/storyboard/storyboard.ts +5 -161
  175. package/scripts/test-diet/lib.ts +31 -1
  176. package/scripts/test-diet/node.ts +134 -17
  177. package/scripts/test-timeout-audit.ts +114 -0
  178. package/scripts/tree-sync-alert.ts +1 -1
  179. package/scripts/tui-regress.ts +71 -0
  180. package/scripts/waitlist-daily-digest.ts +1 -1
  181. package/skills/explainer-video/SKILL.md +4 -0
  182. package/skills/explainer-video/engine/reel.mjs +18 -13
  183. package/skills/explainer-video/engine/reel.sh +24 -5
  184. package/skills/explainer-video/engine/stitch.mjs +145 -0
  185. package/skills/pitch-deck/SKILL.md +36 -0
  186. package/skills/pitch-deck/template.html +80 -0
  187. package/src/acp/codex-approval-adapter.ts +9 -3
  188. package/src/acp/core-turn-bridge.ts +117 -18
  189. package/src/acp/fact-question.ts +11 -0
  190. package/src/acp/server.ts +48 -7
  191. package/src/acp/tool-approval.ts +126 -0
  192. package/src/acp/turn-runner.ts +36 -0
  193. package/src/agent/monad-agent-turn.ts +8 -0
  194. package/src/ask-user-question/pending-questions.ts +25 -10
  195. package/src/ask-user-question/types.ts +9 -0
  196. package/src/auth/setup-link-tokens.fixture.ts +19 -0
  197. package/src/auth/setup-link-tokens.ts +164 -0
  198. package/src/autopilot/build/integrity-gate.ts +12 -0
  199. package/src/boot/daemon-prompt-turn.ts +3 -0
  200. package/src/boot/daemon-tools/index.ts +8 -0
  201. package/src/boot/daemon-tools/types.ts +2 -0
  202. package/src/boot/daemon-tools/web-search.ts +1 -0
  203. package/src/budget/unified-usage.ts +75 -0
  204. package/src/channel-bot-token.ts +37 -10
  205. package/src/chat/ctrl-c-in-input.ts +14 -0
  206. package/src/chat/index.ts +61 -9
  207. package/src/chat/pickers/state.ts +6 -2
  208. package/src/cli/agent-cli.ts +7 -0
  209. package/src/cli/decisions-cli.ts +25 -3
  210. package/src/cli/doctor-bundle-upload.ts +68 -0
  211. package/src/cli/doctor-cli.ts +38 -4
  212. package/src/cli/op-proxy-sync.ts +83 -0
  213. package/src/cli/ops-cli.ts +20 -0
  214. package/src/cli/pod-cli.ts +49 -0
  215. package/src/cli/pr-cli.ts +37 -4
  216. package/src/cli/pwa-stop.ts +163 -13
  217. package/src/cli/pwa-test.ts +3 -0
  218. package/src/cli/release-cli.ts +98 -15
  219. package/src/cli/self-update.ts +14 -0
  220. package/src/cli/storyboard-cli.ts +37 -0
  221. package/src/cli/test-state-dir-flag.ts +11 -4
  222. package/src/connectors/linear.ts +146 -0
  223. package/src/context-bus/coord-events.ts +115 -0
  224. package/src/dashboard/dashboard-boot-fixture.ts +23 -0
  225. package/src/dashboard/dashboard-status-lines.ts +45 -0
  226. package/src/dashboard/first-screen-band.ts +35 -0
  227. package/src/dashboard/index.ts +128 -34
  228. package/src/dashboard/provider-slash-runtime.ts +28 -11
  229. package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
  230. package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
  231. package/src/dashboard/turn-stream-formatter.ts +1 -1
  232. package/src/decisions/decision-cards.ts +50 -2
  233. package/src/decisions/decision-ledger.ts +75 -4
  234. package/src/decisions/decision-linear-projection.ts +157 -0
  235. package/src/decisions/web-push-decision-cards.ts +50 -0
  236. package/src/directive/interpretation-card.ts +36 -0
  237. package/src/discord.ts +2 -1
  238. package/src/domains/coo-admin-tool.ts +62 -0
  239. package/src/domains/core-tools.ts +11 -3
  240. package/src/domains/elanous-card.ts +29 -0
  241. package/src/domains/ops-facts-tool.ts +76 -0
  242. package/src/domains/outbound-alert.ts +41 -13
  243. package/src/domains/release-tool.ts +168 -0
  244. package/src/domains/telegram-kind-route.ts +19 -1
  245. package/src/exec-requests/planner.ts +98 -28
  246. package/src/exec-requests/runner.ts +107 -10
  247. package/src/exec-requests/store.ts +8 -2
  248. package/src/field/field-media.ts +10 -7
  249. package/src/field/field-reel.ts +14 -4
  250. package/src/git-fs/worktree.ts +45 -6
  251. package/src/graph-runner/graph-approval-card.ts +113 -0
  252. package/src/graph-runner/graph-cli.ts +129 -4
  253. package/src/graph-runner/graph-notify.ts +25 -3
  254. package/src/graph-runner/runner.ts +142 -16
  255. package/src/harness/browser-verify.ts +25 -2
  256. package/src/harness/harness-cli-command.ts +231 -35
  257. package/src/harness/harness-incidents-writer.fixture.ts +24 -0
  258. package/src/harness/harness-incidents.ts +129 -0
  259. package/src/harness/harness-stop.ts +53 -3
  260. package/src/hitl/clarification-policy.ts +3 -1
  261. package/src/index.ts +172 -41
  262. package/src/intake-plane/author-goals.ts +37 -4
  263. package/src/intake-plane/check.ts +3 -0
  264. package/src/intake-plane/discord-seat-work.ts +47 -2
  265. package/src/intake-plane/seat-doc-route.ts +170 -6
  266. package/src/intake-plane/telegram-seat-work.ts +44 -2
  267. package/src/intake-plane/telegram-work.ts +1 -0
  268. package/src/maturity/cli-maturity.ts +34 -0
  269. package/src/maturity/feature-maturity.ts +100 -1
  270. package/src/maturity/tui-slash-maturity.ts +31 -0
  271. package/src/memory.ts +32 -4
  272. package/src/nexus/api/exec-requests.ts +33 -3
  273. package/src/nexus/api/http-server.ts +73 -1
  274. package/src/nexus/api/meta-api.ts +38 -0
  275. package/src/nexus/api/operator.ts +61 -0
  276. package/src/nexus/api/ops-api.ts +148 -0
  277. package/src/nexus/api/ops-seats-sources.ts +95 -0
  278. package/src/nexus/api/ops-seats.ts +124 -0
  279. package/src/nexus/api/public-routes.ts +14 -1
  280. package/src/nexus/api/seat-requests.ts +88 -8
  281. package/src/nexus/api/sessions-store.ts +63 -0
  282. package/src/nexus/api/setup-claim.ts +21 -0
  283. package/src/nexus/api/setup-codex-login.ts +160 -0
  284. package/src/nexus/api/usage-api.ts +102 -0
  285. package/src/nexus/config/secrets/file-backend.ts +56 -18
  286. package/src/nexus/index.ts +45 -4
  287. package/src/onboarding/first-run-doctor.ts +54 -0
  288. package/src/onboarding/first-run.ts +89 -0
  289. package/src/onboarding/lan-address.ts +45 -0
  290. package/src/onboarding/web-first.ts +119 -24
  291. package/src/onboarding.ts +4 -0
  292. package/src/ops-now/ops-now-refresh.ts +100 -0
  293. package/src/project/project-store.ts +115 -0
  294. package/src/release-loop/checklist.ts +76 -61
  295. package/src/release-loop/feature-store.ts +460 -0
  296. package/src/release-loop/manifest.ts +59 -5
  297. package/src/release-loop/release-schedule.ts +53 -0
  298. package/src/repl/index.ts +7 -0
  299. package/src/seat-dispatch/ceo-commands.ts +98 -0
  300. package/src/seat-dispatch/ceo-intent.ts +38 -0
  301. package/src/seat-loop/seat-loop.ts +231 -0
  302. package/src/seat-loop/seat-report.ts +64 -0
  303. package/src/self-dev/decompose.ts +66 -11
  304. package/src/self-dev/run-store.ts +10 -1
  305. package/src/self-implement/execution-clarification.ts +115 -0
  306. package/src/self-implement/gate-cli.ts +6 -3
  307. package/src/self-implement/orchestrator.ts +37 -3
  308. package/src/self-implement/pipeline-shape.ts +1 -1
  309. package/src/self-implement/run-status-mapping.ts +5 -3
  310. package/src/session/chat.ts +2 -0
  311. package/src/session/index.ts +9 -0
  312. package/src/shell-env-bootstrap.ts +17 -0
  313. package/src/skills/tools/usage-slash.ts +8 -7
  314. package/src/steward/directive.ts +51 -2
  315. package/src/steward/launch.ts +250 -0
  316. package/src/steward/outcome-notify.ts +115 -0
  317. package/src/steward/steward-cards.ts +8 -2
  318. package/src/steward/triage-plan.ts +32 -4
  319. package/src/steward/triage.ts +93 -9
  320. package/src/steward/working-backwards.ts +64 -0
  321. package/src/storyboard/storyboard.ts +141 -0
  322. package/src/task-cards/card-store.ts +2 -2
  323. package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
  324. package/src/telegram-card-followup.ts +233 -0
  325. package/src/telegram-commands.ts +16 -0
  326. package/src/telegram-report.ts +15 -9
  327. package/src/telegram-run.ts +90 -13
  328. package/src/telegram.ts +29 -8
  329. package/src/thinking-line.ts +28 -20
  330. package/src/tool-runtime/types.ts +4 -0
  331. package/src/tui/chat/execution-badge.ts +1 -1
  332. package/src/tui-client/dashboard-session.ts +2 -0
  333. package/src/user-config.ts +118 -11
  334. package/src/ux-sim/turn-states.ts +1 -1
  335. package/src/version/packed-revision.json +1 -1
  336. package/src/web-push/notify-exec-request.ts +32 -0
  337. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
  382. package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
  383. package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
  384. package/src/dashboard/turn-typeahead-echo.ts +0 -23
  385. /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
@@ -8,9 +8,13 @@ import { debug, redactSecretText } from '../debug/log.js';
8
8
  import { effectiveInstanceRoot } from '../instance/resolve.js';
9
9
  import { redactSecrets } from '../task-cards/card-store.js';
10
10
  import { directiveHash } from './directive.js';
11
- import { recordTriageOnCards, type StewardCardDeps } from './steward-cards.js';
11
+ import { notifyOutcome, type OutcomeNotifyDeps } from './outcome-notify.js';
12
+ import { recordTriageOnCards, recordLaunchOnCard, recordOutcomeOnCard, recordHitlOnCard, type StewardCardDeps } from './steward-cards.js';
13
+ import { planLaunches, launch, collectOutcomes, raiseHitl, readLaunchLedger, saveLaunchLedger, type LaunchDeps } from './launch.js';
14
+ import { InvalidWorkingBackwardsDraft, recordWorkingBackwardsOnCards } from './working-backwards.js';
15
+ import { CardStore } from '../task-cards/card-store.js';
12
16
  import { triageWithinBudget } from './triage-budget.js';
13
- import { askSteward, classifyIssue, planIssues, ruleJudgment, unsafeReason, type ClassifiedIssue, type StewardAsk } from './triage-plan.js';
17
+ import { askSteward, classifyIssue, installedCapabilities, planIssues, ruleJudgment, unsafeReason, type ClassifiedIssue, type StewardAsk } from './triage-plan.js';
14
18
  import { failureAlertText, markStageStart, markStageEnd, readFailureStreak, shouldAlert, writeFailureStreak, type FailureStreak, type StewardStage } from './failure-streak.js';
15
19
 
16
20
  export type Rung = 0 | 1 | 2 | 3 | 4 | 5 | 'hitl';
@@ -24,6 +28,7 @@ export interface TriageDecision {
24
28
  owner?: string;
25
29
  /** Carried from a previous triage without a current judgment; scheduling must not route it. */
26
30
  deferred?: true;
31
+ capability?: ClassifiedIssue['capability'];
27
32
  }
28
33
  export interface ScheduledDecision extends TriageDecision { disposition: 'now' | 'wait' | 'hitl' }
29
34
  export interface StewardSettings {
@@ -32,6 +37,9 @@ export interface StewardSettings {
32
37
  budget?: number;
33
38
  tracks?: Record<string, string>;
34
39
  alertAfterFailures?: number;
40
+ launch?: 'off' | 'shadow' | 'live';
41
+ maxParallel?: number;
42
+ podPool?: string;
35
43
  }
36
44
  export interface StewardDeps {
37
45
  fetch?: typeof fetch;
@@ -44,6 +52,10 @@ export interface StewardDeps {
44
52
  cardStore?: StewardCardDeps['store'];
45
53
  warn?: (message: string) => void;
46
54
  ask?: StewardAsk;
55
+ launchSettings?: Pick<StewardSettings, 'launch' | 'maxParallel' | 'podPool'>;
56
+ launchCommand?: LaunchDeps['command'];
57
+ spawnLaunch?: LaunchDeps['spawn'];
58
+ outcomeNotify?: Pick<OutcomeNotifyDeps, 'document' | 'message' | 'worktreeCommand' | 'loadOrigin' | 'loadOrigins'>;
47
59
  /** Internal command boundary: the current sync has already claimed this pid. */
48
60
  stageOwnedByCommand?: boolean;
49
61
  }
@@ -89,6 +101,8 @@ export async function triageIssues(issues: TriageIssue[], judge?: NonNullable<St
89
101
  const classified: ClassifiedIssue[] = [];
90
102
  const classifiedIds = new Set<string>();
91
103
  const counts = { rule: 0, classify: 0, planning: 0 };
104
+ // Inventory is shared by every classification in this batch; do not spawn --help per issue.
105
+ let inventory: ReturnType<typeof installedCapabilities> | undefined;
92
106
  const signal = (result: TriageDecision): void => {
93
107
  decide({ kind: result.rung === 'hitl' ? 'ESCALATE' : 'ROUTE', what: result.issue, reason: result.why,
94
108
  purpose: 'steward triage', target: result.owner ? `${result.rung} · ${result.owner}` : String(result.rung), refs: { issue: result.issue } });
@@ -99,7 +113,7 @@ export async function triageIssues(issues: TriageIssue[], judge?: NonNullable<St
99
113
  const byRule = ruleJudgment(issue, observedIssues ?? issues);
100
114
  if (byRule) { counts.rule++; return { ...byRule, dependsOn: [], priority: 0 }; }
101
115
  counts.classify++;
102
- const row = await classifyIssue(issue, ask);
116
+ const row = await classifyIssue(issue, ask, inventory ??= installedCapabilities());
103
117
  return { ...row, dependsOn: [], priority: 0 };
104
118
  } catch (error) {
105
119
  try { debug.log('steward.stage', 'judgment-invalid', { issue: issue.identifier, reason: redactSecrets(String(error)) }); } catch { /* fail-soft */ }
@@ -111,6 +125,7 @@ export async function triageIssues(issues: TriageIssue[], judge?: NonNullable<St
111
125
  result.why !== 'triage judgment timed out — human review' &&
112
126
  result.why !== 'triage deferred — deadline') {
113
127
  classified.push({ issue: result.issue, rung: result.rung, why: result.why,
128
+ ...(result.capability ? { capability: result.capability } : {}),
114
129
  ...(result.hitlReason ? { hitlReason: result.hitlReason } : {}) });
115
130
  classifiedIds.add(result.issue);
116
131
  } else signal(result);
@@ -232,9 +247,9 @@ function readState(path: string): { issues: Record<string, string>; digestDay?:
232
247
  catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { issues: {} }; throw error; }
233
248
  }
234
249
 
235
- /** Each command node invokes one stage. No stage can spawn, launch, merge or approve. */
250
+ /** Each command node invokes one stage. Only the launch setting permits spawn; merge and approval remain with the harness and human. */
236
251
  export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'report', deps: StewardDeps = {}): Promise<void> {
237
- const settings = stewardSettings();
252
+ const settings = { ...stewardSettings(), ...deps.launchSettings };
238
253
  if ((settings.mode ?? 'observe') !== 'observe') throw new Error('steward act mode is out of scope');
239
254
  const root = deps.root ?? effectiveInstanceRoot();
240
255
  if (stage === 'sync' && !deps.stageOwnedByCommand) {
@@ -286,7 +301,8 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
286
301
  let cursor: string | null = null;
287
302
  do {
288
303
  const data: { issues: { nodes: Array<{ identifier: string; state?: { type: string } }>; pageInfo: { hasNextPage: boolean; endCursor: string | null } } } = await linear(key,
289
- 'query($ids:[String!]!,$after:String){issues(filter:{identifier:{in:$ids}},first:250,after:$after){nodes{identifier state{type}} pageInfo{hasNextPage endCursor}}}',
304
+ // IssueFilter has no `identifier` field (HTTP 400 GRAPHQL_VALIDATION_FAILED · 10-02 measured); `id` accepts identifiers like ELA-1.
305
+ 'query($ids:[ID!]!,$after:String){issues(filter:{id:{in:$ids}},first:250,after:$after){nodes{identifier state{type}} pageInfo{hasNextPage endCursor}}}',
290
306
  { ids: batch, after: cursor }, fetchFn);
291
307
  if (!Array.isArray(data.issues?.nodes) || !data.issues.pageInfo ||
292
308
  (data.issues.pageInfo.hasNextPage && (!data.issues.pageInfo.endCursor || data.issues.pageInfo.endCursor === cursor))) {
@@ -297,7 +313,46 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
297
313
  } while (cursor);
298
314
  }
299
315
  }
300
- writeFileSync(schedule, JSON.stringify(scheduleTriage(decisions, settings, {}, completed)));
316
+ let rows = scheduleTriage(decisions, settings, {}, completed);
317
+ const issues = existsSync(snapshot) ? JSON.parse(readFileSync(snapshot, 'utf8')) as TriageIssue[] : [];
318
+ // L2 — a new-capability wish gets its PR/FAQ and manual drafts on the card before any launch is recorded.
319
+ // Only new-capability rows: triage sections are written first so the card reads intake → triage → prfaq → manual → launch;
320
+ // the report stage repeats both calls idempotently. A wish whose draft is invalid goes to hitl on its own —
321
+ // the other rows (and the shadow ledger) still proceed this tick.
322
+ {
323
+ const present = new Set(issues.map(issue => issue.identifier));
324
+ const wishes = rows.filter(row => present.has(row.issue) && row.capability === 'new-capability' && row.rung !== 'hitl');
325
+ const store = wishes.length ? deps.cardStore ?? new CardStore(root) : undefined;
326
+ const invalid = new Set<string>();
327
+ if (store) try {
328
+ recordTriageOnCards(wishes, issues, { root, store, now: deps.now });
329
+ for (const wish of wishes) {
330
+ try { await recordWorkingBackwardsOnCards([wish], issues, store, deps.ask ?? askSteward); }
331
+ catch (error) {
332
+ if (!(error instanceof InvalidWorkingBackwardsDraft)) throw error;
333
+ invalid.add(wish.issue);
334
+ debug.log('steward.stage', 'draft-invalid', { issue: wish.issue });
335
+ }
336
+ }
337
+ } finally { if (!deps.cardStore) store.close(); }
338
+ if (invalid.size) rows = rows.map(row => invalid.has(row.issue)
339
+ ? { ...row, rung: 'hitl' as const, disposition: 'hitl' as const, hitlReason: 'other' as const, why: 'working-backwards draft invalid' }
340
+ : row);
341
+ }
342
+ writeFileSync(schedule, JSON.stringify(rows));
343
+ const ledger = readLaunchLedger(root);
344
+ const launchDeps: LaunchDeps = { root, settings, ledger, command: deps.launchCommand, spawn: deps.spawnLaunch };
345
+ const cardDeps = { root, store: deps.cardStore, now: deps.now };
346
+ collectOutcomes(ledger, launchDeps);
347
+ const plan = planLaunches(rows, issues, ledger, settings);
348
+ for (const { issue, row } of plan.hitl) {
349
+ const entry = raiseHitl(issue, row, launchDeps);
350
+ recordHitlOnCard(issue, entry, cardDeps);
351
+ }
352
+ for (const item of plan.launches) {
353
+ const entry = launch(item, launchDeps);
354
+ recordLaunchOnCard(item.issue, entry, cardDeps);
355
+ }
301
356
  } else {
302
357
  const rows = JSON.parse(readFileSync(schedule, 'utf8')) as ScheduledDecision[];
303
358
  const path = reportPath(root);
@@ -305,15 +360,23 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
305
360
  const at = (deps.now ?? (() => new Date()))().toISOString();
306
361
  const issues = JSON.parse(readFileSync(snapshot, 'utf8')) as TriageIssue[];
307
362
  try {
308
- recordTriageOnCards(rows, issues, { root, store: deps.cardStore, now: deps.now });
363
+ const present = new Set(issues.map(issue => issue.identifier));
364
+ const known = rows.filter(row => present.has(row.issue));
365
+ const store = deps.cardStore ?? new CardStore(root);
366
+ try {
367
+ recordTriageOnCards(known, issues, { root, store, now: deps.now });
368
+ // Rows without a capability verdict (legacy, injected or unjudged) proceed; only drafting is skipped for them.
369
+ await recordWorkingBackwardsOnCards(known, issues, store, deps.ask ?? askSteward);
370
+ } finally { if (!deps.cardStore) store.close(); }
309
371
  } catch (error) {
310
372
  const message = `steward report: card write failed: ${redactSecrets(error instanceof Error ? error.message : String(error))}`;
311
373
  debug.log('steward.cards', 'write-failed', { error: message });
312
374
  (deps.warn ?? console.warn)(message);
375
+ if (error instanceof InvalidWorkingBackwardsDraft) throw error;
313
376
  }
314
377
  for (const row of rows) {
315
378
  const issue = issues.find(item => item.identifier === row.issue);
316
- if (!issue) throw new Error(`Missing issue ${row.issue}`);
379
+ if (!issue) continue;
317
380
  const body = `스튜어드 observe · ${row.disposition} · rung ${row.rung} · 우선순위 ${row.priority} · 의존 ${row.dependsOn.join(', ') || '없음'}${row.duplicateOf ? ` · 중복 ${row.duplicateOf}` : ''} · ${row.why}${row.hitlReason ? ` · HITL ${row.hitlReason}` : ''}`;
318
381
  const hash = directiveHash(body);
319
382
  if (state.issues[row.issue] === hash) continue;
@@ -322,6 +385,27 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
322
385
  state.issues[row.issue] = hash;
323
386
  writeFileSync(path, JSON.stringify(state));
324
387
  }
388
+ const ledger = readLaunchLedger(root);
389
+ const outcomes = collectOutcomes(ledger, { root, settings, ledger, command: deps.launchCommand, spawn: deps.spawnLaunch });
390
+ for (const outcome of outcomes) {
391
+ const issue = issues.find(item => item.identifier === outcome.issue) ??
392
+ { identifier: outcome.issue, title: outcome.title, ref: '', body: '' };
393
+ recordOutcomeOnCard(issue, outcome, { root, store: deps.cardStore, now: deps.now });
394
+ notifyOutcome(outcome, { root, ledger, shadow: settings.launch !== 'live', ...deps.outcomeNotify });
395
+ const prefix = ['telegram', 'pwa', 'tui', 'cli'].includes(outcome.source) ? `${outcome.source} · ` : '';
396
+ await (deps.sendDigest ?? sendStewardDigest)(`${prefix}${issue.title} · PR ${outcome.prNumber ?? '없음'} · ${outcome.status}`);
397
+ outcome.reported = true;
398
+ saveLaunchLedger(root, ledger);
399
+ }
400
+ const justCollected = new Set(outcomes.map(outcome => outcome.issue));
401
+ for (const outcome of Object.values(ledger.launches)) {
402
+ if ((settings.launch !== 'live' && outcome.status === 'shadow' && !outcome.notified) ||
403
+ (settings.launch === 'live' && !justCollected.has(outcome.issue) && outcome.reported && outcome.notified !== 'shadow') ||
404
+ // A finished run whose PR waits for merge still owes the wish's chat its PR link (once; failures retry).
405
+ (settings.launch === 'live' && outcome.awaitingMerge === true && !outcome.reported && outcome.notified !== 'sent')) {
406
+ notifyOutcome(outcome, { root, ledger, shadow: settings.launch !== 'live', ...deps.outcomeNotify });
407
+ }
408
+ }
325
409
  const day = at.slice(0, 10);
326
410
  if (state.digestDay !== day) {
327
411
  const send = deps.sendDigest ?? sendStewardDigest;
@@ -0,0 +1,64 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { debug } from '../debug/log.js';
3
+ import { redactSecrets } from '../task-cards/card-store.js';
4
+ import type { ScheduledDecision, TriageIssue } from './triage.js';
5
+ import type { StewardAsk } from './triage-plan.js';
6
+ import type { StewardCardStore } from './steward-cards.js';
7
+
8
+ export interface WorkingBackwardsDraft {
9
+ prfaq: string;
10
+ manual: string;
11
+ }
12
+
13
+ export class InvalidWorkingBackwardsDraft extends Error {
14
+ constructor() { super('Invalid working-backwards draft'); }
15
+ }
16
+
17
+ /** Draft promises before implementation; no document, site, or channel is written here. */
18
+ export async function draftWorkingBackwards(issue: TriageIssue, ask: StewardAsk): Promise<WorkingBackwardsDraft> {
19
+ const raw = await ask(`새 능력 소원의 구현 전 워킹 백워드 초안을 JSON 객체 {"prfaq":"...","manual":"...","signals":["판정 신호: 조건 = ...; 관측 = ...; 기대 = ..."]} 로만 써라. PR/FAQ 는 docs/marketing/TEMPLATE-prfaq-2026-09-29.md 모양의 ① 한 줄, 부제(누가), ② 문제(왜), ③ 해결(무엇), ④ 대표의 말(없는 인용은 확인 대기), ⑤ 시작하기, ⑥ 고객 FAQ(질문·답 셋 이상), ⑦ 내부 FAQ, ⑧ 판정 기준을 포함한다. 매뉴얼은 docs/marketing/TEMPLATE-manual-2026-10-02.md 모양으로 사용자가 칠 명령과 볼 화면을 제안하되 구현 전 가설이라고 명시한다. 아직 구현된 명령·화면이라고 주장하지 마라. 판정 신호 한두 문장은 PR/FAQ ⑧ 에 그대로 포함시켜라.\n소원: ${JSON.stringify(issue)}`, 'planning');
20
+ let value: unknown = raw;
21
+ if (typeof raw === 'string') {
22
+ // A draft that is not JSON is as invalid as one with missing sections — the report must stop on it too.
23
+ try { value = JSON.parse(raw.replace(/^```(?:json)?\s*|\s*```$/g, '').trim()); }
24
+ catch { throw new InvalidWorkingBackwardsDraft(); }
25
+ }
26
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new InvalidWorkingBackwardsDraft();
27
+ const row = value as Record<string, unknown>;
28
+ const prfaq = row.prfaq;
29
+ const manual = row.manual;
30
+ const faq = typeof prfaq === 'string' ? prfaq.split('⑥ 고객 FAQ')[1]?.split('⑦ 내부 FAQ')[0] ?? '' : '';
31
+ const criteria = typeof prfaq === 'string' ? prfaq.split('⑧ 판정 기준')[1] ?? '' : '';
32
+ const answered = faq.split('\n').filter(line => /^\|\s*[^|?]+\?\s*\|\s*[^|\s][^|]*\|/.test(line));
33
+ if (typeof prfaq !== 'string' || typeof manual !== 'string' ||
34
+ !Array.isArray(row.signals) || row.signals.length < 1 || row.signals.length > 2 ||
35
+ !row.signals.every(signal => typeof signal === 'string' && /^판정 신호: 조건 = .+; 관측 = .+; 기대 = .+\.$/.test(signal) && criteria.includes(signal)) ||
36
+ !['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧'].every(section => prfaq.includes(section)) ||
37
+ answered.length < 3 ||
38
+ !/elanous\s+\S+/.test(manual) || !/화면/.test(manual)) throw new InvalidWorkingBackwardsDraft();
39
+ return { prfaq: redactSecrets(prfaq), manual: redactSecrets(manual) };
40
+ }
41
+
42
+ /** Called during the steward report, after triage and before a launch can be recorded. */
43
+ export async function recordWorkingBackwardsOnCards(
44
+ decisions: ScheduledDecision[], issues: TriageIssue[], store: StewardCardStore, ask: StewardAsk,
45
+ ): Promise<void> {
46
+ const byId = new Map(issues.map(issue => [issue.identifier, issue]));
47
+ for (const decision of decisions) {
48
+ const issue = byId.get(decision.issue);
49
+ if (!issue) throw new Error(`Missing issue ${decision.issue}`);
50
+ if (decision.capability !== 'new-capability' || decision.deferred || decision.duplicateOf || decision.rung === 'hitl') {
51
+ debug.log('steward.working-backwards', 'skipped-existing', { issueId: issue.identifier, verdict: decision.capability ?? 'unknown' });
52
+ continue;
53
+ }
54
+ const card = store.createCard({ goalId: `linear:${issue.identifier}`, title: redactSecrets(issue.title) });
55
+ if (card.sections.some(section => section.key.startsWith('prfaq:')) && card.sections.some(section => section.key.startsWith('manual:'))) continue;
56
+ if (card.sections.some(section => section.key.startsWith('launch:'))) throw new Error(`Working-backwards draft follows launch: ${issue.identifier}`);
57
+ const draft = await draftWorkingBackwards(issue, ask);
58
+ for (const [section, content] of [['prfaq', draft.prfaq], ['manual', draft.manual]] as const) {
59
+ const key = `${section}:${createHash('sha256').update(content).digest('hex')}`;
60
+ if (!card.sections.some(item => item.key === key)) store.appendSection(card.id, { key, owner: 'steward', content });
61
+ }
62
+ debug.log('steward.working-backwards', 'drafted', { issueId: issue.identifier, verdict: decision.capability });
63
+ }
64
+ }
@@ -0,0 +1,141 @@
1
+ import { readdirSync, statSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+
4
+ export const STORYBOARD_ROOT = resolve(import.meta.dir, '../..');
5
+ const DIR = join(STORYBOARD_ROOT, 'storyboards');
6
+
7
+ type Text = { text: string; source: string; ref?: string };
8
+ type Shot = {
9
+ id: string; t: [number, number]; stage: string; layer?: string; persona?: string; purpose: string; subject?: string; action?: string;
10
+ camera?: { move?: string; lens_mm?: number; angle?: string; framing?: string }; lighting?: string; color?: string;
11
+ transition_in?: { type: string; intent: string }; on_screen_text?: Text[]; audio?: { vo?: string; sfx?: string; bgm?: string };
12
+ generation?: { tool: string; model?: string; prompt?: string; asset?: string }; real_or_staged: string; state: string; notes?: string;
13
+ };
14
+ /** kind: site — a site's page structure, menu and copy draft, planned like a storyboard (SITE2 · 10-02). */
15
+ type Section = { id: string; title: string; purpose: string; change: string; copy?: Text[]; notes?: string };
16
+ type Site = { domain: string; role: string; preview?: string; branch?: string; nav: Array<{ label: string; href: string }>; pages: Array<{ path: string; sections: Section[] }> };
17
+ export type Storyboard = {
18
+ id: string; kind: string; title: string; version: number; status: string; approved?: { by: string; at: string; note?: string } | null;
19
+ supersedes?: string; owner: string; directive?: string; format: { aspects: string[]; duration_s: number; fps?: number; loop?: boolean; surface?: string };
20
+ scqa?: { s?: string; c?: string; q?: string; a?: string }; principles?: string[]; layers?: Record<string, string>; shots: Shot[]; sites?: Site[];
21
+ references?: string[]; open_questions?: string[];
22
+ };
23
+
24
+ const KINDS = ['teaser', 'site-hero', 'event-demo', 'persona-scene', 'explainer', 'ad', 'site'];
25
+ const CHANGES = ['keep', 'move', 'new', 'replace', 'remove'];
26
+ const STAGES = ['hook', 'buildup', 'climax', 'transition', 'end', 'loop'];
27
+ const SOURCES = ['real-ui', 'real-cli', 'real-output', 'copy', 'example'];
28
+
29
+ export function lint(sb: Storyboard, file = ''): { errors: string[]; warnings: string[] } {
30
+ const errors: string[] = [], warnings: string[] = [];
31
+ const need = (cond: unknown, msg: string) => { if (!cond) errors.push(msg); };
32
+ need(/^[a-z0-9][a-z0-9-]{2,80}$/.test(sb.id ?? ''), 'id: 소문자·숫자·하이픈 3~80자');
33
+ need(KINDS.includes(sb.kind), `kind: ${KINDS.join('|')}`);
34
+ need(Number.isInteger(sb.version) && sb.version >= 1, 'version: 1 이상 정수');
35
+ need(['draft', 'approved', 'superseded'].includes(sb.status), 'status: draft|approved|superseded');
36
+ need(sb.owner, 'owner 필요');
37
+ if (sb.status === 'approved') need(sb.approved?.by && sb.approved?.at, '승인본은 approved.by·at 필요');
38
+ if (file && sb.kind && !file.includes(`/${sb.kind}/`)) errors.push(`파일 위치가 kind(${sb.kind}) 폴더가 아니다`);
39
+ if (sb.kind === 'site') return lintSite(sb, errors, warnings);
40
+ need(sb.format?.aspects?.length && sb.format.duration_s > 0, 'format.aspects ⊕ duration_s 필요');
41
+ if (!Array.isArray(sb.shots) || !sb.shots.length) { errors.push('shots 가 비었다'); return { errors, warnings }; }
42
+ const ids = new Set<string>();
43
+ sb.shots.forEach((s, i) => {
44
+ const at = `shots[${i}](${s.id ?? '?'})`;
45
+ need(s.id && !ids.has(s.id), `${at}: id 없음·중복`); ids.add(s.id);
46
+ need(Array.isArray(s.t) && s.t.length === 2 && s.t[0] < s.t[1], `${at}: t = [시작, 끝] 이고 시작 < 끝`);
47
+ need(STAGES.includes(s.stage), `${at}: stage ${STAGES.join('|')}`);
48
+ need(s.purpose, `${at}: purpose(이 비트의 한 가지) 필요`);
49
+ need(['real', 'staged', 'composite'].includes(s.real_or_staged), `${at}: real_or_staged 필요`);
50
+ need(['planned', 'shot', 'approved'].includes(s.state), `${at}: state planned|shot|approved`);
51
+ if (s.transition_in && !s.transition_in.intent) errors.push(`${at}: 전환에는 의도 한 줄(«그냥 컷»은 설계가 아니다)`);
52
+ for (const txt of s.on_screen_text ?? []) {
53
+ need(SOURCES.includes(txt.source), `${at}: 화면 문구 «${txt.text}» 의 source 가 ${SOURCES.join('|')} 가 아니다`);
54
+ if (txt.source?.startsWith('real-') && !txt.ref) warnings.push(`${at}: 실제 문면 «${txt.text}» 에 ref(출처) 가 없다`);
55
+ if (/\d/.test(txt.text) && txt.source === 'copy') warnings.push(`${at}: 마케팅 문구에 숫자 «${txt.text}» — 실측 근거나 «example» 로`);
56
+ }
57
+ if (s.generation?.tool === 'higgsfield' && !s.generation.prompt) warnings.push(`${at}: 힉스필드 샷에 prompt 가 없다`);
58
+ if (s.real_or_staged !== 'real' && !/연출/.test(JSON.stringify(sb))) warnings.push(`${at}: 연출 샷이 있는데 문서 어디에도 «연출 장면» 표기가 없다`);
59
+ });
60
+ const end = Math.max(...sb.shots.map((s) => s.t?.[1] ?? 0));
61
+ if (sb.format?.duration_s && Math.abs(end - sb.format.duration_s) > 0.5) warnings.push(`마지막 샷 끝(${end}s) ≠ format.duration_s(${sb.format.duration_s}s)`);
62
+ return { errors, warnings };
63
+ }
64
+
65
+ /** A site plan has pages instead of shots: every section says why it is there and what changes; copy names its source. */
66
+ function lintSite(sb: Storyboard, errors: string[], warnings: string[]): { errors: string[]; warnings: string[] } {
67
+ if (!Array.isArray(sb.sites) || !sb.sites.length) { errors.push('site 종류는 sites 가 필요하다'); return { errors, warnings }; }
68
+ for (const site of sb.sites) {
69
+ const at = `sites(${site.domain ?? '?'})`;
70
+ if (!site.domain || !site.role) errors.push(`${at}: domain ⊕ role(이 사이트가 맡는 것) 필요`);
71
+ if (!site.nav?.length) errors.push(`${at}: nav(메뉴) 필요`);
72
+ if (!site.pages?.length) errors.push(`${at}: pages 필요`);
73
+ if (!site.preview) warnings.push(`${at}: preview(미리보기 주소) 없음`);
74
+ const ids = new Set<string>();
75
+ for (const page of site.pages ?? []) for (const sec of page.sections ?? []) {
76
+ const where = `${at}${page.path} #${sec.id ?? '?'}`;
77
+ if (!sec.id || ids.has(`${page.path}#${sec.id}`)) errors.push(`${where}: id 없음·중복`);
78
+ ids.add(`${page.path}#${sec.id}`);
79
+ if (!sec.purpose) errors.push(`${where}: purpose(이 칸이 하는 한 가지) 필요`);
80
+ if (!CHANGES.includes(sec.change)) errors.push(`${where}: change ${CHANGES.join('|')}`);
81
+ for (const txt of sec.copy ?? []) {
82
+ if (!SOURCES.includes(txt.source)) errors.push(`${where}: 문구 «${txt.text}» 의 source 가 ${SOURCES.join('|')} 가 아니다`);
83
+ if (/\d/.test(txt.text) && txt.source === 'copy' && !txt.ref) warnings.push(`${where}: 마케팅 문구에 숫자 «${txt.text}» — ref(실측 근거) 를 단다`);
84
+ }
85
+ }
86
+ }
87
+ return { errors, warnings };
88
+ }
89
+
90
+ function renderSites(sb: Storyboard): string[] {
91
+ return (sb.sites ?? []).flatMap((site) => [
92
+ `## ${site.domain} — ${site.role}`, '',
93
+ ...(site.preview ? [`- 미리보기: ${site.preview}${site.branch ? ` (가지 \`${site.branch}\`)` : ''}`] : []),
94
+ `- 메뉴: ${site.nav.map((n) => `${n.label}(${n.href})`).join(' · ')}`, '',
95
+ ...site.pages.flatMap((page) => [
96
+ `### ${page.path}`, '', '| # | 칸 | 제목 | 목적 | 바뀜 | 문구(출처) | 메모 |', '|---|---|---|---|---|---|---|',
97
+ ...page.sections.map((sec, i) => `| ${i + 1} | ${md(sec.id)} | ${md(sec.title)} | ${md(sec.purpose)} | ${sec.change} | ${md((sec.copy ?? []).map((x) => `«${x.text}»(${x.source}${x.ref ? ` · ${x.ref}` : ''})`).join(' / '))} | ${md(sec.notes)} |`),
98
+ '',
99
+ ]),
100
+ ]);
101
+ }
102
+
103
+ const md = (v: unknown) => String(v ?? '').replace(/\|/g, '\\|').replace(/\n+/g, ' ');
104
+ export function render(sb: Storyboard, file: string): string {
105
+ const head = [
106
+ `# STORYBOARD — ${sb.title} (v${sb.version} · ${sb.status}${sb.approved ? ` · 승인 ${sb.approved.by} ${sb.approved.at}` : ''})`,
107
+ '',
108
+ `> ⛔ 자동 생성 — 손으로 고치지 않는다. 정본 = \`${file}\` · 다시 뽑기 = \`bun scripts/storyboard/storyboard.ts render ${file}\`.`,
109
+ `> 종류 ${sb.kind} · 소유 ${sb.owner}${sb.format ? ` · ${sb.format.aspects.join(' · ')} · ${sb.format.duration_s}초${sb.format.loop ? ' · 반복' : ''}${sb.format.surface ? ` · ${sb.format.surface}` : ''}` : ''}${sb.supersedes ? ` · 앞 판 ${sb.supersedes}` : ''}`,
110
+ ...(sb.directive ? [`> 지시: ${sb.directive}`] : []),
111
+ '',
112
+ ];
113
+ const scqa = sb.scqa ? ['## SCQA', ...(['s', 'c', 'q', 'a'] as const).filter((k) => sb.scqa?.[k]).map((k) => `- **${k.toUpperCase()}** — ${sb.scqa![k]}`), ''] : [];
114
+ const pr = sb.principles?.length ? ['## 원칙', ...sb.principles.map((p, i) => `${i + 1}. ${p}`), ''] : [];
115
+ const layers = sb.layers ? ['## 층', ...Object.entries(sb.layers).map(([k, v]) => `- **${k}** — ${v}`), ''] : [];
116
+ const table = sb.kind === 'site' ? renderSites(sb) : [
117
+ '## 샷 구조표',
118
+ '| # | 초 | 단계 | 층·인물 | 목적 | 화면(피사체·행동) | 카메라 | 빛·색 | 전환(의도) | 화면 문구(출처) | 소리 | 만들기 | 실물/연출 · 상태 |',
119
+ '|---|---|---|---|---|---|---|---|---|---|---|---|---|',
120
+ ...sb.shots.map((s) => `| ${md(s.id)} | ${s.t[0]}–${s.t[1]} | ${s.stage} | ${md([s.layer, s.persona].filter(Boolean).join(' · '))} | ${md(s.purpose)} | ${md([s.subject, s.action].filter(Boolean).join(' — '))} | ${md(s.camera ? [s.camera.move, s.camera.lens_mm ? `${s.camera.lens_mm}mm` : '', s.camera.angle, s.camera.framing].filter(Boolean).join(' · ') : '')} | ${md([s.lighting, s.color].filter(Boolean).join(' · '))} | ${md(s.transition_in ? `${s.transition_in.type} — ${s.transition_in.intent}` : '')} | ${md((s.on_screen_text ?? []).map((x) => `«${x.text}»(${x.source}${x.ref ? ` · ${x.ref}` : ''})`).join(' / '))} | ${md(s.audio ? [s.audio.vo && `VO ${s.audio.vo}`, s.audio.sfx && `SFX ${s.audio.sfx}`, s.audio.bgm && `BGM ${s.audio.bgm}`].filter(Boolean).join(' · ') : '')} | ${md(s.generation ? [s.generation.tool, s.generation.model].filter(Boolean).join(' · ') : '')} | ${s.real_or_staged} · ${s.state} |`),
121
+ '',
122
+ ];
123
+ const shots = sb.shots ?? [];
124
+ const prompts = shots.filter((s) => s.generation?.prompt);
125
+ const pblock = prompts.length ? ['## 생성 프롬프트', ...prompts.flatMap((s) => [`### ${s.id} · ${s.generation!.tool}${s.generation!.model ? ` · ${s.generation!.model}` : ''}`, '```', s.generation!.prompt!.trim(), '```', ...(s.generation!.asset ? [`결과: \`${s.generation!.asset}\``] : []), ''])] : [];
126
+ const notes = shots.filter((s) => s.notes).map((s) => `- ${s.id}: ${s.notes}`);
127
+ const tail = [
128
+ ...(notes.length ? ['## 샷 메모', ...notes, ''] : []),
129
+ ...(sb.open_questions?.length ? ['## 열린 물음', ...sb.open_questions.map((q) => `- ${q}`), ''] : []),
130
+ ...(sb.references?.length ? ['## 참고', ...sb.references.map((r) => `- ${r}`), ''] : []),
131
+ ];
132
+ return [...head, ...scqa, ...pr, ...layers, ...table, ...pblock, ...tail].join('\n');
133
+ }
134
+
135
+ export function files(args: string[]): string[] {
136
+ if (args.length) return args.map((a) => resolve(a));
137
+ const out: string[] = [];
138
+ const walk = (d: string) => { for (const f of readdirSync(d)) { const p = join(d, f); if (statSync(p).isDirectory()) walk(p); else if (/\.ya?ml$/.test(f) && !f.startsWith('_')) out.push(p); } };
139
+ walk(DIR);
140
+ return out.sort();
141
+ }
@@ -38,10 +38,10 @@ type CardEvent =
38
38
  * A key is `<section>` or `<section>:<idempotency key>`: the same section can be appended many times over a
39
39
  * task's life (e.g. workspace diff updates), and the same full key is written once. `incidents` is open to any loop. */
40
40
  export const SECTION_OWNERS: Readonly<Record<string, string | null>> = {
41
- intake: 'steward', triage: 'steward', launch: 'steward', outcome: 'steward',
41
+ intake: 'steward', triage: 'steward', prfaq: 'steward', manual: 'steward', launch: 'steward', outcome: 'steward', hitl: 'steward',
42
42
  gates: 'execution-loop', relations: 'execution-loop', memory: 'execution-loop',
43
43
  workspace: 'executor', run: 'executor',
44
- landing: 'landing-loop', release: 'release-loop',
44
+ landing: 'landing-loop', release: 'release-loop', 'test-diet': 'test-diet',
45
45
  incidents: null,
46
46
  };
47
47