elanous 0.2.9 → 0.2.11

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 (694) 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/Y4zKzL62sakG5momz3SoQ/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1394-556de6d71bbdb0fb.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1452.93700483b9d4fc61.js +9 -0
  7. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 1829-b453a1b22d3999bb.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/390.c373e31b3b9d0c41.js +5 -0
  19. package/apps/pwa/out/_next/static/chunks/3992-2e522a8ac5f2afde.js +25 -0
  20. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/4685-c3c1c625b71accd1.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +2 -0
  25. package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 5cd7e3a9-404fd6b8d0ecd44c.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7334.a6679d9168f37aa8.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/79f8be9e-957d68913b590b08.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8039-df92e08fede5de8f.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/8421-1c4acfb1395a0edf.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/8605-ea5bee9dfae512c7.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 9269.d2a4795bc6812717.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/9656-c1540088f5859c80.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-6a061bd97f0b9398.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-b12b6d2f8d2d1aea.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/chat/page-7c97dcdfe2e37a60.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/control/page-5721537ca8a3598c.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/design-check/page-b654240a572e4c48.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/editor/page-b9750abc897508c3.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/intake/page-c8b1ccda6ba7ae98.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/layout-d6b1d156fb95e74b.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-9688f5b4f1ff950f.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/{page-adfbd2ba9598e59a.js → page-35414e0a007b4082.js} +1 -1
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-e001823c35c9dd67.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-371423d1a573d64b.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4b764b125339ac17.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cdf8e4b01067bcf1.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a24072b35470157.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d37cb0d52f9b6bd5.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-686da0df9bf23e5c.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4dc614f3ce043e63.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d02c782fc792e2af.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/setup/page-239c17b911b84e72.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-dc6a79fb003f746c.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-0fffb84cafdc9da6.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-1218753c82d585f6.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/page-4b5935e67531efb5.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-3285bc87caf1eaa5.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-ad553b0e06505ecd.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-fca096e3e860306f.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ebfa78f0d9885c3.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3dc5f21f7e39622d.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → f48170cc-d05d28bf87a43bf4.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +1 -0
  105. package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +1 -0
  106. package/apps/pwa/out/approvals/index.html +1 -1
  107. package/apps/pwa/out/approvals/index.txt +23 -23
  108. package/apps/pwa/out/autopilot/index.html +1 -1
  109. package/apps/pwa/out/autopilot/index.txt +23 -23
  110. package/apps/pwa/out/board/index.html +1 -1
  111. package/apps/pwa/out/board/index.txt +23 -23
  112. package/apps/pwa/out/botlab/index.html +1 -1
  113. package/apps/pwa/out/botlab/index.txt +22 -22
  114. package/apps/pwa/out/bots/index.html +1 -1
  115. package/apps/pwa/out/bots/index.txt +22 -22
  116. package/apps/pwa/out/chat/index.html +1 -1
  117. package/apps/pwa/out/chat/index.txt +22 -22
  118. package/apps/pwa/out/consult/index.html +1 -1
  119. package/apps/pwa/out/consult/index.txt +23 -23
  120. package/apps/pwa/out/control/index.html +1 -1
  121. package/apps/pwa/out/control/index.txt +23 -23
  122. package/apps/pwa/out/design-check/index.html +1 -1
  123. package/apps/pwa/out/design-check/index.txt +23 -23
  124. package/apps/pwa/out/editor/index.html +1 -1
  125. package/apps/pwa/out/editor/index.txt +23 -23
  126. package/apps/pwa/out/exec/index.html +1 -1
  127. package/apps/pwa/out/exec/index.txt +23 -23
  128. package/apps/pwa/out/field/index.html +1 -1
  129. package/apps/pwa/out/field/index.txt +23 -23
  130. package/apps/pwa/out/healthz +1 -1
  131. package/apps/pwa/out/index.html +1 -1
  132. package/apps/pwa/out/index.txt +22 -22
  133. package/apps/pwa/out/inside/index.html +1 -0
  134. package/apps/pwa/out/inside/index.txt +31 -0
  135. package/apps/pwa/out/intake/index.html +1 -1
  136. package/apps/pwa/out/intake/index.txt +23 -23
  137. package/apps/pwa/out/live/index.html +1 -1
  138. package/apps/pwa/out/live/index.txt +23 -23
  139. package/apps/pwa/out/market/index.html +1 -1
  140. package/apps/pwa/out/market/index.txt +22 -22
  141. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  142. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  143. package/apps/pwa/out/morning/index.html +1 -1
  144. package/apps/pwa/out/morning/index.txt +23 -23
  145. package/apps/pwa/out/observatory/index.html +1 -1
  146. package/apps/pwa/out/observatory/index.txt +23 -23
  147. package/apps/pwa/out/ops/checklist/index.html +1 -1
  148. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  149. package/apps/pwa/out/ops/release/index.html +1 -1
  150. package/apps/pwa/out/ops/release/index.txt +22 -22
  151. package/apps/pwa/out/ops/seats/index.html +1 -0
  152. package/apps/pwa/out/ops/seats/index.txt +30 -0
  153. package/apps/pwa/out/outputs/index.html +1 -0
  154. package/apps/pwa/out/outputs/index.txt +30 -0
  155. package/apps/pwa/out/reflection/index.html +1 -1
  156. package/apps/pwa/out/reflection/index.txt +23 -23
  157. package/apps/pwa/out/scheduler/index.html +1 -1
  158. package/apps/pwa/out/scheduler/index.txt +23 -23
  159. package/apps/pwa/out/sessions/index.html +1 -1
  160. package/apps/pwa/out/sessions/index.txt +22 -22
  161. package/apps/pwa/out/settings/devices/index.html +1 -1
  162. package/apps/pwa/out/settings/devices/index.txt +23 -23
  163. package/apps/pwa/out/settings/index.html +1 -1
  164. package/apps/pwa/out/settings/index.txt +23 -23
  165. package/apps/pwa/out/setup/done/index.html +1 -1
  166. package/apps/pwa/out/setup/done/index.txt +23 -23
  167. package/apps/pwa/out/setup/index.html +1 -1
  168. package/apps/pwa/out/setup/index.txt +23 -23
  169. package/apps/pwa/out/share/index.html +1 -1
  170. package/apps/pwa/out/share/index.txt +23 -23
  171. package/apps/pwa/out/showroom/index.html +1 -1
  172. package/apps/pwa/out/showroom/index.txt +22 -22
  173. package/apps/pwa/out/tasks/index.html +1 -1
  174. package/apps/pwa/out/tasks/index.txt +23 -23
  175. package/apps/pwa/out/term/index.html +1 -1
  176. package/apps/pwa/out/term/index.txt +23 -24
  177. package/apps/pwa/out/today/index.html +1 -0
  178. package/apps/pwa/out/today/index.txt +33 -0
  179. package/apps/pwa/out/trace/index.html +1 -1
  180. package/apps/pwa/out/trace/index.txt +23 -23
  181. package/apps/pwa/out/vault/index.html +1 -1
  182. package/apps/pwa/out/vault/index.txt +23 -23
  183. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  184. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  185. package/apps/pwa/out/workflows/index.html +1 -1
  186. package/apps/pwa/out/workflows/index.txt +23 -23
  187. package/apps/pwa/out/workspace/index.html +1 -1
  188. package/apps/pwa/out/workspace/index.txt +23 -24
  189. package/apps/pwa/out/worktrees/index.html +1 -1
  190. package/apps/pwa/out/worktrees/index.txt +23 -23
  191. package/graphs/drafts/draft-cleanup.yaml +19 -0
  192. package/graphs/drafts/recipes.yaml +23 -0
  193. package/graphs/field/field-feed.yaml +2 -0
  194. package/graphs/implement-loop.yaml +1 -1
  195. package/graphs/intake/intake-daily.yaml +2 -1
  196. package/graphs/ops/recipes.yaml +3 -0
  197. package/graphs/ops/secret-exposure.yaml +24 -0
  198. package/graphs/release/recipes.yaml +3 -0
  199. package/graphs/release/release-loop.yaml +4 -1
  200. package/graphs/video/explainer-line.yaml +2 -2
  201. package/package.json +2 -1
  202. package/plugins/card-followup/graphs/run-step.ts +48 -13
  203. package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
  204. package/plugins/doc-draft/graphs/run-step.ts +13 -6
  205. package/plugins/elanous-hwp/README.md +25 -0
  206. package/plugins/elanous-hwp/cli.ts +22 -0
  207. package/plugins/elanous-hwp/converter.ts +237 -0
  208. package/plugins/elanous-hwp/fixtures/fixed-controls.hwpx +0 -0
  209. package/plugins/elanous-hwp/fixtures/fixed-table-controls.hwpx +0 -0
  210. package/plugins/elanous-hwp/fixtures/fixed.hwpx +0 -0
  211. package/plugins/elanous-hwp/plugin.json +16 -0
  212. package/plugins/elanous-hwp/plugin.ts +40 -0
  213. package/scripts/ci-public-export-leak-gate.ts +75 -14
  214. package/scripts/ci-public-leak-gate.ts +44 -9
  215. package/scripts/ci-pwa-build-gate.ts +160 -0
  216. package/scripts/context-hooks/claude-stop.sh +4 -0
  217. package/scripts/context-hooks/emit.ts +24 -0
  218. package/scripts/context-hooks/post-tool-use.sh +4 -0
  219. package/scripts/coord-channel-watch.sh +54 -8
  220. package/scripts/coord-digest.ts +199 -0
  221. package/scripts/coord-post.sh +60 -3
  222. package/scripts/coord-requests-bridge.ts +143 -0
  223. package/scripts/field-feed/lib.ts +4 -0
  224. package/scripts/fleet/node-setup.sh +20 -3
  225. package/scripts/homebrew-formula-bump.ts +69 -0
  226. package/scripts/install.ps1 +68 -9
  227. package/scripts/install.sh +39 -12
  228. package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
  229. package/scripts/lib/tui-regress-checks.ts +146 -0
  230. package/scripts/marketing/measure-site-numbers.ts +169 -0
  231. package/scripts/measure-gate-shards.ts +309 -0
  232. package/scripts/memory-lifecycle-cycle.ts +2 -2
  233. package/scripts/mission-request-judge.ts +31 -1
  234. package/scripts/morning-report.ts +2 -2
  235. package/scripts/opportunity-scan.ts +1 -1
  236. package/scripts/ops-now-probe.ts +327 -0
  237. package/scripts/publish-internal-dist.ts +11 -1
  238. package/scripts/release-loop/auto-approve-node.ts +21 -4
  239. package/scripts/release-loop/cut-branch.ts +84 -0
  240. package/scripts/release-loop/cutoff.ts +19 -8
  241. package/scripts/release-loop/gate-node.ts +14 -13
  242. package/scripts/release-loop/mac-smoke-node.ts +229 -0
  243. package/scripts/release-loop/node-verdict.ts +3 -3
  244. package/scripts/release-loop/ops-upgrade-node.ts +72 -16
  245. package/scripts/release-loop/release-readiness.ts +9 -0
  246. package/scripts/release-loop/tui-node-checks.ts +42 -0
  247. package/scripts/release-loop/tui-sim-node.ts +32 -8
  248. package/scripts/release-loop/unattended-release.ts +5 -3
  249. package/scripts/release-loop/version-node.ts +112 -13
  250. package/scripts/repo-watch-cycle.ts +1 -1
  251. package/scripts/retro-cycle.ts +2 -2
  252. package/scripts/schedule-health-report.ts +1 -1
  253. package/scripts/signal-digest-cycle.ts +2 -7
  254. package/scripts/signal-digest-photo.ts +11 -0
  255. package/scripts/storyboard/storyboard.ts +5 -161
  256. package/scripts/tech1-public-measure.ts +94 -0
  257. package/scripts/test-diet/lib.ts +30 -1
  258. package/scripts/test-diet/node.ts +83 -8
  259. package/scripts/test-timeout-audit.ts +114 -0
  260. package/scripts/tree-sync-alert.ts +1 -1
  261. package/scripts/tui-regress.ts +71 -0
  262. package/scripts/ux/demo-rehearsal.ts +243 -0
  263. package/scripts/ux/lib/rehearsal-verdict.ts +73 -0
  264. package/scripts/waitlist-daily-digest.ts +1 -1
  265. package/skills/diagram-master/SKILL.md +366 -0
  266. package/skills/diagram-master/references/engines/excalidraw/color-palette.md +67 -0
  267. package/skills/diagram-master/references/engines/excalidraw/element-templates.md +182 -0
  268. package/skills/diagram-master/references/engines/excalidraw/json-schema.md +71 -0
  269. package/skills/diagram-master/references/engines/excalidraw/render_excalidraw.py +189 -0
  270. package/skills/diagram-master/references/engines/excalidraw/render_template.html +57 -0
  271. package/skills/diagram-master/references/engines/matplotlib/matplotlib-patterns.md +235 -0
  272. package/skills/diagram-master/references/engines/matplotlib/render_matplotlib.py +184 -0
  273. package/skills/diagram-master/references/engines/mermaid/domain-templates.md +435 -0
  274. package/skills/diagram-master/references/engines/mermaid/render_mermaid.py +116 -0
  275. package/skills/diagram-master/references/engines/mermaid/syntax-reference.md +2247 -0
  276. package/skills/diagram-master/references/engines/nanobannana/render_nanobannana2.py +168 -0
  277. package/skills/diagram-master/references/engines/svg/color-palette.md +188 -0
  278. package/skills/diagram-master/references/engines/svg/html-base-template.html +332 -0
  279. package/skills/diagram-master/references/engines/svg/interactivity-patterns.md +435 -0
  280. package/skills/diagram-master/references/engines/svg/render_interactive_svg.py +212 -0
  281. package/skills/diagram-master/references/engines/svg/svg-element-templates.md +349 -0
  282. package/skills/diagram-master/references/examples/README.md +69 -0
  283. package/skills/diagram-master/references/examples/ref_grid_architecture.png +0 -0
  284. package/skills/diagram-master/references/examples/ref_layered_flow.png +0 -0
  285. package/skills/diagram-master/references/pyproject.toml +16 -0
  286. package/skills/diagram-master/references/quality-checklist.md +33 -0
  287. package/skills/diagram-master/references/routing-guide.md +164 -0
  288. package/skills/diagram-master/references/uv.lock +1356 -0
  289. package/skills/explainer-video/SKILL.md +4 -0
  290. package/skills/explainer-video/engine/reel.mjs +18 -13
  291. package/skills/explainer-video/engine/reel.sh +24 -5
  292. package/skills/explainer-video/engine/stitch.mjs +145 -0
  293. package/skills/google-workspace/SKILL.md +33 -0
  294. package/skills/google-workspace/scripts/gws-safe.sh +177 -0
  295. package/skills/grill-me/SKILL.md +1 -1
  296. package/skills/lecture-note-digitizer/SKILL.md +926 -0
  297. package/skills/lecture-note-digitizer/references/convert_to_pdf.py +30 -0
  298. package/skills/lecture-note-digitizer/references/extract_slide_diagram.py +73 -0
  299. package/skills/lecture-note-digitizer/references/html-template.md +167 -0
  300. package/skills/lecture-note-digitizer/references/katex-style-guide.md +165 -0
  301. package/skills/omni-crawl/SKILL.md +21 -11
  302. package/skills/omni-crawl/scripts/main.ts +6 -6
  303. package/skills/omni-crawl/src/router.ts +3 -3
  304. package/skills/omni-digest/SKILL.md +17 -9
  305. package/skills/omni-digest/digest.md +2 -2
  306. package/skills/omni-digest/src/fetch.ts +30 -3
  307. package/skills/pitch-deck/SKILL.md +36 -0
  308. package/skills/pitch-deck/template.html +80 -0
  309. package/skills/youtube-master/SKILL.md +163 -0
  310. package/skills/youtube-master/package-lock.json +558 -0
  311. package/skills/youtube-master/package.json +13 -0
  312. package/skills/youtube-master/references/study-note-template.md +124 -0
  313. package/skills/youtube-master/scripts/main.ts +435 -0
  314. package/skills/youtube-master/scripts/transcribe-local.ts +24 -0
  315. package/skills/youtube-master/src/audio.ts +63 -0
  316. package/skills/youtube-master/src/domain-staging.ts +50 -0
  317. package/skills/youtube-master/src/env.ts +36 -0
  318. package/skills/youtube-master/src/obsidian.ts +155 -0
  319. package/skills/youtube-master/src/router.ts +110 -0
  320. package/skills/youtube-master/src/stt.ts +199 -0
  321. package/skills/youtube-master/src/study-note.ts +175 -0
  322. package/skills/youtube-master/src/summarize.ts +358 -0
  323. package/skills/youtube-master/src/transcript.ts +76 -0
  324. package/skills/youtube-master/src/types.ts +91 -0
  325. package/skills/youtube-master/src/util.ts +93 -0
  326. package/skills/youtube-master/src/youtube.ts +70 -0
  327. package/skills/youtube-master/tsconfig.json +13 -0
  328. package/src/acp/codex-approval-adapter.ts +9 -3
  329. package/src/acp/core-turn-bridge.ts +117 -18
  330. package/src/acp/fact-question.ts +11 -0
  331. package/src/acp/server.ts +53 -9
  332. package/src/acp/tool-approval.ts +126 -0
  333. package/src/acp/turn-runner.ts +36 -0
  334. package/src/agent/monad-agent-turn.ts +8 -0
  335. package/src/agent-mission/driver.ts +80 -10
  336. package/src/agent-mission/pty-decision.ts +71 -0
  337. package/src/agent-mission/review-loop.ts +30 -0
  338. package/src/ask-user-question/pending-questions.ts +25 -10
  339. package/src/ask-user-question/types.ts +9 -0
  340. package/src/auth/setup-link-tokens.ts +16 -5
  341. package/src/autopilot/build/integrity-gate.ts +12 -0
  342. package/src/autopilot/build/llm-conflict-merge.ts +16 -3
  343. package/src/autopilot/tool-registry.ts +2 -0
  344. package/src/boot/daemon-prompt-turn.ts +8 -0
  345. package/src/boot/daemon-runtime.ts +9 -3
  346. package/src/boot/daemon-tools/index.ts +8 -0
  347. package/src/boot/daemon-tools/types.ts +2 -0
  348. package/src/boot/daemon-tools/web-search.ts +4 -0
  349. package/src/card-followup/core.ts +109 -0
  350. package/src/chat/ctrl-c-in-input.ts +14 -0
  351. package/src/chat/index.ts +53 -0
  352. package/src/chat/pickers/state.ts +6 -2
  353. package/src/cli/agent-cli.ts +7 -0
  354. package/src/cli/decisions-cli.ts +25 -3
  355. package/src/cli/doctor-bundle-upload.ts +68 -0
  356. package/src/cli/doctor-cli.ts +38 -4
  357. package/src/cli/drive-input-jail.ts +101 -0
  358. package/src/cli/hooks-cli.ts +17 -3
  359. package/src/cli/make-cli.ts +43 -0
  360. package/src/cli/ops-cli.ts +20 -0
  361. package/src/cli/persona-cli.ts +90 -4
  362. package/src/cli/plugin-cli.ts +31 -22
  363. package/src/cli/pod-cli.ts +61 -2
  364. package/src/cli/pr-cli.ts +82 -17
  365. package/src/cli/pty-drive-cli.ts +41 -7
  366. package/src/cli/pty-drive-verdict.ts +40 -0
  367. package/src/cli/pwa-stop.ts +163 -13
  368. package/src/cli/pwa-test.ts +3 -0
  369. package/src/cli/release-cli.ts +80 -24
  370. package/src/cli/self-update.ts +14 -0
  371. package/src/cli/setup-cli.ts +43 -1
  372. package/src/cli/skills-cli.ts +17 -1
  373. package/src/cli/storyboard-cli.ts +37 -0
  374. package/src/cli/test-state-dir-flag.ts +11 -4
  375. package/src/connectors/linear.ts +146 -0
  376. package/src/context-bus/claude-hooks-install.ts +10 -0
  377. package/src/context-bus/context-now-surfaces.ts +48 -0
  378. package/src/context-bus/context-now.ts +104 -0
  379. package/src/context-bus/coord-events.ts +115 -0
  380. package/src/context-bus/external-events.ts +52 -0
  381. package/src/context-bus/long-term-memory.ts +108 -0
  382. package/src/dashboard/dashboard-boot-fixture.ts +23 -0
  383. package/src/dashboard/index.ts +63 -21
  384. package/src/dashboard/slash-runtime/dashboard-handlers.ts +14 -1
  385. package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
  386. package/src/decisions/decision-cards.ts +58 -3
  387. package/src/decisions/decision-ledger.ts +85 -6
  388. package/src/decisions/decision-linear-projection.ts +157 -0
  389. package/src/decisions/web-push-decision-cards.ts +50 -0
  390. package/src/directive/interpretation-card.ts +36 -0
  391. package/src/discord/sprint21-runtime.ts +5 -1
  392. package/src/discord-self-message.ts +32 -0
  393. package/src/discord-slash-wire.ts +5 -1
  394. package/src/discord.ts +19 -2
  395. package/src/domains/coo-admin-tool.ts +62 -0
  396. package/src/domains/core-tools.ts +13 -4
  397. package/src/domains/elanous-card.ts +29 -0
  398. package/src/domains/ops-facts-tool.ts +76 -0
  399. package/src/domains/outbound-alert.ts +46 -13
  400. package/src/domains/release-tool.ts +168 -0
  401. package/src/domains/telegram-kind-route.ts +65 -9
  402. package/src/exec-requests/default-outputs.ts +25 -0
  403. package/src/exec-requests/planner.ts +106 -28
  404. package/src/exec-requests/runner.ts +123 -12
  405. package/src/exec-requests/store.ts +11 -2
  406. package/src/execution-loop/launch-gate.ts +100 -0
  407. package/src/field/field-media.ts +10 -7
  408. package/src/field/field-reel.ts +19 -5
  409. package/src/field-note/index.ts +201 -0
  410. package/src/field-note/transcribe.ts +248 -0
  411. package/src/git-fs/worktree.ts +45 -6
  412. package/src/graph-kinds/registry.ts +3 -3
  413. package/src/graph-runner/graph-approval-card.ts +113 -0
  414. package/src/graph-runner/graph-cli.ts +189 -4
  415. package/src/graph-runner/graph-grow-llm.ts +37 -0
  416. package/src/graph-runner/graph-grow.ts +47 -0
  417. package/src/graph-runner/graph-notify.ts +25 -3
  418. package/src/graph-runner/runner.ts +453 -35
  419. package/src/grok/agent-search.ts +8 -2
  420. package/src/guardian/secret-exposure-scan.ts +101 -0
  421. package/src/harness/browser-verify.ts +25 -2
  422. package/src/harness/harness-cli-command.ts +478 -42
  423. package/src/harness/harness-incidents-writer.fixture.ts +24 -0
  424. package/src/harness/harness-incidents.ts +129 -0
  425. package/src/harness/harness-pod-dispatch.ts +43 -23
  426. package/src/harness/harness-seams.ts +38 -4
  427. package/src/harness/harness-stop.ts +75 -6
  428. package/src/harness/launch-registry.ts +91 -0
  429. package/src/harness/process-garbage.ts +5 -3
  430. package/src/hitl/clarification-policy.ts +3 -1
  431. package/src/hooks/dispatch.ts +82 -0
  432. package/src/hooks/expose.ts +254 -0
  433. package/src/hooks/github-shadow.ts +108 -0
  434. package/src/hooks/providers.ts +71 -5
  435. package/src/hooks/queue.ts +1 -1
  436. package/src/hooks/receiver.ts +21 -20
  437. package/src/index.ts +206 -49
  438. package/src/intake-plane/author-goals.ts +37 -4
  439. package/src/intake-plane/categorize.ts +18 -46
  440. package/src/intake-plane/check.ts +3 -0
  441. package/src/intake-plane/discord-seat-work.ts +57 -2
  442. package/src/intake-plane/front-route-classifier.ts +33 -39
  443. package/src/intake-plane/persona-answer.ts +30 -0
  444. package/src/intake-plane/runtime-callables.ts +8 -1
  445. package/src/intake-plane/seat-doc-route.ts +170 -6
  446. package/src/intake-plane/telegram-seat-work.ts +80 -4
  447. package/src/intake-plane/telegram-work.ts +1 -0
  448. package/src/intake-plane/wish-folder.ts +67 -0
  449. package/src/intent-gate/gate.ts +30 -10
  450. package/src/live/detail-switch.ts +5 -0
  451. package/src/llm/difficulty.ts +57 -0
  452. package/src/llm/judge-layer.ts +101 -0
  453. package/src/llm/mission-router.ts +33 -19
  454. package/src/loops/contract/examples/cmo-seat.loop.yaml +51 -0
  455. package/src/loops/contract/examples/orchestrator.loop.yaml +57 -0
  456. package/src/loops/contract/examples/steward.loop.yaml +50 -0
  457. package/src/loops/contract/loop-agent.schema.json +138 -0
  458. package/src/loops/contract/validate.ts +64 -0
  459. package/src/loops/registry.ts +20 -6
  460. package/src/market/signed-index.ts +4 -0
  461. package/src/maturity/bot-command-maturity.ts +45 -0
  462. package/src/maturity/cli-maturity.ts +34 -0
  463. package/src/maturity/feature-maturity.ts +120 -1
  464. package/src/maturity/tui-slash-maturity.ts +31 -0
  465. package/src/memory.ts +32 -4
  466. package/src/model-tier/index.ts +2 -2
  467. package/src/model-tier/tier-resolver.ts +20 -20
  468. package/src/model-tier/types.ts +6 -6
  469. package/src/mss/logging/log-store.ts +15 -0
  470. package/src/nexus/api/config-model-tier.ts +5 -4
  471. package/src/nexus/api/exec-requests.ts +33 -3
  472. package/src/nexus/api/fabric-plans.ts +53 -0
  473. package/src/nexus/api/harness-api.ts +5 -1
  474. package/src/nexus/api/health.ts +2 -0
  475. package/src/nexus/api/http-server.ts +89 -1
  476. package/src/nexus/api/inside-events.ts +226 -0
  477. package/src/nexus/api/meta-api.ts +32 -8
  478. package/src/nexus/api/missions.ts +6 -1
  479. package/src/nexus/api/ops-api.ts +10 -1
  480. package/src/nexus/api/ops-seats-sources.ts +37 -7
  481. package/src/nexus/api/ops-seats.ts +49 -6
  482. package/src/nexus/api/outputs-route.ts +44 -0
  483. package/src/nexus/api/seat-requests.ts +132 -10
  484. package/src/nexus/api/sessions-store.ts +42 -0
  485. package/src/nexus/api/setup-codex-login.ts +160 -0
  486. package/src/nexus/api/sub-seat-charters.ts +34 -0
  487. package/src/nexus/api/tabs-mutations.ts +3 -1
  488. package/src/nexus/api/trace.ts +18 -6
  489. package/src/nexus/api/usage-api.ts +102 -0
  490. package/src/nexus/api/workflow-run-event-bridge.ts +1 -0
  491. package/src/nexus/api/workflows.ts +80 -6
  492. package/src/nexus/index.ts +47 -4
  493. package/src/nexus/kinds/webterm.ts +2 -1
  494. package/src/nexus/webterm/pty.ts +6 -2
  495. package/src/onboarding/cli-auth-status.ts +80 -0
  496. package/src/onboarding/first-run.ts +89 -0
  497. package/src/onboarding/lan-address.ts +45 -0
  498. package/src/onboarding/voice-ai.ts +3 -3
  499. package/src/onboarding/web-first.ts +119 -24
  500. package/src/onboarding.ts +4 -0
  501. package/src/ops-now/ops-now-refresh.ts +100 -0
  502. package/src/org/seat-budget.ts +48 -0
  503. package/src/org/stall-escalation.ts +63 -0
  504. package/src/outputs/ledger.ts +120 -0
  505. package/src/persona/persona-todo.ts +79 -0
  506. package/src/persona/presets.ts +116 -2
  507. package/src/persona/team-set.ts +155 -0
  508. package/src/plugins/install/plugin-install.ts +14 -0
  509. package/src/plugins/maker/market-bundle.ts +109 -0
  510. package/src/plugins/maker/plugin-maker.ts +254 -11
  511. package/src/plugins/maker/wizard-generate.ts +103 -0
  512. package/src/pod-lease/admission.ts +150 -0
  513. package/src/pod-lease/dependency-state.ts +85 -0
  514. package/src/pod-lease/host-lease.ts +127 -0
  515. package/src/project/project-store.ts +115 -0
  516. package/src/project/project-suggestion.ts +16 -0
  517. package/src/project/session-context.ts +24 -0
  518. package/src/release-loop/checklist.ts +79 -5
  519. package/src/release-loop/feature-store.ts +159 -24
  520. package/src/release-loop/next-md-merge.ts +58 -0
  521. package/src/release-loop/release-note.ts +59 -4
  522. package/src/release-loop/release-schedule.ts +53 -0
  523. package/src/repl/index.ts +7 -0
  524. package/src/seat-dispatch/ceo-commands.ts +41 -8
  525. package/src/seat-dispatch/ceo-intent.ts +38 -0
  526. package/src/seat-dispatch/seat-ask.ts +130 -0
  527. package/src/seat-dispatch/seat-questions.ts +100 -0
  528. package/src/seat-loop/seat-loop.ts +750 -0
  529. package/src/seat-loop/seat-report.ts +99 -0
  530. package/src/self-dev/decompose.ts +66 -11
  531. package/src/self-dev/dev-cli.ts +4 -1
  532. package/src/self-dev/dev-pipeline.ts +37 -1
  533. package/src/self-dev/draft-sweep.ts +38 -33
  534. package/src/self-dev/draft-triage-rules.ts +8 -5
  535. package/src/self-dev/fabric-plan-core.ts +297 -0
  536. package/src/self-dev/orchestrate.ts +36 -4
  537. package/src/self-dev/release-path-guard.ts +62 -0
  538. package/src/self-dev/run-store.ts +10 -1
  539. package/src/self-dev/shard-aggregate.ts +230 -0
  540. package/src/self-dev/shard-placement.ts +60 -0
  541. package/src/self-dev/shard-plan.ts +48 -0
  542. package/src/self-dev/shard-pod-run.ts +77 -0
  543. package/src/self-dev/shard-run.ts +105 -0
  544. package/src/self-implement/execution-clarification.ts +115 -0
  545. package/src/self-implement/gate-baseline.ts +1 -1
  546. package/src/self-implement/gate-cli.ts +46 -4
  547. package/src/self-implement/gate-scope.ts +37 -7
  548. package/src/self-implement/graph-variant.ts +171 -0
  549. package/src/self-implement/harness-policy-gates.ts +1 -1
  550. package/src/self-implement/headless-elanous-driver.ts +22 -3
  551. package/src/self-implement/host-regate.ts +22 -1
  552. package/src/self-implement/importer-test-index.ts +2 -1
  553. package/src/self-implement/l8-merge-queue.ts +130 -0
  554. package/src/self-implement/orchestrator.ts +279 -12
  555. package/src/self-implement/pipeline-shape.ts +1 -1
  556. package/src/self-implement/route-consumer-test-index.ts +122 -0
  557. package/src/self-implement/run-status-mapping.ts +5 -3
  558. package/src/self-implement/seams.ts +72 -11
  559. package/src/self-implement/test-seams.ts +1 -0
  560. package/src/self-implement/uncommitted-work.ts +59 -0
  561. package/src/session/chat.ts +2 -0
  562. package/src/session/index.ts +45 -13
  563. package/src/shell-env-bootstrap.ts +17 -0
  564. package/src/skills/gift-install.ts +215 -0
  565. package/src/skills/tools/web-search.ts +4 -1
  566. package/src/steward/directive.ts +51 -2
  567. package/src/steward/launch.ts +266 -0
  568. package/src/steward/outcome-notify.ts +115 -0
  569. package/src/steward/steward-cards.ts +25 -11
  570. package/src/steward/triage-plan.ts +32 -4
  571. package/src/steward/triage.ts +129 -13
  572. package/src/steward/working-backwards.ts +64 -0
  573. package/src/storyboard/storyboard.ts +141 -0
  574. package/src/task-cards/card-cli.ts +25 -1
  575. package/src/task-cards/card-store.ts +2 -2
  576. package/src/task-orchestrator/mission.ts +5 -0
  577. package/src/task-orchestrator/store.ts +12 -3
  578. package/src/task-orchestrator/surfaces/pod-command-job.ts +29 -7
  579. package/src/task-orchestrator/surfaces/pod-lease.ts +397 -0
  580. package/src/task-orchestrator/surfaces/pod-pool.ts +127 -26
  581. package/src/task-orchestrator/surfaces/self-implement-pod.ts +214 -40
  582. package/src/task-orchestrator/surfaces/self-implement.ts +15 -2
  583. package/src/task-orchestrator/types.ts +2 -0
  584. package/src/telegram-card-followup.ts +153 -0
  585. package/src/telegram-commands.ts +10 -1
  586. package/src/telegram-project-command.ts +71 -0
  587. package/src/telegram-report.ts +29 -19
  588. package/src/telegram-run.ts +7 -0
  589. package/src/telegram.ts +58 -11
  590. package/src/tool-runtime/self-cognition-runtimes.ts +13 -3
  591. package/src/tool-runtime/types.ts +4 -0
  592. package/src/tui-client/dashboard-session.ts +2 -0
  593. package/src/user-config.ts +216 -27
  594. package/src/version/packed-revision.json +1 -1
  595. package/src/web-push/notify-exec-request.ts +32 -0
  596. package/src/web-search/grok.ts +7 -1
  597. package/src/web-search/index.ts +28 -1
  598. package/src/web-search/provider.ts +4 -0
  599. package/src/web-search/x-intent.ts +5 -0
  600. package/src/workflow-runtime/executor.ts +277 -47
  601. package/src/workflow-runtime/index.ts +1 -0
  602. package/src/workflow-runtime/nodes/subworkflow.ts +60 -0
  603. package/src/workflow-runtime/plugin-kind-node.ts +1 -1
  604. package/src/workflow-runtime/schema.ts +79 -4
  605. package/src/workflow-runtime/types.ts +35 -4
  606. package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  609. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  612. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  613. package/apps/pwa/out/_next/static/chunks/2375-cb6debea56cce4fb.js +0 -1
  614. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  615. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
  616. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
  617. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  618. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  619. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  620. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  621. package/apps/pwa/out/_next/static/chunks/5080-1613752891fcadb4.js +0 -10
  622. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  623. package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
  624. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
  625. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
  626. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
  627. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  628. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  629. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  630. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
  631. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
  632. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  633. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  634. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
  635. package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
  636. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
  637. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
  638. package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
  639. package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
  640. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  641. package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
  642. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
  643. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
  644. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
  645. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  646. package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
  647. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
  648. package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
  649. package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
  650. package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
  651. package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
  652. package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
  653. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
  654. package/apps/pwa/out/_next/static/chunks/app/editor/page-973996d584f008c1.js +0 -1
  655. package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  658. package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
  660. package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/app/market/page-8b0402a46a749a3d.js +0 -1
  662. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-037f83e2a1edc8e2.js +0 -1
  663. package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
  664. package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
  666. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
  667. package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-0df30e0c7e50148a.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
  682. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-90670589b87abea8.js +0 -1
  683. package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
  684. package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-35bedde6d885f08f.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  688. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  691. package/apps/pwa/out/_next/static/chunks/webpack-e5bea36f183b25e6.js +0 -1
  692. package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
  693. package/src/dashboard/turn-typeahead-echo.ts +0 -23
  694. /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → Y4zKzL62sakG5momz3SoQ}/_ssgManifest.js +0 -0
