elanous 0.2.35 → 0.2.37
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/{T1mBQqmkwKDLGtyisDCOd → 8kITumG9GAZ1LwT9POrAR}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/1041-5841aae848fb6403.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1087.3177e54f672dc526.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1105.efe1e13503ac7a33.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1224.2ceca6c180d5130b.js +62 -0
- package/apps/pwa/out/_next/static/chunks/{9952.5daf5f94063a2b3e.js → 1262.a89bb27a5b2f844e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1417-1768233ac92f8a5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612.81bbc19af06af16c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1746.3563617ea64023a3.js +115 -0
- package/apps/pwa/out/_next/static/chunks/1766.728d42053070bb2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1773.d38e945481e04125.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1974-c12f9600da3d3c26.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1dc5a567.9b092bfae3d7099b.js → 1ed5e8af.cf424815c7c0efad.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4705-194e64e47ca0fbdb.js → 2002-a43a3f380df873ec.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2061.7232dc9dc058417f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{13.02a07436dec15f89.js → 210.90b2cbd64926cf34.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9512.5a76207a730e0491.js → 2283.edd9627b7bdfb546.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{6866-79aa5b4e47cac6aa.js → 2318-450e75d07605b8fe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{938.66ad1ef0e5abae1c.js → 2375.bf364c85dbb6f085.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/244-a810d282a20f1783.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2479-fe7e188bd9402d14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2514.03b60f266531a015.js +148 -0
- package/apps/pwa/out/_next/static/chunks/2526-a657538869888604.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2533-6f0341ff075809e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2676.e7994c695ef0f30d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2734.3ffdf54254fb6f99.js +27 -0
- package/apps/pwa/out/_next/static/chunks/2770-23252bf3a37880b9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2969.66b8b545139d1544.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3128.a9210d08800ea6a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3199.6a1a9b0e6cd01dc6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3212.12179fd3eee8cef1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9214.63a510d39178e997.js → 3578.4e9f9948a9a6f2f4.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3760-2909ddb9279281cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3825.1921b5901bf33dce.js +36 -0
- package/apps/pwa/out/_next/static/chunks/3994-63eb884ed0547011.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4036.d54e55e1ebf5acdc.js +90 -0
- package/apps/pwa/out/_next/static/chunks/4115.5b438b99f0ae729f.js +226 -0
- package/apps/pwa/out/_next/static/chunks/4269.d8f9f34bb0fc6c43.js +287 -0
- package/apps/pwa/out/_next/static/chunks/431-4eed5bd2c5fe1719.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2018.a741297bce759490.js → 4313.76f232c304a15bde.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1165.bad13fc9151a7288.js → 4324.c99ba3e83eb1b691.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{3f7c4ee5.3dde54f01a816613.js → 4337e36d.f42c3b391b3d8f20.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{7385.50a308a477bdfc21.js → 4383.18e5c54db3a22c88.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4599-96eff3f2a9c242c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8557-e7898769499cd56f.js → 4725-34bed25720cc326b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4825-a9d9d4e9f5892309.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5483-dd5d7697f6bdb106.js → 4833-7934c592c5569f00.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4879-68518f547a207aff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4896.f9b70285363b27b2.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{8444.4c26b379ca2b9596.js → 4931.95cc6dbd230774ca.js} +17 -17
- package/apps/pwa/out/_next/static/chunks/4937-28d437999b77c9dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/49aa54ab.80f39398e0ed0f30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3408.21d1570602f4b6c0.js → 5046.7fd5cbd859299448.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5056.482687319b82638e.js +104 -0
- package/apps/pwa/out/_next/static/chunks/506.16b83d56a437a9ee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5260-20593b0a25d0afbc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5404.cc4e9fec26fd5925.js +1 -0
- package/apps/pwa/out/_next/static/chunks/576.485e83e3f3dec19a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5832-a6be14e663f65726.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5854-56648a9fe646b9a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/58edce7d.f1b0c72acf13b123.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5925.3b1f8b135710249b.js +93 -0
- package/apps/pwa/out/_next/static/chunks/5952.294fc574a8625184.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{b6705a4f-b9a9047de9eecfd0.js → 5f6af561-f90b7a67f4f75d13.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6168.17749aa3905fa073.js +74 -0
- package/apps/pwa/out/_next/static/chunks/6252.240578f10952e091.js +34 -0
- package/apps/pwa/out/_next/static/chunks/6416.5d50b8ec9986ca98.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5101.e32282718f99ed6f.js → 6680.6a693c28aae4cd19.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4718-17d2af636796a330.js → 6745-5a63492933959dd9.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/6758.e7fbf532953c0783.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6795.0cb8a59730d99703.js +149 -0
- package/apps/pwa/out/_next/static/chunks/{6236.e18cced2d5ba6afb.js → 6819.be5c9c4d67f22922.js} +16 -16
- package/apps/pwa/out/_next/static/chunks/6877-84d47b7e85d0c41e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2833-0fe47af99ed13c29.js → 6971-fb5f9dc0a2cb5d2d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{da5176c6.6773b6c5994e1d99.js → 709a918c.4fd3b4e2a32cb4e3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/717.522b08685979b814.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7191.e41eb8f455c7f089.js +41 -0
- package/apps/pwa/out/_next/static/chunks/7233.8e744b7513119409.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7238-97c2226f80504bd8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7285.307963afcf7e7660.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7299-fbe4b8c47a87614a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7314.df0d95930b0c3f50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7406.c0589fdf2ee66629.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7424.c0fe2a595d4232b5.js +23 -0
- package/apps/pwa/out/_next/static/chunks/752-90e3e7daad3d8e39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7524-bcd3d9928c6842d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7549.9a4de3bf231cfc52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/757-4fbaf50942b8c424.js +1 -0
- package/apps/pwa/out/_next/static/chunks/765.76ae970ceb597b5b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7644.17e37ba6f1d4226e.js → 7867.e8725745ddd1d940.js} +4 -4
- package/apps/pwa/out/_next/static/chunks/7893-e444a1b0809cd1e4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7956-33748cabb3a05562.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{0368f25d-ad84af92fefee68f.js → 7c5ed21f-f286708e57cf88f7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8030-aa66b478b53dbfca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8051.c88c07dacbbc5f21.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{359-2250789a2d3499e7.js → 8080-40d781e7704d3671.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8149.80e402d4345e8dae.js +77 -0
- package/apps/pwa/out/_next/static/chunks/8205.50a5268f4e12ba0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8231.a128feaa6f8b6079.js +1 -0
- package/apps/pwa/out/_next/static/chunks/85.4ddb1dda2ea3ffa2.js +286 -0
- package/apps/pwa/out/_next/static/chunks/8537.26517a09e8247beb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8595-e9b7081ab773a61f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8821-30cc65a61a60951f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8905.b3385aadb7b133e6.js +79 -0
- package/apps/pwa/out/_next/static/chunks/8983.246bb3a1ac38f50f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{b2f6ba4d.fb56fdbbf798df33.js → 8c58d09b.e8724a299658d86f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9072.18342f5941a1f3ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9123.9df04866b2d6e00f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1530.0e612369b6fd7ea0.js → 9151.a2c51e93f9156dae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4441-875b97f5821ed58a.js → 9175-7d383df15a82e2be.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9181-bf89c07ce5b8f599.js +1 -0
- package/apps/pwa/out/_next/static/chunks/920-429bbc4504b45f71.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9207.625edf45d656151c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9378.d2575c6ffd6dd756.js +94 -0
- package/apps/pwa/out/_next/static/chunks/957.db5b5c4d32a35240.js +1 -0
- package/apps/pwa/out/_next/static/chunks/958.53a8dcf4729e6997.js +64 -0
- package/apps/pwa/out/_next/static/chunks/9647.8f0cd0898c592a29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9665-26abb02665704c2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9703.e7d81b6faef1050a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9882-7a9164b3c2dacbe5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9998.b74e033efadecd10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c604fb980a634d88.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-ec2d8a4979ca9075.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cb6add08ad3c7660.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-e2eaa321ec223fe6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5b8a09c475b5f844.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-91a77daedfbac191.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-f37fc5e974bda39b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-91429a0b78415a94.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-b95798b58263b910.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-763d53e175e17aa1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-d55023204055b12f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-89d2140d78abc810.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-82dc069abfe27f55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-25d9eee7beb552c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-770b4594c713ee97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c604fb980a634d88.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ba8e0dcae4f83985.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-5c112c6d7c33656e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-09788e1aa9a87db6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-3337c6a1328c2674.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-89acd5d68c0bc560.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-5c13e2c76371d797.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-653a9f18642825c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-fe986fe79b107546.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-47c5d4db587266bd.js → page-1e5da297557d2354.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-91ea9f5869a40943.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-7824e21b999962e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-53d00a60cc75a922.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-1f5a60f70cb4dd0c.js → page-88a1edbff62eaf1d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4e931248b3f9574b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-bac7dee94e5eae4a.js → page-443364c9e4a23b31.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b9af2ac1c4737ce8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-25ab9c614c14129b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-dbcdcfac0a395a8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-bf1de6645089ec58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c4201761a5e839c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-c304f469ea5d70e4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8e5a238cf5e50c9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c604fb980a634d88.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-32b526069ee50b3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-fb9e639c107f5f72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-84220c776608cedc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-e475618cbfd5ca2b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-dc0a532f5b9196ec.js → page-ac2c1e6169cfce57.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-fccd3020af2f8806.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-9c0d7198ff7b5a91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-d54b1ec9b9c6430d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8c4bef1791d732ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-46fcc0b0bbc6fbfe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-c1c80e345fa9d6f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-7dfda00afd172f5b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{d06f9160.a3d9062f95de8ad7.js → d9815f84.4918f682a45a8502.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{cbe38a15.5a032598e39970c6.js → de8446d3.31b73eb239fecd7b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/f21fd540.63ac75328926ed30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-77fdcc873519a6cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-d0d620d2ce235713.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-e0ef79c79337ba90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-84b1bc71e3f9ce6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-2acb61f1363128cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-462ff5727ceb7824.js +1 -0
- package/apps/pwa/out/_next/static/css/{b5d88eba78a3d7bf.css → be9019f696217521.css} +1 -1
- 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 +23 -23
- 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 +22 -22
- 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 -1
- package/apps/pwa/out/live-run/index.txt +23 -23
- 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/docs/goal-context/05-authoring-lessons.jsonl +47 -0
- package/docs/goal-context/README.md +3 -0
- package/graphs/default-loop.yaml +6 -1
- package/graphs/drafts/recipes.yaml +27 -7
- package/graphs/implement-loop.yaml +24 -0
- package/graphs/release/recipes.yaml +6 -0
- package/graphs/release/release-loop.yaml +17 -4
- package/graphs/wizard/recipes.yaml +44 -0
- package/graphs/wizard/wizard-loop.yaml +44 -0
- package/graphs/work/draft-close.yaml +54 -0
- package/graphs/work/green-close.yaml +37 -0
- package/graphs/work/recipes.yaml +46 -1
- package/graphs/work/research-report.yaml +2 -0
- package/graphs/work/run-research.yaml +35 -0
- package/package.json +1 -1
- package/scripts/__fixtures__/authoring-paths-10x3.json +32 -0
- package/scripts/boundary-proof-mapping-audit.ts +88 -0
- package/scripts/ci-typecheck-changed.ts +139 -5
- package/scripts/codex-fallback-live-probe.ts +17 -15
- package/scripts/coord-role-pack.ts +1 -1
- package/scripts/first10-gate.ts +416 -0
- package/scripts/fleet/node-setup.sh +32 -2
- package/scripts/fleet/run-light-worker.sh +9 -0
- package/scripts/fleet/wsl-node-setup.sh +432 -0
- package/scripts/gate-0226-failure-census.ts +507 -0
- package/scripts/goals/authoring-metrics.ts +258 -0
- package/scripts/graph-units/draft-close-node.ts +222 -0
- package/scripts/hook-order-sweep.ts +7 -4
- package/scripts/land-global-checks.ts +69 -1
- package/scripts/measure-nl-routing-corpus.ts +207 -2
- package/scripts/release-loop/docs-node.ts +344 -8
- package/scripts/release-loop/gate-closure.ts +48 -0
- package/scripts/release-loop/gate-diff.ts +2 -1
- package/scripts/release-loop/gate-node.ts +249 -53
- package/scripts/release-loop/known-issues-node.ts +6 -4
- package/scripts/release-loop/node-verdict.ts +2 -0
- package/scripts/release-loop/notes-repair.ts +216 -0
- package/scripts/release-loop/prepare-node.ts +61 -5
- package/scripts/release-loop/resume-cap.ts +14 -34
- package/scripts/release-loop/resume-release.ts +151 -15
- package/scripts/release-loop/resume-test-traffic.ts +10 -0
- package/scripts/release-loop/traffic-node.ts +59 -0
- package/scripts/release-loop/traffic-transition.ts +159 -0
- package/scripts/release-loop/version-node.ts +81 -12
- package/scripts/rhythm/daily.ts +137 -3
- package/scripts/train/green-leg.ts +176 -0
- package/scripts/tui-continue-real-evidence.ts +47 -0
- package/scripts/ux-sim.ts +53 -0
- package/skills/README.md +1 -0
- package/src/acp/client.ts +31 -5
- package/src/acp/core-turn-bridge.ts +54 -6
- package/src/acp/stderr-summary.ts +49 -0
- package/src/agent/delegate-hint.ts +16 -0
- package/src/agent/monad-agent-turn.ts +1 -0
- package/src/agent/runner.ts +82 -2
- package/src/agent/task-notification.ts +56 -10
- package/src/agent/terminal-surface.ts +217 -0
- package/src/agent/types.ts +4 -0
- package/src/agent-substrate/pr-reviewer.ts +64 -4
- package/src/agent-substrate/review-intent.ts +29 -2
- package/src/agent-substrate/review-observation.ts +3 -0
- package/src/agent-substrate/self-review-cli.ts +490 -26
- package/src/autopilot/lineage/sources.ts +1 -1
- package/src/autopilot/mission-codebase-gate.ts +141 -12
- package/src/autopilot/pr-manager.ts +89 -18
- package/src/boot/daemon-runtime.ts +35 -3
- package/src/boot/daemon-tools/pty-shell.ts +2 -1
- package/src/boot/daemon-tools/self-implement.ts +1 -0
- package/src/budget/run-usage-rollup.ts +12 -7
- package/src/budget/unified-usage.ts +20 -0
- package/src/budget/usage-outcomes.ts +8 -4
- package/src/capture/tui-self-report.ts +132 -1
- package/src/chat/goal-continue-verdict.ts +41 -0
- package/src/chat/index.ts +30 -4
- package/src/chat/internal-turn-origin.ts +1 -0
- package/src/chat/paste-fold.ts +86 -0
- package/src/chat/tool-render/block.ts +32 -0
- package/src/chat/tool-render/dispatch.ts +2 -20
- package/src/chat/tool-render/result-text.ts +98 -0
- package/src/chat/turn-typeahead.ts +70 -7
- package/src/cli/autopilot-cli.ts +24 -9
- package/src/cli/config-audit.ts +119 -0
- package/src/cli/decisions-cli.ts +7 -4
- package/src/cli/doctor-cli.ts +137 -6
- package/src/cli/guardian-cli.ts +10 -0
- package/src/cli/herdr-cli.ts +34 -0
- package/src/cli/light-entry-route.ts +6 -3
- package/src/cli/log-event-names.ts +2 -0
- package/src/cli/logs-cli.ts +357 -2
- package/src/cli/make-cli.ts +247 -11
- package/src/cli/memory-cli.ts +30 -1
- package/src/cli/msg-cli.ts +132 -14
- package/src/cli/pr-cli.ts +29 -16
- package/src/cli/pr-granularity.ts +5 -3
- package/src/cli/pr-land-base-read.ts +11 -2
- package/src/cli/pr-land-heavy-remote.ts +6 -3
- package/src/cli/pr-land-overlap-evidence.ts +23 -4
- package/src/cli/pr-land-recovery.ts +117 -1
- package/src/cli/pty-takeover-cli.ts +8 -7
- package/src/cli/release-checklist-read-cli.ts +109 -4
- package/src/cli/release-cli.ts +64 -15
- package/src/cli/release-single-flight.ts +1 -1
- package/src/cli/self-update.ts +23 -5
- package/src/cli/self-utterances-cli.ts +13 -16
- package/src/cli/tasks-card-show.ts +162 -0
- package/src/cli/tasks-cli.ts +868 -352
- package/src/cli/tasks-read-cli.ts +197 -0
- package/src/cli/usage-cli.ts +12 -10
- package/src/context-bus/claude-memory-import.ts +99 -0
- package/src/context-bus/long-term-memory.ts +125 -0
- package/src/core-turn/run-core-turn.ts +4 -2
- package/src/core-turn/run-goal-loop.ts +76 -16
- package/src/core-turn/types.ts +8 -1
- package/src/dashboard/chat/acp-chat.ts +9 -0
- package/src/dashboard/context/keys.ts +8 -31
- package/src/dashboard/index.ts +299 -62
- package/src/dashboard/input/agent-wake.ts +50 -1
- package/src/dashboard/input/chat-main-acp-dispatch.ts +21 -1
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +146 -3
- package/src/dashboard/input/chat-main-slash-command.ts +4 -2
- package/src/dashboard/input/goal-continue.ts +210 -0
- package/src/dashboard/input/goal-interim.ts +2 -0
- package/src/dashboard/input/internal-turn.ts +2 -8
- package/src/dashboard/input/seat-autowake.ts +9 -7
- package/src/dashboard/input/turn-autoheal.ts +52 -0
- package/src/dashboard/replay-preview.ts +25 -0
- package/src/dashboard/seat-boot-preamble.ts +132 -5
- package/src/dashboard/slash-runtime/chat-persona.ts +65 -8
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +20 -10
- package/src/dashboard/slash-runtime/model-choices.ts +15 -0
- package/src/dashboard/stale-install-check.ts +3 -99
- package/src/dashboard/synchronized-frame-output.ts +74 -0
- package/src/debug/batch-queue.ts +119 -0
- package/src/debug/tui-log-tag.ts +67 -0
- package/src/display/agent-surface.ts +7 -0
- package/src/display/coordinator.ts +45 -12
- package/src/display/resize-listener.ts +41 -20
- package/src/display/synchronized-output-region.ts +60 -0
- package/src/domains/standalone-log-sink.ts +15 -2
- package/src/domains/surface-events.ts +80 -1
- package/src/esc-abort-gate.ts +69 -0
- package/src/exec-requests/planner.ts +37 -2
- package/src/execution-loop/launch-gate.ts +4 -3
- package/src/git-fs/cli-stderr.ts +45 -0
- package/src/git-fs/gh-cli.ts +6 -3
- package/src/git-fs/git-cli.ts +11 -5
- package/src/git-fs/worktree.ts +95 -1
- package/src/graph-runner/graph-cli.ts +44 -2
- package/src/graph-runner/graph-run-api.ts +3 -2
- package/src/graph-runner/runner.ts +37 -16
- package/src/graph-wizard/generate.ts +131 -4
- package/src/graph-wizard/steps.ts +15 -0
- package/src/grounding/proactive-ground.ts +86 -0
- package/src/harness/control-inbox.ts +49 -1
- package/src/harness/harness-cli-command.ts +230 -40
- package/src/harness/harness-incidents.ts +52 -1
- package/src/harness/pod-control-send.ts +33 -5
- package/src/harness/review-adapter.ts +26 -6
- package/src/harness/self-send-remote.ts +69 -0
- package/src/harness/self-send-routing-guard.ts +6 -0
- package/src/harness/self-send-target.ts +4 -3
- package/src/index.ts +443 -25
- package/src/input-core/context-key-runtime.ts +21 -0
- package/src/instance/test-write-guard-real-transport.fixture.ts +52 -0
- package/src/light-cli.ts +6 -1
- package/src/llm/route-decision.ts +13 -0
- package/src/llm-tool-name.ts +37 -0
- package/src/llm-tool-schema.ts +35 -0
- package/src/llm-wellformed.ts +70 -0
- package/src/llm.ts +158 -15
- package/src/loops/checker.ts +100 -0
- package/src/loops/consensus.ts +26 -2
- package/src/loops/loop-cli.ts +9 -4
- package/src/loops/msg-wake.ts +35 -39
- package/src/loops/orchestrator/anomaly-cluster.ts +243 -0
- package/src/loops/orchestrator/inbox.ts +59 -11
- package/src/loops/orchestrator/knowhow-pack.ts +364 -0
- package/src/loops/orchestrator/tick.ts +54 -5
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/model-tier/llm-tier-map.ts +12 -17
- package/src/models/prompts.ts +17 -2
- package/src/msg/msg-post-office.ts +116 -0
- package/src/msg/msg-store.ts +17 -0
- package/src/mss/logging/log-store.ts +79 -22
- package/src/native-tool-catalog.ts +1 -1
- package/src/nexus/api/decisions-route.ts +38 -18
- package/src/nexus/api/graph-wizard-api.ts +46 -3
- package/src/nexus/api/grid.ts +24 -4
- package/src/nexus/api/http-server.ts +1 -1
- package/src/nexus/api/ops-api.ts +14 -6
- package/src/nexus/boot/register-mcp-clients.ts +263 -38
- package/src/nexus/onboarding/first-boot-wizard.ts +56 -8
- package/src/nexus/state/state.ts +7 -1
- package/src/oauth/codex-account-store.ts +34 -1
- package/src/onboarding/first-run.ts +37 -6
- package/src/onboarding/web-first.ts +43 -11
- package/src/ops/loops-map.ts +46 -0
- package/src/ops/overview-cache.ts +22 -4
- package/src/ops/overview-collect.ts +12 -6
- package/src/ops/overview.ts +44 -8
- package/src/persona/loader.ts +11 -0
- package/src/persona/types.ts +8 -0
- package/src/policy/llm-policy.ts +3 -1
- package/src/prompt-enhance/enhance.ts +4 -4
- package/src/prompt-library/session-guidance.ts +14 -0
- package/src/provider-summary.ts +8 -0
- package/src/pty-shell/availability.ts +7 -0
- package/src/pty-shell/herdr-agent-report.ts +155 -37
- package/src/pty-shell/herdr-prompt.ts +266 -0
- package/src/pty-shell/herdr-prune-stale.ts +17 -0
- package/src/pty-shell/herdr-sequence-lease.ts +60 -0
- package/src/pty-shell/pty-control-ipc.ts +327 -15
- package/src/pty-shell/pty-special-keys.ts +42 -2
- package/src/pty-shell/registry.ts +25 -15
- package/src/pty-shell/stale-install-check.ts +99 -0
- package/src/pty-shell/tui-run-state.ts +249 -3
- package/src/release-loop/auto-start.ts +20 -2
- package/src/release-loop/checklist.ts +126 -9
- package/src/release-loop/feature-store.ts +9 -9
- package/src/release-loop/landed-but-yellow.ts +93 -12
- package/src/release-loop/rehearsal.ts +36 -20
- package/src/repl/index.ts +27 -1
- package/src/seat-loop/knowhow-lint.ts +1 -1
- package/src/seat-loop/seat-report.ts +1 -0
- package/src/self-dev/ask-launch-flow.ts +86 -10
- package/src/self-dev/decompose-proposal.ts +183 -20
- package/src/self-dev/decompose.ts +61 -0
- package/src/self-dev/dev-cli.ts +37 -3
- package/src/self-dev/dev-pipeline.ts +37 -9
- package/src/self-dev/draft-sweep.ts +241 -16
- package/src/self-dev/draft-triage-rules.ts +50 -5
- package/src/self-dev/glm-credential-guard.ts +77 -0
- package/src/self-dev/graph-journey-nodes.ts +1 -0
- package/src/self-dev/graph-journey-route.ts +12 -2
- package/src/self-dev/orchestrate-cli.ts +68 -3
- package/src/self-dev/orchestrate.ts +116 -17
- package/src/self-dev/review-provider-fallback.ts +45 -7
- package/src/self-dev/run-supervisor.ts +55 -2
- package/src/self-dev/run-types.ts +1 -1
- package/src/self-dev/shard-plan.ts +48 -0
- package/src/self-dev/shard-run.ts +50 -3
- package/src/self-health/autoheal.ts +153 -0
- package/src/self-health/signatures.ts +6 -0
- package/src/self-implement/device-first.ts +116 -0
- package/src/self-implement/doctor-visit.ts +322 -1
- package/src/self-implement/evidence-command-budget.ts +80 -0
- package/src/self-implement/gate-artifacts.ts +6 -2
- package/src/self-implement/gate-baseline.ts +64 -1
- package/src/self-implement/gate-cli.ts +93 -9
- package/src/self-implement/gate-remote.ts +28 -4
- package/src/self-implement/gate-scope.ts +13 -1
- package/src/self-implement/goal-author-cli.ts +1 -0
- package/src/self-implement/goal-author-decompose.ts +265 -4
- package/src/self-implement/goal-author.ts +366 -26
- package/src/self-implement/goal-elaborate.ts +176 -22
- package/src/self-implement/goal-refine.ts +3 -0
- package/src/self-implement/goal-supervisor-fill.ts +75 -26
- package/src/self-implement/graph-variant.ts +40 -6
- package/src/self-implement/headless-elanous-driver.ts +19 -3
- package/src/self-implement/importer-test-index.ts +215 -2
- package/src/self-implement/knowhow-pack.ts +5 -5
- package/src/self-implement/off-diff-evidence.ts +58 -6
- package/src/self-implement/orchestrator.ts +536 -42
- package/src/self-implement/pod-warm-pool-profile.ts +1 -0
- package/src/self-implement/pwa-import-graph.ts +1 -1
- package/src/self-implement/review-round-brief.ts +300 -0
- package/src/self-implement/rework-policy.ts +44 -0
- package/src/self-implement/run-ledger.ts +278 -6
- package/src/self-implement/run-stop.ts +1 -1
- package/src/self-implement/run-ttl.ts +38 -24
- package/src/self-implement/running-runs.ts +17 -3
- package/src/self-implement/scope-adjacency.ts +3 -1
- package/src/self-implement/seams.ts +72 -10
- package/src/self-implement/stop-autoheal.ts +1 -0
- package/src/self-implement/test-seams.ts +13 -1
- package/src/self-implement/train-reach-grouping.ts +8 -2
- package/src/self-implement/wiring-check.ts +358 -0
- package/src/session/chat.ts +7 -1
- package/src/session/id-prefix.ts +60 -0
- package/src/session/index.ts +4 -0
- package/src/session-runtime/index.ts +9 -1
- package/src/shell-env-bootstrap.ts +11 -4
- package/src/skills/runner.ts +4 -2
- package/src/skills/tools/agent.ts +290 -48
- package/src/skills/tools/pty.ts +5 -0
- package/src/skills/tools/schedule-wakeup.ts +23 -4
- package/src/skills/tools/shell.ts +61 -6
- package/src/skills/tools/terminal-modal-inject.ts +1 -2
- package/src/skills/tools/terminal-modal.ts +10 -8
- package/src/skills/url-router.ts +41 -1
- package/src/smart-wizard/run.ts +66 -0
- package/src/status/bar.ts +34 -6
- package/src/status/metrics.ts +12 -1
- package/src/status/status-provider-info.ts +37 -6
- package/src/steward/rejudge-tick.ts +22 -7
- package/src/steward/resync-tick.ts +31 -4
- package/src/steward/ta-repair-event-collect.ts +17 -6
- package/src/task-agent/actions.ts +202 -5
- package/src/task-agent/boundary-proof-mapping.ts +12 -0
- package/src/task-agent/boundary-proof-producer.ts +147 -0
- package/src/task-agent/completion-evidence.ts +2 -1
- package/src/task-agent/green-close-recipe.ts +239 -0
- package/src/task-agent/green-sweep.ts +64 -10
- package/src/task-agent/land-queue.ts +21 -8
- package/src/task-agent/live-moves.ts +70 -9
- package/src/task-agent/map-host-adapter.ts +134 -0
- package/src/task-agent/map-reduce.ts +158 -0
- package/src/task-agent/map-run-ledger.ts +226 -0
- package/src/task-agent/mission.ts +30 -0
- package/src/task-agent/parent-host.ts +21 -11
- package/src/task-agent/rejudge-on-head.ts +87 -5
- package/src/task-agent/repair-cycle.ts +179 -32
- package/src/task-agent/repair-next-move.ts +42 -4
- package/src/task-agent/repair-outcomes.ts +29 -0
- package/src/task-agent/repair-worker.ts +14 -4
- package/src/task-agent/review-prior-fix.ts +12 -2
- package/src/task-agent/shadow.ts +229 -8
- package/src/task-agent/task-card-prefix.ts +2 -0
- package/src/task-agent/task-hand.ts +630 -30
- package/src/task-agent/task-parent-role-llm.ts +45 -0
- package/src/task-agent/tier-split.ts +80 -10
- package/src/task-agent/work-order.ts +53 -2
- package/src/task-orchestrator/dispatcher.ts +46 -1
- package/src/task-orchestrator/generator-prompt.ts +6 -0
- package/src/task-orchestrator/surfaces/pod-account-broker.ts +9 -0
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +16 -6
- package/src/task-orchestrator/surfaces/pod-child-reuse.ts +146 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +86 -5
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +215 -3
- package/src/task-orchestrator/surfaces/pod-launch-history.ts +133 -0
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +8 -6
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +97 -0
- package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +11 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +126 -8
- package/src/task-orchestrator/surfaces/pod-provenance.ts +8 -2
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +18 -3
- package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +16 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-host.ts +224 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-protocol.ts +201 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-provider.ts +444 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-runner.ts +387 -0
- package/src/task-orchestrator/surfaces/pod-si-warm-transport.ts +157 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +4 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +491 -36
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +3 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +612 -54
- package/src/task-orchestrator/surfaces/self-implement.ts +46 -2
- package/src/testing/linux-env.ts +177 -0
- package/src/tool-runtime/index.ts +15 -2
- package/src/tool-runtime/mcp-proxy-bootstrap.ts +29 -0
- package/src/tty-foreground.ts +155 -0
- package/src/tui/chat/goal-interim.ts +177 -0
- package/src/tui/chat/log-view.ts +205 -18
- package/src/tui/chat/self-health-status.ts +232 -0
- package/src/tui/entry.ts +623 -80
- package/src/tui/input/at-picker.ts +55 -0
- package/src/tui/input/composer.ts +8 -1
- package/src/tui/slash/compact-command.ts +7 -0
- package/src/tui/slash/compact.ts +1 -7
- package/src/tui/slash/resume.ts +98 -0
- package/src/tui/slash/signals.ts +114 -0
- package/src/tui/slash/term-next.ts +71 -0
- package/src/tui-client/local-chat-turn.ts +49 -5
- package/src/tui-client/next-dashboard-runtime.ts +8 -0
- package/src/tui-client/next-loop-command.ts +70 -0
- package/src/tui-client/next-wakeup.ts +191 -0
- package/src/tui.ts +37 -0
- package/src/ui/modal-adapter.ts +2 -2
- package/src/user-config.ts +19 -1
- package/src/ux-sim/boot-daemon-tools-pty-shell-device.ts +136 -0
- package/src/ux-sim/classic-tui-seat-scenarios.ts +670 -0
- package/src/ux-sim/cli-config-audit-device.ts +121 -0
- package/src/ux-sim/dashboard-event-loop-stall-monitor-device.ts +238 -0
- package/src/ux-sim/event-monkey.ts +3 -2
- package/src/ux-sim/fake-bin.ts +45 -0
- package/src/ux-sim/fake-gh.ts +174 -0
- package/src/ux-sim/fake-herdr.ts +238 -0
- package/src/ux-sim/fake-kubectl.ts +142 -0
- package/src/ux-sim/fake-llm.ts +116 -6
- package/src/ux-sim/fault-glm-no-credential.ts +116 -0
- package/src/ux-sim/fault-nextmove-dispatch.ts +102 -1
- package/src/ux-sim/fault-scope-escape-lift.ts +33 -6
- package/src/ux-sim/fault-tui-400-autoheal.ts +297 -0
- package/src/ux-sim/goal-device.ts +619 -0
- package/src/ux-sim/in-process-cli.ts +22 -5
- package/src/ux-sim/mock-authoring-first-run-child.ts +35 -0
- package/src/ux-sim/mock-authoring-first-run.ts +97 -0
- package/src/ux-sim/mock-seat-pty-frame-child.ts +29 -0
- package/src/ux-sim/mock-seat-pty-frame.ts +297 -0
- package/src/ux-sim/mock-seat-pty-sequence-child.ts +21 -0
- package/src/ux-sim/mock-seat-pty-sequence.ts +206 -0
- package/src/ux-sim/next-tui-seat-scenarios.ts +749 -0
- package/src/ux-sim/onboarding-first-run-device.ts +269 -0
- package/src/ux-sim/pty-key-modifiers-device.ts +85 -0
- package/src/ux-sim/resume-picker-device.ts +173 -0
- package/src/ux-sim/session-id-seed.ts +60 -0
- package/src/ux-sim/skills-tools-shell-device.ts +156 -0
- package/src/ux-sim/subagent-tools-device.ts +81 -0
- package/src/ux-sim/task-agent-parent-host-device.ts +264 -0
- package/src/ux-sim/task-agent-rejudge-on-head-device.ts +114 -0
- package/src/ux-sim/task-orchestrator-surfaces-self-implement-pod-device.ts +250 -0
- package/src/ux-sim/tty-foreground-steal-child.ts +21 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1540.af38731fa7c4a3e0.js +0 -41
- package/apps/pwa/out/_next/static/chunks/1674.2a65fd5668e1cc5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1850.f2b470ba5a471da0.js +0 -24
- package/apps/pwa/out/_next/static/chunks/193.62da94cd0b88323b.js +0 -74
- package/apps/pwa/out/_next/static/chunks/1975.260f6d249fac5f7c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1982-67cd8dfe8682325f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1993-c0007b7ad0c0ef41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1999-bb415351c8a2846c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1c735de4.f993a30bbd3c57c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2085.8f950b4bda1cbbc7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2159-6179a961376956b9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216.b5c2a421aa1d28a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2251.49fbe369f74c372c.js +0 -148
- package/apps/pwa/out/_next/static/chunks/2286-2939577360d3dcc0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2625.5d870aa999f33db1.js +0 -34
- package/apps/pwa/out/_next/static/chunks/2668.34d21539e62bf111.js +0 -77
- package/apps/pwa/out/_next/static/chunks/2930.d0edb4f108787ef7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2973.77318e7d85f59477.js +0 -62
- package/apps/pwa/out/_next/static/chunks/3103.0df8da390948bb84.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3123.2bee9ba15d320bd2.js +0 -90
- package/apps/pwa/out/_next/static/chunks/3162-6cf652bf7124d8cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3577-7340009f7007a795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3589.5444a06bcf6e775c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3618.f514792d6c59d7fd.js +0 -36
- package/apps/pwa/out/_next/static/chunks/3669.343f9119aa276001.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3677-4215f826f6aa68e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3736.c2efad489604cc3b.js +0 -79
- package/apps/pwa/out/_next/static/chunks/3817.f9aeab2bb6675d73.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4182-0b9b1251c29cc0cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4346.8601952e73bf5803.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4582.bd68683c076649e3.js +0 -286
- package/apps/pwa/out/_next/static/chunks/4605-6e623f55aec77422.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4607.5cc27468628eb4bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4627.f07508b3936fd6c0.js +0 -24
- package/apps/pwa/out/_next/static/chunks/4747-c22262c093e46807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4775-178215aed0daba61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4815-6ffb8c3c991cc45d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4834-beca8b47ad29a898.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5023-fb4802b6d2492b02.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5154.a118f3957ed14e36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5210-a2e0015e3d4d5d9c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5356.84ac712cc15928bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5401.22352141b6fd336e.js +0 -23
- package/apps/pwa/out/_next/static/chunks/5637.281e5dec4ea6ecf1.js +0 -94
- package/apps/pwa/out/_next/static/chunks/5662.02eaa029af05ad46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/56d372f2.681ab4ec4c998fae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5760.22456f433c6f42c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5852.db1f7449f4f74612.js +0 -287
- package/apps/pwa/out/_next/static/chunks/5891-0f11a7d7362a9072.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5943.264d14a44ca32513.js +0 -1
- package/apps/pwa/out/_next/static/chunks/604-1b3c39df083c02aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6121-5f97072330738caf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6193.07d8ca5af4b09086.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6203-b3fea3a60d69ff14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6286.d638fb5953e2e757.js +0 -8
- package/apps/pwa/out/_next/static/chunks/630.fe5a46428d63b013.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6301.ae0245f507e4395e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6673.fbbe5699f6ea5991.js +0 -27
- package/apps/pwa/out/_next/static/chunks/6777-f6d301efb5de828d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6786.423f24c88de034c7.js +0 -93
- package/apps/pwa/out/_next/static/chunks/6854-cef64a948c5e122d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6b89c8bd.24df687a820500b3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7037-fe27f001db30d323.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7080.2d826860534fd88e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7118.8731be28c3ebb14b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7196.44241bc1df3e26fe.js +0 -149
- package/apps/pwa/out/_next/static/chunks/7235.c7c4e6cb5c693c2d.js +0 -104
- package/apps/pwa/out/_next/static/chunks/7706-58bfb1546637f217.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7839.5b91455338c9fd3f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7857-31f13ee96f4011aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/81.f74cb60312fe4001.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8219.baa715d1deee80e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8256.e3ac8421a71f82f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8310-444a6a895c402f12.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8423.f7dda8aca4fcaae5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8437-8d5492094b166cfa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8496.75649af8652c74f4.js +0 -226
- package/apps/pwa/out/_next/static/chunks/8576.65bb654b524f3614.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8609.558ce2f8f9aea2be.js +0 -64
- package/apps/pwa/out/_next/static/chunks/8614.caec96cfb91f4ae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8637.47f5b35afcae5e6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8714.ff0b7ce3ec3e3ae2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8732.367f44a916202353.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9141-7fd1f73a5400bdc2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/921.c4267d0d69a65404.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9261.3962c3321b047691.js +0 -115
- package/apps/pwa/out/_next/static/chunks/936-26efd1a0efdd44d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9627-4b9efe616b27e2e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/967-2aa39b2d04b4c690.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9734.40eb1e558c29ccea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9908.3aa54f7a586a73dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9954-ca8d29752bdbb45a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9974-507fa67cb94066dd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-ce9e00f7fb45506a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fa59d6d17daa3389.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-53f68bbe7bd4b774.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-1c500fbb9f4a7a04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-99d72673aa5c307c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-d2d5adc1e9484c88.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c607c5aae637f1cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fa8c23596d555c0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-bdab8c5e855c8416.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-406b47745b53f269.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-a4276386b7051f50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-8a26bab9ad375f25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-775f48aa1dc7a2d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-1c4dd5f959d0c558.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-c48938a8377c9f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-ce9e00f7fb45506a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-c6e7905859425aa9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-c280f4252452e830.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-cf8c57498f20970a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-ca3a17caf409fc83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-26e6c18a1a750cf6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-3abc872cc6aeee39.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-f7481b7b2174fe3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1ae7ef8077680337.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-415f1e243c7ccb13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-73f380f17b76f95c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-2991073ad66ee2d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fbad60d1e358fb2c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-398047d6949e8ca5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-2d33a2231540ca89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5bf1e6bf70bcb96c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-ba59c19376a0917d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-111f187139add30f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-acaae197d5be5779.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0df20b0f546a0d7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-ce9e00f7fb45506a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-27e1202d24b8fe35.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-bda78c50a78363e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-da592691293d9c9f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f832994317a2e9f0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-cfccbbeae0473404.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-527e2073af84c81b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-bac86e9112671357.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6d59dff410e9d76a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-53a61f8be09f02d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-db8f559e763c17f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c02aa4cc66455093.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-50e6acebc630849b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-7a1ae342d5ed75e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-7ad7d505ba1f92b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-02edba75232cb235.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d6ebae3e8eee4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-e986adaa429163e8.js +0 -1
- /package/apps/pwa/out/_next/static/{T1mBQqmkwKDLGtyisDCOd → 8kITumG9GAZ1LwT9POrAR}/_ssgManifest.js +0 -0
package/src/cli/tasks-cli.ts
CHANGED
|
@@ -1,36 +1,46 @@
|
|
|
1
1
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
4
|
-
import { createHash } from 'node:crypto';
|
|
5
|
-
import { loadRunLedger as loadRunLedgerSync, loadRunLedgerWithMetadata, runLedgerDir, resolveFederatedRunLedgerDirectories } from '../self-implement/run-ledger.js';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
5
|
+
import { loadRunLedger as loadRunLedgerSync, loadRunLedgerWithMetadata, runLedgerDir, resolveFederatedRunLedgerDirectories, type RunLedgerEntry } from '../self-implement/run-ledger.js';
|
|
6
6
|
import { goalRunDbPath, loadGoalRunQuery } from '../self-implement/goal-run-store.js';
|
|
7
7
|
import { loadGoalAskRecords } from '../self-implement/goal-ask-store.js';
|
|
8
8
|
import { dirname, join, resolve } from 'node:path';
|
|
9
9
|
import { InvalidArgumentError, Option, type Command } from 'commander';
|
|
10
10
|
import { debug } from '../debug/log.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { checklistDevVersion, listChecklist } from '../release-loop/checklist.js';
|
|
11
|
+
import { readRunExits } from '../harness/harness-incidents.js';
|
|
12
|
+
import { checklistDevVersion, listChecklist, setItemIfSnapshot } from '../release-loop/checklist.js';
|
|
14
13
|
import { assignedVersions, releasedVersion, validateVersion } from '../release-loop/feature-store.js';
|
|
15
14
|
import { compareVersions } from '../directives/version-at.js';
|
|
16
|
-
import {
|
|
17
|
-
import type
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { readCardSalvageBranch, recordWiringLiveVerified, refreshCardRunBinding, type CardRunEvidenceDeps } from '../task-agent/card-evidence.js';
|
|
21
|
-
import { captureReviewResults } from '../task-agent/review-result-capture.js';
|
|
22
|
-
import { replayRemoteCardMoves } from '../task-agent/shadow.js';
|
|
23
|
-
import { remoteLedgerMirrorDirs } from '../task-agent/parent-host.js';
|
|
15
|
+
import type { BoardNode } from '../task-agent/board.js';
|
|
16
|
+
import { boundPrTerminal, cardPrNumber, CLOSED_BY_HAND_EVENT, closeTaskCard, COMPLETION_KINDS, defaultDraftClaimGhPorts, handDraftClaim, HAND_MOVE_HISTORY_EVENT, updateTaskCard, resolveHandText, readTaskAgentState, readTaskCard, runTagChange, taskAgentStatePath as taskStatePathDefault, shellQuote, TASK_SEATS, type CompletionKind, type DraftClaimGhPorts, type TaskCard, type TaskCardEvent, type TaskLaunchContext, type TaskLaunchReceipt, type TaskLauncher, type TaskSeat } from '../task-agent/task-hand.js';
|
|
17
|
+
import { recordWiringLiveVerified, type CardRunEvidenceDeps } from '../task-agent/card-evidence.js';
|
|
18
|
+
import { remoteLedgerMirrorDir, remoteLedgerMirrorDirs } from '../task-agent/parent-host.js';
|
|
24
19
|
import { buildWorkOrder } from '../task-agent/work-order-build.js';
|
|
25
|
-
import {
|
|
20
|
+
import { cellIdFromBody } from '../pr-tool/census.js';
|
|
21
|
+
import { ambiguousGreenSweepPrs, applyGreenSweepDecisions, decideGreenSweep, greenSweepCandidates, greenSweepEvidenceTestArgs, observeGreenSweep, type GreenSweepPr } from '../task-agent/green-sweep.js';
|
|
22
|
+
import { mapInventory, renderMapReduceTable, type MapInventory, type MapItem, type MapPorts } from '../task-agent/map-reduce.js';
|
|
23
|
+
import type { MapRecipe, MapRunRow } from '../task-agent/map-run-ledger.js';
|
|
24
|
+
import { createMapHostLauncher, type HostMapItem } from '../task-agent/map-host-adapter.js';
|
|
25
|
+
import type { GraphRunOptions, GraphRunState } from '../graph-runner/runner.js';
|
|
26
|
+
import { parseAskTargetPathHintsResult } from '../self-dev/launch-preflight-text.js';
|
|
27
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
26
28
|
import { HAND_MOVE_KINDS, recordHandMove } from '../task-agent/live-move-history.js';
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
+
import type { TierSizedDecompose } from '../task-agent/tier-split.js';
|
|
30
|
+
import type { AdvanceMissionResult, MissionDecompose, PieceEvidenceReader } from '../task-agent/mission.js';
|
|
29
31
|
import { formatSevenVerbLive, formatTaskAgentCover, measureSevenVerbLive, measureTaskAgentCover, type CoverRow, type ImplementCardRunBinding, type ImplementRunMergeEvent, type SevenVerbLive, type TaskAgentCoverInput } from '../task-agent/cover.js';
|
|
32
|
+
import { measureRepairOutcomes } from '../task-agent/repair-outcomes.js';
|
|
30
33
|
import { LogStore, STORE_SAFETY_MAX, type LogQuery, type LogStoreRow } from '../mss/logging/log-store.js';
|
|
31
34
|
import { resolveLogTargets } from './logs-cli.js';
|
|
35
|
+
import { createTasksCliContext, handleError, NEXUS_DOWN, NexusUnavailable, registerTaskAgentSink, registerTaskListCommand, registerTaskShowCommand, registerTasksRoot, request, responseJson, TASK_AGENT_SINK_SURFACE } from './tasks-read-cli.js';
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
// CLI-ENTRY-LAZY — 가벼운 읽기 경로(`tasks-read-cli.ts`)로 옮긴 공용 표면은 옛 import 경로 그대로 남긴다.
|
|
38
|
+
export { registerTaskAgentSink, TASK_AGENT_SINK_SURFACE };
|
|
39
|
+
|
|
40
|
+
function headLine(text: string): string {
|
|
41
|
+
const first = text.split('\n')[0]!.trim();
|
|
42
|
+
return first.length > 100 ? `${first.slice(0, 100)}…` : first;
|
|
43
|
+
}
|
|
34
44
|
|
|
35
45
|
export interface TasksCliDeps {
|
|
36
46
|
baseUrl?: string;
|
|
@@ -40,6 +50,14 @@ export interface TasksCliDeps {
|
|
|
40
50
|
/** green-sweep entrypoint test seam: keep the actual CLI decision chain, replace only process reads. */
|
|
41
51
|
greenSweepChecklist?: typeof listChecklist;
|
|
42
52
|
greenSweepRun?: (command: string, args: string[]) => { status: number | null; stdout: string };
|
|
53
|
+
greenSweepEvidenceRun?: (args: string[]) => { status: number | null; stdout: string };
|
|
54
|
+
greenSweepWrite?: typeof setItemIfSnapshot;
|
|
55
|
+
/** Test inventory seam; production reads current checklist or GitHub draft inventory. */
|
|
56
|
+
mapInventorySource?: (recipe: MapRecipe, source: 'landed-but-yellow' | 'drafts', limit?: number,
|
|
57
|
+
mapRunId?: string) => Promise<MapInventory<MapCliItem>>;
|
|
58
|
+
mapRoot?: string;
|
|
59
|
+
/** Focused test seam; production uses the real host graph runner. */
|
|
60
|
+
mapGraphRun?: (path: string, options: GraphRunOptions) => Promise<GraphRunState>;
|
|
43
61
|
/** `task hand --live` 의 발사기(시험 주입). 기본은 운영 config-dir 로 `harness say` 를 떼어 띄운다. */
|
|
44
62
|
taskLauncher?: TaskLauncher;
|
|
45
63
|
/** TA-REVIEW-RESULT-CAPTURE — `tasks show` 의 리뷰 결과 회수 시각(시험 주입 · 기본 지금). */
|
|
@@ -48,6 +66,8 @@ export interface TasksCliDeps {
|
|
|
48
66
|
taskStatePath?: string;
|
|
49
67
|
/** 조합 선택 시험에서만 그래프 저장소 루트를 바꾼다. */
|
|
50
68
|
taskCompositionRoot?: string;
|
|
69
|
+
/** DRAFT-CLAIM-AUTO-2-FU — `tasks hand --draft-claim <pr>` 의 관측·게시·수확 리뷰 포트(시험 주입). 기본 = `defaultDraftClaimGhPorts()`(gh CLI ⊕ 생산 리뷰 러너). */
|
|
70
|
+
draftClaimGh?: DraftClaimGhPorts;
|
|
51
71
|
/** logs.db 싱크 등록(시험 주입). 기본 = `registerStandaloneLogSink`. `hand`·`show` 가 부른다. */
|
|
52
72
|
registerSink?: (surface: string) => Promise<unknown>;
|
|
53
73
|
/** `task hand --mission` 의 분해기(시험 주입). 기본 = 나열형은 결정론 · 아니면 `decomposeSelfDevGoal`. */
|
|
@@ -96,6 +116,15 @@ export interface TasksCliDeps {
|
|
|
96
116
|
outcomesNow?: () => number;
|
|
97
117
|
/** `tasks outcomes` 페이지 상한(시험 주입). 기본 = `OUTCOMES_QUERY_LIMIT`. */
|
|
98
118
|
outcomesLimit?: number;
|
|
119
|
+
/** `tasks doctor` read-only adapters; tests inject L1 evidence without calling gh or the operating ledger. */
|
|
120
|
+
doctorVersion?: () => string;
|
|
121
|
+
doctorChecklist?: typeof listChecklist;
|
|
122
|
+
doctorRun?: (command: string, args: string[]) => { status: number | null; stdout: string };
|
|
123
|
+
/** `tasks hand --live --checklist` checks open PRs before creating another implementation run. */
|
|
124
|
+
handOpenPrRun?: (command: string, args: string[]) => { status: number | null; stdout: string };
|
|
125
|
+
/** Repository selected for the open-PR census; the same origin is used by PR adoption. */
|
|
126
|
+
handOriginRepo?: () => string;
|
|
127
|
+
doctorLedger?: (runId: string, statePath: string) => import('../self-implement/run-ledger.js').RunLedgerEntry[] | null;
|
|
99
128
|
/** TA-REJUDGE-ON-HEAD — `tasks rejudge` 의 판단 의존(시험 주입 · 기본 = 설정 liveMoves ⊕ 실물 gh·리뷰·재게이트·land). */
|
|
100
129
|
rejudge?: Partial<import('../task-agent/rejudge-on-head.js').RejudgeDeps>;
|
|
101
130
|
/** TA-REJUDGE-ON-HEAD — `tasks regate-run` 의 재게이트(시험 주입 · 기본 `runHostRegate`). */
|
|
@@ -122,21 +151,6 @@ function advanceLines(result: AdvanceMissionResult): string[] {
|
|
|
122
151
|
];
|
|
123
152
|
}
|
|
124
153
|
|
|
125
|
-
/** `tasks hand`·`show` 가 남기는 `task-agent` 관측의 logs.db surface 라벨. */
|
|
126
|
-
export const TASK_AGENT_SINK_SURFACE = 'task-agent';
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 독립 CLI 프로세스는 데몬의 logs.db 싱크를 상속하지 않는다 — 등록하지 않으면 `debug.log('task-agent', …)` 가
|
|
130
|
-
* 파일 트레일에만 남고 `elanous logs --category task-agent` 에 0건으로 보인다(10-06 실측: 카드 3장 · 이벤트 0).
|
|
131
|
-
* fail-open — 등록 실패가 과제 넘기기를 막지 않는다.
|
|
132
|
-
*/
|
|
133
|
-
export async function registerTaskAgentSink(register?: (surface: string) => Promise<unknown>): Promise<void> {
|
|
134
|
-
try {
|
|
135
|
-
const fn = register ?? (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink;
|
|
136
|
-
await fn(TASK_AGENT_SINK_SURFACE);
|
|
137
|
-
} catch { /* fail-open — 파일 트레일이 진실원 */ }
|
|
138
|
-
}
|
|
139
|
-
|
|
140
154
|
/**
|
|
141
155
|
* 기본 발사기 — 지금 도는 진입점으로 `--config-dir <운영 config>` 를 붙여 떼어 띄운다.
|
|
142
156
|
* config-dir 은 명시(`--config-dir`/`--test`)가 있으면 그것, 없으면 운영 루트(`prodInstanceRoot()`)다.
|
|
@@ -181,7 +195,8 @@ export interface DefaultTaskLauncherDeps {
|
|
|
181
195
|
now?: () => number;
|
|
182
196
|
/** Isolated integration seam; production still reads the HQ rotation snapshot and config directly. */
|
|
183
197
|
ownerCodexRotation?: () => ReturnType<typeof import('../oauth/codex-account-store.js').inspectCodexRotation>;
|
|
184
|
-
ownerCodexRotationConfig?: { accountOrder?: readonly string[]; thresholdPercentByAccount?: Readonly<Record<string, unknown
|
|
198
|
+
ownerCodexRotationConfig?: { accountOrder?: readonly string[]; thresholdPercentByAccount?: Readonly<Record<string, unknown>>;
|
|
199
|
+
creditsAllowed?: boolean; creditReserve?: number };
|
|
185
200
|
/** The HQ owner fence is checked before any token refresh. */
|
|
186
201
|
ownerCodexLeaseHolder?: () => boolean;
|
|
187
202
|
/** 원격 → 로컬 되돌림 알림(시험 주입). 기본 = stderr 한 줄. */
|
|
@@ -226,7 +241,8 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
|
|
|
226
241
|
import('../oauth/codex.js'),
|
|
227
242
|
import('../hq/hq.js'),
|
|
228
243
|
]);
|
|
229
|
-
const { inspectCodexRotation } = await import('../oauth/codex-account-store.js');
|
|
244
|
+
const { inspectCodexRotation, codexQuotaPolicyFromConfig } = await import('../oauth/codex-account-store.js');
|
|
245
|
+
const { codexPolicyAllowsCredits } = await import('../oauth/codex-quota-policy.js');
|
|
230
246
|
const { planOwnerCodexMirrorCandidates, podAccountThresholdPercent } = await import('../task-orchestrator/surfaces/pod-account-broker.js');
|
|
231
247
|
const { getUserConfig } = await import('../user-config.js');
|
|
232
248
|
const rotation = deps.ownerCodexRotation?.() ?? inspectCodexRotation();
|
|
@@ -240,6 +256,8 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
|
|
|
240
256
|
const eligible = planOwnerCodexMirrorCandidates(candidates, {
|
|
241
257
|
accountOrder: ownerConfig?.accountOrder ?? config.llm?.codexAccountOrder ?? [],
|
|
242
258
|
thresholdPercentByAccount: ownerConfig?.thresholdPercentByAccount ?? config.llm?.codexAccountRotationThresholdPercentByAccount,
|
|
259
|
+
creditsAllowed: ownerConfig?.creditsAllowed ?? codexPolicyAllowsCredits(codexQuotaPolicyFromConfig()),
|
|
260
|
+
creditReserve: ownerConfig?.creditReserve ?? config.llm?.codexCreditReserve,
|
|
243
261
|
});
|
|
244
262
|
if (candidates.length > 0 && eligible.length === 0) throw new NoEligibleOwnerCodexAccountError('HQ 소유 Codex 계정이 모두 사용 임계에 도달해 원격 부모 발사를 거부한다');
|
|
245
263
|
const prerefreshDeps = {
|
|
@@ -339,9 +357,19 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
|
|
|
339
357
|
const capacities = Object.fromEntries(multi ? eligible.map((target) => [target.host, parentHostModule.readParentHostCapacity(target, deps.ssh)]) : []);
|
|
340
358
|
const measured = eligible.filter((target) => fits[target.host]?.state === 'fits');
|
|
341
359
|
const unknown = eligible.filter((target) => fits[target.host]?.state !== 'fits');
|
|
342
|
-
const
|
|
343
|
-
? [...parentHostModule.
|
|
344
|
-
: eligible;
|
|
360
|
+
const ranked = multi
|
|
361
|
+
? [...parentHostModule.rankParentHosts(measured, context.runId, capacities), ...parentHostModule.rankParentHosts(unknown, context.runId, capacities)]
|
|
362
|
+
: eligible.map((target) => ({ target, weight: null, score: null }));
|
|
363
|
+
const ordered = ranked.map(({ target }) => target);
|
|
364
|
+
let rankingRecorded = false;
|
|
365
|
+
const recordHostRanking = (selectedHost: string | null): void => {
|
|
366
|
+
if (rankingRecorded) return;
|
|
367
|
+
rankingRecorded = true;
|
|
368
|
+
for (const { target, weight, score } of ranked) {
|
|
369
|
+
const sample = capacities[target.host];
|
|
370
|
+
try { debug.log('task-agent.parent-host', 'host-ranked', { host: target.host, runId: context.runId, active: sample?.active ?? null, waiting: sample?.waiting ?? null, headroom: sample?.approvalHeadroom ?? null, capacityState: !sample ? 'unmeasured' : sample.approvalHeadroom > 0 ? 'available' : 'saturated', load: sample?.load ?? null, weight: sample ? weight : null, score: sample && Number.isFinite(score) ? score : null, selected: selectedHost === target.host }); } catch { /* fail-soft */ }
|
|
371
|
+
}
|
|
372
|
+
};
|
|
345
373
|
for (const target of ordered) {
|
|
346
374
|
if (multi && (!capacities[target.host] || capacities[target.host]!.approvalHeadroom <= 0)) {
|
|
347
375
|
failed(target, ['capacity-unavailable']);
|
|
@@ -394,6 +422,7 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
|
|
|
394
422
|
else {
|
|
395
423
|
try { debug.log('task-agent.parent-host', 'codex-access-mirror-unavailable', { host: target.host, runId: context.runId, reason: mirrorReason }); } catch { /* fail-soft */ }
|
|
396
424
|
failed(target, ['codex-access-mirror-unavailable']);
|
|
425
|
+
recordHostRanking(null);
|
|
397
426
|
return null;
|
|
398
427
|
}
|
|
399
428
|
if (selected?.account) {
|
|
@@ -406,15 +435,18 @@ async function tryRemoteParent(args: string[], cwd: string | undefined, context:
|
|
|
406
435
|
}
|
|
407
436
|
const launched = parentHostModule.launchRemoteParent(target, args, remoteEnv, deps.ssh,
|
|
408
437
|
grokAccessMirror ? () => grokAccessMirror : undefined, effectiveChildProvider, deps.now);
|
|
438
|
+
recordHostRanking(target.host);
|
|
409
439
|
try { debug.log('task-agent', 'parent-host-launched', { host: target.host, runId: context.runId, pid: launched.pid, log: launched.log }); } catch { /* fail-soft */ }
|
|
410
440
|
return { runId: context.runId, parentHost: { host: target.host, ...(target.configDir ? { configDir: target.configDir } : {}), pid: launched.pid, log: launched.log } };
|
|
411
441
|
} catch (error) {
|
|
412
442
|
if (error instanceof parentHostModule.RemoteParentNotLaunchedError) { failed(target, [error.message]); continue; }
|
|
413
|
-
if (error instanceof NoEligibleOwnerCodexAccountError) throw error;
|
|
443
|
+
if (error instanceof NoEligibleOwnerCodexAccountError) { recordHostRanking(null); throw error; }
|
|
414
444
|
try { debug.log('task-agent', 'parent-host-launch-uncertain', { host: target.host, runId: context.runId, error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
445
|
+
recordHostRanking(null);
|
|
415
446
|
throw error;
|
|
416
447
|
}
|
|
417
448
|
}
|
|
449
|
+
recordHostRanking(null);
|
|
418
450
|
return null;
|
|
419
451
|
}
|
|
420
452
|
|
|
@@ -534,157 +566,130 @@ export function spawnDetachedConfirmed(spawnFn: DetachedSpawn, command: string,
|
|
|
534
566
|
});
|
|
535
567
|
}
|
|
536
568
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
let card: TaskCard | undefined;
|
|
540
|
-
try { card = readTaskCard(cardId, deps.taskStatePath); } catch { return; }
|
|
541
|
-
if (!card?.parentHost || !card.runId) return;
|
|
542
|
-
try {
|
|
543
|
-
if (deps.remoteLedgerMirror) { deps.remoteLedgerMirror(card); return; }
|
|
544
|
-
const { mirrorRemoteRunLedgers } = await import('../task-agent/parent-host.js');
|
|
545
|
-
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
546
|
-
mirrorRemoteRunLedgers(card.parentHost, card.runId, effectiveInstanceRoot(), deps.cardRunEvidence?.ssh);
|
|
547
|
-
} catch (error) {
|
|
548
|
-
try { debug.log('task-agent', 'remote-ledger-mirror-failed', { card: cardId, host: card.parentHost.host, runId: card.runId, error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function moveLine(card: TaskCard): string {
|
|
553
|
-
const move = nextMoveFor(card);
|
|
554
|
-
return `${move.kind}${move.command ? ` — elanous ${move.command.map(shellQuote).join(' ')}` : ''} (${move.reason})`;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function headLine(text: string): string {
|
|
558
|
-
const first = text.split('\n')[0]!.trim();
|
|
559
|
-
return first.length > 100 ? `${first.slice(0, 100)}…` : first;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/** 런 연결 한 줄 — 런 id 가 없으면 없다(옛 카드 · shadow). */
|
|
563
|
-
function runLinkLine(card: TaskCard): string[] {
|
|
564
|
-
if (!card.runId) return [];
|
|
565
|
-
const number = cardPrNumber(card);
|
|
566
|
-
const url = typeof card.pr === 'object' ? card.pr.url : undefined;
|
|
567
|
-
const pr = number !== undefined ? `#${number}${url ? ` ${url}` : ''}` : '-';
|
|
568
|
-
return [`런: ${card.runId} · 골: ${card.goalId ?? '-'} · PR: ${pr}${card.runChildId ? ` · 자식 런: ${card.runChildId}` : ''}`];
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
/** RUN-IDENTITY-TAGS — 신분 한 줄 + Pod 라벨 한 줄(원장 정본 2줄 렌더 · 신분 없는 카드는 «미기록»으로 적고 기본값 정책을 밝힌다). */
|
|
572
|
-
function cardIdentityLines(card: TaskCard): string[] {
|
|
573
|
-
const identity = card.identity;
|
|
574
|
-
const { labels } = taskCardLabelContext({ id: card.id });
|
|
575
|
-
// 경계: Pod 에 실제로 붙는 라벨은 카드 id 하나뿐이다 — 신분 값은 Pod 라벨에 아직 안 실린다(RUN-IDENTITY-ADMIT 몫). 화면도 그렇게 말한다.
|
|
576
|
-
const podLine = `Pod 라벨 ${Object.entries(labels).map(([key, value]) => `${key}=${value}`).join(' ')} (신분 값은 Pod 라벨 미탑재 — RUN-IDENTITY-ADMIT)`;
|
|
577
|
-
if (!identity) return [`신분 미기록 — 원장에 identity 없음 · 승인·선점은 기본 class=${runPriorityClass(identity)} 로 읽는다`, podLine];
|
|
578
|
-
// ⛔ 모르는 칸을 기본값으로 보이지 않는다 — 원장에 없는 칸은 «미기록»으로 적는다(확인된 칸만 값으로).
|
|
579
|
-
const identityText = RUN_TAG_KEYS.map((key) => `${key}=${runIdentityFieldText(identity, key)}`).join(' · ');
|
|
580
|
-
const originParts = [identity.origin?.checklistId, identity.origin?.version, identity.origin?.seat, identity.origin?.launcher].filter((part) => part !== undefined);
|
|
581
|
-
return [
|
|
582
|
-
`신분 ${identityText}${originParts.length > 0 ? ` — 출생 ${originParts.join(' · ')}` : ''}`,
|
|
583
|
-
podLine,
|
|
584
|
-
];
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
function taskCardLines(card: TaskCard): string[] {
|
|
588
|
-
return [
|
|
589
|
-
[card.id, card.status, card.seat ?? '-', card.checklistId ?? '-', card.createdAt, card.text].join('\t'),
|
|
590
|
-
...cardIdentityLines(card),
|
|
591
|
-
...(card.targetPaths !== undefined ? [`대상 경로: ${card.targetPaths.length ? card.targetPaths.join(' · ') : '(유효한 경로 없음)'}`] : []),
|
|
592
|
-
...(card.mission ? [`미션: ${card.mission} · after: ${card.after?.length ? card.after.join(', ') : '-'}`] : []),
|
|
593
|
-
...runLinkLine(card),
|
|
594
|
-
...liveMoveLines(card),
|
|
595
|
-
`다음 수: ${moveLine(card)}`,
|
|
596
|
-
];
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/** TA-LIVE-MOVE-CARD-HISTORY — 카드 history 의 live-move 줄(실행·그림자)을 한 줄씩. 없으면 없다(옛 카드 그대로). */
|
|
600
|
-
function liveMoveLines(card: TaskCard): string[] {
|
|
601
|
-
return (card.history ?? []).filter((item) => item.event === LIVE_MOVE_HISTORY_EVENT || item.event === LIVE_MOVE_RESULT_EVENT).map((item) => item.event === LIVE_MOVE_RESULT_EVENT ? [
|
|
602
|
-
// TA-REVIEW-RESULT-CAPTURE — 회수한 리뷰 결과(또는 «결과 없음») 줄.
|
|
603
|
-
`결과: ${item.at}`, `${item.kind ?? '?'}`, `PR ${item.pr !== undefined ? `#${item.pr}` : '-'}${item.head ? ` head ${item.head.slice(0, 12)}` : ''}`,
|
|
604
|
-
`verdict=${item.verdict ?? '—'} reviewed=${item.reviewed ?? '—'} must-fix=${item.mustFix ?? '—'} route=${item.reviewRoute ?? '—'}`,
|
|
605
|
-
`효과: ${item.effect ?? 'unknown'}`, (item.detail ?? '').replace(/\r\n|\r|\n/g, (match) => (match === '\r' ? '\\r' : '\\n')),
|
|
606
|
-
].join(' · ') : [
|
|
607
|
-
`수: ${item.at}`, `${item.kind ?? '?'} ${item.executorResult ?? '?'}`, `executed=${item.executed === true} ok=${item.ok === undefined ? '—' : item.ok}`,
|
|
608
|
-
`PR ${item.pr !== undefined ? `#${item.pr}` : '-'}${item.head ? ` head ${item.head.slice(0, 12)}` : ''}`,
|
|
609
|
-
`효과: ${item.effect ?? 'unknown'}`, (item.detail ?? '').replace(/\r\n|\r|\n/g, (match) => (match === '\r' ? '\\r' : '\\n')),
|
|
610
|
-
].join(' · '));
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/** 미션 카드 — 머리 한 줄 ⊕ 조각 한 줄씩(상태·다음 수·선행) ⊕ 간선 한 줄씩. */
|
|
614
|
-
function missionLines(mission: TaskCard, pieces: TaskCard[]): string[] {
|
|
615
|
-
const edges = pieces.flatMap((piece) => (piece.after ?? []).map((before) => `간선: ${before} -> ${piece.id}`));
|
|
616
|
-
return [
|
|
617
|
-
[mission.id, 'mission', mission.seat ?? '-', mission.checklistId ?? '-', mission.createdAt, `조각 ${pieces.length} · 간선 ${edges.length} · 출처 ${mission.splitSource ?? '-'}`, headLine(mission.text)].join('\t'),
|
|
618
|
-
...pieces.map((piece) => [`조각: ${piece.id}`, piece.status, nextMoveFor(piece).kind, `after: ${piece.after?.length ? piece.after.join(',') : '-'}`, headLine(piece.text),
|
|
619
|
-
...(piece.runId ? [`런: ${piece.runId}${cardPrNumber(piece) !== undefined ? ` · PR #${cardPrNumber(piece)}` : ''}`] : [])].join('\t')),
|
|
620
|
-
...edges,
|
|
621
|
-
...(mission.greenProposal ? [`green 제안: ${mission.greenProposal.at} · 칸 ${mission.greenProposal.checklistId ?? '-'} · 근거 ${Object.entries(mission.greenProposal.evidence).map(([id, ref]) => `${id}=${ref}`).join(', ')}`] : []),
|
|
622
|
-
];
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
const PRIORITY_RANK: Record<Task['priority'], number> = { urgent: 0, high: 1, medium: 2, low: 3 };
|
|
626
|
-
const NEXUS_DOWN = '넥서스가 안 떠 있다 — `elanous nexus run`';
|
|
627
|
-
|
|
628
|
-
function taskOrder(a: TaskRow, b: TaskRow): number {
|
|
629
|
-
return PRIORITY_RANK[a.priority] - PRIORITY_RANK[b.priority]
|
|
630
|
-
|| a.createdAt - b.createdAt || a.id.localeCompare(b.id);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
function taskSource(task: TaskRow): string {
|
|
634
|
-
return task.generatedBy?.kind === 'external'
|
|
635
|
-
? `${task.generatedBy.provider}:${task.generatedBy.ref}` : '-';
|
|
569
|
+
function orchestrationId(): string {
|
|
570
|
+
return randomUUID();
|
|
636
571
|
}
|
|
637
572
|
|
|
638
|
-
|
|
639
|
-
return [task.id, task.priority, task.status, task.approval?.state ?? '-', taskSource(task), task.title].join('\t');
|
|
640
|
-
}
|
|
573
|
+
type MapCliItem = MapItem & { text?: string; graphInput?: HostMapItem['graphInput']; inventoryMissing?: boolean };
|
|
641
574
|
|
|
642
|
-
function
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
// daemon accepts loopback callers without one — send the bearer only when there is one.
|
|
648
|
-
let token = deps.bearerToken;
|
|
649
|
-
if (token === undefined) {
|
|
650
|
-
try { token = readFileSync(join(getElanousConfigDir(), 'acp-token'), 'utf8').trim() || undefined; }
|
|
651
|
-
catch { token = undefined; }
|
|
652
|
-
}
|
|
653
|
-
return { baseUrl, ...(token ? { token } : {}) };
|
|
575
|
+
function selectMapIds(inventory: MapInventory<MapCliItem>, requestedIds: readonly string[] | undefined): MapInventory<MapCliItem> {
|
|
576
|
+
if (!requestedIds) return inventory;
|
|
577
|
+
if (!inventory.complete) return inventory;
|
|
578
|
+
const byId = new Map(inventory.items.map((item) => [item.id, item]));
|
|
579
|
+
return { ...inventory, items: requestedIds.map((id) => byId.get(id) ?? { id, text: `requested ${id}`, inventoryMissing: true }) };
|
|
654
580
|
}
|
|
655
581
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
582
|
+
/** Read a bounded, current inventory. A failed read is a HOLD, never an empty inventory. */
|
|
583
|
+
async function currentMapInventory(recipe: MapRecipe, source: 'landed-but-yellow' | 'drafts', limit: number | undefined,
|
|
584
|
+
root: string, mapRunId: string, taskStatePath: string, requestedIds?: readonly string[]): Promise<MapInventory<MapCliItem>> {
|
|
585
|
+
const run = (command: string, args: string[]): string => {
|
|
586
|
+
const result = spawnSync(command, args, { cwd: process.cwd(), encoding: 'utf8', timeout: 30_000 });
|
|
587
|
+
if (result.status !== 0) throw new Error(`${command} ${args.slice(0, 2).join(' ')}: ${result.error?.message ?? result.stderr?.trim() ?? 'read failed'}`);
|
|
588
|
+
return result.stdout.trim();
|
|
589
|
+
};
|
|
590
|
+
const recipePath = join(process.cwd(), 'graphs', 'work', `${recipe}.yaml`);
|
|
591
|
+
if (!existsSync(recipePath)) throw new Error(`recipe-missing: ${recipePath}`);
|
|
592
|
+
const recipeSha = run('git', ['hash-object', recipePath]);
|
|
593
|
+
const mainSha = run('git', ['rev-parse', 'origin/main']);
|
|
594
|
+
if (!/^[a-f0-9]{40}$/.test(recipeSha) || !/^[a-f0-9]{40}$/.test(mainSha)
|
|
595
|
+
|| run('git', ['ls-remote', 'origin', 'refs/heads/main']).split(/\s+/)[0] !== mainSha)
|
|
596
|
+
throw new Error('origin-main-unverified');
|
|
597
|
+
const originUrl = run('git', ['remote', 'get-url', 'origin']);
|
|
598
|
+
const repo = /github\.com[:/]([^/\s]+\/[^/\s]+?)(?:\.git)?$/i.exec(originUrl)?.[1]?.toLowerCase();
|
|
599
|
+
if (!repo) throw new Error('origin-repository-unverified');
|
|
600
|
+
const snapshotTime = new Date().toISOString();
|
|
601
|
+
let items: MapCliItem[];
|
|
602
|
+
let checklistSnapshotPath: string | null = null;
|
|
603
|
+
let snapshotRef: string;
|
|
604
|
+
if (source === 'landed-but-yellow') {
|
|
605
|
+
const version = checklistDevVersion();
|
|
606
|
+
const snapshot = listChecklist(version);
|
|
607
|
+
const cards = Object.values(readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(taskStatePath).tasks ?? {});
|
|
608
|
+
items = [];
|
|
609
|
+
const selected = requestedIds ? new Set(requestedIds) : null;
|
|
610
|
+
for (const item of snapshot.items.filter((entry) => entry.status === 'yellow' && (!selected || selected.has(entry.id)))) {
|
|
611
|
+
const candidates = greenSweepCandidates(item, version, cards);
|
|
612
|
+
for (const candidate of candidates) {
|
|
613
|
+
const raw = run('gh', ['pr', 'view', String(candidate.pr), '--repo', repo, '--json', 'number,state,mergeCommit,url']);
|
|
614
|
+
const pr = JSON.parse(raw) as { number?: number; state?: string; mergeCommit?: { oid?: string }; url?: string };
|
|
615
|
+
if (pr.number !== candidate.pr) throw new Error(`pr-identity-unverified: ${candidate.pr}`);
|
|
616
|
+
const prRepo = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/\d+/i.exec(pr.url ?? '')?.[1]?.toLowerCase();
|
|
617
|
+
if (prRepo !== repo) throw new Error(`pr-repository-unverified: ${candidate.pr}`);
|
|
618
|
+
let mergeSha = pr.state === 'MERGED' ? pr.mergeCommit?.oid ?? '' : '';
|
|
619
|
+
if (pr.state === 'MERGED' && !mergeSha) throw new Error(`pr-merge-unverified: ${candidate.pr}`);
|
|
620
|
+
const train = candidate.card?.trainLanding;
|
|
621
|
+
if (!mergeSha && pr.state === 'CLOSED' && train?.componentPr === candidate.pr) {
|
|
622
|
+
const integration = JSON.parse(run('gh', ['pr', 'view', String(train.integrationPr), '--repo', repo, '--json', 'number,state,mergeCommit'])) as
|
|
623
|
+
{ number?: number; state?: string; mergeCommit?: { oid?: string } };
|
|
624
|
+
if (integration.number === train.integrationPr && integration.state === 'MERGED'
|
|
625
|
+
&& integration.mergeCommit?.oid === train.mergeSha) mergeSha = train.mergeSha;
|
|
626
|
+
}
|
|
627
|
+
if (!mergeSha) continue;
|
|
628
|
+
if (!/^[a-f0-9]{40}$/i.test(mergeSha)) throw new Error(`pr-merge-unverified: ${candidate.pr}`);
|
|
629
|
+
const ancestor = spawnSync('git', ['merge-base', '--is-ancestor', mergeSha, 'origin/main'], { cwd: process.cwd() });
|
|
630
|
+
if (ancestor.status !== 0) throw new Error(`pr-main-ancestor-unverified: ${candidate.pr}`);
|
|
631
|
+
// A card can add scope, but cannot remove the current checklist's declared paths.
|
|
632
|
+
// Invalid current scope stays HOLD even if an old TA card has valid paths.
|
|
633
|
+
const paths = greenCloseDeclaredPaths(item.title, candidate.card?.targetPaths);
|
|
634
|
+
items.push({ id: item.id, text: item.title,
|
|
635
|
+
graphInput: { cellId: item.id, version, title: item.title, evidence: item.evidence ?? '', paths,
|
|
636
|
+
candidates: [{ pr: candidate.pr, prUrl: pr.url, mergeSha }], main: { ref: 'origin/main', sha: mainSha } } });
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
if (!requestedIds && limit !== undefined && items.length >= limit) break;
|
|
640
|
+
}
|
|
641
|
+
const snapshotBody = `${JSON.stringify({ version, mainSha, checklist: snapshot,
|
|
642
|
+
selectedIds: items.map((item) => item.id), requestedIds: requestedIds ?? null })}\n`;
|
|
643
|
+
snapshotRef = `checklist-${version}@${createHash('sha256').update(snapshotBody).digest('hex')}`;
|
|
644
|
+
checklistSnapshotPath = join(root, 'map-runs', `${mapRunId}-checklist.json`);
|
|
645
|
+
mkdirSync(dirname(checklistSnapshotPath), { recursive: true });
|
|
646
|
+
writeFileSync(checklistSnapshotPath, snapshotBody, { flag: 'wx', mode: 0o600 });
|
|
647
|
+
} else {
|
|
648
|
+
const cap = 1000;
|
|
649
|
+
const parsed: unknown = JSON.parse(run('gh', ['pr', 'list', '--repo', repo, '--state', 'open', '--draft', '--limit', String(cap),
|
|
650
|
+
'--json', 'number,title,headRefOid']));
|
|
651
|
+
if (!Array.isArray(parsed) || parsed.length >= cap || parsed.some((entry) => !entry || typeof entry !== 'object'
|
|
652
|
+
|| !Number.isSafeInteger((entry as { number?: unknown }).number) || typeof (entry as { title?: unknown }).title !== 'string'
|
|
653
|
+
|| typeof (entry as { headRefOid?: unknown }).headRefOid !== 'string'))
|
|
654
|
+
throw new Error('draft-inventory-incomplete');
|
|
655
|
+
const selected = requestedIds ? new Set(requestedIds) : null;
|
|
656
|
+
items = parsed.filter((entry) => !selected || selected.has(`pr-${(entry as { number: number }).number}`))
|
|
657
|
+
.slice(0, requestedIds ? undefined : limit).map((entry) => ({ id: `pr-${(entry as { number: number }).number}`,
|
|
658
|
+
text: (entry as { title: string }).title }));
|
|
659
|
+
if (new Set(items.map((item) => item.id)).size !== items.length) throw new Error('draft-inventory-duplicate');
|
|
660
|
+
const openDrafts = parsed.map((entry) => ({ number: (entry as { number: number }).number,
|
|
661
|
+
title: (entry as { title: string }).title }));
|
|
662
|
+
for (const item of items) {
|
|
663
|
+
const number = Number(item.id.slice(3));
|
|
664
|
+
const detail = JSON.parse(run('gh', ['pr', 'view', String(number), '--repo', repo,
|
|
665
|
+
'--json', 'number,title,body,headRefOid,headRefName,labels,isDraft,state,createdAt,files,changedFiles'])) as {
|
|
666
|
+
number?: number; title?: string; body?: string; headRefOid?: string; headRefName?: string;
|
|
667
|
+
labels?: { name: string }[]; isDraft?: boolean; state?: string; createdAt?: string;
|
|
668
|
+
files?: { path: string }[]; changedFiles?: number;
|
|
669
|
+
};
|
|
670
|
+
if (detail.number !== number || detail.headRefOid !== (parsed.find((entry) => (entry as { number: number }).number === number) as { headRefOid: string }).headRefOid
|
|
671
|
+
|| detail.state !== 'OPEN' || detail.isDraft !== true) throw new Error(`draft-head-or-state-changed: ${number}`);
|
|
672
|
+
const changedFiles = detail.files?.map((file) => file.path) ?? [];
|
|
673
|
+
const fullFiles = Number.isSafeInteger(detail.changedFiles) && detail.changedFiles === changedFiles.length;
|
|
674
|
+
const graphItem = { pr: number, headOid: detail.headRefOid, repository: repo, state: 'open', isDraft: true,
|
|
675
|
+
labels: detail.labels?.map((label) => label.name) ?? [], branch: detail.headRefName,
|
|
676
|
+
title: detail.title, body: detail.body, createdAt: detail.createdAt,
|
|
677
|
+
changedFiles, changedFilesComplete: fullFiles, latestFileChanges: {},
|
|
678
|
+
openDrafts, openDraftsComplete: true, mergedTwins: [], mergedTwinsComplete: false,
|
|
679
|
+
liveBranches: [], runStatus: 'unmeasured' };
|
|
680
|
+
item.graphInput = { mapRunId, snapshotTime, inventoryComplete: true, mainSha,
|
|
681
|
+
inventory: { complete: true, snapshotTime, mainSha, items: openDrafts }, item: graphItem };
|
|
682
|
+
}
|
|
683
|
+
snapshotRef = `drafts@${createHash('sha256').update(JSON.stringify(parsed)).digest('hex')}`;
|
|
667
684
|
}
|
|
685
|
+
return { items, complete: true, snapshotTime, snapshotRef, recipeSha, mainSha, checklistSnapshotPath,
|
|
686
|
+
requestedPool: 'cold-job', selectedPool: null, imageCommit: null };
|
|
668
687
|
}
|
|
669
688
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
function httpError(response: Response): Error {
|
|
676
|
-
// Do not echo server-supplied error bodies: they may contain credentials.
|
|
677
|
-
return new Error(`HTTP ${response.status}`);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
function handleError(error: unknown, out: (line: string) => void): void {
|
|
681
|
-
if (error instanceof NexusUnavailable) {
|
|
682
|
-
out(NEXUS_DOWN);
|
|
683
|
-
process.exitCode = 2;
|
|
684
|
-
} else {
|
|
685
|
-
out(error instanceof Error ? error.message : '태스크 요청 실패');
|
|
686
|
-
process.exitCode = 1;
|
|
687
|
-
}
|
|
689
|
+
export function greenCloseDeclaredPaths(title: string, cardPaths?: readonly string[]): string[] {
|
|
690
|
+
const declared = parseAskTargetPathHintsResult(title);
|
|
691
|
+
if (declared.labelMissing || declared.rejected.length > 0) return [];
|
|
692
|
+
return [...new Set([...declared.paths, ...(cardPaths ?? [])])];
|
|
688
693
|
}
|
|
689
694
|
|
|
690
695
|
const COVER_QUERY_LIMIT = STORE_SAFETY_MAX;
|
|
@@ -1022,37 +1027,56 @@ export interface TaskOutcomeRow {
|
|
|
1022
1027
|
cell: string | null;
|
|
1023
1028
|
card: string;
|
|
1024
1029
|
runId: string | null;
|
|
1025
|
-
/**
|
|
1030
|
+
/** 사유 원장을 실제로 읽은 자식 런(부모 pod-child-run 매핑). */
|
|
1026
1031
|
childRunId: string | null;
|
|
1027
1032
|
/** 부모 호스트 원격 이름 — HQ 로컬 발사면 null. */
|
|
1028
1033
|
host: string | null;
|
|
1029
|
-
/**
|
|
1034
|
+
/** 이 행의 카드 또는 PR 종결 사건 시각(ISO). */
|
|
1030
1035
|
endedAt: string;
|
|
1036
|
+
cardTerminalAt: string | null;
|
|
1037
|
+
/** 카드가 결속 PR의 종단을 관측한 시각. GitHub 원본 mergedAt/closedAt 와 동일하다고 추정하지 않는다. */
|
|
1038
|
+
prTerminalAt: string | null;
|
|
1039
|
+
/** 이 카드 원장은 GitHub 원본 terminal timestamp 를 보존하지 않는다. */
|
|
1040
|
+
ghTerminalAt: null;
|
|
1041
|
+
ghTerminalMissing: 'not-stored-in-card';
|
|
1042
|
+
/** 이 행이 들어가는 독립 분모. 같은 카드의 카드·PR 종결은 별도 행이다. */
|
|
1043
|
+
terminalKind: 'card' | 'pr';
|
|
1044
|
+
/** 이 사건을 센 근거 — 카드 상태·이력 사건 또는 결속된 PR 상태를 그대로 적는다. */
|
|
1045
|
+
countReason: string;
|
|
1031
1046
|
/** 원시 카드 상태. board가 PR 종결로 투영한 상태와 구별한다. */
|
|
1032
1047
|
cardStatus: TaskCard['status'];
|
|
1033
1048
|
/** PR 결속 — 번호 ⊕ 종결 상태(종결을 못 봤으면 state null). */
|
|
1034
1049
|
pr: { number: number; state: 'MERGED' | 'CLOSED' | null } | null;
|
|
1035
|
-
/** 종결
|
|
1036
|
-
reason: null;
|
|
1037
|
-
|
|
1050
|
+
/** 종결 사유 원문 — 자식 원장의 마지막 pr-open-failed/child-error data.error 꼬리(없으면 null). */
|
|
1051
|
+
reason: string | null;
|
|
1052
|
+
/** 종결 사유를 읽은 곳(`child-ledger:<event>` · 못 읽었으면 null). */
|
|
1053
|
+
reasonFrom: string | null;
|
|
1054
|
+
/** 원본 증거 또는 그 증거를 못 실은 이유. */
|
|
1055
|
+
evidence: { childLedger: { event: string; error: string } | null; childLedgerMissing: string | null;
|
|
1056
|
+
lastLines: string[] | null; lastLinesMissing: string | null };
|
|
1038
1057
|
}
|
|
1039
1058
|
|
|
1040
1059
|
export interface TaskOutcomeReport {
|
|
1041
1060
|
since: string | null;
|
|
1042
1061
|
until: string | null;
|
|
1043
1062
|
rows: TaskOutcomeRow[];
|
|
1044
|
-
/** 사유별 집계 —
|
|
1045
|
-
aggregate: { cards: number;
|
|
1063
|
+
/** 사유별 집계 — 사유 분모는 실패 카드 종단뿐이다(PR/성공 종단은 포함하지 않는다). */
|
|
1064
|
+
aggregate: { cards: number; cardTerminals: number; prTerminals: number; failedCardTerminals: number;
|
|
1065
|
+
reasons: Array<{ reason: string; count: number }>; missingReason: number };
|
|
1046
1066
|
/** 조회 상한에 닿아 절단됐나 — 그때 집계는 «이 창 안에서 센 행»만 센다. */
|
|
1047
1067
|
limitReached: boolean;
|
|
1048
1068
|
/** 절단 사유 — 카드 조회 상한. */
|
|
1049
1069
|
stopReason: 'page-limit' | null;
|
|
1050
|
-
/** 자식 원장
|
|
1051
|
-
childLedgerObserved:
|
|
1052
|
-
/**
|
|
1053
|
-
reasonObserved:
|
|
1070
|
+
/** 자식 원장(자식 런 원장 파일)을 실제로 읽었나. */
|
|
1071
|
+
childLedgerObserved: boolean;
|
|
1072
|
+
/** 종결 사유 원문을 하나라도 관측했나. */
|
|
1073
|
+
reasonObserved: boolean;
|
|
1054
1074
|
/** 끝났는데 종료 시각을 몰라 창에 넣지 못한 카드 수. */
|
|
1055
1075
|
undatedCards: number;
|
|
1076
|
+
/** 시각 없는 카드의 기간 귀속은 계산할 수 없다. undatedCards 는 창 밖까지 포함한 수다. */
|
|
1077
|
+
undatedPeriodAttribution: 'unknown';
|
|
1078
|
+
/** 두 종결 사건이 모두 창 안에 있는 카드 ID. --by 축 필터에서도 같은 목록을 낸다. */
|
|
1079
|
+
overlapCardIds: string[];
|
|
1056
1080
|
/** 아직 끝나지 않은 카드 수(창 대상 아님). */
|
|
1057
1081
|
openCards: number;
|
|
1058
1082
|
warning?: string;
|
|
@@ -1063,19 +1087,50 @@ function outcomeTerminalHistoryLine(card: TaskCard): TaskCardEvent | undefined {
|
|
|
1063
1087
|
return (card.history ?? []).filter((item) => OUTCOME_TERMINAL_HISTORY_EVENTS.has(item.event)).at(-1);
|
|
1064
1088
|
}
|
|
1065
1089
|
|
|
1090
|
+
/** 종결 사유 원문을 주는 자식 원장 이벤트 — 뒤에서부터 마지막 것을 쓴다. */
|
|
1091
|
+
const OUTCOME_REASON_LEDGER_EVENTS = new Set(['pr-open-failed', 'child-error']);
|
|
1092
|
+
|
|
1093
|
+
/** 부모 runId 원장의 마지막 pod-child-run 매핑이 가리키는 자식 런. 매핑이 없으면 null. */
|
|
1094
|
+
function outcomeChildRunId(card: TaskCard, ledgerDir: string): string | null {
|
|
1095
|
+
if (typeof card.runId !== 'string' || card.runId === '') return null;
|
|
1096
|
+
const parent = loadRunLedgerSync(card.runId, ledgerDir);
|
|
1097
|
+
if (parent === null) return null;
|
|
1098
|
+
for (let i = parent.length - 1; i >= 0; i -= 1) {
|
|
1099
|
+
const entry = parent[i]!;
|
|
1100
|
+
if (entry.event !== 'pod-child-run') continue;
|
|
1101
|
+
const childRunId = entry.data?.childRunId;
|
|
1102
|
+
return typeof childRunId === 'string' && childRunId !== '' ? childRunId : null;
|
|
1103
|
+
}
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/** 자식 원장의 마지막 pr-open-failed/child-error data.error 꼬리 — 없으면 null. */
|
|
1108
|
+
function outcomeChildLedgerReason(childEntries: readonly RunLedgerEntry[]): { reason: string; reasonFrom: string } | null {
|
|
1109
|
+
for (let i = childEntries.length - 1; i >= 0; i -= 1) {
|
|
1110
|
+
const entry = childEntries[i]!;
|
|
1111
|
+
if (!OUTCOME_REASON_LEDGER_EVENTS.has(entry.event)) continue;
|
|
1112
|
+
const error = entry.data?.error;
|
|
1113
|
+
if (typeof error !== 'string' || error === '') continue;
|
|
1114
|
+
return { reason: error, reasonFrom: `child-ledger:${entry.event}` };
|
|
1115
|
+
}
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1066
1119
|
/**
|
|
1067
1120
|
* TA 카드·PR 종결 사실을 기간으로 센다(읽기 전용). 창은 «종료 시각»에 걸린다.
|
|
1068
|
-
*
|
|
1121
|
+
* 종결 사유 원문은 부모 runId → pod-child-run 매핑 → 자식 원장의 마지막
|
|
1122
|
+
* pr-open-failed/child-error data.error 꼬리에서 읽는다.
|
|
1069
1123
|
*/
|
|
1070
1124
|
export function readTaskOutcomes(
|
|
1071
|
-
window: { sinceMs?: number; untilMs?: number } = {},
|
|
1125
|
+
window: { sinceMs?: number; untilMs?: number; terminalKind?: 'card' | 'pr' } = {},
|
|
1072
1126
|
deps: Pick<TasksCliDeps, 'taskStatePath' | 'outcomesLimit'> = {},
|
|
1073
1127
|
snapshot?: Record<string, TaskCard>,
|
|
1074
1128
|
): Omit<TaskOutcomeReport, 'since' | 'until'> {
|
|
1075
1129
|
const statePath = deps.taskStatePath ?? taskStatePathDefault();
|
|
1076
1130
|
const cards = snapshot ?? readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {};
|
|
1077
1131
|
const limit = deps.outcomesLimit ?? OUTCOMES_QUERY_LIMIT;
|
|
1078
|
-
const candidates: Array<{ card: TaskCard; terminal: TaskCard['prTerminal']; endedAt: string
|
|
1132
|
+
const candidates: Array<{ card: TaskCard; terminal: TaskCard['prTerminal']; endedAt: string;
|
|
1133
|
+
cardTerminalAt: string | null; prTerminalAt: string | null; terminalKind: 'card' | 'pr'; countReason: string }> = [];
|
|
1079
1134
|
let undatedCards = 0;
|
|
1080
1135
|
let openCards = 0;
|
|
1081
1136
|
for (const card of Object.values(cards)) {
|
|
@@ -1092,53 +1147,365 @@ export function readTaskOutcomes(
|
|
|
1092
1147
|
? (OUTCOME_SUCCESS_HISTORY_EVENTS.has(terminalHistory?.event ?? '') || manualClosure ? terminalHistory : undefined)
|
|
1093
1148
|
: failedStatus && (terminalHistory?.event === 'failed' || terminalHistory?.event === 'launch-failed')
|
|
1094
1149
|
? terminalHistory : undefined;
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
if ((
|
|
1103
|
-
|
|
1150
|
+
const validAt = (at: string | undefined): string | null => at && Number.isFinite(Date.parse(at)) ? at : null;
|
|
1151
|
+
const cardTerminalAt = validAt(history?.at);
|
|
1152
|
+
const prTerminalAt = validAt(terminal?.at);
|
|
1153
|
+
if (cardTerminalAt === null && prTerminalAt === null) { undatedCards++; continue; }
|
|
1154
|
+
const inWindow = (at: string | null): at is string => at !== null
|
|
1155
|
+
&& (window.sinceMs === undefined || Date.parse(at) >= window.sinceMs)
|
|
1156
|
+
&& (window.untilMs === undefined || Date.parse(at) <= window.untilMs);
|
|
1157
|
+
if (inWindow(cardTerminalAt)) {
|
|
1158
|
+
candidates.push({ card, terminal, endedAt: cardTerminalAt, cardTerminalAt, prTerminalAt,
|
|
1159
|
+
terminalKind: 'card', countReason: `card:${card.status}:${history!.event}@${cardTerminalAt}` });
|
|
1160
|
+
}
|
|
1161
|
+
if (inWindow(prTerminalAt)) {
|
|
1162
|
+
candidates.push({ card, terminal, endedAt: prTerminalAt, cardTerminalAt, prTerminalAt,
|
|
1163
|
+
terminalKind: 'pr', countReason: `pr:${terminal!.state}:#${terminal!.pr}@${prTerminalAt}` });
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
const overlap = new Map<string, Set<'card' | 'pr'>>();
|
|
1167
|
+
for (const item of candidates) {
|
|
1168
|
+
const kinds = overlap.get(item.card.id) ?? new Set<'card' | 'pr'>();
|
|
1169
|
+
kinds.add(item.terminalKind);
|
|
1170
|
+
overlap.set(item.card.id, kinds);
|
|
1104
1171
|
}
|
|
1105
|
-
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1172
|
+
const overlapCardIds = [...overlap].filter(([, kinds]) => kinds.size === 2).map(([id]) => id).sort();
|
|
1173
|
+
const visible = window.terminalKind ? candidates.filter((item) => item.terminalKind === window.terminalKind) : candidates;
|
|
1174
|
+
visible.sort((a, b) => Date.parse(b.endedAt) - Date.parse(a.endedAt) || a.card.id.localeCompare(b.card.id)
|
|
1175
|
+
|| a.terminalKind.localeCompare(b.terminalKind));
|
|
1176
|
+
const limitReached = visible.length > limit;
|
|
1177
|
+
const selected = limitReached ? visible.slice(0, limit) : visible;
|
|
1178
|
+
const stateRoot = dirname(statePath);
|
|
1179
|
+
const localLedgerDirs = implementLedgerDirectories(statePath, false);
|
|
1180
|
+
const runExits = readRunExits(stateRoot);
|
|
1181
|
+
let childLedgerObserved = false;
|
|
1182
|
+
let reasonObserved = false;
|
|
1183
|
+
const rows = selected.map(({ card, terminal, endedAt, cardTerminalAt, prTerminalAt, terminalKind, countReason }): TaskOutcomeRow => {
|
|
1110
1184
|
const number = cardPrNumber(card);
|
|
1185
|
+
// 종결 사유 원문 — 부모 runId → pod-child-run 매핑 → 자식 원장 꼬리. 원장을 못 찾으면 null.
|
|
1186
|
+
const remoteDir = card.parentHost ? remoteLedgerMirrorDir(stateRoot, card.parentHost.host) : null;
|
|
1187
|
+
// 원격 부모는 해당 호스트 거울에서만 결속한다. 로컬 동명 런을 원격 증거로 오인하지 않는다.
|
|
1188
|
+
const ledgerDirs = remoteDir ? [remoteDir] : localLedgerDirs;
|
|
1189
|
+
const ledgerDir = card.runId ? ledgerDirs.find((dir) => loadRunLedgerSync(card.runId!, dir) !== null) : undefined;
|
|
1190
|
+
const mappedChildRunId = ledgerDir ? outcomeChildRunId(card, ledgerDir) : null;
|
|
1191
|
+
const childBindingMismatch = card.runChildId && mappedChildRunId && card.runChildId !== mappedChildRunId;
|
|
1192
|
+
const childRunId = childBindingMismatch ? null : mappedChildRunId;
|
|
1193
|
+
const childEntries = childRunId === null || !ledgerDir ? null : loadRunLedgerSync(childRunId, ledgerDir);
|
|
1194
|
+
if (childEntries !== null) childLedgerObserved = true;
|
|
1195
|
+
const reasonTail = childEntries === null ? null : outcomeChildLedgerReason(childEntries);
|
|
1196
|
+
const cardReason = terminalKind === 'card' && (card.status === 'failed' || card.status === 'launch-failed') ? reasonTail : null;
|
|
1197
|
+
if (cardReason !== null) reasonObserved = true;
|
|
1198
|
+
const exit = card.runId ? runExits.find((row) => row.runId === card.runId) : undefined;
|
|
1199
|
+
const lastLines = exit?.lastLines?.length ? exit.lastLines : null;
|
|
1111
1200
|
return {
|
|
1112
1201
|
cell: card.checklistId ?? null,
|
|
1113
1202
|
card: card.id,
|
|
1114
1203
|
runId: card.runId ?? null,
|
|
1115
|
-
childRunId
|
|
1204
|
+
childRunId,
|
|
1116
1205
|
host: card.parentHost?.host ?? null,
|
|
1117
1206
|
endedAt,
|
|
1207
|
+
cardTerminalAt,
|
|
1208
|
+
prTerminalAt,
|
|
1209
|
+
ghTerminalAt: null,
|
|
1210
|
+
ghTerminalMissing: 'not-stored-in-card',
|
|
1211
|
+
terminalKind,
|
|
1212
|
+
countReason,
|
|
1118
1213
|
cardStatus: card.status,
|
|
1119
1214
|
pr: number === undefined ? null : { number, state: terminal?.state ?? null },
|
|
1120
|
-
reason: null,
|
|
1121
|
-
reasonFrom: null,
|
|
1215
|
+
reason: cardReason?.reason ?? null,
|
|
1216
|
+
reasonFrom: cardReason?.reasonFrom ?? null,
|
|
1217
|
+
evidence: {
|
|
1218
|
+
childLedger: reasonTail ? { event: reasonTail.reasonFrom.slice('child-ledger:'.length), error: reasonTail.reason } : null,
|
|
1219
|
+
childLedgerMissing: reasonTail ? null : !card.runId ? 'run-id-missing'
|
|
1220
|
+
: remoteDir && !existsSync(remoteDir) ? 'remote-ledger-mirror-missing'
|
|
1221
|
+
: !ledgerDir ? (remoteDir ? 'remote-parent-ledger-missing' : 'parent-ledger-missing')
|
|
1222
|
+
: childBindingMismatch ? 'parent-child-binding-mismatch'
|
|
1223
|
+
: childRunId === null ? 'parent-child-binding-missing'
|
|
1224
|
+
: childEntries === null ? (remoteDir ? 'remote-child-ledger-missing' : 'child-ledger-missing') : 'child-ledger-error-tail-missing',
|
|
1225
|
+
lastLines,
|
|
1226
|
+
lastLinesMissing: lastLines ? null : !card.runId ? 'run-id-missing'
|
|
1227
|
+
: !exit ? 'run-exit-missing' : 'run-exit-last-lines-missing',
|
|
1228
|
+
},
|
|
1122
1229
|
};
|
|
1123
1230
|
});
|
|
1124
|
-
const
|
|
1231
|
+
const reasonTally = new Map<string, number>();
|
|
1232
|
+
for (const row of rows) {
|
|
1233
|
+
if (row.reason === null) continue;
|
|
1234
|
+
reasonTally.set(row.reason, (reasonTally.get(row.reason) ?? 0) + 1);
|
|
1235
|
+
}
|
|
1236
|
+
const reasons = [...reasonTally.entries()]
|
|
1237
|
+
.map(([reason, count]) => ({ reason, count }))
|
|
1238
|
+
.sort((a, b) => b.count - a.count || a.reason.localeCompare(b.reason));
|
|
1239
|
+
const failedCardRows = rows.filter((row) => row.terminalKind === 'card'
|
|
1240
|
+
&& (row.cardStatus === 'failed' || row.cardStatus === 'launch-failed'));
|
|
1241
|
+
const missingReason = failedCardRows.filter((row) => row.reason === null).length;
|
|
1125
1242
|
const warnings = [
|
|
1126
|
-
limitReached ?
|
|
1127
|
-
missingReason > 0 ?
|
|
1243
|
+
limitReached ? `사건 조회 상한 ${limit} 도달 — 창 안 ${visible.length}행 중 ${rows.length}행만 셌다(집계도 이 행들만 센다)` : null,
|
|
1244
|
+
missingReason > 0 ? `실패 카드 종결 사유 원문 없음 — ${missingReason}/${failedCardRows.length}행(증거 부재 이유는 행별 evidence 참조)` : null,
|
|
1245
|
+
undatedCards > 0 ? `종결 시각 미보존 ${undatedCards}장 — 기간 귀속 불가(창 밖 카드 포함)` : null,
|
|
1128
1246
|
].filter((line): line is string => line !== null);
|
|
1129
1247
|
return {
|
|
1130
1248
|
rows,
|
|
1131
|
-
aggregate: { cards: rows.
|
|
1249
|
+
aggregate: { cards: new Set(rows.map((row) => row.card)).size,
|
|
1250
|
+
cardTerminals: rows.filter((row) => row.terminalKind === 'card').length,
|
|
1251
|
+
prTerminals: rows.filter((row) => row.terminalKind === 'pr').length,
|
|
1252
|
+
failedCardTerminals: failedCardRows.length, reasons, missingReason },
|
|
1132
1253
|
limitReached,
|
|
1133
1254
|
stopReason: limitReached ? 'page-limit' : null,
|
|
1134
|
-
childLedgerObserved
|
|
1135
|
-
reasonObserved
|
|
1255
|
+
childLedgerObserved,
|
|
1256
|
+
reasonObserved,
|
|
1136
1257
|
undatedCards,
|
|
1258
|
+
undatedPeriodAttribution: 'unknown',
|
|
1259
|
+
overlapCardIds,
|
|
1137
1260
|
openCards,
|
|
1138
1261
|
...(warnings.length ? { warning: warnings.join(' · ') } : {}),
|
|
1139
1262
|
};
|
|
1140
1263
|
}
|
|
1141
1264
|
|
|
1265
|
+
type DoctorTruth = true | false | 'unknown';
|
|
1266
|
+
type DoctorCondition = { value: DoctorTruth; source: string; detail: string };
|
|
1267
|
+
type DoctorPr = { number: number; state: string | null; draft: boolean | null; head: string | null; changedFiles: number | null };
|
|
1268
|
+
|
|
1269
|
+
export interface TaskDoctorReport {
|
|
1270
|
+
card: string;
|
|
1271
|
+
cell: string | null;
|
|
1272
|
+
version: string;
|
|
1273
|
+
runId: string | null;
|
|
1274
|
+
childRunId: string | null;
|
|
1275
|
+
host: string | null;
|
|
1276
|
+
head: string | null;
|
|
1277
|
+
terminalReason: string | null;
|
|
1278
|
+
terminalReasonSource: string | null;
|
|
1279
|
+
lastGate: { passed: boolean | null; status: string | null; head: string | null; at: string | null; source: string } | null;
|
|
1280
|
+
pr: DoctorPr | null;
|
|
1281
|
+
conditions: { currentYellowUnheldNoOtherPr: DoctorCondition; noPreservedImplementation: DoctorCondition;
|
|
1282
|
+
environmentFailure: DoctorCondition; failureRoundsBelowTwo: DoctorCondition };
|
|
1283
|
+
unknownSources: string[];
|
|
1284
|
+
nextAction: 'relaunch' | 'harvest' | 'reauthor' | 'human';
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function doctorCondition(value: DoctorTruth, source: string, detail: string): DoctorCondition { return { value, source, detail }; }
|
|
1288
|
+
function doctorObject(value: unknown): Record<string, unknown> | null {
|
|
1289
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
1290
|
+
}
|
|
1291
|
+
function doctorString(value: unknown): string | null { return typeof value === 'string' && value.trim() ? value : null; }
|
|
1292
|
+
function doctorTerminal(entry: import('../self-implement/run-ledger.js').RunLedgerEntry): boolean {
|
|
1293
|
+
if (entry.event === 'stop') return true;
|
|
1294
|
+
if (entry.event === 'run-status') return ['failed', 'completed', 'cancelled', 'parked'].includes(String(entry.data.runStatus));
|
|
1295
|
+
return entry.event === 'terminal' && (typeof entry.data.ok === 'boolean' || typeof entry.data.terminal === 'string');
|
|
1296
|
+
}
|
|
1297
|
+
function doctorTerminalFailed(entry: import('../self-implement/run-ledger.js').RunLedgerEntry): boolean {
|
|
1298
|
+
if (entry.event === 'stop') return true;
|
|
1299
|
+
if (entry.event === 'run-status') return entry.data.runStatus === 'failed';
|
|
1300
|
+
return entry.data.ok === false || ['failed', 'aborted', 'gate-failed', 'review-blocked', 'timed-out'].includes(String(entry.data.terminal));
|
|
1301
|
+
}
|
|
1302
|
+
function defaultDoctorLedger(runId: string, statePath: string): import('../self-implement/run-ledger.js').RunLedgerEntry[] | null {
|
|
1303
|
+
let found: import('../self-implement/run-ledger.js').RunLedgerEntry[] | null = null;
|
|
1304
|
+
for (const dir of implementLedgerDirectories(statePath, false)) {
|
|
1305
|
+
const loaded = loadRunLedgerWithMetadata(runId, dir);
|
|
1306
|
+
if (!loaded) continue;
|
|
1307
|
+
if (loaded.skippedTrailingBytes) throw new Error(`incomplete run ledger: ${runId}`);
|
|
1308
|
+
if (found && JSON.stringify(found) !== JSON.stringify(loaded.entries)) throw new Error(`conflicting run ledgers: ${runId}`);
|
|
1309
|
+
found = loaded.entries;
|
|
1310
|
+
}
|
|
1311
|
+
return found;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/** Read-only relaunch preflight. Each decision names its evidence, and a missing adapter remains unknown. */
|
|
1315
|
+
export function readTaskDoctor(target: string, opts: { traffic?: string } = {}, deps: TasksCliDeps = {}): TaskDoctorReport {
|
|
1316
|
+
const statePath = deps.taskStatePath ?? taskStatePathDefault();
|
|
1317
|
+
const state = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath);
|
|
1318
|
+
const cards = Object.values(state.tasks ?? {});
|
|
1319
|
+
const matches = cards.filter((item) => item?.id === target || item?.runId === target || item?.runChildId === target);
|
|
1320
|
+
if (matches.length !== 1) throw new Error(matches.length ? `카드 결속 모호: ${target}` : `카드·런 없음: ${target}`);
|
|
1321
|
+
const card = matches[0]!;
|
|
1322
|
+
const version = (deps.doctorVersion ?? checklistDevVersion)();
|
|
1323
|
+
const cell = card.checklistId ?? null;
|
|
1324
|
+
const run = deps.doctorRun ?? ((command: string, args: string[]) => spawnSync(command, args,
|
|
1325
|
+
{ cwd: process.cwd(), encoding: 'utf8', timeout: 20_000, maxBuffer: 4 * 1024 * 1024 }));
|
|
1326
|
+
|
|
1327
|
+
let yellow: DoctorCondition = doctorCondition('unknown', 'checklist', '카드의 칸을 모른다');
|
|
1328
|
+
if (cell) {
|
|
1329
|
+
try {
|
|
1330
|
+
const snapshot = (deps.doctorChecklist ?? listChecklist)(version);
|
|
1331
|
+
const item = snapshot.items.find((entry) => entry.id === cell);
|
|
1332
|
+
const versionMatches = !card.checklistVersion || card.checklistVersion === version;
|
|
1333
|
+
yellow = doctorCondition(item ? item.status === 'yellow' && versionMatches : false, `checklist:${version}:${cell}`,
|
|
1334
|
+
item ? `status=${item.status}${versionMatches ? '' : `, cardVersion=${card.checklistVersion}`}` : '현재 판에 칸 없음');
|
|
1335
|
+
} catch { yellow = doctorCondition('unknown', `checklist:${version}:${cell}`, '체크리스트 조회 실패'); }
|
|
1336
|
+
}
|
|
1337
|
+
let unheld: DoctorCondition = doctorCondition('unknown', 'traffic', 'traffic 경로 없음 (--traffic 필요)');
|
|
1338
|
+
if (opts.traffic) {
|
|
1339
|
+
try {
|
|
1340
|
+
const traffic = doctorObject(JSON.parse(readFileSync(opts.traffic, 'utf8')));
|
|
1341
|
+
const holds = traffic?.hold;
|
|
1342
|
+
unheld = Array.isArray(holds) && holds.every((id) => typeof id === 'string') && cell
|
|
1343
|
+
? doctorCondition(!holds.includes(cell), `traffic:${opts.traffic}`, holds.includes(cell) ? 'hold 활성' : 'hold 없음')
|
|
1344
|
+
: doctorCondition('unknown', `traffic:${opts.traffic}`, 'hold 배열 또는 칸 결속 불명');
|
|
1345
|
+
} catch { unheld = doctorCondition('unknown', `traffic:${opts.traffic}`, 'traffic 읽기 실패'); }
|
|
1346
|
+
}
|
|
1347
|
+
let noOtherPr: DoctorCondition = doctorCondition('unknown', 'gh:open-pr-list', '열린 PR 조회 실패');
|
|
1348
|
+
try {
|
|
1349
|
+
const result = run('gh', ['pr', 'list', '--state', 'open', '--limit', '500', '--json', 'number,body']);
|
|
1350
|
+
if (result.status === 0) {
|
|
1351
|
+
const rows: unknown = JSON.parse(result.stdout);
|
|
1352
|
+
if (Array.isArray(rows) && rows.length < 500 && rows.every((row) =>
|
|
1353
|
+
Number.isSafeInteger(doctorObject(row)?.number) && typeof doctorObject(row)?.body === 'string')) {
|
|
1354
|
+
const conflicts = rows.filter((row) => {
|
|
1355
|
+
const pr = doctorObject(row)!;
|
|
1356
|
+
return cellIdFromBody(pr.body as string) === cell && pr.number !== cardPrNumber(card);
|
|
1357
|
+
}).map((row) => doctorObject(row)!.number as number);
|
|
1358
|
+
noOtherPr = doctorCondition(conflicts.length === 0, 'gh:open-pr-list:cell-marker',
|
|
1359
|
+
conflicts.length ? `다른 열린 PR: ${conflicts.map((id) => `#${id}`).join(',')}` : '같은 칸의 다른 열린 PR 없음');
|
|
1360
|
+
} else noOtherPr = doctorCondition('unknown', 'gh:open-pr-list', 'PR 목록 형식 불명 또는 500건 상한');
|
|
1361
|
+
}
|
|
1362
|
+
} catch { /* unknown stays explicit */ }
|
|
1363
|
+
const firstParts = [yellow, unheld, noOtherPr];
|
|
1364
|
+
const firstValue: DoctorTruth = firstParts.some((part) => part.value === 'unknown') ? 'unknown'
|
|
1365
|
+
: firstParts.every((part) => part.value === true);
|
|
1366
|
+
const currentYellowUnheldNoOtherPr = doctorCondition(firstValue,
|
|
1367
|
+
firstParts.map((part) => part.source).join(' + '), firstParts.map((part) => part.detail).join('; '));
|
|
1368
|
+
|
|
1369
|
+
const prNumber = cardPrNumber(card);
|
|
1370
|
+
let pr: DoctorPr | null = prNumber === undefined ? null
|
|
1371
|
+
: { number: prNumber, state: null, draft: null, head: null, changedFiles: null };
|
|
1372
|
+
let prFiles: Array<{ path: string; additions: number; deletions: number }> | null = null;
|
|
1373
|
+
let prSource = 'gh:bound-pr';
|
|
1374
|
+
if (prNumber !== undefined) {
|
|
1375
|
+
try {
|
|
1376
|
+
const result = run('gh', ['pr', 'view', String(prNumber), '--json', 'number,state,isDraft,headRefOid,changedFiles,files']);
|
|
1377
|
+
if (result.status === 0) {
|
|
1378
|
+
const view = doctorObject(JSON.parse(result.stdout));
|
|
1379
|
+
const files = view?.files;
|
|
1380
|
+
const changedFiles = view?.changedFiles;
|
|
1381
|
+
if (view?.number === prNumber && typeof view.state === 'string' && typeof view.isDraft === 'boolean'
|
|
1382
|
+
&& Number.isSafeInteger(changedFiles) && (changedFiles as number) >= 0 && Array.isArray(files)
|
|
1383
|
+
&& files.every((file) => typeof doctorObject(file)?.path === 'string'
|
|
1384
|
+
&& Number.isSafeInteger(doctorObject(file)?.additions) && Number.isSafeInteger(doctorObject(file)?.deletions))) {
|
|
1385
|
+
pr = { number: prNumber, state: view.state, draft: view.isDraft,
|
|
1386
|
+
head: doctorString(view.headRefOid), changedFiles: changedFiles as number };
|
|
1387
|
+
prFiles = files.map((file) => doctorObject(file) as { path: string; additions: number; deletions: number });
|
|
1388
|
+
} else prSource = 'gh:bound-pr:malformed';
|
|
1389
|
+
} else prSource = 'gh:bound-pr:unreadable';
|
|
1390
|
+
} catch { prSource = 'gh:bound-pr:unreadable'; }
|
|
1391
|
+
} else prSource = 'card:pr-unbound';
|
|
1392
|
+
let noPreservedImplementation = doctorCondition('unknown', prSource, 'PR diff를 못 읽었다');
|
|
1393
|
+
if (pr && prFiles && pr.changedFiles === prFiles.length) {
|
|
1394
|
+
const docsOnly = prFiles.length > 0 && prFiles.every((file) => /(?:^|\/)[^/]+\.md$/i.test(file.path));
|
|
1395
|
+
const empty = pr.changedFiles === 0;
|
|
1396
|
+
noPreservedImplementation = doctorCondition(empty || docsOnly, `gh:pr-${pr.number}:files`,
|
|
1397
|
+
empty ? 'diff 0' : docsOnly ? 'docs-only diff' : '보존할 구현 diff 있음');
|
|
1398
|
+
} else if (pr && prFiles) noPreservedImplementation = doctorCondition('unknown', `gh:pr-${pr.number}:files`, 'changedFiles와 파일 목록 수 불일치');
|
|
1399
|
+
|
|
1400
|
+
const ledgerId = card.runChildId;
|
|
1401
|
+
let entries: import('../self-implement/run-ledger.js').RunLedgerEntry[] | null = null;
|
|
1402
|
+
let ledgerSource = ledgerId ? `run-ledger:${ledgerId}` : 'card:child-run-unbound';
|
|
1403
|
+
if (ledgerId) {
|
|
1404
|
+
try { entries = (deps.doctorLedger ?? defaultDoctorLedger)(ledgerId, statePath); }
|
|
1405
|
+
catch { ledgerSource += ':unreadable'; }
|
|
1406
|
+
}
|
|
1407
|
+
let parentEntries: import('../self-implement/run-ledger.js').RunLedgerEntry[] | null = null;
|
|
1408
|
+
let parentLedgerSource = card.runId ? `run-ledger:${card.runId}` : 'card:parent-run-unbound';
|
|
1409
|
+
if (card.runId) {
|
|
1410
|
+
try { parentEntries = (deps.doctorLedger ?? defaultDoctorLedger)(card.runId, statePath); }
|
|
1411
|
+
catch { parentLedgerSource += ':unreadable'; }
|
|
1412
|
+
}
|
|
1413
|
+
const terminal = entries?.slice().reverse().find(doctorTerminal);
|
|
1414
|
+
const terminalData = doctorObject(terminal?.data);
|
|
1415
|
+
const parentTerminal = parentEntries?.slice().reverse().find(doctorTerminal);
|
|
1416
|
+
const parentData = doctorObject(parentTerminal?.data);
|
|
1417
|
+
const parentChildEvidence = doctorObject(parentData?.childEvidence);
|
|
1418
|
+
const prFailure = doctorObject(terminalData?.prFailure);
|
|
1419
|
+
const childTerminalReason = doctorString(terminalData?.terminationReason) ?? doctorString(terminalData?.cause)
|
|
1420
|
+
?? doctorString(terminalData?.reason) ?? doctorString(terminalData?.error)
|
|
1421
|
+
?? doctorString(prFailure?.errorTail);
|
|
1422
|
+
const childReasonSource = childTerminalReason ? `${ledgerSource}:${terminal?.event}` : null;
|
|
1423
|
+
const parentTerminalReason = doctorString(parentData?.terminationReason) ?? doctorString(parentData?.cause)
|
|
1424
|
+
?? doctorString(parentData?.reason) ?? doctorString(parentChildEvidence?.terminationReason)
|
|
1425
|
+
?? doctorString(parentChildEvidence?.error) ?? null;
|
|
1426
|
+
const cardTerminal = card.history?.slice().reverse().find((item) =>
|
|
1427
|
+
(item.event === 'failed' || item.event === 'launch-failed') && (!item.runId || item.runId === card.runId));
|
|
1428
|
+
const terminalReason = childTerminalReason ?? parentTerminalReason ?? doctorString(cardTerminal?.detail);
|
|
1429
|
+
const reauthorPattern = /scope[- ]escape|UNCONVERGEABLE|goal[- ]revision[- ]required/i;
|
|
1430
|
+
const environmentPattern = /worktree add.{0,100}(?:timed? ?out|timeout)|(?:timed? ?out|timeout).{0,100}worktree add|model catalog.{0,100}unknown|unknown.{0,100}model catalog|stale pod image|pod image.{0,100}stale/i;
|
|
1431
|
+
const childEvidence = doctorObject(terminalData?.childEvidence);
|
|
1432
|
+
const structuredReauthorSource = terminalData?.scopeEscape === true ? `${ledgerSource}:${terminal?.event}:scopeEscape`
|
|
1433
|
+
: childEvidence?.scopeEscape === true ? `${ledgerSource}:${terminal?.event}:childEvidence.scopeEscape`
|
|
1434
|
+
: reauthorPattern.test(doctorString(terminalData?.supervisorVerdict) ?? '')
|
|
1435
|
+
? `${ledgerSource}:${terminal?.event}:supervisorVerdict`
|
|
1436
|
+
: parentData?.scopeEscape === true ? `${parentLedgerSource}:${parentTerminal?.event}:scopeEscape`
|
|
1437
|
+
: parentChildEvidence?.scopeEscape === true ? `${parentLedgerSource}:${parentTerminal?.event}:childEvidence.scopeEscape`
|
|
1438
|
+
: reauthorPattern.test(doctorString(parentChildEvidence?.supervisorVerdict) ?? '')
|
|
1439
|
+
? `${parentLedgerSource}:${parentTerminal?.event}:childEvidence.supervisorVerdict` : null;
|
|
1440
|
+
const structuredReauthor = structuredReauthorSource !== null;
|
|
1441
|
+
const scopeSignals = [terminalData?.scopeEscape, childEvidence?.scopeEscape,
|
|
1442
|
+
parentData?.scopeEscape, parentChildEvidence?.scopeEscape].filter((value): value is boolean => typeof value === 'boolean');
|
|
1443
|
+
const scopeSignalConflict = scopeSignals.includes(true) && scopeSignals.includes(false);
|
|
1444
|
+
let environmentFailure = doctorCondition('unknown', ledgerSource, '자식 종결 원장 또는 원문 없음');
|
|
1445
|
+
if (!terminal) environmentFailure = doctorCondition('unknown', ledgerSource, '자식 종결 원장 결손');
|
|
1446
|
+
else if (!parentTerminal) environmentFailure = doctorCondition('unknown', parentLedgerSource,
|
|
1447
|
+
'부모 종결 원장 결손·충돌 — scopeEscape 재확인 불가');
|
|
1448
|
+
else if (!doctorTerminalFailed(parentTerminal)) environmentFailure = doctorCondition('unknown',
|
|
1449
|
+
`${parentLedgerSource}:${parentTerminal.event}`, '부모 런이 실패로 종결되지 않음');
|
|
1450
|
+
else if (childTerminalReason && parentTerminalReason
|
|
1451
|
+
&& !parentTerminalReason.includes(childTerminalReason) && !childTerminalReason.includes(parentTerminalReason)) {
|
|
1452
|
+
environmentFailure = doctorCondition('unknown', `${ledgerSource} + ${parentLedgerSource}`,
|
|
1453
|
+
'부모·자식 종결 사유 불일치');
|
|
1454
|
+
} else if (scopeSignalConflict) environmentFailure = doctorCondition('unknown', `${ledgerSource} + ${parentLedgerSource}`,
|
|
1455
|
+
'부모·자식 scopeEscape 신호 불일치');
|
|
1456
|
+
else if (structuredReauthor) environmentFailure = doctorCondition(false, structuredReauthorSource!, '구조화된 재저작 신호');
|
|
1457
|
+
else if (childTerminalReason) environmentFailure = doctorCondition(
|
|
1458
|
+
!doctorTerminalFailed(terminal) || reauthorPattern.test(childTerminalReason) ? false : environmentPattern.test(childTerminalReason),
|
|
1459
|
+
childReasonSource ?? ledgerSource,
|
|
1460
|
+
!doctorTerminalFailed(terminal) ? '자식 런이 실패로 종결되지 않음' : reauthorPattern.test(childTerminalReason)
|
|
1461
|
+
? '골 재저작 필요' : environmentPattern.test(childTerminalReason) ? '허용된 환경 실패' : '허용된 환경 실패 아님');
|
|
1462
|
+
if (card.status !== 'failed' && card.status !== 'launch-failed') environmentFailure = doctorCondition(false,
|
|
1463
|
+
`card:${card.id}:status`, `현재 카드 status=${card.status} — 재발사 대상 실패 카드 아님`);
|
|
1464
|
+
if (pr?.state === 'MERGED') environmentFailure = doctorCondition(false,
|
|
1465
|
+
`gh:pr-${pr.number}:state`, '결속 PR이 이미 병합됨');
|
|
1466
|
+
|
|
1467
|
+
const roundCards = cell ? cards.filter((item) => item?.checklistId === cell) : [];
|
|
1468
|
+
const knownFailureRuns = new Set<string>();
|
|
1469
|
+
let unboundFailureCards = 0;
|
|
1470
|
+
let ambiguousRounds = false;
|
|
1471
|
+
for (const item of roundCards) {
|
|
1472
|
+
const failures = (item.history ?? []).filter((entry) => entry.event === 'failed' || entry.event === 'launch-failed');
|
|
1473
|
+
const itemRuns = new Set(failures.map((entry) => doctorString(entry.runId)).filter((id): id is string => id !== null));
|
|
1474
|
+
const statusFailed = item.status === 'failed' || item.status === 'launch-failed';
|
|
1475
|
+
if (statusFailed && item.runId) itemRuns.add(item.runId);
|
|
1476
|
+
for (const id of itemRuns) knownFailureRuns.add(id);
|
|
1477
|
+
const unbound = failures.some((entry) => !doctorString(entry.runId));
|
|
1478
|
+
if (unbound && itemRuns.size) ambiguousRounds = true;
|
|
1479
|
+
else if (!itemRuns.size && (unbound || statusFailed)) unboundFailureCards++;
|
|
1480
|
+
}
|
|
1481
|
+
const failureRounds = knownFailureRuns.size + unboundFailureCards;
|
|
1482
|
+
const failureRoundsBelowTwo = doctorCondition(cell && !ambiguousRounds ? failureRounds < 2 : 'unknown',
|
|
1483
|
+
'card-status+history:same-cell-failures', !cell ? '같은 칸 실패 이력 불명'
|
|
1484
|
+
: ambiguousRounds ? 'runId 없는 실패 사건과 결속된 런이 섞여 라운드 중복 불명' : `실패 라운드 ${failureRounds}`);
|
|
1485
|
+
const lastRegate = card.regates?.at(-1);
|
|
1486
|
+
const childGate = entries?.slice().reverse().find((entry) => entry.event === 'gated');
|
|
1487
|
+
const useRegate = lastRegate && (!childGate || Date.parse(lastRegate.at) >= Date.parse(childGate.timestamp ?? ''));
|
|
1488
|
+
const lastGate = useRegate && lastRegate ? { passed: lastRegate.passed ?? null, status: lastRegate.status ?? null,
|
|
1489
|
+
head: lastRegate.head, at: lastRegate.at, source: 'card:regates' }
|
|
1490
|
+
: childGate ? { passed: typeof childGate.data.passed === 'boolean' ? childGate.data.passed : null,
|
|
1491
|
+
status: childGate.data.gateExecuted === false ? 'skipped' : 'executed',
|
|
1492
|
+
head: doctorString(childGate.data.head),
|
|
1493
|
+
at: childGate.timestamp ?? null, source: `${ledgerSource}:gated` } : null;
|
|
1494
|
+
const conditions = { currentYellowUnheldNoOtherPr, noPreservedImplementation, environmentFailure, failureRoundsBelowTwo };
|
|
1495
|
+
const unknownSources = Object.entries(conditions).filter(([, part]) => part.value === 'unknown')
|
|
1496
|
+
.map(([name, part]) => `${name}:${part.source}`);
|
|
1497
|
+
const nextAction = unknownSources.length ? 'human'
|
|
1498
|
+
: structuredReauthor || (terminalReason && reauthorPattern.test(terminalReason)) || failureRoundsBelowTwo.value === false
|
|
1499
|
+
? 'reauthor' : noPreservedImplementation.value === false ? 'harvest'
|
|
1500
|
+
: Object.values(conditions).every((part) => part.value === true) ? 'relaunch' : 'human';
|
|
1501
|
+
return { card: card.id, cell, version, runId: card.runId ?? null, childRunId: card.runChildId ?? null,
|
|
1502
|
+
host: card.parentHost?.host ?? null, head: pr?.head ?? lastRegate?.head ?? card.adoptedPr?.head ?? card.prTerminal?.head ?? null,
|
|
1503
|
+
terminalReason, terminalReasonSource: childReasonSource
|
|
1504
|
+
?? (parentTerminalReason && parentTerminal ? `run-ledger:${card.runId}:${parentTerminal.event}`
|
|
1505
|
+
: cardTerminal ? `card-history:${cardTerminal.event}` : null),
|
|
1506
|
+
lastGate, pr, conditions, unknownSources, nextAction };
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1142
1509
|
/** MODEL-SIZE-OUTCOME-MEASURE — `tasks outcomes --by` 가 아는 집계 축. */
|
|
1143
1510
|
export const TASK_OUTCOME_BY_DIMENSIONS = ['model', 'size'] as const;
|
|
1144
1511
|
|
|
@@ -1277,7 +1644,13 @@ export function readTaskOutcomeModelSize(
|
|
|
1277
1644
|
const observed = report ?? readTaskOutcomes(window, deps, cards);
|
|
1278
1645
|
const unknown = { model: 0, tier: 0, size: 0 };
|
|
1279
1646
|
const cells = new Map<string, TaskOutcomeModelSizeCell>();
|
|
1280
|
-
|
|
1647
|
+
// 종단 사건 행은 카드·PR로 둘일 수 있어도 모델×크기 표본은 카드 한 장이다.
|
|
1648
|
+
const seenCards = new Set<string>();
|
|
1649
|
+
const rows = observed.rows.filter((row) => {
|
|
1650
|
+
if (seenCards.has(row.card)) return false;
|
|
1651
|
+
seenCards.add(row.card);
|
|
1652
|
+
return true;
|
|
1653
|
+
}).map((row): TaskOutcomeModelSizeCard => {
|
|
1281
1654
|
const card = cards[row.card];
|
|
1282
1655
|
const model = outcomeCardModel(card);
|
|
1283
1656
|
const tier = outcomeCardTier(card);
|
|
@@ -1312,16 +1685,9 @@ export function readTaskOutcomeModelSize(
|
|
|
1312
1685
|
}
|
|
1313
1686
|
|
|
1314
1687
|
export function registerTasksCommands(program: Command, deps: TasksCliDeps = {}): void {
|
|
1315
|
-
|
|
1316
|
-
const
|
|
1317
|
-
const
|
|
1318
|
-
const authenticate = () => {
|
|
1319
|
-
const auth = credentials(deps);
|
|
1320
|
-
activeToken = auth?.token;
|
|
1321
|
-
if (!auth) throw new NexusUnavailable();
|
|
1322
|
-
return auth;
|
|
1323
|
-
};
|
|
1324
|
-
const tasks = program.command('tasks').alias('task').description('TOX 태스크 조회 및 승인 · TASK-AGENT 과제 넘기기(hand)');
|
|
1688
|
+
const ctx = createTasksCliContext(deps);
|
|
1689
|
+
const { out, authenticate } = ctx;
|
|
1690
|
+
const tasks = registerTasksRoot(program);
|
|
1325
1691
|
|
|
1326
1692
|
tasks.command('work-order').description('TASK-AGENT 작업 지시서')
|
|
1327
1693
|
.command('build')
|
|
@@ -1356,14 +1722,16 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1356
1722
|
});
|
|
1357
1723
|
|
|
1358
1724
|
tasks.command('green-sweep')
|
|
1359
|
-
.description('병합된 과제 PR
|
|
1725
|
+
.description('병합된 과제 PR·칸 근거를 대조한다 (기본 dry-run, --apply 는 검증된 한 칸만 기록)')
|
|
1360
1726
|
.requiredOption('--version <version>', '조회할 체크리스트 판')
|
|
1361
1727
|
.option('--cell <id>', '한 칸만 조회')
|
|
1362
1728
|
.option('--traffic <path>', '실물 green 판정에는 traffic.json 필수 — 빠지거나 못 읽으면 hold 불명으로 보수 판정')
|
|
1729
|
+
.option('--apply', '최신 clean main 에서 완료 경계 시험까지 통과한 한 칸만 green 으로 기록')
|
|
1363
1730
|
.option('--json', 'JSON 출력')
|
|
1364
|
-
.action((opts: { version: string; cell?: string; traffic?: string; json?: boolean }) => {
|
|
1731
|
+
.action((opts: { version: string; cell?: string; traffic?: string; apply?: boolean; json?: boolean }) => {
|
|
1365
1732
|
try {
|
|
1366
1733
|
validateVersion(opts.version);
|
|
1734
|
+
if (opts.apply && !opts.cell) throw new Error('--apply 는 단일 --cell 과 함께 써야 한다');
|
|
1367
1735
|
const snapshot = (deps.greenSweepChecklist ?? listChecklist)(opts.version);
|
|
1368
1736
|
const run = deps.greenSweepRun ?? ((command: string, args: string[]) => spawnSync(command, args,
|
|
1369
1737
|
{ cwd: process.cwd(), encoding: 'utf8', timeout: 20_000 }));
|
|
@@ -1394,6 +1762,13 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1394
1762
|
const localMainSha = localMain.status === 0 ? localMain.stdout.trim() : '';
|
|
1395
1763
|
const remoteMainSha = remoteMain.status === 0 ? remoteMain.stdout.trim().split(/\s+/)[0] : '';
|
|
1396
1764
|
const mainFresh = /^[a-f0-9]{40}$/i.test(localMainSha) && localMainSha === remoteMainSha;
|
|
1765
|
+
let currentMainClean = false;
|
|
1766
|
+
if (opts.apply && mainFresh) {
|
|
1767
|
+
const head = run('git', ['rev-parse', 'HEAD']);
|
|
1768
|
+
const status = run('git', ['status', '--porcelain', '--untracked-files=all']);
|
|
1769
|
+
currentMainClean = head.status === 0 && head.stdout.trim() === remoteMainSha
|
|
1770
|
+
&& status.status === 0 && status.stdout.trim() === '';
|
|
1771
|
+
}
|
|
1397
1772
|
if (opts.cell && !snapshot.items.some((item) => item.id === opts.cell)) throw new Error(`없는 칸: ${opts.cell}`);
|
|
1398
1773
|
const decisions = snapshot.items.filter((item) => !opts.cell || item.id === opts.cell).flatMap((item) => {
|
|
1399
1774
|
const candidates = greenSweepCandidates(item, opts.version, cards);
|
|
@@ -1442,9 +1817,34 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1442
1817
|
const counts = { candidate: decisions.length, green: decisions.filter((d) => d.verdict === 'green').length,
|
|
1443
1818
|
recordOnly: decisions.filter((d) => d.verdict === 'record-only').length,
|
|
1444
1819
|
skipped: decisions.filter((d) => d.verdict === 'skip').length };
|
|
1445
|
-
|
|
1820
|
+
let writes: Array<{ cell: string; written: boolean; reason?: string }> = [];
|
|
1821
|
+
let e2Failure: string | undefined;
|
|
1822
|
+
if (opts.apply) {
|
|
1823
|
+
const passed = new Set<string>();
|
|
1824
|
+
if (currentMainClean) {
|
|
1825
|
+
const evidenceRun = deps.greenSweepEvidenceRun ?? ((args: string[]) => {
|
|
1826
|
+
const result = spawnSync('bun', ['test', ...args], { cwd: process.cwd(), encoding: 'utf8', timeout: 120_000 });
|
|
1827
|
+
return { status: result.status, stdout: `${result.stdout ?? ''}${result.stderr ?? ''}` };
|
|
1828
|
+
});
|
|
1829
|
+
const item = snapshot.items.find((entry) => entry.id === opts.cell);
|
|
1830
|
+
const args = item ? greenSweepEvidenceTestArgs(item.title) : null;
|
|
1831
|
+
const greenDecision = decisions.some((decision) => decision.cell === opts.cell && decision.verdict === 'green');
|
|
1832
|
+
if (args && greenDecision) {
|
|
1833
|
+
const result = evidenceRun(args);
|
|
1834
|
+
if (result.status === 0) passed.add(opts.cell!);
|
|
1835
|
+
else e2Failure = `E2 failed · ${opts.cell} · rc=${result.status ?? 'signal'} · ${result.stdout.slice(-4000)}`;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
writes = applyGreenSweepDecisions(opts.version, decisions, snapshot.items, passed,
|
|
1839
|
+
deps.greenSweepWrite ?? setItemIfSnapshot);
|
|
1840
|
+
}
|
|
1841
|
+
if (opts.json) out(JSON.stringify({ dryRun: !opts.apply, version: opts.version, counts, decisions, writes,
|
|
1842
|
+
e2: opts.apply ? (e2Failure ? 'failed' : currentMainClean ? 'main-clean' : 'main-head-or-tree-mismatch') : 'not-run',
|
|
1843
|
+
...(e2Failure ? { e2Failure } : {}) }));
|
|
1446
1844
|
else {
|
|
1447
|
-
out(
|
|
1845
|
+
out(`${opts.apply ? 'apply' : 'dry-run'} · ${opts.version} · 후보 ${counts.candidate} · green ${counts.green} · 기록만 ${counts.recordOnly} · 건너뜀 ${counts.skipped}`);
|
|
1846
|
+
if (e2Failure) out(e2Failure);
|
|
1847
|
+
if (opts.apply) for (const write of writes) out(`${write.written ? 'wrote green' : 'no write'}\t${write.cell}\t${write.reason ?? 'snapshot-guarded'}`);
|
|
1448
1848
|
for (const d of decisions) out(`${d.verdict}\t${d.cell}\t#${d.pr}\t${d.mergeSha ?? '-'}\t${d.reason}\t${d.evidence}\tbefore=${JSON.stringify(d.existingEvidence)}\tafter=${JSON.stringify(d.plannedEvidence)}\tholdDiagnostic=${d.holdDiagnostic ?? ''}`);
|
|
1449
1849
|
}
|
|
1450
1850
|
} catch (error) { out(`green-sweep 조회 실패: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
@@ -1502,11 +1902,33 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1502
1902
|
} catch (error) { out(error instanceof Error ? error.message : '커버 조회 실패'); process.exitCode = 1; }
|
|
1503
1903
|
});
|
|
1504
1904
|
|
|
1905
|
+
tasks.command('repair-outcomes')
|
|
1906
|
+
.description('TA 카드 원장의 repair-result 집계(읽기 전용 · 기간 내 runId별 최신 결과, 기간 밖의 정정은 제외)')
|
|
1907
|
+
.requiredOption('--since <iso>', '시작 시각(ISO, 포함)')
|
|
1908
|
+
.option('--until <iso>', '끝 시각(ISO, 미포함 · 기본 현재)')
|
|
1909
|
+
.option('--json', 'JSON 출력')
|
|
1910
|
+
.action((opts: { since: string; until?: string; json?: boolean }) => {
|
|
1911
|
+
try {
|
|
1912
|
+
const since = Date.parse(opts.since);
|
|
1913
|
+
const until = opts.until === undefined ? Date.now() : Date.parse(opts.until);
|
|
1914
|
+
const valid = (value: string) => /^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d\d:\d\d)$/.test(value);
|
|
1915
|
+
if (!valid(opts.since) || !Number.isFinite(since) || opts.until !== undefined && (!valid(opts.until) || !Number.isFinite(until)) || since >= until)
|
|
1916
|
+
throw new Error('--since/--until 는 순서가 맞는 ISO 시각이어야 한다');
|
|
1917
|
+
const path = deps.taskStatePath ?? taskStatePathDefault();
|
|
1918
|
+
if (!existsSync(path)) throw new Error('TA 카드 원장 없음 — 수리 0건으로 판정 불가');
|
|
1919
|
+
const tasks = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(path).tasks;
|
|
1920
|
+
if (!tasks || typeof tasks !== 'object' || Array.isArray(tasks)) throw new Error('TA 카드 원장 tasks 없음 — 수리 0건으로 판정 불가');
|
|
1921
|
+
const report = measureRepairOutcomes(Object.values(tasks), { since, until });
|
|
1922
|
+
if (opts.json) out(JSON.stringify(report));
|
|
1923
|
+
else out(`수리 결과(카드 원장) ${report.applied} 적용 · ${report.failed} 실패 · ${report.unconfirmed} 미확정 · 카드 ${report.cards} · 결과 ${report.results} · runId 결손 ${report.missingRunId} · 미지 효과 ${report.unknownEffect} (기간 내 최신 결과 · 판단 로그 아님)`);
|
|
1924
|
+
} catch (error) { out(error instanceof Error ? error.message : '수리 결과 원장 읽기 실패'); process.exitCode = 1; }
|
|
1925
|
+
});
|
|
1926
|
+
|
|
1505
1927
|
tasks.command('outcomes')
|
|
1506
|
-
.description('TA 카드·PR 종결
|
|
1928
|
+
.description('TA 카드·PR 종결 사건을 별도 분모로 센다 — 결속 자식 원장·종료 꼬리 원문과 부재 이유 포함(읽기 전용 · 사건 페이지 상한)')
|
|
1507
1929
|
.option('--since <기간>', '시작 시각 — ISO 또는 상대 기간(24h · 3d)')
|
|
1508
1930
|
.option('--until <기간>', '끝 시각 — ISO 또는 지금에서 뺀 상대 기간(24h · 3d)')
|
|
1509
|
-
.option('--by <축>', '집계 축 —
|
|
1931
|
+
.option('--by <축>', '집계 축 — card-terminal | pr-terminal | model,size(읽기 전용)')
|
|
1510
1932
|
.option('--json', 'JSON 출력(기본값)')
|
|
1511
1933
|
.action((opts: { since?: string; until?: string; by?: string; json?: boolean }) => {
|
|
1512
1934
|
try {
|
|
@@ -1516,11 +1938,12 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1516
1938
|
// 창이 뒤집혔으면 아무것도 읽지 않는다(«빈 결과»로 속이지 않는다).
|
|
1517
1939
|
if (sinceMs !== undefined && untilMs !== undefined && sinceMs > untilMs) throw new Error('--since 가 --until 보다 늦다');
|
|
1518
1940
|
// 축 이름이 틀렸으면 원장도 읽지 않는다(지어낸 집계를 내지 않게).
|
|
1519
|
-
const
|
|
1941
|
+
const terminalKind = opts.by === 'card-terminal' ? 'card' : opts.by === 'pr-terminal' ? 'pr' : undefined;
|
|
1942
|
+
const dimensions = terminalKind ? null : parseOutcomeDimensions(opts.by);
|
|
1520
1943
|
const snapshot = dimensions
|
|
1521
1944
|
? readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(deps.taskStatePath ?? taskStatePathDefault()).tasks ?? {}
|
|
1522
1945
|
: undefined;
|
|
1523
|
-
const observed = readTaskOutcomes({ sinceMs, untilMs }, deps, snapshot);
|
|
1946
|
+
const observed = readTaskOutcomes({ sinceMs, untilMs, terminalKind }, deps, snapshot);
|
|
1524
1947
|
const report: TaskOutcomeReport = {
|
|
1525
1948
|
since: sinceMs === undefined ? null : new Date(sinceMs).toISOString(),
|
|
1526
1949
|
until: untilMs === undefined ? null : new Date(untilMs).toISOString(),
|
|
@@ -1528,7 +1951,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1528
1951
|
};
|
|
1529
1952
|
try { debug.log('task-agent', 'card-outcomes', { since: report.since, until: report.until, rows: report.rows.length,
|
|
1530
1953
|
missingReason: report.aggregate.missingReason, stopReason: report.stopReason,
|
|
1531
|
-
childLedgerObserved:
|
|
1954
|
+
childLedgerObserved: report.childLedgerObserved, reasonObserved: report.reasonObserved }); } catch { /* fail-soft */ }
|
|
1532
1955
|
// `--by model,size` 는 지금 JSON 에 «한 칸»을 더할 뿐 — 없으면 종전 JSON 그대로다.
|
|
1533
1956
|
out(JSON.stringify(dimensions ? { ...report, byModelSize: readTaskOutcomeModelSize({ sinceMs, untilMs }, deps, observed, snapshot) } : report));
|
|
1534
1957
|
} catch (error) {
|
|
@@ -1537,37 +1960,125 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1537
1960
|
}
|
|
1538
1961
|
});
|
|
1539
1962
|
|
|
1540
|
-
tasks.command('
|
|
1541
|
-
.description('
|
|
1542
|
-
.option('--
|
|
1543
|
-
.option('--
|
|
1963
|
+
tasks.command('doctor <cardIdOrRunId>')
|
|
1964
|
+
.description('카드·런의 재발사 조건과 출처를 읽기 전용으로 판정한다')
|
|
1965
|
+
.option('--traffic <path>', 'hold 원장 traffic.json — 생략·판독 실패 시 hold=unknown')
|
|
1966
|
+
.option('--json', '한 줄 JSON 출력(기본값)')
|
|
1967
|
+
.action((target: string, opts: { traffic?: string; json?: boolean }) => {
|
|
1968
|
+
try { out(JSON.stringify(readTaskDoctor(target, { traffic: opts.traffic }, deps))); }
|
|
1969
|
+
catch (error) { out(JSON.stringify({ error: error instanceof Error ? error.message : 'doctor 조회 실패' })); process.exitCode = 1; }
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1972
|
+
registerTaskListCommand(tasks, ctx);
|
|
1973
|
+
|
|
1974
|
+
const mapCommand = tasks.command('map')
|
|
1975
|
+
.description('MAPREDUCE-FANOUT — 현재 재고를 map-run 원장에 기록하고 결과를 읽기 전용으로 집계한다')
|
|
1976
|
+
.option('--recipe <name>', 'green-close 또는 draft-close')
|
|
1977
|
+
.option('--from <source>', 'landed-but-yellow 또는 drafts')
|
|
1978
|
+
.option('--limit <n>', '선택할 최대 항목 수')
|
|
1979
|
+
.option('--ids <csv>', '현재 재고에서 지정 ID만 선택 (최대 10; 재고 밖 ID는 HOLD 행)')
|
|
1980
|
+
.option('--concurrency <n>', '동시 카드 처리 상한 (기본 2)')
|
|
1981
|
+
.option('--dry-run', '기본값: 카드 발사·PR·체크리스트 쓰기 없음')
|
|
1982
|
+
.option('--reduce <map-run>', '기존 map-run의 결과 행 표를 읽기 전용으로 출력')
|
|
1983
|
+
.option('--live', '호스트 graph-run으로 레시피 실행 · 결과 원장만 기록 (C2: 최대 10건/동시 2)')
|
|
1544
1984
|
.option('--json', 'JSON 출력')
|
|
1545
|
-
.action(async (opts: {
|
|
1985
|
+
.action(async (opts: { recipe?: string; from?: string; limit?: string; ids?: string; concurrency?: string; dryRun?: boolean;
|
|
1986
|
+
reduce?: string; live?: boolean; json?: boolean }) => {
|
|
1546
1987
|
try {
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
const cards = body.tasks.filter((task) => !opts.status || task.status === opts.status);
|
|
1554
|
-
// The list endpoint returns board cards without approval or provenance.
|
|
1555
|
-
// Obtain those fields from the detail endpoint rather than displaying guesses.
|
|
1556
|
-
const rows: TaskRow[] = [];
|
|
1557
|
-
for (const card of cards) {
|
|
1558
|
-
const detail = await request(deps, auth, `/v1/tasks/${encodeURIComponent(card.id)}`);
|
|
1559
|
-
if (!detail.ok) throw httpError(detail);
|
|
1560
|
-
const data = await responseJson(detail) as { task?: TaskRow };
|
|
1561
|
-
if (!data?.task || data.task.id !== card.id) throw new Error('넥서스 태스크 상세 형식이 유효하지 않다');
|
|
1562
|
-
if (!opts.provider || (data.task.generatedBy?.kind === 'external' && data.task.generatedBy.provider === opts.provider)) rows.push(data.task);
|
|
1988
|
+
const root = deps.mapRoot ?? effectiveInstanceRoot();
|
|
1989
|
+
if (opts.reduce) {
|
|
1990
|
+
if (opts.recipe || opts.from || opts.limit || opts.ids || opts.concurrency || opts.live || opts.dryRun) throw new Error('reduce-options-conflict');
|
|
1991
|
+
const table = renderMapReduceTable(opts.reduce, root);
|
|
1992
|
+
out(opts.json ? JSON.stringify(table) : table.markdown);
|
|
1993
|
+
return;
|
|
1563
1994
|
}
|
|
1564
|
-
|
|
1565
|
-
if (opts.
|
|
1995
|
+
if (!opts.recipe || !opts.from) throw new Error('recipe-and-from-required');
|
|
1996
|
+
if (opts.live && opts.dryRun) throw new Error('live-dry-run-conflict');
|
|
1997
|
+
if (opts.recipe !== 'green-close' && opts.recipe !== 'draft-close') throw new Error('recipe-invalid');
|
|
1998
|
+
if (opts.from !== 'landed-but-yellow' && opts.from !== 'drafts') throw new Error('source-invalid');
|
|
1999
|
+
const requestedIds = opts.ids === undefined ? undefined : opts.ids.split(',').map((id) => id.trim());
|
|
2000
|
+
if (requestedIds && (requestedIds.length < 1 || requestedIds.length > 10
|
|
2001
|
+
|| requestedIds.some((id) => !/^[A-Za-z0-9][A-Za-z0-9_-]{0,95}$/.test(id))
|
|
2002
|
+
|| new Set(requestedIds).size !== requestedIds.length)) throw new Error('map-ids-invalid-or-duplicate: max 10');
|
|
2003
|
+
const limit = opts.limit === undefined ? (opts.live || requestedIds ? 10 : 100) : Number(opts.limit);
|
|
2004
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000) throw new Error('limit-out-of-range: 1..1000');
|
|
2005
|
+
if (requestedIds && requestedIds.length > limit) throw new Error('map-ids-exceed-limit');
|
|
2006
|
+
const concurrency = opts.concurrency === undefined ? 2 : Number(opts.concurrency);
|
|
2007
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1 || concurrency > 32) throw new Error('concurrency-out-of-range: 1..32');
|
|
2008
|
+
if (opts.live && (limit > 10 || concurrency > 2)) throw new Error('host-map-c2-cap: limit<=10 concurrency<=2');
|
|
2009
|
+
const mapRunId = `map-${randomUUID()}`;
|
|
2010
|
+
const recipe = opts.recipe as MapRecipe;
|
|
2011
|
+
const source = opts.from as 'landed-but-yellow' | 'drafts';
|
|
2012
|
+
const hostLaunch = createMapHostLauncher({ recipeRoot: join(process.cwd(), 'graphs', 'work'),
|
|
2013
|
+
...(deps.mapGraphRun ? { run: deps.mapGraphRun } : {}) });
|
|
2014
|
+
let attempted = 0;
|
|
2015
|
+
const ports: MapPorts<MapCliItem> = {
|
|
2016
|
+
inventory: async (r, s, selectedLimit) => selectMapIds(deps.mapInventorySource
|
|
2017
|
+
? await deps.mapInventorySource(r, s, requestedIds ? undefined : selectedLimit, mapRunId)
|
|
2018
|
+
: await currentMapInventory(r, s, selectedLimit, root, mapRunId,
|
|
2019
|
+
deps.taskStatePath ?? taskStatePathDefault(), requestedIds), requestedIds),
|
|
2020
|
+
buildCard: (item, context) => ({ id: `map-card-${createHash('sha256').update(`${context.mapRunId}:${item.id}`).digest('hex').slice(0, 24)}`, itemId: item.id,
|
|
2021
|
+
mapRunId: context.mapRunId, recipe: context.recipe, text: item.text?.trim() || `verify ${item.id}` }),
|
|
2022
|
+
launchCard: async (card, item, context) => {
|
|
2023
|
+
attempted++;
|
|
2024
|
+
if (item.inventoryMissing) return { event: 'row', mapRunId: card.mapRunId, itemId: card.itemId,
|
|
2025
|
+
recipe: card.recipe, kind: 'hold', verdict: 'hold', reason: 'requested-id-not-in-eligible-inventory', evidence: [] };
|
|
2026
|
+
if (!item.graphInput) return { event: 'row', mapRunId: card.mapRunId, itemId: card.itemId,
|
|
2027
|
+
recipe: card.recipe, kind: 'hold', verdict: 'hold', reason: 'recipe-input-missing', evidence: [] };
|
|
2028
|
+
return hostLaunch(card, item as HostMapItem, context);
|
|
2029
|
+
},
|
|
2030
|
+
previewCard: (card, item): MapRunRow => ({ event: 'row', mapRunId: card.mapRunId, itemId: card.itemId,
|
|
2031
|
+
recipe: card.recipe, kind: 'hold', verdict: 'hold',
|
|
2032
|
+
reason: item.inventoryMissing ? 'requested-id-not-in-eligible-inventory' : 'dry-run-no-recipe-execution', evidence: [] }),
|
|
2033
|
+
};
|
|
2034
|
+
const result = await mapInventory({ recipe, source, limit, concurrency, dryRun: !opts.live, root, mapRunId }, ports);
|
|
2035
|
+
const table = renderMapReduceTable(result.mapRunId, root);
|
|
2036
|
+
if (opts.json) out(JSON.stringify({ ...result, ...table, status: opts.live ? 'host-graph-run' : 'preview-only',
|
|
2037
|
+
attempted, launched: opts.live ? undefined : 0, transport: opts.live ? 'host-graph-run' : null, externalWrites: 0 }));
|
|
2038
|
+
else out(table.markdown);
|
|
2039
|
+
} catch (error) {
|
|
2040
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2041
|
+
out(opts.json ? JSON.stringify({ status: 'HOLD', reason, launched: 0, writes: 0 }) : `HOLD\t${reason}\t발사 0 · 쓰기 0`);
|
|
2042
|
+
process.exitCode = 1;
|
|
2043
|
+
}
|
|
2044
|
+
});
|
|
2045
|
+
|
|
2046
|
+
mapCommand.command('dry-run')
|
|
2047
|
+
.requiredOption('--inventory <path>', '세 항목 inventory JSON 파일')
|
|
2048
|
+
.option('--json', '계획 또는 HOLD를 JSON으로 출력')
|
|
2049
|
+
.action((opts: { inventory: string; json?: boolean }) => {
|
|
2050
|
+
const json = opts.json || mapCommand.opts<{ json?: boolean }>().json;
|
|
2051
|
+
try {
|
|
2052
|
+
const parsed: unknown = JSON.parse(readFileSync(opts.inventory, 'utf8'));
|
|
2053
|
+
const entries = Array.isArray(parsed) ? parsed
|
|
2054
|
+
: parsed && typeof parsed === 'object' && Array.isArray((parsed as { items?: unknown }).items)
|
|
2055
|
+
? (parsed as { items: unknown[] }).items : [];
|
|
2056
|
+
const validEntries = entries.length === 3 && entries.every((entry) => entry && typeof entry === 'object'
|
|
2057
|
+
&& !Array.isArray(entry) && typeof (entry as { id?: unknown }).id === 'string'
|
|
2058
|
+
&& (entry as { id: string }).id.trim().length > 0);
|
|
2059
|
+
const ids = validEntries ? entries.map((entry) => (entry as { id: string }).id.trim()) : [];
|
|
2060
|
+
const unique = new Set(ids).size === ids.length;
|
|
2061
|
+
if (!validEntries || !unique) {
|
|
2062
|
+
const result = { mode: 'dry-run', status: 'HOLD', reason: !validEntries ? 'incomplete-inventory' : 'duplicate-id', rows: [] };
|
|
2063
|
+
out(json ? JSON.stringify(result) : `HOLD\t${result.reason}\t발사 0`);
|
|
2064
|
+
process.exitCode = 1;
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
const rows = entries.map((entry, index) => ({
|
|
2068
|
+
id: ids[index]!, orchestrationId: orchestrationId(), action: 'planned', source: entry,
|
|
2069
|
+
}));
|
|
2070
|
+
const result = { mode: 'dry-run', status: 'planned', rows, effects: { launched: false, pullRequestWrites: false,
|
|
2071
|
+
checklistWrites: false, autoMerge: false } };
|
|
2072
|
+
if (json) out(JSON.stringify(result));
|
|
1566
2073
|
else {
|
|
1567
|
-
out(
|
|
1568
|
-
for (const row of rows) out(
|
|
2074
|
+
out(`dry-run\tplanned\t${rows.length} 계획 행 · PR/체크리스트 쓰기 0 · 자동 병합 0`);
|
|
2075
|
+
for (const row of rows) out(`${row.id}\t${row.orchestrationId}\tplanned`);
|
|
1569
2076
|
}
|
|
1570
|
-
} catch (error) {
|
|
2077
|
+
} catch (error) {
|
|
2078
|
+
const result = { mode: 'dry-run', status: 'HOLD', reason: 'inventory-unreadable', rows: [] };
|
|
2079
|
+
out(json ? JSON.stringify(result) : `HOLD\tinventory-unreadable\t발사 0`);
|
|
2080
|
+
process.exitCode = 1;
|
|
2081
|
+
}
|
|
1571
2082
|
});
|
|
1572
2083
|
|
|
1573
2084
|
tasks.command('hand [text]')
|
|
@@ -1585,10 +2096,12 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1585
2096
|
.option('--ta-land', 'TA-LIVE-LAND-2 — 호스트는 재게이트만 하고 병합은 TASK-AGENT 의 propose-land(`pr land --expected-head`)에 맡긴다 · 카드 taLand ⊕ 발사 env ELANOUS_TA_LAND=1 · taskAgent.liveMoves 에 propose-land 가 없으면 종전대로 호스트가 병합')
|
|
1586
2097
|
.option('--child-llm <provider/model>', 'TASKS-HAND-CHILD-LLM — 구현 자식 LLM(예: grok/grok-4.7) → 발사 인자 --child-llm-provider <provider> --child-llm-model <model> · «첫» `/` 에서만 가른다(모델 id 의 `/` 는 보존) · openrouter 는 모델 id 가 openrouter/ 접두를 포함하므로 전체를 모델로(openrouter/z-ai/glm-5.3 → --child-llm-model openrouter/z-ai/glm-5.3) · 카드 childLlm · 안 주면 명령·카드 무변경')
|
|
1587
2098
|
.option('--child-llm-effort <level>', '구현 자식 추론 노력 → 발사 인자 --child-llm-effort(검증은 하니스 · --child-llm 과 함께)')
|
|
2099
|
+
.option('--role-llm <role=provider[/tier]>', 'TA 부모 역할 LLM(반복 가능 · planning|classify|audit|review만). 정확한 모델은 role={"provider":"openrouter","model":"openrouter/z-ai/glm-5.3"}; 자식 모델은 --child-llm', (value: string, previous: string[] = []) => [...previous, value])
|
|
1588
2100
|
.addOption(new Option('--tier <level>', '디스패처 난이도(하|중|상|특) — 카드에 보존해 수리 모델 정책에 사용').choices(['하', '중', '상', '특']))
|
|
1589
2101
|
.option('--tag <k=v>', 'RUN-IDENTITY-TAGS — 출생 신분을 덮는다(class|care|rights|protect 의 k=v · 반복: --tag class=gate --tag protect=true) — 카드 원장 identity 에 남는다', (value: string, previous: string[] | undefined) => [...(previous ?? []), value])
|
|
2102
|
+
.option('--draft-claim <pr>', 'DRAFT-CLAIM-AUTO-2-FU — PR 를 관측(gh pr view)해 draftClaim 을 조립해 넘긴다 · 관측한 PR 머리에서 `elanous self review <pr> --json` 로 손 수확을 먼저 확정한다(확정 못 하면 claim 문면을 만들지 않는다) · claim 문면을 실제 PR 댓글로 게시(gh pr comment)한다 · 같은 PR 의 살아 있는 claim 기록(카드 draftClaim)이 있으면 거부한다')
|
|
1590
2103
|
.option('--json', 'JSON 출력')
|
|
1591
|
-
.action(async (text: string | undefined, opts: { seat?: TaskSeat; checklist?: string; version?: string; completion?: CompletionKind; mission?: string; project?: string; target?: string; goal?: string; milestone?: string; live?: boolean; taLand?: boolean; childLlm?: string; childLlmEffort?: string; tier?: TaskCard['tier']; tag?: string[]; json?: boolean }) => {
|
|
2104
|
+
.action(async (text: string | undefined, opts: { seat?: TaskSeat; checklist?: string; version?: string; completion?: CompletionKind; mission?: string; project?: string; target?: string; goal?: string; milestone?: string; live?: boolean; taLand?: boolean; childLlm?: string; childLlmEffort?: string; roleLlm?: string[]; tier?: TaskCard['tier']; tag?: string[]; draftClaim?: string; json?: boolean }) => {
|
|
1592
2105
|
const suppliedText = text !== undefined;
|
|
1593
2106
|
/** RUN-IDENTITY-TAGS — 칸을 찾은 판(출생 신분이 그 판의 칸 우선순위를 읽는다 · 개발 판 밖 칸도). */
|
|
1594
2107
|
let birthBoardVersion: string | undefined;
|
|
@@ -1629,9 +2142,11 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1629
2142
|
return aFuture ? compareVersions(a, b) : compareVersions(b, a);
|
|
1630
2143
|
});
|
|
1631
2144
|
const matches: Array<{ version: string; title: string }> = [];
|
|
2145
|
+
// CLI-ENTRY-LAZY — board(전이 폐포가 가장 크다)는 칸을 찾을 때만 싣는다.
|
|
2146
|
+
const packBoard = deps.releaseBoard ?? (await import('../task-agent/board.js')).releasePackBoard;
|
|
1632
2147
|
for (const version of orderedVersions) {
|
|
1633
2148
|
try {
|
|
1634
|
-
const title = findTitle((
|
|
2149
|
+
const title = findTitle(packBoard(version));
|
|
1635
2150
|
if (title !== undefined) matches.push({ version, title });
|
|
1636
2151
|
} catch (error) {
|
|
1637
2152
|
if (opts.version !== undefined) {
|
|
@@ -1668,13 +2183,14 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1668
2183
|
process.exitCode = 1;
|
|
1669
2184
|
return;
|
|
1670
2185
|
}
|
|
1671
|
-
if (opts.mission !== undefined && (opts.completion !== undefined || opts.project !== undefined || opts.goal !== undefined || opts.milestone !== undefined || opts.taLand === true || opts.childLlm !== undefined || opts.childLlmEffort !== undefined || opts.tier !== undefined || opts.tag !== undefined)) {
|
|
1672
|
-
out('--completion/--project/--target/--goal/--milestone/--ta-land/--child-llm/--tier/--tag 는 과제 한 줄에만 준다 — --mission 조각은 종전처럼 code-pr 이다');
|
|
2186
|
+
if (opts.mission !== undefined && (opts.completion !== undefined || opts.project !== undefined || opts.goal !== undefined || opts.milestone !== undefined || opts.taLand === true || opts.childLlm !== undefined || opts.childLlmEffort !== undefined || opts.roleLlm !== undefined || opts.tier !== undefined || opts.tag !== undefined)) {
|
|
2187
|
+
out('--completion/--project/--target/--goal/--milestone/--ta-land/--child-llm/--role-llm/--tier/--tag 는 과제 한 줄에만 준다 — --mission 조각은 종전처럼 code-pr 이다');
|
|
1673
2188
|
process.exitCode = 1;
|
|
1674
2189
|
return;
|
|
1675
2190
|
}
|
|
1676
2191
|
if (opts.mission !== undefined) {
|
|
1677
2192
|
try {
|
|
2193
|
+
const { handMission } = await import('../task-agent/mission.js');
|
|
1678
2194
|
const result = await handMission({
|
|
1679
2195
|
text: opts.mission,
|
|
1680
2196
|
...(opts.seat ? { seat: opts.seat } : {}),
|
|
@@ -1713,8 +2229,61 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1713
2229
|
if (resolved.kind === 'inlined') {
|
|
1714
2230
|
try { debug.log('task-agent.hand', 'goal-file-inlined', { path: resolved.path, chars: resolved.chars, sha256: resolved.sha256 }); } catch { /* fail-soft */ }
|
|
1715
2231
|
}
|
|
1716
|
-
|
|
1717
|
-
|
|
2232
|
+
if (opts.live && opts.checklist && birthBoardVersion) {
|
|
2233
|
+
const origin = deps.handOriginRepo?.() ?? execFileSync('git', ['remote', 'get-url', 'origin'],
|
|
2234
|
+
{ cwd: process.cwd(), encoding: 'utf8' }).trim().replace(/\.git\/?$/, '');
|
|
2235
|
+
const repo = origin.match(/^(?:git@github\.com:|https:\/\/github\.com\/|ssh:\/\/git@github\.com\/)?([^/\s]+\/[^/\s]+)\/?$/)?.[1];
|
|
2236
|
+
if (!repo) throw new Error(`현재 worktree origin이 GitHub 저장소가 아니다: ${origin}`);
|
|
2237
|
+
const run = deps.handOpenPrRun ?? ((command: string, args: string[]) => spawnSync(command, args,
|
|
2238
|
+
{ cwd: process.cwd(), encoding: 'utf8', timeout: 20_000, maxBuffer: 16 * 1024 * 1024 }));
|
|
2239
|
+
const listed = run('gh', ['pr', 'list', '--repo', repo, '--state', 'open', '--limit', '500', '--json', 'number,body']);
|
|
2240
|
+
if (listed.status !== 0) throw new Error(`같은 칸 OPEN PR 조회 실패 — 구현 발사 0: ${opts.checklist}`);
|
|
2241
|
+
let rows: unknown;
|
|
2242
|
+
try { rows = JSON.parse(listed.stdout); } catch { throw new Error(`OPEN PR 목록 해석 실패 — 구현 발사 0: ${opts.checklist}`); }
|
|
2243
|
+
if (!Array.isArray(rows) || rows.length >= 500 || !rows.every((row) =>
|
|
2244
|
+
Number.isSafeInteger(doctorObject(row)?.number) && typeof doctorObject(row)?.body === 'string')) {
|
|
2245
|
+
throw new Error(`OPEN PR 목록 불완전 — 구현 발사 0: ${opts.checklist}`);
|
|
2246
|
+
}
|
|
2247
|
+
const openPrs = rows.filter((row) => cellIdFromBody(doctorObject(row)!.body as string) === opts.checklist)
|
|
2248
|
+
.map((row) => doctorObject(row)!.number as number);
|
|
2249
|
+
if (openPrs.length > 1) throw new Error(`같은 칸 OPEN PR 복수 — 구현 발사 0: ${opts.checklist} ${openPrs.map((pr) => `#${pr}`).join(', ')}`);
|
|
2250
|
+
if (openPrs.length === 1) {
|
|
2251
|
+
const number = openPrs[0]!;
|
|
2252
|
+
const view = (deps.adoptPrView ?? ((pr: number, cwd: string) => JSON.parse(execFileSync('gh',
|
|
2253
|
+
['pr', 'view', String(pr), '--repo', repo, '--json', 'number,url,state,baseRefName,headRefOid'], { cwd, encoding: 'utf8' }))))(number, process.cwd());
|
|
2254
|
+
if (view.number !== number) throw new Error(`조회 PR 번호가 다르다: 요청 #${number} · 응답 #${view.number}`);
|
|
2255
|
+
const viewedRepo = typeof view.url === 'string'
|
|
2256
|
+
? view.url.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)\/?$/) : null;
|
|
2257
|
+
if (view.state !== 'OPEN' || view.baseRefName !== 'main' || !/^[a-f0-9]{40}$/i.test(view.headRefOid)
|
|
2258
|
+
|| viewedRepo?.[1]?.toLowerCase() !== repo.toLowerCase() || Number(viewedRepo?.[2]) !== number) {
|
|
2259
|
+
throw new Error(`OPEN/main PR 신원 불일치: #${number} — 구현 발사 0`);
|
|
2260
|
+
}
|
|
2261
|
+
const statePath = deps.taskStatePath ?? taskStatePathDefault();
|
|
2262
|
+
const boundCards = Object.values(readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(statePath).tasks ?? {})
|
|
2263
|
+
.filter((card) => card.checklistId === opts.checklist && cardPrNumber(card) === number);
|
|
2264
|
+
if (boundCards.length) {
|
|
2265
|
+
const bound = boundCards.length === 1 ? boundCards[0] : undefined;
|
|
2266
|
+
const same = bound?.status === 'launched' && bound.taLand === true
|
|
2267
|
+
&& typeof bound.pr === 'object' && bound.pr.url === view.url
|
|
2268
|
+
&& (!bound.checklistVersion || bound.checklistVersion === birthBoardVersion);
|
|
2269
|
+
if (!same) throw new Error(`같은 칸 OPEN PR #${number}에 기존 카드 ${boundCards.map((card) => card.id).join(', ')}가 있다 — 구현 발사 0; tasks adopt-pr ${number} --card <카드> --version ${birthBoardVersion} 확인`);
|
|
2270
|
+
if (opts.json) out(JSON.stringify({ card: bound.id, pr: number, head: view.headRefOid, launched: false, next: `tasks rejudge ${bound.id}` }));
|
|
2271
|
+
else out(`${bound.id}\tPR #${number} already-bound\t구현 발사 0 · 다음: tasks rejudge ${bound.id}`);
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
const { adoptExistingPr } = await import('../task-agent/adopt-pr.js');
|
|
2275
|
+
const current = (deps.adoptChecklist ?? listChecklist)(birthBoardVersion).items.find((item) => item.id === opts.checklist);
|
|
2276
|
+
if (!current || current.status === 'green' || current.status === 'done') throw new Error(`입양할 미완 칸 없음: ${birthBoardVersion}/${opts.checklist}`);
|
|
2277
|
+
const adopted = adoptExistingPr({ pr: view, repo, statePath,
|
|
2278
|
+
checklist: { version: birthBoardVersion, id: current.id, title: current.title, status: current.status },
|
|
2279
|
+
checklistAtWrite: () => (deps.adoptChecklist ?? listChecklist)(birthBoardVersion).items.find((item) => item.id === opts.checklist) });
|
|
2280
|
+
try { debug.log('task-agent.hand', 'open-pr-adopted', { card: adopted.card.id, pr: number, head: view.headRefOid, launched: false }); } catch { /* fail-soft */ }
|
|
2281
|
+
if (opts.json) out(JSON.stringify({ card: adopted.card.id, pr: number, head: view.headRefOid, launched: false, next: `tasks rejudge ${adopted.card.id}` }));
|
|
2282
|
+
else out(`${adopted.card.id}\tPR #${number} adopted\t구현 발사 0 · 다음: tasks rejudge ${adopted.card.id}`);
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
const handOpts = {
|
|
1718
2287
|
text: resolved.text,
|
|
1719
2288
|
...(opts.project !== undefined && opts.target !== undefined ? { project: { id: opts.project, target: opts.target } } : {}),
|
|
1720
2289
|
...(opts.goal !== undefined ? { goal: opts.goal } : {}),
|
|
@@ -1727,13 +2296,28 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1727
2296
|
...(opts.taLand === true ? { taLand: true } : {}),
|
|
1728
2297
|
...(opts.childLlm !== undefined ? { childLlm: opts.childLlm } : {}),
|
|
1729
2298
|
...(opts.childLlmEffort !== undefined ? { childLlmEffort: opts.childLlmEffort } : {}),
|
|
2299
|
+
...(opts.roleLlm?.length ? { parentRoleLlm: opts.roleLlm } : {}),
|
|
1730
2300
|
...(opts.tier !== undefined ? { tier: opts.tier } : {}),
|
|
1731
2301
|
...(opts.tag !== undefined ? { tags: opts.tag } : {}),
|
|
1732
2302
|
...(birthBoardVersion !== undefined ? { checklistVersion: birthBoardVersion } : {}),
|
|
1733
2303
|
...(deps.taskStatePath ? { statePath: deps.taskStatePath } : {}),
|
|
1734
2304
|
...(deps.taskCompositionRoot ? { compositionRoot: deps.taskCompositionRoot } : {}),
|
|
1735
2305
|
launcher: deps.taskLauncher ?? defaultTaskLauncher,
|
|
1736
|
-
}
|
|
2306
|
+
};
|
|
2307
|
+
// DRAFT-CLAIM-AUTO-2-FU — 생산 배선: `--draft-claim <pr>` 로 관측·게시·수확 리뷰 포트를 끼워 draftClaim(⊕ 손 수확)을
|
|
2308
|
+
// «조립해» 넘긴다 — handDraftClaim 이 관측한 PR 머리와 리뷰 러너로 수확을 확정한 뒤에만 claim 문면을 게시한다.
|
|
2309
|
+
const draftClaimPr = opts.draftClaim === undefined ? undefined : Number(opts.draftClaim);
|
|
2310
|
+
if (opts.draftClaim !== undefined && (!Number.isInteger(draftClaimPr) || (draftClaimPr ?? 0) <= 0)) {
|
|
2311
|
+
out(`--draft-claim 은 PR 번호다: ${opts.draftClaim}`);
|
|
2312
|
+
process.exitCode = 1;
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
if (draftClaimPr !== undefined && opts.live && (opts.tier === '상' || opts.tier === '특')) {
|
|
2316
|
+
try { debug.log('task-agent.hand', 'tier-split-bypassed', { reason: 'draft-claim', pr: draftClaimPr, tier: opts.tier }); } catch { /* fail-soft */ }
|
|
2317
|
+
}
|
|
2318
|
+
const handed = draftClaimPr !== undefined
|
|
2319
|
+
? { kind: 'whole' as const, result: await handDraftClaim({ ...handOpts, pr: draftClaimPr, gh: deps.draftClaimGh ?? defaultDraftClaimGhPorts() }) }
|
|
2320
|
+
: await (await import('../task-agent/tier-split.js')).handTaskWithTierSplit({ ...handOpts, ...('tierSizedDecompose' in deps ? { decompose: deps.tierSizedDecompose } : {}) });
|
|
1737
2321
|
if (handed.kind === 'split') {
|
|
1738
2322
|
// TIER-SPLIT-BEFORE-LAUNCH — 상·특 hand 를 조각으로 나눠 쐈다: 첫 줄 = 묶음 · 조각마다 한 줄(미션 출력과 같은 꼴).
|
|
1739
2323
|
if (opts.json) out(JSON.stringify(handed));
|
|
@@ -1776,7 +2360,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1776
2360
|
.option('--by <seat>', '바꾼 자리(예: OP·MK·TC·UX) — 안 주면 호출 자리 ELANOUS_TRACK, 그것도 없으면 «unknown» 으로 감사 줄에 적는다(지어내지 않는다)')
|
|
1777
2361
|
.option('--reason <text>', '왜 바꾸나 — 감사 줄에 남긴다')
|
|
1778
2362
|
.option('--json', '바뀐 카드를 JSON 으로 출력')
|
|
1779
|
-
.action((id: string, kv: string, opts: { by?: string; reason?: string; json?: boolean }) => {
|
|
2363
|
+
.action(async (id: string, kv: string, opts: { by?: string; reason?: string; json?: boolean }) => {
|
|
1780
2364
|
try {
|
|
1781
2365
|
const by = opts.by?.trim() || process.env.ELANOUS_TRACK?.trim() || 'unknown';
|
|
1782
2366
|
const card = runTagChange(id, { tag: kv, by, ...(opts.reason !== undefined ? { reason: opts.reason } : {}) }, deps.taskStatePath ?? taskStatePathDefault());
|
|
@@ -1790,6 +2374,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1790
2374
|
return;
|
|
1791
2375
|
}
|
|
1792
2376
|
out(`${card.id}\t${kv}`);
|
|
2377
|
+
const { cardIdentityLines } = await import('./tasks-card-show.js');
|
|
1793
2378
|
for (const line of cardIdentityLines(card)) out(line);
|
|
1794
2379
|
} catch (error) {
|
|
1795
2380
|
out(error instanceof Error ? error.message : '태그 바꾸기 실패');
|
|
@@ -1984,6 +2569,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
1984
2569
|
await registerTaskAgentSink(deps.registerSink);
|
|
1985
2570
|
try {
|
|
1986
2571
|
// 내부 팩은 elanous 프로젝트만 낸다 — 다른 프로젝트로 거르면 릴리스 원장을 열지 않는다(그 원장의 오류가 카드 보드를 막지 않게).
|
|
2572
|
+
const { cardsBoard, countBoardTasks, INTERNAL_PACK_PROJECT, mergeBoards, releasePackBoard } = await import('../task-agent/board.js');
|
|
1987
2573
|
const wantsPack = opts.project === undefined || opts.project === INTERNAL_PACK_PROJECT;
|
|
1988
2574
|
const board = mergeBoards(
|
|
1989
2575
|
cardsBoard(deps.taskStatePath ?? taskStatePathDefault()),
|
|
@@ -2016,6 +2602,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
2016
2602
|
.option('--json', 'JSON 출력')
|
|
2017
2603
|
.action(async (missionId: string | undefined, opts: { all?: boolean; pr?: Array<[string, string]>; goal?: Array<[string, string]>; live?: boolean; json?: boolean }) => {
|
|
2018
2604
|
await registerTaskAgentSink(deps.registerSink);
|
|
2605
|
+
const { advanceMission, MissionAdvanceError, openMissionIds, recordPieceRef } = await import('../task-agent/mission.js');
|
|
2019
2606
|
const started = Date.now();
|
|
2020
2607
|
if ((missionId === undefined) === (opts.all !== true)) {
|
|
2021
2608
|
const reason = '미션 id «또는» --all 중 하나만 준다';
|
|
@@ -2091,84 +2678,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
2091
2678
|
}
|
|
2092
2679
|
});
|
|
2093
2680
|
|
|
2094
|
-
tasks
|
|
2095
|
-
.description(`태스크 상세 보기 (id 가 \`${TASK_CARD_PREFIX}\` 로 시작하면 TASK-AGENT 과제 카드 ⊕ 다음 수)`)
|
|
2096
|
-
.option('--json', 'JSON 출력')
|
|
2097
|
-
.action(async (id: string, opts: { json?: boolean }) => {
|
|
2098
|
-
if (id.startsWith(TASK_CARD_PREFIX)) {
|
|
2099
|
-
await registerTaskAgentSink(deps.registerSink);
|
|
2100
|
-
let card: TaskCard | undefined;
|
|
2101
|
-
try {
|
|
2102
|
-
card = readTaskCard(id, deps.taskStatePath);
|
|
2103
|
-
// 런 id 가 있으면 원장에서 골 id · PR 을 묶는다(찾았을 때만 적는다 · 실패는 그대로 보인다).
|
|
2104
|
-
const bindable = card?.pieces ? card.pieces : card ? [card.id] : [];
|
|
2105
|
-
for (const cardId of bindable) {
|
|
2106
|
-
// HARNESS-PARENT-ON-MSB1: 원격 부모 런은 원장이 그 호스트에 있다 — HQ 거울로 먼저 당긴다(읽기 전용).
|
|
2107
|
-
await mirrorRemoteCardLedger(cardId, deps);
|
|
2108
|
-
try { await replayRemoteCardMoves(cardId, deps.taskStatePath ?? taskStatePathDefault(), deps.cardRunEvidence ?? {}); } catch (error) {
|
|
2109
|
-
try { debug.log('task-agent', 'card-binding-replay-failed', { card: cardId, reason: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
2110
|
-
}
|
|
2111
|
-
try { await refreshCardRunBinding(cardId, deps.taskStatePath ?? taskStatePathDefault(), deps.cardRunEvidence ?? {}); } catch (error) {
|
|
2112
|
-
// 근거 없음으로 보인다 — 원장 부재와 갈리게 실패는 남긴다.
|
|
2113
|
-
try { debug.log('task-agent', 'card-bind-failed', { card: cardId, via: 'show', error: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
2114
|
-
}
|
|
2115
|
-
// TA-REVIEW-RESULT-CAPTURE — 떼어 띄운 live review 의 결과 파일을 이 카드 history 로 회수한다(대기 중인 요청이 있을 때만 쓴다 · 던지지 않는다).
|
|
2116
|
-
captureReviewResults(deps.taskStatePath ?? taskStatePathDefault(), { cardIds: [cardId], ...(deps.reviewResultNow ? { now: deps.reviewResultNow } : {}) });
|
|
2117
|
-
}
|
|
2118
|
-
if (card) card = readTaskCard(id, deps.taskStatePath);
|
|
2119
|
-
} catch (error) {
|
|
2120
|
-
out(error instanceof Error ? error.message : '과제 카드 읽기 실패');
|
|
2121
|
-
process.exitCode = 1;
|
|
2122
|
-
return;
|
|
2123
|
-
}
|
|
2124
|
-
if (!card) { out(`과제 카드 없음: ${id}`); process.exitCode = 1; return; }
|
|
2125
|
-
if (card.pieces) {
|
|
2126
|
-
let all: Record<string, TaskCard>;
|
|
2127
|
-
try { all = readTaskAgentState<{ tasks?: Record<string, TaskCard> }>(deps.taskStatePath ?? taskStatePathDefault()).tasks ?? {}; } catch (error) {
|
|
2128
|
-
out(error instanceof Error ? error.message : '과제 카드 읽기 실패');
|
|
2129
|
-
process.exitCode = 1;
|
|
2130
|
-
return;
|
|
2131
|
-
}
|
|
2132
|
-
const pieces = card.pieces.map((pieceId) => all[pieceId]).filter((piece): piece is TaskCard => piece !== undefined);
|
|
2133
|
-
const missingPieces = card.pieces.filter((pieceId) => !all[pieceId]);
|
|
2134
|
-
if (opts.json) {
|
|
2135
|
-
out(JSON.stringify({ card, nextMove: nextMoveFor(card), pieces: pieces.map((piece) => ({ card: piece, nextMove: nextMoveFor(piece) })), missingPieces }));
|
|
2136
|
-
if (missingPieces.length) process.exitCode = 1;
|
|
2137
|
-
}
|
|
2138
|
-
else {
|
|
2139
|
-
for (const line of missionLines(card, pieces)) out(line);
|
|
2140
|
-
for (const missing of missingPieces) { out(`조각 카드 없음: ${missing} — 선후 기록이 불완전하다`); process.exitCode = 1; }
|
|
2141
|
-
}
|
|
2142
|
-
return;
|
|
2143
|
-
}
|
|
2144
|
-
// DRAFT-NOT-ARCHIVE: 멈춘 런이 draft PR 대신 남긴 수확 가지(원장 `salvaged`). 못 읽었으면 «못 읽음»(⛔ 없음 아님).
|
|
2145
|
-
let salvage: Awaited<ReturnType<typeof readCardSalvageBranch>> = { salvageBranch: null };
|
|
2146
|
-
try { salvage = await readCardSalvageBranch(card, deps.cardRunEvidence ?? {}); }
|
|
2147
|
-
catch (error) { salvage = { salvageBranch: null, readErrors: [error instanceof Error ? error.message : String(error)] }; }
|
|
2148
|
-
const salvageUnread = !salvage.salvageBranch && !!salvage.readErrors?.length;
|
|
2149
|
-
if (opts.json) out(JSON.stringify({ card, nextMove: nextMoveFor(card), salvageBranch: salvage.salvageBranch, ...(salvageUnread ? { salvageBranchUnread: true } : {}) }));
|
|
2150
|
-
else {
|
|
2151
|
-
for (const line of taskCardLines(card)) out(line);
|
|
2152
|
-
if (card.parentHost) out(`부모 호스트: ${card.parentHost.host}${card.parentHost.pid ? ` (pid ${card.parentHost.pid})` : ''} · PR 까지만 — 병합은 HQ${card.parentHost.log ? ` · 로그 ${card.parentHost.host}:${card.parentHost.log}` : ''}`);
|
|
2153
|
-
if (salvage.salvageBranch) out(`수확 가지: ${salvage.salvageBranch} (draft PR 대신)`);
|
|
2154
|
-
else if (salvageUnread) out('수확 가지: 못 읽음(원장 읽기 실패)');
|
|
2155
|
-
}
|
|
2156
|
-
return;
|
|
2157
|
-
}
|
|
2158
|
-
try {
|
|
2159
|
-
activeToken = undefined;
|
|
2160
|
-
const auth = authenticate();
|
|
2161
|
-
const response = await request(deps, auth, `/v1/tasks/${encodeURIComponent(id)}`);
|
|
2162
|
-
if (!response.ok) throw httpError(response);
|
|
2163
|
-
const data = await responseJson(response) as { task?: TaskRow };
|
|
2164
|
-
if (!data?.task) throw new Error('넥서스 태스크 상세 형식이 유효하지 않다');
|
|
2165
|
-
if (opts.json) out(JSON.stringify(data));
|
|
2166
|
-
else {
|
|
2167
|
-
out(taskLine(data.task));
|
|
2168
|
-
out(JSON.stringify(data, null, 2));
|
|
2169
|
-
}
|
|
2170
|
-
} catch (error) { handleError(error, out); }
|
|
2171
|
-
});
|
|
2681
|
+
registerTaskShowCommand(tasks, ctx);
|
|
2172
2682
|
|
|
2173
2683
|
tasks.command('rejudge [id]')
|
|
2174
2684
|
.description('TA-REJUDGE-ON-HEAD — 런 멈춤 뒤 카드 PR 머리가 바뀌었으면 재게이트(떼어 띄움)·리뷰 재요청·land(재게이트 통과 ⊕ 지금 머리 pass ⊕ must-fix 0 · 겹침 증거)를 다시 판단한다 · 허용은 설정 taskAgent.liveMoves 그대로')
|
|
@@ -2239,7 +2749,9 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
2239
2749
|
const reviewBudget = opts.all ? { max: 1, requested: 0 } : undefined;
|
|
2240
2750
|
const landBudget = opts.all ? { max: 1, requested: 0 } : undefined;
|
|
2241
2751
|
const all = [];
|
|
2752
|
+
const { replayRemoteCardMoves } = await import('../task-agent/shadow.js');
|
|
2242
2753
|
for (const cardId of ids) {
|
|
2754
|
+
const { mirrorRemoteCardLedger } = await import('./tasks-card-show.js');
|
|
2243
2755
|
await mirrorRemoteCardLedger(cardId, deps);
|
|
2244
2756
|
try { await replayRemoteCardMoves(cardId, statePath, deps.cardRunEvidence ?? {}); } catch (error) {
|
|
2245
2757
|
try { debug.log('task-agent', 'card-binding-replay-failed', { card: cardId, reason: error instanceof Error ? error.message : String(error) }); } catch { /* fail-soft */ }
|
|
@@ -2295,9 +2807,11 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
2295
2807
|
.requiredOption('--card <id>').requiredOption('--pr <n>').requiredOption('--head <sha>')
|
|
2296
2808
|
.requiredOption('--repo <root>').requiredOption('--out <path>').requiredOption('--goal-file <path>')
|
|
2297
2809
|
.requiredOption('--run-id <id>').requiredOption('--model-provider <provider>').requiredOption('--model-id <id>')
|
|
2810
|
+
.option('--role-llm <role=provider[/tier]>', 'TA 부모 역할 고정(task hand와 같은 문법 · 반복 가능)', (value: string, previous: string[] = []) => [...previous, value])
|
|
2298
2811
|
.option('--seat <seat>')
|
|
2812
|
+
.option('--move-id <id>', 'HARNESS-FULL-GRAPH-NEXTMOVE — 이 발사를 고른 next-move 결정의 수 id(결과 파일·완료 사건에 그대로 싣는다)')
|
|
2299
2813
|
.action(async (opts: { card: string; pr: string; head: string; repo: string; out: string; goalFile: string;
|
|
2300
|
-
runId: string; modelProvider: string; modelId: string; seat?: string }) => {
|
|
2814
|
+
runId: string; modelProvider: string; modelId: string; seat?: string; moveId?: string; roleLlm?: string[] }) => {
|
|
2301
2815
|
const { readFileSync } = await import('node:fs');
|
|
2302
2816
|
const { getElanousConfigDirOverride } = await import('../elanous-config-dir.js');
|
|
2303
2817
|
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
@@ -2306,6 +2820,8 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
2306
2820
|
repo: opts.repo, resultPath: opts.out, entry: ELANOUS_CLI_ENTRY,
|
|
2307
2821
|
configDir: getElanousConfigDirOverride() ?? effectiveInstanceRoot(), runId: opts.runId,
|
|
2308
2822
|
model: { provider: opts.modelProvider, model: opts.modelId }, seat: opts.seat,
|
|
2823
|
+
...(opts.roleLlm?.length ? { parentRoleLlm: opts.roleLlm } : {}),
|
|
2824
|
+
...(opts.moveId ? { moveId: opts.moveId } : {}),
|
|
2309
2825
|
goal: readFileSync(opts.goalFile, 'utf8') });
|
|
2310
2826
|
if (!result.ok) process.exitCode = 1;
|
|
2311
2827
|
});
|
|
@@ -2314,7 +2830,7 @@ export function registerTasksCommands(program: Command, deps: TasksCliDeps = {})
|
|
|
2314
2830
|
.description('외부 태스크 승인')
|
|
2315
2831
|
.action(async (ids: string[]) => {
|
|
2316
2832
|
try {
|
|
2317
|
-
|
|
2833
|
+
ctx.clearToken();
|
|
2318
2834
|
const auth = authenticate();
|
|
2319
2835
|
let failed = false;
|
|
2320
2836
|
for (const id of ids) {
|