elanous 0.2.19 → 0.2.21
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.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/MObU8ujkMciAKr6ljAV05/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
- package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
- package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
- package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-c97eba9fb72a9a5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
- package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
- package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
- package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
- package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2977-e38442e86289f1d3.js → 211-9c9090c59e2c5907.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
- package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
- package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
- package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
- package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
- package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
- package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
- package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
- package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +1 -0
- package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{4847.56f5bc454fbcc827.js → 6110.1297d4093fdfbb0d.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/6158.566c890adc06a459.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
- package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
- package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
- package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
- package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
- package/apps/pwa/out/_next/static/chunks/{1470.98a6ab61ccb4131c.js → 726.83532d69452ee24b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
- package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
- package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
- package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
- package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7647.2fcd30d894d5ee30.js → 935.4ddaa99fc1d1126c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
- package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-d781be3e3a9cfd74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-b0a80997903852d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-2b1263165630d9c8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ea998f7f5d156bea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-e2d6bed48ac20120.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-111cabe4d1fd69ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-544dd4bd4847eeb7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-3ee191319bf97d87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a449f1ed472b81c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-9045094d6d3b5cd0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-c06673596849ef45.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-9f81233f29dcd409.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-b70cd005d9169e25.js → page-be8b4615c919e36c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-7a12f61be4a579d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-c85b0589591a99c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-725a4da02dd129eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8cc707bd09a497d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-21bec4afeb2f0917.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-dc39ee7a36d26385.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-84ae759f76053672.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-3db58ddb61fe2be9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c5315e30f3528dc4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-2fe368efb6de3b6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8d04132dd0ac5e10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-f23484de467c9ea3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-c69762987b751174.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d75f7cd65845ce98.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-a6eb73b8a4840f1c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6c0b88068c143f15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7f8d7fcdf058a845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-22163556cd38bc88.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{2ea96958.e8c7e112de50b067.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
- package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
- package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +24 -24
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +23 -23
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/live-run/index.html +1 -0
- package/apps/pwa/out/live-run/index.txt +35 -0
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/demo/editor-recipes.yaml +22 -0
- package/graphs/drafts/recipes.yaml +4 -4
- package/graphs/implement-loop.yaml +277 -5
- package/graphs/retro/daily.yaml +25 -0
- package/graphs/retro/recipes.yaml +24 -0
- package/package.json +1 -1
- package/scripts/ask-marker-check.ts +3 -1
- package/scripts/coord-channel-watch.sh +31 -4
- package/scripts/docs-lint.ts +4 -1
- package/scripts/land-global-checks.ts +89 -0
- package/scripts/lib/pch-regress-map.ts +2 -1
- package/scripts/marketing/generate-ax-brief.ts +18 -1
- package/scripts/measure-nl-routing-corpus.ts +11 -3
- package/scripts/release-loop/gate-node.ts +296 -25
- package/scripts/release-loop/gate-timing.ts +116 -7
- package/scripts/release-loop/preflight.ts +6 -4
- package/scripts/release-loop/publish-window.ts +141 -0
- package/scripts/release-loop/release-docs.ts +1 -4
- package/scripts/release-loop/release-universe.ts +38 -0
- package/scripts/release-loop/resume-release.ts +4 -1
- package/scripts/release-loop/shard-plan.ts +50 -5
- package/scripts/release-loop/unattended-release.ts +10 -2
- package/scripts/release-story/draft.ts +5 -1
- package/scripts/retro/shadow-compare.ts +71 -0
- package/scripts/rhythm/daily.ts +8 -2
- package/src/agent-mission/driver.ts +34 -6
- package/src/autopilot/build/llm-conflict-merge.ts +132 -7
- package/src/autopilot/build/merge-intent.ts +135 -7
- package/src/away/release-watch.ts +20 -3
- package/src/browser-cdp/client.ts +10 -5
- package/src/cli/config-drift.ts +513 -0
- package/src/cli/log-event-names.ts +4 -0
- package/src/cli/ops-cli.ts +11 -0
- package/src/cli/ops-overview-cli.ts +50 -0
- package/src/cli/pod-cli.ts +1 -0
- package/src/cli/pr-cli.ts +20 -0
- package/src/cli/release-cli.ts +20 -5
- package/src/cli/retro-cli.ts +78 -0
- package/src/cli/tasks-cli.ts +200 -8
- package/src/dashboard/index.ts +40 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +5 -4
- package/src/dashboard/slash-runtime/help-from-registry.ts +57 -31
- package/src/domains/outbound-log-probe.ts +6 -1
- package/src/esc-abort-gate.ts +3 -1
- package/src/graph-runner/graph-cli.ts +57 -0
- package/src/graph-runner/graph-run-api.ts +218 -0
- package/src/graph-runner/runner.ts +11 -1
- package/src/graph-wizard/archetypes.ts +88 -0
- package/src/graph-wizard/generate.ts +545 -0
- package/src/graph-wizard/saved-steps.ts +120 -0
- package/src/graph-wizard/steps.ts +193 -0
- package/src/harness/harness-cli-command.ts +482 -59
- package/src/harness/harness-queue.ts +315 -25
- package/src/harness/harness-salvage-cli.ts +62 -0
- package/src/hq/fence-outcomes.ts +63 -0
- package/src/hq/fence-wrapper.ts +33 -0
- package/src/hq/hq.ts +11 -1
- package/src/index.ts +76 -10
- package/src/intake-plane/digest.ts +18 -11
- package/src/maturity/feature-maturity.ts +4 -3
- package/src/nexus/api/draft-metrics.ts +114 -0
- package/src/nexus/api/graph-kinds.ts +10 -4
- package/src/nexus/api/graph-wizard-api.ts +53 -0
- package/src/nexus/api/graphs-api.ts +220 -10
- package/src/nexus/api/harness-traversal.ts +205 -0
- package/src/nexus/api/http-server.ts +69 -3
- package/src/nexus/api/loop-edges.ts +4 -2
- package/src/nexus/api/ops-api.ts +22 -7
- package/src/nexus/api/ops-board-route.ts +48 -0
- package/src/nexus/outbound/router.ts +28 -8
- package/src/onboarding/entry-hints.ts +5 -0
- package/src/onboarding/first-run.ts +3 -0
- package/src/onboarding.ts +24 -6
- package/src/ops/overview-collect.ts +345 -0
- package/src/ops/overview-seat-era.ts +90 -0
- package/src/ops/overview.ts +223 -0
- package/src/release-loop/feature-store.ts +14 -11
- package/src/release-loop/gate-shards.ts +163 -0
- package/src/release-loop/release-schedule.ts +6 -2
- package/src/retro/daily.ts +372 -0
- package/src/seat-loop/op-decide.ts +173 -0
- package/src/seat-loop/seat-loop.ts +60 -3
- package/src/seat-loop/seat-report.ts +58 -3
- package/src/self-dev/ask-launch-flow.ts +9 -6
- package/src/self-dev/ask-launch-io.ts +52 -2
- package/src/self-dev/ask-path-candidates.ts +40 -2
- package/src/self-dev/draft-sweep.ts +371 -16
- package/src/self-dev/draft-triage-rules.ts +19 -4
- package/src/self-dev/graph-journey-nodes.ts +157 -0
- package/src/self-dev/graph-journey-route.ts +105 -0
- package/src/self-dev/launch-preflight.ts +43 -3
- package/src/self-dev/pr-preview.ts +84 -16
- package/src/self-dev/release-path-guard.ts +7 -4
- package/src/self-implement/blocked-draft-policy.ts +101 -0
- package/src/self-implement/execution-clarification.ts +11 -0
- package/src/self-implement/frozen-merges.ts +23 -0
- package/src/self-implement/gate-env-retry.ts +83 -0
- package/src/self-implement/goal-author.ts +14 -0
- package/src/self-implement/heal-intake.ts +3 -0
- package/src/self-implement/orchestrator.ts +234 -6
- package/src/self-implement/run-stop.ts +123 -0
- package/src/self-implement/run-ttl.ts +258 -0
- package/src/self-implement/salvage-retention.ts +123 -0
- package/src/self-implement/seams.ts +7 -0
- package/src/self-implement/self-implement-cli.ts +2 -0
- package/src/self-implement/self-implement-runtime.ts +12 -0
- package/src/self-implement/sibling-resync.ts +223 -0
- package/src/self-implement/stop-autoheal.ts +204 -0
- package/src/session-runtime/index.ts +5 -3
- package/src/shell-env-bootstrap.ts +17 -3
- package/src/task-agent/board.ts +1 -1
- package/src/task-agent/card-evidence.ts +105 -10
- package/src/task-agent/judge.ts +7 -0
- package/src/task-agent/live-moves.ts +120 -20
- package/src/task-agent/mission.ts +1 -1
- package/src/task-agent/parent-host.ts +315 -0
- package/src/task-agent/shadow.ts +10 -1
- package/src/task-agent/task-hand.ts +16 -4
- package/src/task-orchestrator/surfaces/pod-command-job.ts +60 -11
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
- package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +93 -10
- package/src/task-orchestrator/surfaces/pod-pool.ts +147 -34
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +58 -9
- package/src/telegram-commands.ts +41 -0
- package/src/testing/real-cli-spawn.ts +106 -0
- package/src/tool-runtime/browser-runtime.ts +87 -3
- package/src/tool-runtime/index.ts +1 -0
- package/src/user-config.ts +76 -7
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +0 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-e061bb24cd2b5cde.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +0 -2
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +0 -25
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +0 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +0 -1
- /package/apps/pwa/out/_next/static/{J5H7BvqgSI8DUjtRRcA_S → MObU8ujkMciAKr6ljAV05}/_ssgManifest.js +0 -0
- /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
|
@@ -36,7 +36,7 @@ const SCHEMA_COLUMNS: Record<string, readonly string[]> = {
|
|
|
36
36
|
imported_versions: ['json_hash', 'imported_at'],
|
|
37
37
|
events: ['reason', 'generation'],
|
|
38
38
|
assignments: ['priority', 'predecessors', 'deadline_version', 'ceo_minutes', 'ceo_date', 'accelerator', 'refs'],
|
|
39
|
-
release_schedules: ['freeze_from', 'freeze_until'],
|
|
39
|
+
release_schedules: ['freeze_from', 'freeze_until', 'publish_at'],
|
|
40
40
|
};
|
|
41
41
|
function schemaCurrent(db: Database): boolean {
|
|
42
42
|
return Object.entries(SCHEMA_COLUMNS).every(([table, names]) => {
|
|
@@ -83,16 +83,16 @@ function open(root = releaseLedgerRoot()): Database {
|
|
|
83
83
|
if (!assignmentColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE assignments ADD COLUMN ${name} ${sql}`);
|
|
84
84
|
}
|
|
85
85
|
const scheduleColumns = db.query('PRAGMA table_info(release_schedules)').all() as Array<{ name: string }>;
|
|
86
|
-
for (const name of ['freeze_from', 'freeze_until']) if (!scheduleColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE release_schedules ADD COLUMN ${name} TEXT`);
|
|
86
|
+
for (const name of ['freeze_from', 'freeze_until', 'publish_at']) if (!scheduleColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE release_schedules ADD COLUMN ${name} TEXT`);
|
|
87
87
|
}).immediate();
|
|
88
88
|
return db;
|
|
89
89
|
} catch (error) { db.close(); throw error; }
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
export interface ScheduleRow { version: string; cutAt: string; landBy: string | null; freezeFrom?: string | null; freezeUntil?: string | null; updatedAt: string; updatedBy: string }
|
|
93
|
-
type StoredSchedule = { version: string; cut_at: string; land_by: string | null; freeze_from: string | null; freeze_until: string | null; updated_at: string; updated_by: string };
|
|
92
|
+
export interface ScheduleRow { version: string; cutAt: string; landBy: string | null; freezeFrom?: string | null; freezeUntil?: string | null; publishAt?: string | null; updatedAt: string; updatedBy: string }
|
|
93
|
+
type StoredSchedule = { version: string; cut_at: string; land_by: string | null; freeze_from: string | null; freeze_until: string | null; publish_at?: string | null; updated_at: string; updated_by: string };
|
|
94
94
|
function scheduleRow(row: StoredSchedule): ScheduleRow {
|
|
95
|
-
return { version: row.version, cutAt: row.cut_at, landBy: row.land_by, ...(row.freeze_from ? { freezeFrom: row.freeze_from } : {}), ...(row.freeze_until ? { freezeUntil: row.freeze_until } : {}), updatedAt: row.updated_at, updatedBy: row.updated_by };
|
|
95
|
+
return { version: row.version, cutAt: row.cut_at, landBy: row.land_by, ...(row.freeze_from ? { freezeFrom: row.freeze_from } : {}), ...(row.freeze_until ? { freezeUntil: row.freeze_until } : {}), ...(row.publish_at ? { publishAt: row.publish_at } : {}), updatedAt: row.updated_at, updatedBy: row.updated_by };
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
export function readSchedule(version: string, root?: string): ScheduleRow | null {
|
|
@@ -119,7 +119,7 @@ export function assignedVersions(root = releaseLedgerRoot()): string[] {
|
|
|
119
119
|
finally { db.close(); }
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
export function writeSchedule(version: string, patch: { cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string }, by: string, root?: string): ScheduleRow {
|
|
122
|
+
export function writeSchedule(version: string, patch: { cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string; publishAt?: string }, by: string, root?: string): ScheduleRow {
|
|
123
123
|
validateVersion(version);
|
|
124
124
|
const db = open(root);
|
|
125
125
|
try {
|
|
@@ -130,19 +130,22 @@ export function writeSchedule(version: string, patch: { cutAt?: string; landBy?:
|
|
|
130
130
|
const landBy = patch.landBy ?? previous?.land_by ?? null;
|
|
131
131
|
const freezeFrom = patch.freezeFrom ?? previous?.freeze_from ?? null;
|
|
132
132
|
const freezeUntil = patch.freezeUntil ?? previous?.freeze_until ?? null;
|
|
133
|
+
const publishAt = patch.publishAt ?? previous?.publish_at ?? null;
|
|
134
|
+
if (publishAt && Date.parse(publishAt) <= Date.parse(cutAt)) throw new CliUserError('발행 시각은 컷보다 뒤여야 한다');
|
|
133
135
|
if (Boolean(freezeFrom) !== Boolean(freezeUntil) || (freezeFrom && freezeUntil && Date.parse(freezeFrom) >= Date.parse(freezeUntil))) throw new CliUserError('동결 시작·끝은 함께 주고 시작이 끝보다 앞서야 한다');
|
|
134
|
-
if (previous && previous.cut_at === cutAt && previous.land_by === landBy && previous.freeze_from === freezeFrom && previous.freeze_until === freezeUntil) return scheduleRow(previous);
|
|
136
|
+
if (previous && previous.cut_at === cutAt && previous.land_by === landBy && previous.freeze_from === freezeFrom && previous.freeze_until === freezeUntil && (previous.publish_at ?? null) === publishAt) return scheduleRow(previous);
|
|
135
137
|
const at = new Date().toISOString();
|
|
136
|
-
db.query(`INSERT INTO release_schedules (version, cut_at, land_by, freeze_from, freeze_until, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
137
|
-
ON CONFLICT(version) DO UPDATE SET cut_at=excluded.cut_at, land_by=excluded.land_by, freeze_from=excluded.freeze_from, freeze_until=excluded.freeze_until, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
138
|
-
.run(version, cutAt, landBy, freezeFrom, freezeUntil, at, by);
|
|
138
|
+
db.query(`INSERT INTO release_schedules (version, cut_at, land_by, freeze_from, freeze_until, publish_at, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
139
|
+
ON CONFLICT(version) DO UPDATE SET cut_at=excluded.cut_at, land_by=excluded.land_by, freeze_from=excluded.freeze_from, freeze_until=excluded.freeze_until, publish_at=excluded.publish_at, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
140
|
+
.run(version, cutAt, landBy, freezeFrom, freezeUntil, publishAt, at, by);
|
|
139
141
|
for (const [field, from, to] of [
|
|
140
142
|
['cut_at', previous?.cut_at ?? null, cutAt], ['land_by', previous?.land_by ?? null, landBy],
|
|
141
143
|
['freeze_from', previous?.freeze_from ?? null, freezeFrom], ['freeze_until', previous?.freeze_until ?? null, freezeUntil],
|
|
144
|
+
['publish_at', previous?.publish_at ?? null, publishAt],
|
|
142
145
|
] as const) {
|
|
143
146
|
if (from !== to) record(db, version, { at, by, id: '@version', field, from, to, released: releasedVersion(root), dev: devVersion() });
|
|
144
147
|
}
|
|
145
|
-
return { version, cutAt, landBy, updatedAt: at, updatedBy: by };
|
|
148
|
+
return { version, cutAt, landBy, ...(publishAt ? { publishAt } : {}), updatedAt: at, updatedBy: by };
|
|
146
149
|
});
|
|
147
150
|
} finally { db.close(); }
|
|
148
151
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// GATE-LIVE-OBS — 게이트 «조각 상태» 한 장(`<ledger>/release/<v>/gate-logs/cut/shards.json`)의 계약.
|
|
2
|
+
// 쓰는 쪽 = gate-node(TC · GATE-TIMEOUT-HEAL) · 읽는 쪽 = /v1/ops/release/runs ⊕ 외출 알림(UX).
|
|
3
|
+
// 로그가 아니라 발행 원장 파일인 이유: 게이트 사건은 실행 트리의 test 우주로 가지만 원장은 운영 한 곳이다(TC·OP 10-07 19:12).
|
|
4
|
+
// ⛔ 못 읽으면 `null` — 「조각 0」으로 읽지 않는다.
|
|
5
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
8
|
+
|
|
9
|
+
export const SHARD_STATES = ['pending', 'running', 'done', 'retry', 'timeout', 'failed'] as const;
|
|
10
|
+
export type ShardState = typeof SHARD_STATES[number];
|
|
11
|
+
|
|
12
|
+
export interface GateShard {
|
|
13
|
+
id: string;
|
|
14
|
+
state: ShardState;
|
|
15
|
+
startedAt?: string;
|
|
16
|
+
endedAt?: string;
|
|
17
|
+
/** 설치(install) 단계에 쓴 초. */
|
|
18
|
+
installSec?: number;
|
|
19
|
+
rc?: number;
|
|
20
|
+
/** 대기 이유(예: «CPU 부족 31.2/32»). pending 일 때 화면이 그대로 보인다. */
|
|
21
|
+
waitReason?: string;
|
|
22
|
+
/** 계획 분 — 남은 시간 추정의 재료. */
|
|
23
|
+
plannedMin?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface GateShardsFile {
|
|
27
|
+
v: 1;
|
|
28
|
+
version: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
shards: GateShard[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function gateShardsPath(version: string, root: string = releaseLedgerRoot()): string {
|
|
34
|
+
return join(root, 'release', version, 'gate-logs', 'cut', 'shards.json');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const str = (value: unknown, max = 160): string | undefined =>
|
|
38
|
+
typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : undefined;
|
|
39
|
+
const num = (value: unknown): number | undefined => typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
40
|
+
|
|
41
|
+
function shardOf(value: unknown): GateShard | null {
|
|
42
|
+
if (!value || typeof value !== 'object') return null;
|
|
43
|
+
const raw = value as Record<string, unknown>;
|
|
44
|
+
const id = str(raw.id, 80);
|
|
45
|
+
const state = SHARD_STATES.find((s) => s === raw.state);
|
|
46
|
+
if (!id || !state) return null;
|
|
47
|
+
const startedAt = str(raw.startedAt, 40);
|
|
48
|
+
const endedAt = str(raw.endedAt, 40);
|
|
49
|
+
const installSec = num(raw.installSec);
|
|
50
|
+
const rc = num(raw.rc);
|
|
51
|
+
const waitReason = str(raw.waitReason);
|
|
52
|
+
const plannedMin = num(raw.plannedMin);
|
|
53
|
+
return {
|
|
54
|
+
id, state,
|
|
55
|
+
...(startedAt ? { startedAt } : {}), ...(endedAt ? { endedAt } : {}),
|
|
56
|
+
...(installSec !== undefined ? { installSec } : {}), ...(rc !== undefined ? { rc } : {}),
|
|
57
|
+
...(waitReason ? { waitReason } : {}), ...(plannedMin !== undefined ? { plannedMin } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 파일이 없거나 깨졌으면 null. 알 수 없는 상태값의 조각은 버리되 파일 전체는 살린다. */
|
|
62
|
+
export function readGateShards(path: string): GateShardsFile | null {
|
|
63
|
+
let parsed: unknown;
|
|
64
|
+
try { parsed = JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
65
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
66
|
+
const raw = parsed as Record<string, unknown>;
|
|
67
|
+
if (raw.v !== 1 || !Array.isArray(raw.shards)) return null;
|
|
68
|
+
const version = str(raw.version, 40);
|
|
69
|
+
const updatedAt = str(raw.updatedAt, 40);
|
|
70
|
+
if (!version || !updatedAt) return null;
|
|
71
|
+
return { v: 1, version, updatedAt, shards: raw.shards.map(shardOf).filter((s): s is GateShard => s !== null) };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** tmp → rename — 읽는 쪽이 반쯤 쓴 파일을 보지 않게. */
|
|
75
|
+
export function writeGateShards(path: string, file: GateShardsFile): void {
|
|
76
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
77
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
78
|
+
writeFileSync(tmp, `${JSON.stringify(file)}\n`);
|
|
79
|
+
renameSync(tmp, path);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface GateShardsSummary {
|
|
83
|
+
total: number;
|
|
84
|
+
counts: Record<ShardState, number>;
|
|
85
|
+
/** 대기 이유별 조각 수(많은 순). */
|
|
86
|
+
waitReasons: Array<{ reason: string; count: number }>;
|
|
87
|
+
/** 남은 분 추정 — 계획 분이 모자라면 null(«추정 불가»). */
|
|
88
|
+
etaMin: number | null;
|
|
89
|
+
/** 끝났지만 rc≠0 인 조각 수 — 쓰는 쪽의 `done` 은 «조각 끝»일 뿐 «통과»가 아니다(판정은 게이트 노드가 기존/새로 가른다).
|
|
90
|
+
* 0.2.20 컷 실측: 끝난 넷이 모두 rc 1 인데 화면이 «통과»로 읽었다. */
|
|
91
|
+
doneWithFailures: number;
|
|
92
|
+
/** 도는 조각 중 계획 분을 가장 많이 넘긴 분(없으면 0). 넘겼으면 etaMin 은 «하한»일 뿐이라 화면이 그렇게 말한다
|
|
93
|
+
* — canary 10-07 실측: 계획 0.1분 · 실제 22분 → 넘김을 안 보이면 내내 «남은 약 0분». */
|
|
94
|
+
overrunMin: number;
|
|
95
|
+
/** 파일이 마지막으로 바뀐 뒤 지난 분. 크면 쓰는 쪽이 멎었다는 뜻이다. */
|
|
96
|
+
staleMin: number | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const minutesBetween = (fromIso: string | undefined, now: number): number | null => {
|
|
100
|
+
const at = fromIso ? Date.parse(fromIso) : Number.NaN;
|
|
101
|
+
return Number.isFinite(at) ? Math.max(0, (now - at) / 60_000) : null;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 남은 시간 추정: 도는 조각은 «계획 − 경과», 기다리는 조각(pending·retry)은 계획 분을 지금 도는 자리 수로 나눠 뒤에 잇는다.
|
|
106
|
+
* 도는 조각이 없으면 자리 1로 본다. 계획 분이 없는 조각이 하나라도 남아 있으면 null.
|
|
107
|
+
*/
|
|
108
|
+
function estimate(shards: GateShard[], now: number): number | null {
|
|
109
|
+
const running = shards.filter((s) => s.state === 'running');
|
|
110
|
+
const waiting = shards.filter((s) => s.state === 'pending' || s.state === 'retry');
|
|
111
|
+
if (!running.length && !waiting.length) return 0;
|
|
112
|
+
if ([...running, ...waiting].some((s) => s.plannedMin === undefined)) return null;
|
|
113
|
+
const runningLeft = running.map((s) => Math.max(0, s.plannedMin! - (minutesBetween(s.startedAt, now) ?? 0)));
|
|
114
|
+
const slots = Math.max(1, running.length);
|
|
115
|
+
const waitingSum = waiting.reduce((sum, s) => sum + s.plannedMin!, 0);
|
|
116
|
+
return Math.ceil(Math.max(0, ...runningLeft) + waitingSum / slots);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function summarizeShards(file: GateShardsFile, now: number = Date.now()): GateShardsSummary {
|
|
120
|
+
const counts = Object.fromEntries(SHARD_STATES.map((s) => [s, 0])) as Record<ShardState, number>;
|
|
121
|
+
const reasons = new Map<string, number>();
|
|
122
|
+
for (const shard of file.shards) {
|
|
123
|
+
counts[shard.state] += 1;
|
|
124
|
+
if (shard.waitReason && (shard.state === 'pending' || shard.state === 'retry')) {
|
|
125
|
+
reasons.set(shard.waitReason, (reasons.get(shard.waitReason) ?? 0) + 1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const stale = minutesBetween(file.updatedAt, now);
|
|
129
|
+
const overrun = Math.max(0, ...file.shards
|
|
130
|
+
.filter((s) => s.state === 'running' && s.plannedMin !== undefined)
|
|
131
|
+
.map((s) => (minutesBetween(s.startedAt, now) ?? 0) - s.plannedMin!));
|
|
132
|
+
return {
|
|
133
|
+
total: file.shards.length,
|
|
134
|
+
counts,
|
|
135
|
+
waitReasons: [...reasons].map(([reason, count]) => ({ reason, count })).sort((a, b) => b.count - a.count),
|
|
136
|
+
doneWithFailures: file.shards.filter((s) => s.state === 'done' && s.rc !== undefined && s.rc !== 0).length,
|
|
137
|
+
etaMin: estimate(file.shards, now),
|
|
138
|
+
overrunMin: Math.floor(overrun),
|
|
139
|
+
staleMin: stale === null ? null : Math.floor(stale),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const SHARD_WORD: Record<ShardState, string> = {
|
|
144
|
+
pending: '대기', running: '돌기', done: '끝', retry: '재시도', timeout: '잘림', failed: '실패',
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** 외출 알림·/release 한 줄 — «조각 24 · 돌기 5 · 대기 8(CPU 부족 8) · 잘림 2 · 통과 9 · 남은 약 40분». */
|
|
148
|
+
export function shardsLine(summary: GateShardsSummary): string {
|
|
149
|
+
const order: ShardState[] = ['running', 'pending', 'retry', 'timeout', 'failed', 'done'];
|
|
150
|
+
const parts = [`조각 ${summary.total}`];
|
|
151
|
+
for (const state of order) {
|
|
152
|
+
const n = summary.counts[state];
|
|
153
|
+
if (!n) continue;
|
|
154
|
+
const why = state === 'pending' && summary.waitReasons.length
|
|
155
|
+
? `(${summary.waitReasons.slice(0, 2).map((w) => `${w.reason} ${w.count}`).join(' · ')})` : '';
|
|
156
|
+
const reported = state === 'done' && summary.doneWithFailures ? `(실패 보고 ${summary.doneWithFailures})` : '';
|
|
157
|
+
parts.push(`${SHARD_WORD[state]} ${n}${why}${reported}`);
|
|
158
|
+
}
|
|
159
|
+
if (summary.overrunMin >= 1) parts.push(`계획보다 ${summary.overrunMin}분 넘게 도는 중 — 남은 시간 추정 불가`);
|
|
160
|
+
else parts.push(summary.etaMin === null ? '남은 시간 추정 불가(계획 분 없음)' : `남은 약 ${summary.etaMin}분`);
|
|
161
|
+
if (summary.staleMin !== null && summary.staleMin >= 15) parts.push(`⚠️ ${summary.staleMin}분째 갱신 없음`);
|
|
162
|
+
return parts.join(' · ');
|
|
163
|
+
}
|
|
@@ -35,12 +35,13 @@ export function listSchedules(root?: string): ReleaseSchedule[] {
|
|
|
35
35
|
return schedules;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export function setSchedule(version: string, patch: { cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string }, by: string, root?: string): ReleaseSchedule {
|
|
38
|
+
export function setSchedule(version: string, patch: { cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string; publishAt?: string }, by: string, root?: string): ReleaseSchedule {
|
|
39
39
|
const schedule = writeSchedule(version, {
|
|
40
40
|
...(patch.cutAt !== undefined ? { cutAt: utcIso(patch.cutAt) } : {}),
|
|
41
41
|
...(patch.landBy !== undefined ? { landBy: utcIso(patch.landBy) } : {}),
|
|
42
42
|
...(patch.freezeFrom !== undefined ? { freezeFrom: utcIso(patch.freezeFrom) } : {}),
|
|
43
43
|
...(patch.freezeUntil !== undefined ? { freezeUntil: utcIso(patch.freezeUntil) } : {}),
|
|
44
|
+
...(patch.publishAt !== undefined ? { publishAt: utcIso(patch.publishAt) } : {}),
|
|
44
45
|
}, by, root);
|
|
45
46
|
debug.log('release.schedule', 'set', { version, cutAt: schedule.cutAt, landBy: schedule.landBy });
|
|
46
47
|
return schedule;
|
|
@@ -51,5 +52,8 @@ export function formatSchedule(schedule: ReleaseSchedule): string {
|
|
|
51
52
|
const deadline = schedule.landBy ? formatKst(schedule.landBy) : null;
|
|
52
53
|
const sameDay = schedule.landBy && new Date(Date.parse(schedule.landBy) + 9 * 60 * 60_000).toISOString().slice(0, 10)
|
|
53
54
|
=== new Date(Date.parse(schedule.cutAt) + 9 * 60 * 60_000).toISOString().slice(0, 10);
|
|
54
|
-
|
|
55
|
+
const publish = schedule.publishAt ? formatKst(schedule.publishAt) : null;
|
|
56
|
+
const publishSameDay = schedule.publishAt && new Date(Date.parse(schedule.publishAt) + 9 * 60 * 60_000).toISOString().slice(0, 10)
|
|
57
|
+
=== new Date(Date.parse(schedule.cutAt) + 9 * 60 * 60_000).toISOString().slice(0, 10);
|
|
58
|
+
return `${schedule.version} 컷 ${cut}${deadline ? ` · 착지 마감 ${sameDay ? deadline.split(' ')[1] : deadline}` : ''}${publish ? ` · 발행 ${publishSameDay ? publish.split(' ')[1] : publish}` : ''}`;
|
|
55
59
|
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* RETRO-DAILY — 매일 회고 그래프(`graphs/retro/daily.yaml`)의 노드 본체.
|
|
4
|
+
*
|
|
5
|
+
* collect → cluster → recur → insight → act → report
|
|
6
|
+
*
|
|
7
|
+
* - 사실은 `elanous retro facts --since 24h --json`(RETRO-FACTS) 에서만 온다. 글은 읽지 않는다.
|
|
8
|
+
* - 반복(지난 7일 회고 파일)과 «초록으로 닫은 뒤 다시 깨진 것»을 1순위로 올린다.
|
|
9
|
+
* - 조치는 최대 셋, 칸 «초안 글»뿐이다 — 체크리스트에 쓰지 않는다(그림자).
|
|
10
|
+
* - `retro.daily.mode` = shadow(기본) | live. shadow 는 체크리스트·메시지·브리핑 어느 것도 건드리지 않는다.
|
|
11
|
+
* live 는 day 파일에 `briefing: true` 를 찍어 08:30 데일리 리뷰가 세 줄을 싣게 한다(체크리스트 쓰기는 아직 없다).
|
|
12
|
+
*
|
|
13
|
+
* RFC: 내부 문서 `RFC-retro-loop-daily-weekly-insight-2026-10-07` §1 (§0 표가 첫 판의 정답지).
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
import { spawnSync } from 'node:child_process';
|
|
17
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { dirname, join, resolve } from 'node:path';
|
|
19
|
+
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
20
|
+
import { getUserConfig } from '../user-config.js';
|
|
21
|
+
import { debug } from '../debug/log.js';
|
|
22
|
+
|
|
23
|
+
export const RETRO_DAILY_GRAPH_ID = 'retro-daily';
|
|
24
|
+
export const RETRO_MAX_ACTIONS = 3;
|
|
25
|
+
export const RETRO_LOOKBACK_DAYS = 7;
|
|
26
|
+
const MAX_FACTS = 2000;
|
|
27
|
+
|
|
28
|
+
export type RetroMode = 'shadow' | 'live';
|
|
29
|
+
export type RetroStage = 'collect' | 'cluster' | 'recur' | 'insight' | 'act' | 'report';
|
|
30
|
+
export const RETRO_STAGES: readonly RetroStage[] = ['collect', 'cluster', 'recur', 'insight', 'act', 'report'];
|
|
31
|
+
|
|
32
|
+
export type RetroFactIn = { store?: string; at: string; category?: string; event?: string; kind: string; data?: unknown };
|
|
33
|
+
export type RetroFact = RetroFactIn & { id: string; signature: string; text: string };
|
|
34
|
+
/** `greenAt` = 그 칸이 마지막으로 green/done 이 된 시각(체크리스트 history). 모르면 비운다 — «모름»은 «재발 아님»으로 읽지 않는다. */
|
|
35
|
+
export type GreenCell = { id: string; title?: string; status: string; version?: string; greenAt?: string };
|
|
36
|
+
|
|
37
|
+
export type RetroTheme = {
|
|
38
|
+
id: string;
|
|
39
|
+
title: string;
|
|
40
|
+
owner: string;
|
|
41
|
+
/** 사실의 서명·본문에 맞는 낱말. 순서대로 처음 맞는 것이 이긴다. */
|
|
42
|
+
pattern: RegExp;
|
|
43
|
+
/** RFC §0 의 «한 단 위 원인» 틀. 사실이 이 틀을 «증명»하지는 않는다 — insight 가 사실 id 로 근거를 단다. */
|
|
44
|
+
rootCause: string;
|
|
45
|
+
falsifier: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* RFC §0 다섯 반복 문제의 서명 사전. 순서가 의미를 갖는다(조용한 실패가 draft 낱말을 같이 가져도 «조용한 실패»다).
|
|
50
|
+
* 새 반복이 생기면 여기에 줄을 더한다 — 사전에 없는 서명은 `sig:<서명>` 주제로 그대로 남는다(버리지 않는다).
|
|
51
|
+
*/
|
|
52
|
+
export const RETRO_THEMES: readonly RetroTheme[] = [
|
|
53
|
+
{ id: 'silent-failure', title: '조용한 실패', owner: 'TC',
|
|
54
|
+
pattern: /silent|조용|send-disabled|deliveryState|delivery-state|truncat|절단|no[-_ ]?alert|not[-_ ]?sent|\/tmp|log[-_ ]?only|repeated_failure/i,
|
|
55
|
+
rootCause: '«돌았다»를 «일했다»로 읽는다 — 실패가 알림으로 가지 않는다',
|
|
56
|
+
falsifier: '루프 failed·deliveryState≠sent 가 알림 없이 24h 넘게 이어진 수' },
|
|
57
|
+
{ id: 'universe-confusion', title: '우주 혼동', owner: 'TC',
|
|
58
|
+
pattern: /universe|우주|test[-_ ]?instance|config-dir|empty[-_ ]?value|빈 값/i,
|
|
59
|
+
rootCause: '자리 트리 = test 우주라는 기본값이 사람·에이전트 직관과 반대다',
|
|
60
|
+
falsifier: 'test 우주를 읽어 «빈 값»으로 오판한 수/일' },
|
|
61
|
+
{ id: 'serialization', title: '직렬화 재발', owner: 'OP',
|
|
62
|
+
pattern: /serial|직렬|overlap|겹침|hotPaths|hot-path|order[-_ ]?edge/i,
|
|
63
|
+
rootCause: '«병렬 우선»(R-RUN17)이 규칙에는 있는데 새 도구를 만들 때 검사하지 않는다',
|
|
64
|
+
falsifier: '겹침을 이유로 뺀 칸·순서 간선 수/일' },
|
|
65
|
+
{ id: 'unreadable-stop', title: '멈춤을 읽을 수 없음', owner: 'TC',
|
|
66
|
+
pattern: /unreadable|unknown|읽을 수 없|non[-_ ]?convergen|unconvergeable|비수렴|no[-_ ]?reason/i,
|
|
67
|
+
rootCause: '멈춤이 사람이 읽는 글로만 남는다 — 기계가 다음 수를 고를 어휘가 없다',
|
|
68
|
+
falsifier: '멈춤 사유를 읽을 수 없는 draft 비율' },
|
|
69
|
+
{ id: 'draft-backlog', title: 'draft 적체', owner: 'OP',
|
|
70
|
+
pattern: /draft|rework|supersede|적체|DRAFT/i,
|
|
71
|
+
rootCause: '고친 것을 «초록»으로 닫고 효과를 다시 재지 않았다',
|
|
72
|
+
falsifier: '열린 draft 수(7일 이동평균)' },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// ── 작은 도구 ──────────────────────────────────────────────────────────
|
|
76
|
+
const record = (value: unknown): Record<string, unknown> => value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
77
|
+
const str = (value: unknown): string => typeof value === 'string' ? value : '';
|
|
78
|
+
export const kstDay = (date: Date): string => new Date(date.getTime() + 9 * 3_600_000).toISOString().slice(0, 10);
|
|
79
|
+
const norm = (value: string): string => value.trim().toLowerCase().replace(/\s+/g, '-').replace(/[^\p{L}\p{N}._:/-]+/gu, '').slice(0, 80) || 'none';
|
|
80
|
+
|
|
81
|
+
export function retroDailyDir(root: string): string { return join(root, 'retro', 'daily'); }
|
|
82
|
+
export function retroDayFile(root: string, day: string): string { return join(retroDailyDir(root), `${day}.json`); }
|
|
83
|
+
|
|
84
|
+
export function factId(fact: RetroFactIn): string {
|
|
85
|
+
return `F-${createHash('sha1').update(JSON.stringify([fact.store ?? '', fact.at, fact.category ?? '', fact.event ?? '', fact.kind, fact.data ?? null])).digest('hex').slice(0, 8)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 서명 = 묶음 열쇠. 실패 테스트 > 멈춤 사유 > 카드 종류 순으로 고른다(RFC §1 cluster). */
|
|
89
|
+
export function factSignature(fact: RetroFactIn): string {
|
|
90
|
+
const data = record(fact.data);
|
|
91
|
+
const test = str(data.failingTest) || str(data.test) || (Array.isArray(data.failingTests) ? str(data.failingTests[0]) : '');
|
|
92
|
+
if (test) return `test:${norm(test)}`;
|
|
93
|
+
if (fact.kind === 'draft-birth' || fact.kind === 'draft-death') return fact.kind;
|
|
94
|
+
if (fact.kind === 'stop-reason') return `stop:${norm(str(data.stopReason) || 'unknown')}`;
|
|
95
|
+
if (fact.kind === 'ta-outcome') {
|
|
96
|
+
const result = str(data.result);
|
|
97
|
+
// 결과를 모르면 ok 로 접지 않는다 — 판정할 낱말이 없으면 «no-result» 로 남긴다.
|
|
98
|
+
const verdict = !result.trim() ? 'no-result' : /fail|error|실패|abandon|stuck|멈춤/i.test(result) ? 'failed'
|
|
99
|
+
: /\b(ok|done|success|merged|landed|passed|updated|completed)\b|완료|성공|착지/i.test(result) ? 'ok' : 'unjudged';
|
|
100
|
+
return `card:${norm(str(data.cardKind) || str(data.card) || 'task-agent')}:${verdict}`;
|
|
101
|
+
}
|
|
102
|
+
return `${norm(fact.kind)}:${norm(str(data.signature) || str(data.cardKind) || str(data.reason) || str(data.stopReason) || '')}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function factText(fact: RetroFactIn): string {
|
|
106
|
+
const data = record(fact.data);
|
|
107
|
+
return [fact.category, fact.event, str(data.stopReason), str(data.result), str(data.reason), str(data.summary), str(data.title), str(data.cardKind)]
|
|
108
|
+
.filter(Boolean).join(' · ').slice(0, 300);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function themeOf(signature: string, texts: readonly string[]): RetroTheme | undefined {
|
|
112
|
+
if (signature === 'draft-birth' || signature === 'draft-death') return RETRO_THEMES.find(t => t.id === 'draft-backlog');
|
|
113
|
+
// 서명이 먼저 말하면 서명을 믿는다 — 본문은 서명이 침묵할 때만 본다.
|
|
114
|
+
return RETRO_THEMES.find(t => t.pattern.test(signature)) ?? RETRO_THEMES.find(t => texts.some(text => t.pattern.test(text)));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── 단계 순수 함수 ─────────────────────────────────────────────────────
|
|
118
|
+
export function normalizeFacts(raw: unknown): { facts: RetroFact[]; truncated: boolean } {
|
|
119
|
+
const list = Array.isArray(raw) ? raw : Array.isArray(record(raw).facts) ? record(raw).facts as unknown[] : [];
|
|
120
|
+
const facts = list.map(record).filter(f => typeof f.at === 'string' && typeof f.kind === 'string')
|
|
121
|
+
.map(f => {
|
|
122
|
+
const fact = f as unknown as RetroFactIn;
|
|
123
|
+
return { ...fact, id: factId(fact), signature: factSignature(fact), text: factText(fact) };
|
|
124
|
+
});
|
|
125
|
+
return { facts: facts.slice(0, MAX_FACTS), truncated: facts.length > MAX_FACTS };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type Cluster = { signature: string; theme: string; themeTitle: string; count: number; factIds: string[]; lastAt: string; samples: string[] };
|
|
129
|
+
|
|
130
|
+
export function clusterFacts(facts: readonly RetroFact[]): Cluster[] {
|
|
131
|
+
const groups = new Map<string, RetroFact[]>();
|
|
132
|
+
for (const fact of facts) groups.set(fact.signature, [...(groups.get(fact.signature) ?? []), fact]);
|
|
133
|
+
return [...groups.entries()].map(([signature, members]) => {
|
|
134
|
+
const theme = themeOf(signature, members.map(m => m.text));
|
|
135
|
+
return { signature, theme: theme?.id ?? `sig:${signature}`, themeTitle: theme?.title ?? signature, count: members.length,
|
|
136
|
+
factIds: members.map(m => m.id), lastAt: members.map(m => m.at).sort().at(-1) ?? '', samples: [...new Set(members.map(m => m.text).filter(Boolean))].slice(0, 3) };
|
|
137
|
+
}).sort((a, b) => b.count - a.count || a.signature.localeCompare(b.signature));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type PriorDay = { day: string; themes: string[]; signatures: string[]; actedThemes: string[] };
|
|
141
|
+
export type Recurrence = {
|
|
142
|
+
theme: string; title: string; count: number; signatures: string[]; factIds: string[];
|
|
143
|
+
daysSeen: string[]; actedBefore: string[]; greenCells: string[];
|
|
144
|
+
/** 초록(green/done) 칸이 «사실보다 먼저» 있었는데 다시 나왔다. 지난 조치 «초안»은 초록이 아니다(→ recurAfterAction). */
|
|
145
|
+
reBrokeAfterGreen: boolean;
|
|
146
|
+
/** 지난 회고가 조치 초안을 냈는데 다시 나왔다. 1순위 다음. */
|
|
147
|
+
recurAfterAction: boolean; recurring: boolean; rank: number;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export function readPriorDays(root: string, day: string, lookback = RETRO_LOOKBACK_DAYS): PriorDay[] {
|
|
151
|
+
const dir = retroDailyDir(root);
|
|
152
|
+
if (!existsSync(dir)) return [];
|
|
153
|
+
return readdirSync(dir).filter(name => /^\d{4}-\d{2}-\d{2}\.json$/.test(name) && name.slice(0, 10) < day).sort().slice(-lookback)
|
|
154
|
+
.flatMap((name) => {
|
|
155
|
+
try {
|
|
156
|
+
const file = record(JSON.parse(readFileSync(join(dir, name), 'utf8')));
|
|
157
|
+
return [{ day: name.slice(0, 10),
|
|
158
|
+
themes: Array.isArray(file.themes) ? file.themes.map(t => str(record(t).theme)).filter(Boolean) : [],
|
|
159
|
+
signatures: Array.isArray(file.clusters) ? file.clusters.map(c => str(record(c).signature)).filter(Boolean) : [],
|
|
160
|
+
actedThemes: Array.isArray(file.actions) ? file.actions.map(a => str(record(a).theme)).filter(Boolean) : [] }];
|
|
161
|
+
} catch { return []; }
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** RFC §1 recur — ① 지난 회고 ② 이미 green/done 인 칸 ③ 지난 조치. «초록 뒤 재발»이 1순위다. */
|
|
166
|
+
export function findRecurrences(clusters: readonly Cluster[], prior: readonly PriorDay[], greenCells: readonly GreenCell[]): Recurrence[] {
|
|
167
|
+
const byTheme = new Map<string, Cluster[]>();
|
|
168
|
+
for (const cluster of clusters) byTheme.set(cluster.theme, [...(byTheme.get(cluster.theme) ?? []), cluster]);
|
|
169
|
+
const out = [...byTheme.entries()].map(([theme, members]) => {
|
|
170
|
+
const def = RETRO_THEMES.find(t => t.id === theme);
|
|
171
|
+
const signatures = members.map(m => m.signature);
|
|
172
|
+
const count = members.reduce((sum, m) => sum + m.count, 0);
|
|
173
|
+
const daysSeen = prior.filter(p => p.themes.includes(theme) || p.signatures.some(s => signatures.includes(s))).map(p => p.day);
|
|
174
|
+
const actedBefore = prior.filter(p => p.actedThemes.includes(theme)).map(p => p.day);
|
|
175
|
+
const lastAt = members.map(m => m.lastAt).sort().at(-1) ?? '';
|
|
176
|
+
// 초록이 된 시각이 그 주제의 마지막 사실보다 «앞»이어야 «초록 뒤 재발»이다. 시각을 모르면(greenAt 없음) 후보로 둔다.
|
|
177
|
+
const green = def ? greenCells.filter(c => (c.status === 'green' || c.status === 'done') && def.pattern.test(`${c.id} ${c.title ?? ''}`)
|
|
178
|
+
&& (!c.greenAt || !lastAt || Date.parse(c.greenAt) < Date.parse(lastAt)))
|
|
179
|
+
.map(c => `${c.id}${c.version ? `@${c.version}` : ''}${c.greenAt ? '' : '(시각 모름)'}`) : [];
|
|
180
|
+
const reBrokeAfterGreen = green.length > 0;
|
|
181
|
+
const recurAfterAction = actedBefore.length > 0;
|
|
182
|
+
const recurring = reBrokeAfterGreen || recurAfterAction || daysSeen.length > 0 || count >= 2;
|
|
183
|
+
return { theme, title: def?.title ?? members[0]!.themeTitle, count, signatures, factIds: members.flatMap(m => m.factIds),
|
|
184
|
+
daysSeen, actedBefore, greenCells: green, reBrokeAfterGreen, recurAfterAction, recurring, rank: 0 };
|
|
185
|
+
}).sort((a, b) => Number(b.reBrokeAfterGreen) - Number(a.reBrokeAfterGreen) || Number(b.recurAfterAction) - Number(a.recurAfterAction) || Number(b.recurring) - Number(a.recurring)
|
|
186
|
+
|| b.daysSeen.length - a.daysSeen.length || b.count - a.count || a.theme.localeCompare(b.theme));
|
|
187
|
+
return out.map((r, i) => ({ ...r, rank: i + 1 }));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export type Insight = { theme: string; title: string; text: string; rootCause: string; cites: string[] };
|
|
191
|
+
|
|
192
|
+
/** 결정적 insight — 사실 id 를 반드시 단다. (LLM 노드로 바꿀 때도 `cites` 검사는 그대로 남긴다.) */
|
|
193
|
+
export function buildInsights(recurrences: readonly Recurrence[]): Insight[] {
|
|
194
|
+
return recurrences.filter(r => r.recurring).map((r) => {
|
|
195
|
+
const def = RETRO_THEMES.find(t => t.id === r.theme);
|
|
196
|
+
const cites = r.factIds.slice(0, 5);
|
|
197
|
+
const why = [
|
|
198
|
+
r.greenCells.length ? `초록으로 닫은 칸(${r.greenCells.join(', ')}) 뒤 다시 나왔다` : '',
|
|
199
|
+
r.actedBefore.length ? `지난 회고 조치(${r.actedBefore.join(', ')}) 뒤에도 남았다` : '',
|
|
200
|
+
r.daysSeen.length ? `지난 ${r.daysSeen.length}일 회고에도 있었다` : '',
|
|
201
|
+
].filter(Boolean).join(' · ');
|
|
202
|
+
const rootCause = def?.rootCause ?? `서명 ${r.signatures.join(', ')} 이 반복된다 — 사전에 없는 반복이라 원인 틀이 아직 없다`;
|
|
203
|
+
return { theme: r.theme, title: r.title, rootCause, cites,
|
|
204
|
+
text: `${r.title} ${r.count}건 [${cites.join(', ')}]${why ? ` — ${why}` : ''} ⇒ ${rootCause}` };
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function assertInsightsCite(insights: readonly Insight[], facts: readonly { id: string }[]): void {
|
|
209
|
+
const known = new Set(facts.map(f => f.id));
|
|
210
|
+
for (const insight of insights) {
|
|
211
|
+
if (!insight.cites.length || insight.cites.some(id => !known.has(id))) throw new Error(`insight ${insight.theme} cites unknown or no fact ids`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export type DraftCell = { id: string; theme: string; title: string; owner: string; verdictLine: string; falsifier: string; evidence: string[]; status: 'draft' };
|
|
216
|
+
|
|
217
|
+
export function draftActions(insights: readonly Insight[], recurrences: readonly Recurrence[], day: string): DraftCell[] {
|
|
218
|
+
return insights.slice(0, RETRO_MAX_ACTIONS).map((insight) => {
|
|
219
|
+
const def = RETRO_THEMES.find(t => t.id === insight.theme);
|
|
220
|
+
const rec = recurrences.find(r => r.theme === insight.theme)!;
|
|
221
|
+
const slug = insight.theme.replace(/^sig:/, '').replace(/[^a-z0-9]+/gi, '-').toUpperCase().slice(0, 32);
|
|
222
|
+
return { id: `RETRO-${slug}-${day.replace(/-/g, '')}`, theme: insight.theme, status: 'draft' as const,
|
|
223
|
+
title: `${insight.title} 한 단 위 수리 — ${insight.rootCause}`, owner: def?.owner ?? 'OP',
|
|
224
|
+
verdictLine: rec.reBrokeAfterGreen || rec.recurAfterAction ? '다음 7일 회고에서 이 주제가 다시 나오지 않는다' : '다음 회고에서 이 주제의 건수가 줄어든다',
|
|
225
|
+
falsifier: def?.falsifier ?? `서명 ${rec.signatures.join(', ')} 건수/일`, evidence: insight.cites };
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function summaryLines(recurrences: readonly Recurrence[], actions: readonly DraftCell[], factCount: number, mode: RetroMode): string[] {
|
|
230
|
+
const top = recurrences.filter(r => r.recurring);
|
|
231
|
+
const first = top[0];
|
|
232
|
+
return [
|
|
233
|
+
first ? `회고 1순위: ${first.title} ${first.count}건${first.reBrokeAfterGreen ? ' · 초록 뒤 재발' : first.recurAfterAction ? ' · 조치 뒤 재발' : first.daysSeen.length ? ` · ${first.daysSeen.length}일째` : ''}` : '회고: 반복 없음',
|
|
234
|
+
`사실 ${factCount} · 반복 주제 ${top.length}(${top.slice(0, 5).map(r => r.title).join(' · ') || '없음'})`,
|
|
235
|
+
`조치 초안 ${actions.length}: ${actions.map(a => `${a.id}(${a.owner})`).join(', ') || '없음'} · ${mode}`,
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ── 모드 ───────────────────────────────────────────────────────────────
|
|
240
|
+
export function resolveRetroMode(input: Record<string, unknown>, readConfig: () => unknown = () => getUserConfig().raw): RetroMode {
|
|
241
|
+
// 입력은 «더 안전한 쪽»으로만 바꿀 수 있다(shadow 강제). live 는 config 에서만 온다.
|
|
242
|
+
if (input.mode === 'shadow') return 'shadow';
|
|
243
|
+
try { return record(record(record(readConfig()).retro).daily).mode === 'live' ? 'live' : 'shadow'; } catch { return 'shadow'; }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── 노드 실행 ─────────────────────────────────────────────────────────
|
|
247
|
+
type Ctx = { graphId?: string; nodeId?: string; input: Record<string, unknown> | null; outputs: Record<string, Record<string, unknown> | null> };
|
|
248
|
+
export type RetroDeps = { now?: Date; readFacts?: (since: string, root: string) => unknown; readGreenCells?: () => GreenCell[]; readConfig?: () => unknown; repo?: string };
|
|
249
|
+
|
|
250
|
+
const repoRoot = resolve(import.meta.dir, '..', '..');
|
|
251
|
+
|
|
252
|
+
function readFactsViaCli(since: string, root: string, repo = repoRoot): unknown {
|
|
253
|
+
const args = ['bin/elanous.mjs', ...(root === prodInstanceRoot() ? [] : [`--test=${root}`]), 'retro', 'facts', '--since', since, '--json'];
|
|
254
|
+
const result = spawnSync('bun', args, { cwd: repo, encoding: 'utf8', timeout: 570_000, maxBuffer: 64 * 1024 * 1024 });
|
|
255
|
+
if (result.error || result.status !== 0) throw new Error(`retro facts failed: ${result.error?.message ?? result.stderr?.trim() ?? `exit ${result.status}`}`);
|
|
256
|
+
return JSON.parse(result.stdout);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** 최근 판 열 개의 체크리스트에서 green/done 칸을 모은다(예: DRAFT3 0.2.12 · DRAFT-TRIAGE 0.2.18). 못 읽은 판은 건너뛴다. */
|
|
260
|
+
async function readGreenCellsFromChecklist(span = 10): Promise<GreenCell[]> {
|
|
261
|
+
const { listChecklist, checklistDevVersion } = await import('../release-loop/checklist.js');
|
|
262
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(checklistDevVersion());
|
|
263
|
+
if (!match) throw new Error(`unparseable version: ${checklistDevVersion()}`);
|
|
264
|
+
const cells: GreenCell[] = [];
|
|
265
|
+
for (let patch = Number(match[3]); patch >= Math.max(0, Number(match[3]) - span); patch--) {
|
|
266
|
+
const version = `${match[1]}.${match[2]}.${patch}`;
|
|
267
|
+
try {
|
|
268
|
+
// 체크리스트는 릴리스 원장(releaseLedgerRoot — 기본 prod)에 있다. 회고 런의 우주가 아니라 원장 우주를 읽는다(쓰기 없음).
|
|
269
|
+
const list = listChecklist(version);
|
|
270
|
+
const greenAt = (id: string) => list.history.filter(h => h.id === id && h.field === 'status' && (h.to === 'green' || h.to === 'done')).map(h => h.at).sort().at(-1);
|
|
271
|
+
cells.push(...list.items.filter(i => i.status === 'green' || i.status === 'done').map(i => ({ id: i.id, title: i.title, status: i.status, version, greenAt: greenAt(i.id) })));
|
|
272
|
+
}
|
|
273
|
+
catch { /* 그 판의 체크리스트가 없다 */ }
|
|
274
|
+
}
|
|
275
|
+
return cells;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function writeAtomic(file: string, text: string): void {
|
|
279
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
280
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
281
|
+
writeFileSync(tmp, text);
|
|
282
|
+
renameSync(tmp, file);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export async function runRetroStage(stage: RetroStage, ctx: Ctx, root: string, dryRun: boolean, deps: RetroDeps = {}): Promise<Record<string, unknown>> {
|
|
286
|
+
const input = ctx.input ?? {};
|
|
287
|
+
const now = deps.now ?? new Date();
|
|
288
|
+
const out = ctx.outputs;
|
|
289
|
+
const day = str(out.collect?.day) || str(input.day) || kstDay(now);
|
|
290
|
+
switch (stage) {
|
|
291
|
+
case 'collect': {
|
|
292
|
+
const since = str(input.since) || '24h';
|
|
293
|
+
const sources: Record<string, string> = {};
|
|
294
|
+
let raw: unknown;
|
|
295
|
+
if (typeof input.factsFile === 'string') { raw = JSON.parse(readFileSync(input.factsFile, 'utf8')); sources.facts = `file:${input.factsFile}`; }
|
|
296
|
+
else if (Array.isArray(input.facts)) { raw = input.facts; sources.facts = 'input'; }
|
|
297
|
+
else { raw = (deps.readFacts ?? ((s, r) => readFactsViaCli(s, r, deps.repo)))(since, root); sources.facts = `retro facts --since ${since}`; }
|
|
298
|
+
const { facts, truncated } = normalizeFacts(raw);
|
|
299
|
+
let greenCells: GreenCell[] = [];
|
|
300
|
+
if (Array.isArray(input.greenCells)) { greenCells = input.greenCells.map(record).map(c => ({ id: str(c.id), title: str(c.title), status: str(c.status), version: str(c.version) || undefined, greenAt: str(c.greenAt) || undefined })); sources.green = 'input'; }
|
|
301
|
+
else {
|
|
302
|
+
try { greenCells = (deps.readGreenCells ? deps.readGreenCells() : await readGreenCellsFromChecklist()).map(c => ({ ...c, title: (c.title ?? "").slice(0, 120) })); sources.green = 'checklist'; }
|
|
303
|
+
catch (error) { sources.green = `unreadable: ${error instanceof Error ? error.message.split('\n')[0] : String(error)}`; }
|
|
304
|
+
}
|
|
305
|
+
const unavailable = Array.isArray(record(raw).unavailable) ? record(raw).unavailable : [];
|
|
306
|
+
return { outcome: 'ok', day, since, mode: resolveRetroMode(input, deps.readConfig), facts, factCount: facts.length, truncated, unavailable, greenCells, sources };
|
|
307
|
+
}
|
|
308
|
+
case 'cluster': {
|
|
309
|
+
const clusters = clusterFacts((out.collect?.facts ?? []) as RetroFact[]);
|
|
310
|
+
return { outcome: 'ok', clusters };
|
|
311
|
+
}
|
|
312
|
+
case 'recur': {
|
|
313
|
+
const prior = readPriorDays(root, day);
|
|
314
|
+
const recurrences = findRecurrences((out.cluster?.clusters ?? []) as Cluster[], prior, (out.collect?.greenCells ?? []) as GreenCell[]);
|
|
315
|
+
return { outcome: 'ok', priorDays: prior.map(p => p.day), recurrences };
|
|
316
|
+
}
|
|
317
|
+
case 'insight': {
|
|
318
|
+
const insights = buildInsights((out.recur?.recurrences ?? []) as Recurrence[]);
|
|
319
|
+
assertInsightsCite(insights, (out.collect?.facts ?? []) as RetroFact[]);
|
|
320
|
+
return { outcome: 'ok', insights };
|
|
321
|
+
}
|
|
322
|
+
case 'act': {
|
|
323
|
+
// 그림자든 live 든 이 노드는 «글»만 낸다 — `checklist add` 를 부르지 않는다(RETRO-DAILY 첫 조각).
|
|
324
|
+
const actions = draftActions((out.insight?.insights ?? []) as Insight[], (out.recur?.recurrences ?? []) as Recurrence[], day);
|
|
325
|
+
return { outcome: 'ok', actions, wroteChecklist: false };
|
|
326
|
+
}
|
|
327
|
+
case 'report': {
|
|
328
|
+
const mode = (str(out.collect?.mode) || 'shadow') as RetroMode;
|
|
329
|
+
const facts = (out.collect?.facts ?? []) as RetroFact[];
|
|
330
|
+
const recurrences = (out.recur?.recurrences ?? []) as Recurrence[];
|
|
331
|
+
const actions = (out.act?.actions ?? []) as DraftCell[];
|
|
332
|
+
const summary = summaryLines(recurrences, actions, facts.length, mode);
|
|
333
|
+
const themes = recurrences.map(r => ({ theme: r.theme, title: r.title, count: r.count, recurring: r.recurring, reBrokeAfterGreen: r.reBrokeAfterGreen, recurAfterAction: r.recurAfterAction, greenCells: r.greenCells, rank: r.rank }));
|
|
334
|
+
const dayFile = { day, mode, briefing: mode === 'live', generatedAt: now.toISOString(), factCount: facts.length,
|
|
335
|
+
truncated: out.collect?.truncated === true, unavailable: out.collect?.unavailable ?? [], sources: out.collect?.sources ?? {},
|
|
336
|
+
priorDays: out.recur?.priorDays ?? [], clusters: ((out.cluster?.clusters ?? []) as Cluster[]).map(({ samples: _s, ...c }) => c),
|
|
337
|
+
themes, insights: out.insight?.insights ?? [], actions, summary };
|
|
338
|
+
const file = retroDayFile(root, day);
|
|
339
|
+
const md = [`# 매일 회고 ${day} (${mode})`, '', ...summary.map(l => `- ${l}`), '', '## 반복 (순위)',
|
|
340
|
+
...recurrences.filter(r => r.recurring).map(r => `${r.rank}. ${r.title} — ${r.count}건${r.reBrokeAfterGreen ? ' · 초록 뒤 재발' : r.recurAfterAction ? ' · 조치 뒤 재발' : ''}${r.daysSeen.length ? ` · ${r.daysSeen.length}일째` : ''} · ${r.signatures.join(', ')}`),
|
|
341
|
+
'', '## 인사이트', ...((out.insight?.insights ?? []) as Insight[]).map(i => `- ${i.text}`),
|
|
342
|
+
'', '## 조치 초안 (그림자 — 체크리스트에 쓰지 않았다)', ...actions.map(a => `- ${a.id} · ${a.owner} · ${a.title} · 판정선: ${a.verdictLine} · 반증: ${a.falsifier} · 근거: ${a.evidence.join(', ')}`), ''].join('\n');
|
|
343
|
+
if (!dryRun) {
|
|
344
|
+
writeAtomic(file, JSON.stringify(dayFile, null, 2) + '\n');
|
|
345
|
+
writeAtomic(file.replace(/\.json$/, '.md'), md);
|
|
346
|
+
}
|
|
347
|
+
debug.log('retro.daily', 'report', { day, mode, dryRun, facts: facts.length, recurring: themes.filter(t => t.recurring).length, actions: actions.length });
|
|
348
|
+
return { outcome: 'ok', day, mode, dryRun, file: dryRun ? null : file, summary, actions: actions.length, sent: false, wroteChecklist: false };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** 08:30 데일리 리뷰가 싣는 세 줄 — live 로 쓴 회고 파일만 읽는다(shadow 는 브리핑에 안 나간다). */
|
|
354
|
+
export function retroBriefingLines(root: string, day: string): string[] {
|
|
355
|
+
try {
|
|
356
|
+
const file = record(JSON.parse(readFileSync(retroDayFile(root, day), 'utf8')));
|
|
357
|
+
if (file.briefing !== true || !Array.isArray(file.summary)) return [];
|
|
358
|
+
return file.summary.map(str).filter(Boolean).slice(0, 3);
|
|
359
|
+
} catch { return []; }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (import.meta.main) {
|
|
363
|
+
const stage = process.argv[2] as RetroStage;
|
|
364
|
+
const path = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
365
|
+
if (!path || !RETRO_STAGES.includes(stage)) throw new Error('invalid retro-daily graph stage');
|
|
366
|
+
const ctx = JSON.parse(readFileSync(path, 'utf8')) as Ctx;
|
|
367
|
+
if (ctx.graphId !== RETRO_DAILY_GRAPH_ID || ctx.nodeId !== stage) throw new Error('retro-daily graph context mismatch');
|
|
368
|
+
// <root>/graph-runs/<graphId>/<runId>.json.contexts/<n>.json — 런 원장의 우주가 곧 회고 파일의 우주다.
|
|
369
|
+
const statePath = resolve(path, '..', '..', '..', '..');
|
|
370
|
+
const root = statePath || effectiveInstanceRoot();
|
|
371
|
+
console.log(JSON.stringify(await runRetroStage(stage, ctx, root, process.env.ELANOUS_GRAPH_DRY_RUN === '1')));
|
|
372
|
+
}
|