@@ -0,0 +1,100 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { debug } from '../debug/log.js';
4
+ import { extractVerbatimOriginalAsk } from '../self-implement/goal-author.js';
5
+ import { loadRunLedger } from '../self-implement/run-ledger.js';
6
+ import { queryRunningRuns, type QueriedRunningRunsResult } from '../self-implement/running-runs.js';
7
+ import type { BudgetDecision } from '../self-implement/budget-gate.js';
8
+
9
+ export type ActiveRunsForGoal = readonly string[] | 'unknown';
10
+
11
+ /** Match an un-authored CLI request to the verbatim ask recorded in an authored run. */
12
+ export function launchRequestId(text: string): string {
13
+ return `request-${createHash('sha256').update(text).digest('hex').slice(0, 32)}`;
14
+ }
15
+
16
+ export interface ActiveRunsForGoalDeps {
17
+ queryRuns?: () => QueriedRunningRunsResult;
18
+ loadLedger?: typeof loadRunLedger;
19
+ readGoalDocument?: (path: string) => string;
20
+ }
21
+
22
+ /** Only a live process with an exact goal ID or verbatim authored ask is a duplicate. Incomplete observation is not absence. */
23
+ export function activeRunsForGoal(goalId: string, deps: ActiveRunsForGoalDeps = {}): ActiveRunsForGoal {
24
+ try {
25
+ const observation = (deps.queryRuns ?? (() => queryRunningRuns({ includeTest: true, caller: 'execution-loop.launch-gate', noCache: true })))();
26
+ const loadLedger = deps.loadLedger ?? loadRunLedger;
27
+ const readGoalDocument = deps.readGoalDocument ?? ((path: string) => readFileSync(path, 'utf8'));
28
+ let unknown = observation.completeness !== 'complete' || observation.pty.unreadable.length > 0;
29
+ const active = new Set<string>();
30
+ for (const run of observation.entries) {
31
+ if (run.status === 'ended-unclosed') continue;
32
+ if (run.ledgerDirectories.length === 0) { unknown = true; continue; }
33
+ let identified = false;
34
+ for (const directory of run.ledgerDirectories) {
35
+ try {
36
+ const ledger = loadLedger(run.runId, directory);
37
+ if (ledger === null) { unknown = true; continue; }
38
+ const ids = new Set(ledger.flatMap((entry) => entry.goalId ? [entry.goalId] : []));
39
+ if (ids.size !== 1) { unknown = true; continue; }
40
+ identified = true;
41
+ let matches = ids.has(goalId);
42
+ if (!matches && goalId.startsWith('request-') && run.status === 'running' && run.ptyRefs.length > 0) {
43
+ const document = ledger.find((entry) => entry.event === 'start')?.data.goalFile;
44
+ if (typeof document === 'string') {
45
+ try {
46
+ const ask = extractVerbatimOriginalAsk(readGoalDocument(document));
47
+ matches = ask?.range !== undefined && launchRequestId(ask.ask) === goalId;
48
+ } catch { unknown = true; }
49
+ }
50
+ }
51
+ if (matches) {
52
+ if (run.status === 'running' && run.ptyRefs.length > 0) active.add(run.runId);
53
+ else unknown = true;
54
+ }
55
+ } catch { unknown = true; }
56
+ }
57
+ if (!identified) unknown = true;
58
+ }
59
+ // Positive evidence wins even if a different process or store is unreadable.
60
+ return active.size > 0 ? [...active].sort() : unknown ? 'unknown' : [];
61
+ } catch {
62
+ return 'unknown';
63
+ }
64
+ }
65
+
66
+ export interface PreLaunchGateInput {
67
+ goalId: string;
68
+ budget?: BudgetDecision | 'unknown';
69
+ forceLaunch?: boolean;
70
+ }
71
+
72
+ export interface PreLaunchGateDeps extends ActiveRunsForGoalDeps {
73
+ activeRuns?: (goalId: string) => ActiveRunsForGoal;
74
+ }
75
+
76
+ export interface PreLaunchGateDecision {
77
+ action: 'proceed' | 'blocked-duplicate' | 'blocked-budget' | 'wait-reset';
78
+ sameGoalActiveRuns: ActiveRunsForGoal;
79
+ budget: BudgetDecision | 'unknown';
80
+ reason: string;
81
+ }
82
+
83
+ /** Force bypasses only a duplicate observation, never an exhausted budget. */
84
+ export function preLaunchGate(input: PreLaunchGateInput, deps: PreLaunchGateDeps = {}): PreLaunchGateDecision {
85
+ const decision = decidePreLaunch(input, deps);
86
+ debug.log('execution-loop.launch-gate', 'decision', { goalId: input.goalId, action: decision.action, sameGoalActiveRuns: decision.sameGoalActiveRuns, budget: decision.budget === 'unknown' ? 'unknown' : decision.budget.action, forceLaunch: input.forceLaunch === true, reason: decision.reason });
87
+ return decision;
88
+ }
89
+
90
+ function decidePreLaunch(input: PreLaunchGateInput, deps: PreLaunchGateDeps): PreLaunchGateDecision {
91
+ const sameGoalActiveRuns = (deps.activeRuns ?? ((goalId) => activeRunsForGoal(goalId, deps)))(input.goalId);
92
+ const budget = input.budget ?? 'unknown';
93
+ if (budget !== 'unknown' && (budget.action === 'stop' || budget.action === 'wait-reset')) {
94
+ return { action: budget.action === 'stop' ? 'blocked-budget' : 'wait-reset', sameGoalActiveRuns, budget, reason: budget.reasons.join(' · ') || `budget: ${budget.action}` };
95
+ }
96
+ if (sameGoalActiveRuns !== 'unknown' && sameGoalActiveRuns.length > 0 && !input.forceLaunch) {
97
+ return { action: 'blocked-duplicate', sameGoalActiveRuns, budget, reason: `same-goal active runs: ${sameGoalActiveRuns.join(', ')}` };
98
+ }
99
+ return { action: 'proceed', sameGoalActiveRuns, budget, reason: sameGoalActiveRuns === 'unknown' ? 'active runs unknown' : input.forceLaunch && sameGoalActiveRuns.length > 0 ? 'duplicate overridden by force-launch' : 'no confirmed duplicate' };
100
+ }
@@ -219,19 +219,22 @@ function uniqueName(dir: string, name: string, size: number, taken: Set<string>)
219
219
 
220
220
  export const FIELD_CAPTION_TAG = '#현장';
221
221
 
222
- /** 캡션에 독립 토큰 `#현장` 이 있으면 `{ event? }`, 없으면 null.
223
- * `#현장 <slug>` — 바로 다음 토큰을 소문자로 바꿔 슬러그 규칙에 맞으면 행사 이름, 아니면 기본 행사. */
224
- export function parseFieldCaption(caption: string | undefined | null): { event?: string; text?: string } | null {
222
+ /** 캡션에 독립 토큰 `#현장` 이 있으면 `{ event?, mode?, text? }`, 없으면 null.
223
+ * `#현장 <slug>` — 모드 토큰을 건너뛴 바로 다음 토큰을 소문자로 바꿔 슬러그 규칙에 맞으면 행사 이름, 아니면 기본 행사. */
224
+ export function parseFieldCaption(caption: string | undefined | null): { event?: string; mode?: 'instant'; text?: string } | null {
225
225
  if (!caption) return null;
226
226
  const tokens = caption.trim().split(/\s+/);
227
227
  const at = tokens.indexOf(FIELD_CAPTION_TAG);
228
228
  if (at < 0) return null;
229
- const next = tokens[at + 1]?.toLowerCase();
229
+ const isInstant = (token: string) => token === '즉석' || token.toLowerCase() === 'instant';
230
+ const mode = tokens.some(isInstant) ? 'instant' : undefined;
231
+ const eventAt = at + 1 + (tokens[at + 1] && isInstant(tokens[at + 1]) ? 1 : 0);
232
+ const next = tokens[eventAt]?.toLowerCase();
230
233
  const hasEvent = !!next && isFieldSlug(next);
231
- // 태그·행사 이름을 뺀 나머지가 «글»(자막) — 태그 앞에 쓴 글도 같이.
232
- const rest = tokens.filter((_, i) => i !== at && !(hasEvent && i === at + 1)).join(' ');
234
+ // 태그·행사 이름·모드를 뺀 나머지가 «글»(자막) — 태그 앞에 쓴 글도 같이.
235
+ const rest = tokens.filter((token, i) => i !== at && !(hasEvent && i === eventAt) && !isInstant(token)).join(' ');
233
236
  const text = normalizeFieldCaption(rest);
234
- return { ...(hasEvent ? { event: next } : {}), ...(text ? { text } : {}) };
237
+ return { ...(hasEvent ? { event: next } : {}), ...(mode ? { mode } : {}), ...(text ? { text } : {}) };
235
238
  }
236
239
 
237
240
  export const FIELD_CAPTIONS_FILE = 'captions.txt';
@@ -4,11 +4,13 @@ import { join, resolve } from 'node:path';
4
4
  import { randomUUID } from 'node:crypto';
5
5
  import { spawn, execFileSync } from 'node:child_process';
6
6
  import { debug } from '../debug/log.js';
7
+ import { mergeCapturedPath } from '../shell-env-bootstrap.js';
7
8
  import { listFieldMedia } from './field-media.js';
8
9
  import { startFieldFeed, type FieldFeedStart } from './field-feed-auto.js';
10
+ import { recordOutput } from '../outputs/ledger.js';
9
11
 
10
12
  export interface FieldReelResult { ok: boolean; file?: string; seconds: number; error?: string; feed?: FieldFeedStart }
11
- export type FieldReelRunner = (dir: string, opts: { title: string; sub: string }) => Promise<FieldReelResult>;
13
+ export type FieldReelRunner = (dir: string, opts: { title: string; sub: string; mode?: 'standard' | 'instant' }) => Promise<FieldReelResult>;
12
14
  export interface FieldReelStatus {
13
15
  state: 'waiting' | 'rendering' | 'done' | 'failed';
14
16
  items: number;
@@ -23,6 +25,7 @@ export interface FieldReelOptions {
23
25
  quietMs?: number;
24
26
  title?: string;
25
27
  sub?: string;
28
+ mode?: 'standard' | 'instant';
26
29
  runner?: FieldReelRunner;
27
30
  onDone?: (result: FieldReelResult) => void | Promise<void>;
28
31
  /** 같은 수신자가 앨범 항목마다 예약해도 한 렌더당 한 번만 통지한다. */
@@ -96,10 +99,12 @@ export function readFieldReelStatus(dir: string): FieldReelStatus | null {
96
99
  }
97
100
  }
98
101
 
99
- export const defaultFieldReelRunner: FieldReelRunner = (dir, { title, sub }) => new Promise((finish) => {
102
+ export const defaultFieldReelRunner = (dir: string, { title, sub, mode = 'standard' }: { title: string; sub: string; mode?: 'standard' | 'instant' }, spawnReel: typeof spawn = spawn): Promise<FieldReelResult> => new Promise((finish) => {
100
103
  const script = resolve(import.meta.dir, '../../skills/explainer-video/engine/reel.sh');
101
104
  const started = Date.now();
102
- const child = spawn('zsh', [script, dir, '--title', title, '--sub', sub], { stdio: 'ignore' });
105
+ const env = { ...process.env };
106
+ mergeCapturedPath(env, { capture: true });
107
+ const child = spawnReel('zsh', [script, dir, '--title', title, '--sub', sub, ...(mode === 'instant' ? ['--instant'] : [])], { stdio: 'ignore', env });
103
108
  let settled = false;
104
109
  const done = (error?: string) => {
105
110
  if (settled) return;
@@ -133,6 +138,7 @@ async function render(dir: string, job: Job): Promise<void> {
133
138
  // 현재 렌더의 수신자 스냅샷. 렌더 중 업로드의 수신자는 다음 렌더로 넘긴다.
134
139
  const callbacks = job.callbacks;
135
140
  const renderOpts = job.opts;
141
+ let reelTitle = renderOpts.title || event;
136
142
  job.callbacks = new Map();
137
143
  writeStatus(dir, { state: 'rendering', items, startedAt });
138
144
  mkdirSync(join(dir, 'reel'), { recursive: true }); // 실행기 계약 — 산출 폴더는 있다(reel.sh 는 스스로 다시 만든다)
@@ -144,9 +150,11 @@ async function render(dir: string, job: Job): Promise<void> {
144
150
  let titleLines: string[] = [];
145
151
  try { titleLines = readFileSync(join(dir, 'title.txt'), 'utf8').split(/\r?\n/); }
146
152
  catch (err) { if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err; }
153
+ reelTitle = titleLines[0]?.trim() || renderOpts.title || event;
147
154
  result = await (renderOpts.runner ?? defaultFieldReelRunner)(dir, {
148
- title: titleLines[0]?.trim() || renderOpts.title || event,
155
+ title: reelTitle,
149
156
  sub: titleLines[1]?.trim() || renderOpts.sub || sub,
157
+ ...(renderOpts.mode ? { mode: renderOpts.mode } : {}),
150
158
  });
151
159
  } catch (err) {
152
160
  result = { ok: false, seconds: 0, error: err instanceof Error ? err.message : String(err) };
@@ -158,12 +166,18 @@ async function render(dir: string, job: Job): Promise<void> {
158
166
  writeStatus(dir, { state: result.ok ? 'done' : 'failed', items, startedAt, finishedAt,
159
167
  seconds: result.seconds, ...(result.ok ? { file: result.file } : { error: result.error ?? 'render failed' }) });
160
168
  debug.log('field.reel', result.ok ? 'done' : 'failed', { event, items, seconds: result.seconds });
169
+ if (result.ok && result.file) recordOutput({ source: 'field-reel', sourceId: event, kind: 'video', title: reelTitle, path: result.file });
161
170
  if (result.ok) {
162
171
  // EV10b: 엔진이 timeline.json 에 남긴 비전 호출 수·성공·«현장 N» 강등·걸린 ms — codex 잔량과 겹치므로 관측한다(OP 10-01).
172
+ let cardsCache: 'hit' | 'miss' | 'n/a' = 'n/a';
163
173
  try {
164
- const t = JSON.parse(readFileSync(join(dir, 'reel', 'timeline.json'), 'utf8')) as { vision?: Record<string, unknown>; music?: unknown };
174
+ const t = JSON.parse(readFileSync(join(dir, 'reel', 'timeline.json'), 'utf8')) as { vision?: Record<string, unknown>; music?: unknown; cardsCache?: 'hit' | 'miss' };
165
175
  if (t.vision) debug.log('field.reel', 'vision', { event, ...t.vision, music: t.music === true });
176
+ if (renderOpts.mode === 'instant' && (t.cardsCache === 'hit' || t.cardsCache === 'miss')) cardsCache = t.cardsCache;
166
177
  } catch { debug.log('field.reel', 'vision-unread', { event }); }
178
+ // A custom runner without an explicit mode may render any way — say «unknown» instead of guessing the default.
179
+ const mode = renderOpts.mode ?? (renderOpts.runner ? 'unknown' : 'standard');
180
+ debug.log('field.reel', 'rendered', { mode, seconds: result.seconds, cardsCache });
167
181
  }
168
182
  // 뒤이은 업로드로 다시 렌더할 예정이면(dirty) 피드는 그 마지막 렌더 뒤에 한 번만 띄운다.
169
183
  if (result.ok && !job.dirty) result = { ...result, feed: (renderOpts.startFeed ?? ((d: string) => startFieldFeed(d, { enabled: renderOpts.autoFeed })))(dir) };
@@ -0,0 +1,201 @@
1
+ export interface FieldPhoto {
2
+ file: string;
3
+ /** EXIF `YYYY:MM:DD HH:mm:ss`, ISO local time, or ISO time with an offset. */
4
+ exifTakenAt?: string | null;
5
+ }
6
+
7
+ export interface TranscriptEntry {
8
+ speaker: string;
9
+ timestamp: string;
10
+ text: string;
11
+ }
12
+
13
+ export interface FieldNoteOptions {
14
+ /** Minutes east of UTC for offset-free EXIF and transcript timestamps; KST by default. */
15
+ timeZoneOffsetMinutes?: number;
16
+ /** Minutes added to every dated photo after interpreting its EXIF timestamp. */
17
+ photoCorrectionMinutes?: number;
18
+ /** Maximum gap between consecutive photos in the same scene; 15 minutes by default. */
19
+ sceneGapMinutes?: number;
20
+ }
21
+
22
+ export interface TimedPhoto extends FieldPhoto {
23
+ takenAt: string;
24
+ }
25
+
26
+ export interface FieldScene {
27
+ start: string;
28
+ end: string;
29
+ photos: TimedPhoto[];
30
+ transcript: TranscriptEntry[];
31
+ }
32
+
33
+ export interface FieldTimeline {
34
+ scenes: FieldScene[];
35
+ undatedPhotos: FieldPhoto[];
36
+ /** Chronological entries when there are no dated photos to anchor a scene. */
37
+ transcriptWithoutPhotos: TranscriptEntry[];
38
+ timeZoneOffsetMinutes: number;
39
+ }
40
+
41
+ export interface FieldNoteLlm {
42
+ /** Only used for a raw transcript string; JSON entries bypass transcription. */
43
+ transcribe?: (source: string) => Promise<TranscriptEntry[]>;
44
+ summarize?: (entries: readonly TranscriptEntry[]) => Promise<string>;
45
+ }
46
+
47
+ const MINUTE = 60_000;
48
+
49
+ function validOffset(minutes: number): boolean {
50
+ return Number.isInteger(minutes) && minutes >= -14 * 60 && minutes <= 14 * 60;
51
+ }
52
+
53
+ function parseTime(value: string, offsetMinutes: number): number | null {
54
+ const input = value.trim();
55
+ const local = /^(\d{4})[:-](\d{2})[:-](\d{2})[ T](\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/.exec(input);
56
+ const zoned = local ? null : /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(?:Z|[+-]\d{2}:\d{2})$/.exec(input);
57
+ const match = local ?? zoned;
58
+ if (!match) return null;
59
+ const [, year, month, day, hour, minute, second, fraction] = match;
60
+ if (+year! < 100) return null;
61
+ const milliseconds = fraction ? Number(fraction.padEnd(3, '0')) : 0;
62
+ const utc = Date.UTC(+year!, +month! - 1, +day!, +hour!, +minute!, +second!, milliseconds);
63
+ const date = new Date(utc);
64
+ if (date.getUTCFullYear() !== +year! || date.getUTCMonth() + 1 !== +month! ||
65
+ date.getUTCDate() !== +day! || date.getUTCHours() !== +hour! ||
66
+ date.getUTCMinutes() !== +minute! || date.getUTCSeconds() !== +second!) return null;
67
+ if (local) return utc - offsetMinutes * MINUTE;
68
+ // Offset-bearing ISO timestamps are absolute instants, not local wall-clock readings.
69
+ const parsed = Date.parse(input);
70
+ return Number.isFinite(parsed) ? parsed : null;
71
+ }
72
+
73
+ function iso(ms: number): string {
74
+ return new Date(ms).toISOString();
75
+ }
76
+
77
+ /** Pure alignment: neither mutates its inputs nor consults the machine clock/timezone. */
78
+ export function alignFieldNote(
79
+ photos: readonly FieldPhoto[],
80
+ transcript: readonly TranscriptEntry[],
81
+ options: FieldNoteOptions = {},
82
+ ): FieldTimeline {
83
+ const offset = options.timeZoneOffsetMinutes ?? 540;
84
+ const correction = options.photoCorrectionMinutes ?? 0;
85
+ const gap = options.sceneGapMinutes ?? 15;
86
+ if (!validOffset(offset) || !Number.isFinite(correction) || !Number.isFinite(gap) || gap < 0) {
87
+ throw new RangeError('Invalid field-note timezone, photo correction, or scene gap');
88
+ }
89
+ const dated: Array<{ photo: TimedPhoto; time: number; order: number }> = [];
90
+ const undatedPhotos: FieldPhoto[] = [];
91
+ photos.forEach((photo, order) => {
92
+ const parsed = photo.exifTakenAt ? parseTime(photo.exifTakenAt, offset) : null;
93
+ if (parsed === null) undatedPhotos.push({ ...photo });
94
+ else dated.push({ photo: { ...photo, takenAt: iso(parsed + correction * MINUTE) }, time: parsed + correction * MINUTE, order });
95
+ });
96
+ dated.sort((a, b) => a.time - b.time || a.order - b.order);
97
+
98
+ const groups: typeof dated[] = [];
99
+ for (const item of dated) {
100
+ const last = groups.at(-1);
101
+ if (!last || item.time - last[last.length - 1]!.time > gap * MINUTE) groups.push([item]);
102
+ else last.push(item);
103
+ }
104
+
105
+ const entries = transcript.map((entry, order) => {
106
+ const time = parseTime(entry.timestamp, offset);
107
+ if (time === null) throw new RangeError(`Invalid transcript timestamp at index ${order}: ${entry.timestamp}`);
108
+ return { entry: { ...entry }, time, order };
109
+ }).sort((a, b) => a.time - b.time || a.order - b.order);
110
+
111
+ // An utterance belongs to the scene whose photo span [first, last] is nearest in time (inside the span = distance 0);
112
+ // a tie goes to the earlier scene. So a 10:55 line next to an 11:00 photo joins the 11:00 scene, not 09:00.
113
+ const spans = groups.map((group) => ({ start: group[0]!.time, end: group[group.length - 1]!.time }));
114
+ const sceneEntries: Array<Array<(typeof entries)[number]>> = groups.map(() => []);
115
+ for (const item of entries) {
116
+ if (groups.length === 0) break;
117
+ let best = 0;
118
+ let bestDistance = Infinity;
119
+ spans.forEach((span, index) => {
120
+ const distance = item.time < span.start ? span.start - item.time : item.time > span.end ? item.time - span.end : 0;
121
+ if (distance < bestDistance) { best = index; bestDistance = distance; }
122
+ });
123
+ sceneEntries[best]!.push(item);
124
+ }
125
+ const scenes = groups.map((group, index) => {
126
+ const assigned = sceneEntries[index]!;
127
+ const times = [group[0]!.time, group[group.length - 1]!.time, ...assigned.map((item) => item.time)];
128
+ return {
129
+ start: iso(Math.min(...times)), end: iso(Math.max(...times)),
130
+ photos: group.map((item) => item.photo), transcript: assigned.map((item) => item.entry),
131
+ };
132
+ });
133
+ return {
134
+ scenes, undatedPhotos,
135
+ transcriptWithoutPhotos: groups.length === 0 ? entries.map((item) => item.entry) : [],
136
+ timeZoneOffsetMinutes: offset,
137
+ };
138
+ }
139
+
140
+ function localTime(instant: string, offset: number): string {
141
+ const shifted = new Date(Date.parse(instant) + offset * MINUTE).toISOString().slice(0, 16).replace('T', ' ');
142
+ const sign = offset >= 0 ? '+' : '-';
143
+ const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0');
144
+ const minutes = String(Math.abs(offset) % 60).padStart(2, '0');
145
+ return `${shifted} UTC${sign}${hours}:${minutes}`;
146
+ }
147
+
148
+ function photoLine(photo: FieldPhoto, label: string): string {
149
+ const file = encodeURIComponent(photo.file).replace(/%2F/gi, '/').replace(/[()]/g, (char) => char === '(' ? '%28' : '%29');
150
+ const alt = photo.file.split(/[\\/]/).at(-1)?.replace(/\]/g, '\\]') ?? photo.file;
151
+ return `- ${label} ![${alt}](<${file}>)`;
152
+ }
153
+
154
+ /** Synchronous, side-effect-free Markdown rendering; summaries are optional placeholders. */
155
+ export function renderFieldNote(timeline: FieldTimeline, summaries: readonly string[] = []): string {
156
+ if (summaries.length > timeline.scenes.length + (timeline.transcriptWithoutPhotos.length ? 1 : 0)) {
157
+ throw new RangeError('More summaries than transcript sections');
158
+ }
159
+ const lines = ['# 현장 노트', ''];
160
+ timeline.scenes.forEach((scene, i) => {
161
+ lines.push(`## 장면 ${i + 1} · ${localTime(scene.start, timeline.timeZoneOffsetMinutes)} – ${localTime(scene.end, timeline.timeZoneOffsetMinutes)}`);
162
+ lines.push('', '### 전사 요약', summaries[i]?.trim() || '_(전사 요약 자리)_', '', '### 사진');
163
+ for (const photo of scene.photos) lines.push(photoLine(photo, localTime(photo.takenAt, timeline.timeZoneOffsetMinutes)));
164
+ lines.push('');
165
+ });
166
+ if (timeline.transcriptWithoutPhotos.length) {
167
+ lines.push('## 사진 없는 전사', '', '### 전사 요약',
168
+ summaries[timeline.scenes.length]?.trim() || '_(전사 요약 자리)_', '', '### 시간순 전사');
169
+ for (const entry of timeline.transcriptWithoutPhotos) {
170
+ lines.push(`- ${entry.timestamp} · ${entry.speaker}: ${entry.text}`);
171
+ }
172
+ lines.push('');
173
+ }
174
+ if (timeline.undatedPhotos.length) {
175
+ lines.push('## 시각 없음', '');
176
+ for (const photo of timeline.undatedPhotos) lines.push(photoLine(photo, '시각 없음'));
177
+ lines.push('');
178
+ }
179
+ return lines.join('\n').trimEnd() + '\n';
180
+ }
181
+
182
+ /** LLM calls are supplied by the caller; JSON transcripts need no transcription call. */
183
+ export async function generateFieldNote(
184
+ photos: readonly FieldPhoto[],
185
+ transcript: readonly TranscriptEntry[] | string,
186
+ llm: FieldNoteLlm = {},
187
+ options: FieldNoteOptions = {},
188
+ ): Promise<string> {
189
+ let entries: readonly TranscriptEntry[];
190
+ if (typeof transcript === 'string') {
191
+ if (!llm.transcribe) throw new Error('A transcribe dependency is required for raw transcript input');
192
+ entries = await llm.transcribe(transcript);
193
+ } else entries = transcript;
194
+ const timeline = alignFieldNote(photos, entries, options);
195
+ const sections = timeline.scenes.map((scene) => scene.transcript);
196
+ if (timeline.transcriptWithoutPhotos.length) sections.push(timeline.transcriptWithoutPhotos);
197
+ const summaries = llm.summarize
198
+ ? await Promise.all(sections.map((segment) => segment.length ? llm.summarize!(segment) : ''))
199
+ : [];
200
+ return renderFieldNote(timeline, summaries);
201
+ }
@@ -0,0 +1,248 @@
1
+ import type { TranscriptEntry } from './index';
2
+
3
+ /** Mono, normalized PCM. The capture instant is required for NOTE1's absolute photo alignment. */
4
+ export interface FieldAudio {
5
+ samples: Float32Array;
6
+ sampleRate: number;
7
+ recordedAt: string;
8
+ }
9
+
10
+ export interface AudioChunk {
11
+ samples: Float32Array;
12
+ sampleRate: number;
13
+ /** Offset from the first sample of the recording, in seconds. */
14
+ startSeconds: number;
15
+ }
16
+
17
+ export interface SpeechSegment {
18
+ /** Start and end in seconds relative to the supplied chunk. */
19
+ startSeconds: number;
20
+ endSeconds: number;
21
+ text: string;
22
+ speaker?: string;
23
+ }
24
+
25
+ export interface EngineRequest {
26
+ language: string;
27
+ /** Preserve source-language speech rather than translating it. */
28
+ task: 'transcribe';
29
+ }
30
+
31
+ export interface TranscriptionEngine {
32
+ transcribe(chunk: AudioChunk, request: EngineRequest): Promise<readonly SpeechSegment[]>;
33
+ }
34
+
35
+ /** The caller provides the local Whisper runtime; this module never downloads a model. */
36
+ export interface LocalWhisperClient {
37
+ transcribe(input: {
38
+ audio: Float32Array;
39
+ sampleRate: number;
40
+ language: string;
41
+ task: 'transcribe';
42
+ }): Promise<readonly SpeechSegment[]>;
43
+ }
44
+
45
+ /** The caller provides authentication/transport; no network call occurs on construction. */
46
+ export interface CloudSttClient {
47
+ transcribe(input: {
48
+ audio: Float32Array;
49
+ sampleRate: number;
50
+ language: string;
51
+ timestamps: true;
52
+ diarization: true;
53
+ }): Promise<readonly SpeechSegment[]>;
54
+ }
55
+
56
+ export function createLocalWhisperEngine(client: LocalWhisperClient): TranscriptionEngine {
57
+ return {
58
+ transcribe: (chunk, request) => client.transcribe({
59
+ audio: chunk.samples, sampleRate: chunk.sampleRate, language: request.language, task: request.task,
60
+ }),
61
+ };
62
+ }
63
+
64
+ export function createCloudSttEngine(client: CloudSttClient): TranscriptionEngine {
65
+ return {
66
+ transcribe: (chunk, request) => client.transcribe({
67
+ audio: chunk.samples, sampleRate: chunk.sampleRate, language: request.language,
68
+ timestamps: true, diarization: true,
69
+ }),
70
+ };
71
+ }
72
+
73
+ export interface TranscriptionOptions {
74
+ /** Default: ko. */
75
+ language?: string;
76
+ /** Default: 30 seconds. */
77
+ chunkSeconds?: number;
78
+ /** Default: 2 seconds. Must be shorter than chunkSeconds. */
79
+ overlapSeconds?: number;
80
+ /** Used only if an engine omits a speaker label. Otherwise the label remains 화자 미상. */
81
+ defaultSpeaker?: string;
82
+ }
83
+
84
+ function validateAudio(audio: FieldAudio): number {
85
+ if (!Number.isInteger(audio.sampleRate) || audio.sampleRate <= 0) {
86
+ throw new RangeError('Audio sampleRate must be a positive integer');
87
+ }
88
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,3})?(Z|[+-]\d{2}:\d{2})$/.exec(audio.recordedAt);
89
+ if (!match) throw new RangeError('Audio recordedAt must be an ISO timestamp with a timezone offset');
90
+ const [, year, month, day, hour, minute, second, zone] = match;
91
+ const calendar = new Date(Date.UTC(+year!, +month! - 1, +day!, +hour!, +minute!, +second!));
92
+ const recordedAt = Date.parse(audio.recordedAt);
93
+ if (calendar.getUTCFullYear() !== +year! || calendar.getUTCMonth() + 1 !== +month! ||
94
+ calendar.getUTCDate() !== +day! || calendar.getUTCHours() !== +hour! ||
95
+ calendar.getUTCMinutes() !== +minute! || calendar.getUTCSeconds() !== +second! ||
96
+ (zone !== 'Z' && (+zone!.slice(1, 3) > 23 || +zone!.slice(4) > 59)) ||
97
+ !Number.isFinite(recordedAt)) {
98
+ throw new RangeError('Invalid audio recordedAt timestamp');
99
+ }
100
+ return recordedAt;
101
+ }
102
+
103
+ /** Sample-index arithmetic avoids gaps and drift from repeated floating-point additions. */
104
+ export function splitAudioIntoChunks(audio: FieldAudio, options: TranscriptionOptions = {}): AudioChunk[] {
105
+ validateAudio(audio);
106
+ const chunkSeconds = options.chunkSeconds ?? 30;
107
+ const overlapSeconds = options.overlapSeconds ?? 2;
108
+ if (!Number.isFinite(chunkSeconds) || !Number.isFinite(overlapSeconds) ||
109
+ chunkSeconds <= 0 || overlapSeconds < 0 || overlapSeconds >= chunkSeconds) {
110
+ throw new RangeError('Audio chunk length must exceed nonnegative overlap');
111
+ }
112
+ const size = Math.round(chunkSeconds * audio.sampleRate);
113
+ const overlap = Math.round(overlapSeconds * audio.sampleRate);
114
+ if (size <= 0 || overlap >= size) throw new RangeError('Audio chunk and overlap must span valid samples');
115
+ const chunks: AudioChunk[] = [];
116
+ for (let start = 0; start < audio.samples.length;) {
117
+ const end = Math.min(start + size, audio.samples.length);
118
+ chunks.push({ samples: audio.samples.subarray(start, end), sampleRate: audio.sampleRate,
119
+ startSeconds: start / audio.sampleRate });
120
+ if (end === audio.samples.length) break;
121
+ start = end - overlap;
122
+ }
123
+ return chunks;
124
+ }
125
+
126
+ function mergeOverlappingText(left: string, right: string): string | null {
127
+ if (left === right) return left;
128
+ if (left.includes(right)) return left;
129
+ if (right.includes(left)) return right;
130
+ for (let length = Math.min(left.length, right.length); length >= 2; length--) {
131
+ if (left.endsWith(right.slice(0, length))) return left + right.slice(length);
132
+ }
133
+ return null;
134
+ }
135
+
136
+ interface TimedSegment {
137
+ start: number;
138
+ end: number;
139
+ speaker: string;
140
+ text: string;
141
+ chunkIndex: number;
142
+ }
143
+
144
+ /** Return absolute ISO timestamps accepted by NOTE1, preserving speaker labels and chronological order. */
145
+ export async function transcribeFieldAudio(
146
+ audio: FieldAudio,
147
+ engine: TranscriptionEngine,
148
+ options: TranscriptionOptions = {},
149
+ ): Promise<TranscriptEntry[]> {
150
+ const origin = validateAudio(audio);
151
+ const chunks = splitAudioIntoChunks(audio, options);
152
+ const segments: TimedSegment[] = [];
153
+ for (const [chunkIndex, chunk] of chunks.entries()) {
154
+ const result = await engine.transcribe(chunk, { language: options.language ?? 'ko', task: 'transcribe' });
155
+ const duration = chunk.samples.length / chunk.sampleRate;
156
+ for (const segment of result) {
157
+ if (!Number.isFinite(segment.startSeconds) || !Number.isFinite(segment.endSeconds) ||
158
+ segment.startSeconds < 0 || segment.endSeconds < segment.startSeconds ||
159
+ // Only floating-point slack — a segment may not end after the chunk's real audio (NOTE3a review must-fix).
160
+ segment.endSeconds > duration + 1e-9) {
161
+ throw new RangeError(`Invalid transcription segment times in chunk ${chunkIndex}`);
162
+ }
163
+ const text = segment.text.trim();
164
+ if (!text) continue;
165
+ segments.push({
166
+ start: chunk.startSeconds + segment.startSeconds,
167
+ end: chunk.startSeconds + segment.endSeconds,
168
+ text, speaker: segment.speaker?.trim() || options.defaultSpeaker?.trim() || '화자 미상',
169
+ chunkIndex,
170
+ });
171
+ }
172
+ }
173
+ segments.sort((a, b) => a.chunkIndex - b.chunkIndex || a.start - b.start);
174
+ const stitched: TimedSegment[] = [];
175
+ const center = (segment: TimedSegment) => {
176
+ const source = chunks[segment.chunkIndex]!;
177
+ return source.startSeconds + source.samples.length / source.sampleRate / 2;
178
+ };
179
+ // Engine speaker IDs are chunk-scoped. A later chunk's ID that matched an earlier chunk's speech in the overlap takes
180
+ // the earlier global label; an ID that never matched is qualified with its chunk so different people never share a label.
181
+ const unlabeled = new Set(['화자 미상', options.defaultSpeaker?.trim()].filter((label): label is string => !!label));
182
+ const qualify = (speaker: string, chunkIndex: number) => chunkIndex && !unlabeled.has(speaker) ? `${speaker} · 구간 ${chunkIndex + 1}` : speaker;
183
+ for (const [chunkIndex, chunk] of chunks.entries()) {
184
+ const incoming = segments.filter((segment) => segment.chunkIndex === chunkIndex);
185
+ const speakerMap = new Map<string, string>();
186
+ const previous = stitched.filter((segment) => segment.chunkIndex === chunkIndex - 1);
187
+ const sharedStart = chunk.startSeconds;
188
+ const sharedEnd = chunkIndex ? Math.min(chunk.startSeconds + chunk.samples.length / chunk.sampleRate,
189
+ chunks[chunkIndex - 1]!.startSeconds + chunks[chunkIndex - 1]!.samples.length / audio.sampleRate) : sharedStart;
190
+ const eligible = (segment: TimedSegment) => segment.start <= sharedEnd && segment.end >= sharedStart;
191
+ const consumedPrevious = new Set<TimedSegment>();
192
+ const consumedIncoming = new Set<TimedSegment>();
193
+ const replacements: TimedSegment[] = [];
194
+ // A chunk may split one sentence into several timed segments while its
195
+ // neighbor reports the entire sentence. Match contiguous groups on both sides.
196
+ while (sharedEnd > sharedStart) {
197
+ let best: { earlier: TimedSegment[]; later: TimedSegment[]; text: string } | undefined;
198
+ for (let a = 0; a < previous.length; a++) {
199
+ for (let b = a + 1; b <= previous.length; b++) {
200
+ const earlier = previous.slice(a, b);
201
+ if (earlier.some((item) => consumedPrevious.has(item) || !eligible(item))) break;
202
+ for (let c = 0; c < incoming.length; c++) {
203
+ for (let d = c + 1; d <= incoming.length; d++) {
204
+ const later = incoming.slice(c, d);
205
+ if (later.some((item) => consumedIncoming.has(item) || !eligible(item))) break;
206
+ // Speaker IDs are scoped to each engine call, not stable across chunks.
207
+ // Keep distinct speakers within a chunk separate, but use speech and time across chunks.
208
+ if (earlier.some((item) => item.speaker !== earlier[0]!.speaker) ||
209
+ later.some((item) => item.speaker !== later[0]!.speaker)) continue;
210
+ const earlierStart = earlier[0]!.start;
211
+ const earlierEnd = Math.max(...earlier.map((item) => item.end));
212
+ const laterStart = later[0]!.start;
213
+ const laterEnd = Math.max(...later.map((item) => item.end));
214
+ if (earlier.some((item) => item.start > laterEnd || item.end < laterStart) ||
215
+ later.some((item) => item.start > earlierEnd || item.end < earlierStart)) continue;
216
+ const text = mergeOverlappingText(earlier.map((item) => item.text).join(' '),
217
+ later.map((item) => item.text).join(' '));
218
+ if (text !== null && (!best || earlier.length + later.length > best.earlier.length + best.later.length)) {
219
+ best = { earlier, later, text };
220
+ }
221
+ }
222
+ }
223
+ }
224
+ }
225
+ if (!best) break;
226
+ for (const item of best.earlier) consumedPrevious.add(item);
227
+ for (const item of best.later) consumedIncoming.add(item);
228
+ const earlier = best.earlier[0]!;
229
+ const later = best.later[0]!;
230
+ const chosen = Math.abs((later.start + later.end) / 2 - center(later)) <
231
+ Math.abs((earlier.start + earlier.end) / 2 - center(earlier)) ? later : earlier;
232
+ // The earlier side already carries a global label; first match decides the mapping for this chunk-local ID.
233
+ for (const item of best.later) if (!speakerMap.has(item.speaker)) speakerMap.set(item.speaker, earlier.speaker);
234
+ replacements.push({ ...chosen, speaker: earlier.speaker, chunkIndex, text: best.text,
235
+ start: Math.min(...best.earlier.map((item) => item.start), ...best.later.map((item) => item.start)),
236
+ end: Math.max(...best.earlier.map((item) => item.end), ...best.later.map((item) => item.end)) });
237
+ }
238
+ for (let i = stitched.length - 1; i >= 0; i--) {
239
+ if (consumedPrevious.has(stitched[i]!)) stitched.splice(i, 1);
240
+ }
241
+ stitched.push(...incoming.filter((item) => !consumedIncoming.has(item))
242
+ .map((item) => ({ ...item, speaker: speakerMap.get(item.speaker) ?? qualify(item.speaker, chunkIndex) })), ...replacements);
243
+ stitched.sort((a, b) => a.start - b.start || a.chunkIndex - b.chunkIndex);
244
+ }
245
+ return stitched.map(({ start, speaker, text }) => ({
246
+ speaker, text, timestamp: new Date(origin + Math.round(start * 1000)).toISOString(),
247
+ }));
248
+ }