elanous 0.2.24 → 0.2.27
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/{Q-7iofoxOEv1WX18S35of → Ev2jivDMTSoEc-5rIXdcy}/_buildManifest.js +1 -1
- package/apps/pwa/out/_next/static/chunks/1027.a0f04663d24c2779.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1038.676007ae505822e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1113.a37eadf7f740d955.js +1 -0
- package/apps/pwa/out/_next/static/chunks/112-fa7c526c20ce1405.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1144.5fa1fa1d48dbcd12.js +36 -0
- package/apps/pwa/out/_next/static/chunks/1146.80d15ea7c6e6ee95.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1157.f08bf2b70915e176.js +148 -0
- package/apps/pwa/out/_next/static/chunks/119b405e.8fdd0e44d1ff37aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1296.04560d98e73fbf51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1314-f604ba013ec535b0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1454-a9c338f5bb556f42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1464.f3209af0d8ae4ff2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8532.a86e99acf9b761cb.js → 1516.83dd2c1b86b4e507.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{89849432.9eaa2f4c9c010965.js → 16e57f67.3ea19e893461e611.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/1729-d73dd25e564679d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1505.5826671109111e75.js → 1743.563d3168e95825ef.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/1843.5924c24295243261.js +27 -0
- package/apps/pwa/out/_next/static/chunks/1987.e28149724b92918b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2066-5ca18ffa5a4be68b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2240.d10a66695155fd93.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2391.e2cbc98d4abbb41a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2528.3868070ef1be69f9.js +24 -0
- package/apps/pwa/out/_next/static/chunks/2566-734cb83bbd405886.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2697.76ca2ac3efef6f3b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/275.0e6bad414729bfa0.js +32 -0
- package/apps/pwa/out/_next/static/chunks/2803-33bdae771855a8d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2846.d6a433b8663819a5.js +309 -0
- package/apps/pwa/out/_next/static/chunks/3143.257997a5f1ed79df.js +34 -0
- package/apps/pwa/out/_next/static/chunks/{5967-c0a80f8ffe5d4050.js → 3259-86f3be56e43eaf6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3280-e177c33bfbdcfb0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3294-2b6a6d45a46a6c1c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3309.364573ceedbdfd5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3522.758840fc5e7fe630.js +287 -0
- package/apps/pwa/out/_next/static/chunks/3525-a7c989c1a661e880.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3694.b1a66387756458c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/370.124f9e678a76fc01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3797-9643105335e660fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5578.ac963634373b28c6.js → 4084.15ecaf4240bd9e77.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4089-b3382b8843114eee.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8438.ee7f0df3b3f6fcb4.js → 4204.a409ab6250c0f6a8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4293.928a8d573c16aab7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4402.f9238a3b18a7bd43.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4490-9b090b0f9427f902.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4548.13502d0ca3b4f862.js +8 -0
- package/apps/pwa/out/_next/static/chunks/4565-67e4c620597965c8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7905.b230b9d06cb4ad5d.js → 4593.10f5b9bd702d4a6c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4599-548bc96cda44a5a7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4625.206574e33a9e7ae8.js +104 -0
- package/apps/pwa/out/_next/static/chunks/4862-392c9a71f0d8dfb4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5054.2906d1b02542edc5.js +149 -0
- package/apps/pwa/out/_next/static/chunks/{8569.b853a8daa8f31d12.js → 5081.a240b63fad28663a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5102-c822bbbee5a022a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5194-8c93f2e7e4b6f717.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5305-bacd7eca0b9a5848.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5311.9071ee3f44fd7ff5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5369.9f34353442daa421.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2528.0af5c0e1fad88df6.js → 5408.377c8c543635b81f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9502-32c5dc22e8f120f5.js → 5492-e05e26a033ba80d2.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/5785-ea8d2c633b6688e3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/589-49d730cb8138b397.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5904-264a6412ae3c9028.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5980.728531d5c566535c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/599-a914cc0468f56143.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6011.f525002e21bef0ac.js +23 -0
- package/apps/pwa/out/_next/static/chunks/6132.4381ab93683f1acf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6149.2e698635e454d8f6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5974.360f8441eb0ac1ec.js → 6196.34d6b229079e9e28.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6356-0afafc4230cf9767.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6406.64756073f7df35b8.js +77 -0
- package/apps/pwa/out/_next/static/chunks/6479-16e62aedafa0e310.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6599.b5246b46a2453329.js +64 -0
- package/apps/pwa/out/_next/static/chunks/{ce383d63.d2ede338788ed18e.js → 65ebff95.081daeff622d238e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6610.5c26eec011565acb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/666.37d18474edc1c552.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6904.a0b5b1ed4f2d2c20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{83cfc5c8.349535543c1548b6.js → 6def8a8e.c81f9ca41028eccf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7247-1f4aba94aba50d9f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7274-e71a7c5b7760cd02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7290.1fcaeef3adb5d0d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7477-2c64e42760f82f33.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2721.bc9a8f36bd85c07a.js → 7483.ba01c6c209b81bba.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7505-47002b76ff1aef6c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7600-11884ca4da68e5d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7607.40d8bb3689697ccb.js +62 -0
- package/apps/pwa/out/_next/static/chunks/{6702.ebbd165d5e4a4a14.js → 7752.5cbdd0b433c54af9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7763.f51fcc19ca1414ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7785.846140c859b1adf6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7807-a0fdac6393e0bf36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7837-20a63b4b7e9342b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/800.7815414cf280545d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8925-2abfb48af477fb7f.js → 8085-1e5fce59500a2cc2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8100.415f2105faa63e97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8290.701aa42ed13aa767.js +226 -0
- package/apps/pwa/out/_next/static/chunks/{b4e12462-5fab4a813a63dadd.js → 83a80514-b897b64df43ce108.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8444.d9c74214a3145a43.js +93 -0
- package/apps/pwa/out/_next/static/chunks/8569.98c915534928c7f9.js +24 -0
- package/apps/pwa/out/_next/static/chunks/{8264.00e434fbd02e9311.js → 8578.dff22d988cc2b106.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/8591.b1c7f876e5ec7cfb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8715-f63756021d3ab989.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8879.776c2ec7732aa114.js +115 -0
- package/apps/pwa/out/_next/static/chunks/8881.ddca15b3c82c490c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8946.5da5c87f32a2e1a8.js +41 -0
- package/apps/pwa/out/_next/static/chunks/9117.080b9b75be90e14d.js +74 -0
- package/apps/pwa/out/_next/static/chunks/9167.f3ecb28f367d6e69.js +94 -0
- package/apps/pwa/out/_next/static/chunks/9224.66095370ee28c93b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9238-0682b013c83d6449.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7518.49161e8f5814abdb.js → 9311.24c8f5ea793253dd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9976.8f28864317639fe6.js → 94.4cfed21c0b781a06.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/9449.d3ed795da4befb1c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/949.1e7d45ccb71580aa.js +90 -0
- package/apps/pwa/out/_next/static/chunks/9638-d268329f2c6f4f8d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{0e3acd34.d42b2ef87fdf4234.js → 973cbf1b.3bbcf2b41548eca7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9842-d6644eb53d3dc6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9863.94ca3263e760696e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6571-926ddf024a6ad5df.js → 9917-9e26abc5f7772735.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9938.b23e4d4dfa0114cd.js +79 -0
- package/apps/pwa/out/_next/static/chunks/{740.90b31555290236a9.js → 9974.e77b4ff4884557b3.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/{4c5e95ce.83e9b16add6fcd8c.js → ab4d57c2.e90bc2cb31949045.js} +7 -7
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d9babd8795f396a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-a1ce306364f08da9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-07110da72c45e68f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2d76d4a7eae91bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-30007380f2e2a3c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e5a9302c105725a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-b93c04b5311c97ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-718a49e86bf25f9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-20662900d6b4b455.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f707a509515ac50c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6a16a48d1aa6e91f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-26345242e4049f55.js → page-fcb06e305f515527.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-6acf5ec4e6c6d1af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-39f82acf619cca23.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-7c2c8f81b185f9c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d9babd8795f396a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ae7aff07e32676bb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-482c60c1bc540d0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-7cb996c1aa08b6f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-623e08fe4477d606.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-db460cd8aecb8490.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-ff27902d0b11ef4f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-8397a5c6ca569bf4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1917ea87ea71ab6b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-25a302273b054fd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-670510eda8b9443f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d73c3f1e00d93986.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-65caacf54389081e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4bb7556e2e97a750.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-77e04b4f8762c666.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-b92caddb5c715f30.js → page-15c0a4095baddfba.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-4eb96fc7241c2456.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-63e272a4cd3b6634.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d64ff212dc55d03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-7f096248993fc2d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e47fc72049149a38.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-f7d808e31e005895.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-5c2b24b5496dad73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d9babd8795f396a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-76cdb531668b5191.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-e2b63345bba1a11f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d376e0c83edc041a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d6db55fe2c04d128.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-87b88f551f421d8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-dab163b338e3b966.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-08019efef111ed2e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-0260ffc7eda50db1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-980ce6fe36ad95eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-99c4e6c7345a9156.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-0ab42e0f05823013.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ad9220422d20ba33.js +1 -0
- package/apps/pwa/out/_next/static/chunks/dcf24383-2f82e35f2d827393.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{53746282.1be36038d1abd949.js → e117f801.e92bf10242653497.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/e87d89b9.2e71c0ae475d28a1.js +122 -0
- package/apps/pwa/out/_next/static/chunks/{0673cc54.8e2ef1c23e49e190.js → f4dfbfc0.a69fb12f854882aa.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-bf135b997155e90e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-5571537543bea91c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-25c51c7df7f3a442.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-e3b2c1f5d82abd65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-083f2e23e9bc3641.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-e94d22b7a028217b.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +22 -22
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +21 -21
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +22 -22
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +22 -22
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +22 -22
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +21 -21
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +21 -21
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +21 -21
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +21 -21
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +21 -21
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/graphs/release/release-loop.yaml +5 -5
- package/graphs/steward/recipes.yaml +4 -0
- package/graphs/steward/steward.yaml +10 -1
- package/package.json +1 -1
- package/scripts/bench-report.ts +46 -1
- package/scripts/ci-typecheck-changed.ts +88 -7
- package/scripts/coord-msg-bridge.ts +209 -0
- package/scripts/coord-post.sh +40 -7
- package/scripts/coord-requests-bridge.ts +10 -5
- package/scripts/expose-rubric-suggest.ts +1 -1
- package/scripts/measure-tsc-affected-roots.ts +10 -1
- package/scripts/release-loop/auto-approve-node.ts +6 -0
- package/scripts/release-loop/export-check-node.ts +8 -5
- package/scripts/release-loop/gate-closure.ts +286 -0
- package/scripts/release-loop/gate-node.ts +73 -13
- package/scripts/release-loop/gate-partial.ts +34 -6
- package/scripts/release-loop/node-verdict.ts +7 -2
- package/scripts/release-loop/ops-upgrade-node.ts +63 -42
- package/scripts/release-loop/publish-node.ts +6 -0
- package/scripts/release-loop/resume-release.ts +37 -10
- package/scripts/release-loop/resume.ts +69 -4
- package/scripts/tsc-baseline-cache.ts +28 -3
- package/scripts/usage-rollup.ts +27 -13
- package/scripts/ux-sim.ts +30 -1
- package/skills/youtube-master/scripts/main.ts +4 -1
- package/skills/youtube-master/src/obsidian.ts +9 -0
- package/src/agent/task-notification.ts +30 -1
- package/src/agent-substrate/acp-implementer.ts +14 -7
- package/src/agent-substrate/pr-reviewer.ts +30 -10
- package/src/agent-substrate/review-observation.ts +9 -2
- package/src/agent-substrate/self-review-cli.ts +154 -16
- package/src/boot/daemon-tools/self-implement.ts +72 -4
- package/src/budget/cost-tier-advisor.ts +91 -0
- package/src/budget/llm-cost.ts +5 -1
- package/src/budget/shadow-arms.ts +77 -0
- package/src/budget/types.ts +5 -3
- package/src/budget/unified-usage.ts +172 -8
- package/src/cli/control-cli.ts +29 -0
- package/src/cli/doctor-cli.ts +106 -3
- package/src/cli/friendly-errors.ts +113 -0
- package/src/cli/intake-cli.ts +3 -1
- package/src/cli/memory-cli.ts +182 -0
- package/src/cli/model-watch-cli.ts +4 -1
- package/src/cli/msg-cli.ts +15 -5
- package/src/cli/pr-cli.ts +53 -0
- package/src/cli/pr-land-overlap-evidence.ts +18 -10
- package/src/cli/pr-land-pod-shadow.ts +54 -0
- package/src/cli/release-cli.ts +41 -12
- package/src/cli/release-run-if-ready.ts +11 -0
- package/src/cli/resource-cli.ts +48 -0
- package/src/cli/retro-cli.ts +12 -3
- package/src/cli/seat-cli.ts +121 -0
- package/src/cli/seat-requests-cli.ts +25 -0
- package/src/cli/self-utterances-cli.ts +140 -0
- package/src/cli/tasks-cli.ts +168 -14
- package/src/cli/usage-cli.ts +3 -1
- package/src/compact/verify-probe.ts +23 -3
- package/src/context-card/cell.ts +40 -0
- package/src/context-card/index.ts +50 -0
- package/src/control/control-status.ts +30 -1
- package/src/dashboard/boot-timing.ts +35 -0
- package/src/dashboard/index.ts +86 -24
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +10 -138
- package/src/dashboard/slash-runtime/shell-slash.ts +145 -0
- package/src/debug/log-diet.ts +16 -4
- package/src/debug/log.ts +1 -1
- package/src/domains/morning-essential.ts +18 -3
- package/src/domains/release-tool.ts +29 -8
- package/src/domains/surface-events.ts +16 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/execution-loop/launch-gate.ts +158 -2
- package/src/fleet/host-doctor.ts +446 -0
- package/src/goals/judge.ts +20 -6
- package/src/graph-runner/runner.ts +12 -6
- package/src/graph-wizard/archetypes.ts +14 -8
- package/src/graph-wizard/generate.ts +77 -1
- package/src/grok.ts +13 -7
- package/src/harness/harness-cli-command.ts +102 -51
- package/src/harness/harness-stop.ts +93 -19
- package/src/index.ts +52 -179
- package/src/instance/current.ts +2 -6
- package/src/instance/resolve.ts +5 -6
- package/src/intake-plane/collect-github.ts +7 -2
- package/src/intake-plane/lens.ts +46 -4
- package/src/intake-plane/requirement-funnel.ts +53 -1
- package/src/intelligence-map/catalog-feed.ts +119 -0
- package/src/intelligence-map/model-watch-intake.ts +40 -0
- package/src/intelligence-map/model-watch-mission.ts +14 -1
- package/src/llm/open-weight-discipline.ts +21 -0
- package/src/llm/openai-sampling-fields.ts +89 -0
- package/src/llm/runtime-model-family.ts +61 -0
- package/src/llm/zai-coding-plan.ts +27 -1
- package/src/llm.ts +17 -97
- package/src/machines/machine-ledger.ts +31 -3
- package/src/maturity/cli-maturity.ts +13 -3
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/msg/msg-store.ts +69 -7
- package/src/nexus/api/http-server.ts +22 -0
- package/src/oauth/codex-account-rotation.ts +4 -1
- package/src/oauth/codex-account-store.ts +20 -7
- package/src/oauth/codex-quota-policy.ts +33 -4
- package/src/ops/overview-collect.ts +3 -2
- package/src/ops/overview-owner.ts +29 -0
- package/src/plugin-hooks/shell-hook.ts +17 -2
- package/src/plugin-missions/evaluator-runner.ts +18 -2
- package/src/plugins/install/plugin-install.ts +11 -1
- package/src/preview/terminal.ts +15 -5
- package/src/project/project-store.ts +20 -0
- package/src/prompt-enhance/enhance.ts +11 -0
- package/src/release-loop/auto-start.ts +7 -5
- package/src/release-loop/checklist.ts +52 -5
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/gate-shards.ts +4 -0
- package/src/release-loop/light-rc.ts +84 -13
- package/src/release-loop/merged-pr-checklist.ts +35 -5
- package/src/release-loop/rehearsal.ts +1 -1
- package/src/seat-loop/duty-ledger.ts +64 -0
- package/src/seat-loop/seat-loop.ts +5 -1
- package/src/self-dev/decompose.ts +37 -3
- package/src/self-dev/dev-pipeline.ts +32 -7
- package/src/self-dev/draft-residue.ts +16 -6
- package/src/self-dev/draft-sweep.ts +75 -18
- package/src/self-dev/fabric-decompose-adapter.ts +9 -2
- package/src/self-dev/orchestrate.ts +6 -2
- package/src/self-dev/run-supervisor.ts +72 -8
- package/src/self-implement/gate-cli.ts +29 -10
- package/src/self-implement/gate-remote.ts +4 -1
- package/src/self-implement/goal-author.ts +15 -29
- package/src/self-implement/goal-command-execution.ts +26 -0
- package/src/self-implement/goal-new-paths.ts +42 -0
- package/src/self-implement/host-regate.ts +124 -30
- package/src/self-implement/importer-test-index.ts +3 -1
- package/src/self-implement/l8-merge-queue.ts +80 -2
- package/src/self-implement/merge-train.ts +19 -0
- package/src/self-implement/off-diff-evidence.ts +40 -1
- package/src/self-implement/orchestrator.ts +90 -20
- package/src/self-implement/premise-review.ts +159 -0
- package/src/self-implement/pwa-import-graph.ts +194 -7
- package/src/self-implement/regate-detail.ts +79 -0
- package/src/self-implement/review-deferred-to-host.ts +21 -0
- package/src/self-implement/rework-budget-judgment.ts +23 -3
- package/src/self-implement/running-runs.ts +2 -2
- package/src/self-implement/scope-adjacency.ts +23 -0
- package/src/self-implement/seams.ts +42 -7
- package/src/self-implement/self-implement-runtime.ts +126 -21
- package/src/session/index.ts +14 -4
- package/src/session-runtime/index.ts +55 -11
- package/src/skills/tools/agent.ts +16 -1
- package/src/skills/tools/persistent-grounding.ts +11 -1
- package/src/skills/tools/shell.ts +33 -1
- package/src/steward/rejudge-tick.ts +142 -0
- package/src/task-agent/board.ts +92 -3
- package/src/task-agent/card-evidence.ts +88 -1
- package/src/task-agent/cover.ts +85 -1
- package/src/task-agent/live-moves.ts +14 -6
- package/src/task-agent/parent-host.ts +27 -3
- package/src/task-agent/rejudge-on-head.ts +370 -29
- package/src/task-agent/review-result-capture.ts +67 -2
- package/src/task-agent/shadow.ts +148 -21
- package/src/task-agent/task-hand.ts +22 -3
- package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -0
- package/src/task-orchestrator/surfaces/pod-host-acp-review.ts +122 -0
- package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +23 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +53 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +236 -28
- package/src/tool-runtime/tool-search-runtime.ts +25 -0
- package/src/tui.ts +20 -1
- package/src/user-config.ts +21 -63
- package/src/ux-sim/fault-cred-expiry.ts +100 -0
- package/src/ux-sim/fault-pod-death.ts +74 -0
- package/src/ux-sim/fault-pool-cap.ts +81 -0
- package/src/version/packed-revision.json +1 -1
- package/src/webclone/qa-table.ts +22 -0
- package/apps/pwa/out/_next/static/chunks/0a09e062-34f747a17d8d8c2e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1168.a0fb3d9db76c00bd.js +0 -77
- package/apps/pwa/out/_next/static/chunks/1325-dc3f3c21eb181cb4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1357.a843c41d2cd7ec31.js +0 -34
- package/apps/pwa/out/_next/static/chunks/1430.53f49882b17607c2.js +0 -24
- package/apps/pwa/out/_next/static/chunks/1459-be6509b3419d8a9f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/153.0a4e3cd516930491.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1572.7bc291352e241a45.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1593-d07ad068c120dfb0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1608.b7c900baa56cab32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1615-4d8156febaac4b35.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1646.e03f0fbc26bc72f6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1669-da843ed73aa301f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/176.13ba0659e3e96fd4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1990.9c60f495ef33c070.js +0 -36
- package/apps/pwa/out/_next/static/chunks/2042e469.f86158dd5c1e51a1.js +0 -122
- package/apps/pwa/out/_next/static/chunks/2543-28ec6aeb80a5e1e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2561.43c99d6ee914ee6a.js +0 -62
- package/apps/pwa/out/_next/static/chunks/2638.b39ab2f674caa1a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/272-0eed0fb0aab716b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2778.67e0c1d484189ccf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2841.10ff309f6a3168af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2886-9639f97d523d752a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2978.57fb87ce21cc5a85.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3664.15f968c39d15bd28.js +0 -149
- package/apps/pwa/out/_next/static/chunks/3786-935eb3fcde90e496.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3813.df4a01ae89c6f63f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3961-aaae04ed1d32622f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3992.f79c70480e8c4077.js +0 -309
- package/apps/pwa/out/_next/static/chunks/40-413e8641de9ed1e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4054-521f11a503daed64.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4315.e7356f34c7d8ec79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4489-bf629de6f04f7681.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4540.557733cc88c6a82b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/462.0275b4338c5b6337.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4658-7ed9266098a787e4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4945-0f4046cfc3228ea3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5105-18b4165c5d3a86b0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5205-6992210fe31f45f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5287-72898b126d722457.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5313.1697309c532b9856.js +0 -2
- package/apps/pwa/out/_next/static/chunks/5474.991b90688c3cb321.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5544-871146192d095594.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5935.d5e8ca3e096772e8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6006.84e8b418a7a0db97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6013-1267ba7454d6764b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6038-1e0f7ee1086da4cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/617.709dd0019fe99796.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6261.fb9bf94947094636.js +0 -74
- package/apps/pwa/out/_next/static/chunks/6411.1966866fae31ffa9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6481-b829d99aa8d158f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6541.2b7daa043100d0fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6597.2d7a6d77ab485b27.js +0 -64
- package/apps/pwa/out/_next/static/chunks/67.5dd2dc543c2fa3de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6777-312496b0d774719b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/681.0116f7036cfb4e8d.js +0 -32
- package/apps/pwa/out/_next/static/chunks/6871.30b2bf23b3126e36.js +0 -24
- package/apps/pwa/out/_next/static/chunks/6882.737d366d54e29d83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7035.54c2c535164b1c9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7086-8bfa7e8f796a03d9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7126.5e5599f251726b67.js +0 -93
- package/apps/pwa/out/_next/static/chunks/7203-47ff0740a293e284.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7268.017f49dafb5f0ff3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7355.5d65a87e9567ae08.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7392.4018c2a62177f296.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7471.3a3d1be50559cfb8.js +0 -148
- package/apps/pwa/out/_next/static/chunks/7500.73489a345cef2201.js +0 -226
- package/apps/pwa/out/_next/static/chunks/7565.c2f72a15c1ed0396.js +0 -27
- package/apps/pwa/out/_next/static/chunks/7651.6d1c984d3d8587a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7713.7ab863ed59b2d661.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7793.d7cd2026767ddd54.js +0 -115
- package/apps/pwa/out/_next/static/chunks/7837.c7c267e5dceadb11.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7891.09266382daf70d1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-cbf6a01bcbdcb7e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8119-819c549e0c197786.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8186-74ea19a3700d04a8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8267-e7b3262b735ae106.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8386.86518e55b75f7cf5.js +0 -8
- package/apps/pwa/out/_next/static/chunks/8437.ba6eeeb29590042f.js +0 -23
- package/apps/pwa/out/_next/static/chunks/8469-1500be57e582296f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8483-7164cecdaf178ca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8722-4c940f02c496ad36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/887-41d0dcf5af48c628.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9039.da86361496797522.js +0 -90
- package/apps/pwa/out/_next/static/chunks/905.d5745a25884754d4.js +0 -94
- package/apps/pwa/out/_next/static/chunks/9089-d909f3d4478b0628.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9096.dc5c7a267f83790c.js +0 -41
- package/apps/pwa/out/_next/static/chunks/93.a567257b6bdb7fe4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9320.419acb9665b46597.js +0 -287
- package/apps/pwa/out/_next/static/chunks/9356.b8ceda6634612ff8.js +0 -79
- package/apps/pwa/out/_next/static/chunks/94.e9e1043b35cd3eed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9400-7498ea90b2e6ee8f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9930-58644af9c813d6e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9932.367cf4725fedd6e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9935.1628b904cdc8ce0e.js +0 -104
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-a2aa8709d193ee0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3b9fc39362473ae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f2b62c19ca68fef8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d7602d489edf5f37.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-24282bf9f90031a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-8b7c5ab6b5bc8d64.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-4eb84d454f8c3415.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-b2e494bdad57fcb5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c0ab229673a8b0eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-3e2b45f3c36dbac4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-c22aa511b28743ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-bf88c0f558d8ef54.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-52fec523e5b5d3ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-67723dfdcde92b31.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-a2aa8709d193ee0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-22725e2570f1126b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-1891335950d68b70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-3267af1ebdc73cf5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-c314b75c11234998.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-9f9c85415f61782b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c1d4276059085587.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-833aab587f26ad4c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8526c17ae38e6a50.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-f998637a8402115c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-b7bb53828b17b335.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1b1850de0257e46d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-6f23159fedb41f67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1d809a87139dd024.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-da9e570234d481fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-8ce3a38e210b810a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-57ffeeb6019440fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-abbb417bf514630b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2c2b3f29cec1efa4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-9369279f72fc7b51.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-64861f14039499c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ddc4c93efd8ab94b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-a2aa8709d193ee0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-901a1391b81053bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-68d9bbaac9841a58.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-b5c0c996f329371e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-89881ccbcf728978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-0e87a803b3d7165e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-58d4e5cb30ef4871.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-02ce4c3b5f39e4d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-e620d1bb8eadcec2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-acf868c64d77f950.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-54fa605fa6dba457.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-bc79f0833533fa84.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d5c4398eea03d8f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/cd39fa05.fac4545f42a3ef70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-b2763b10dc6b3836.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-27893e93b0ec193d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-f280047194e1daf8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-e90eff2d0b70564c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-70f2d654b3435d34.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-4794f053bd5e4230.js +0 -1
- /package/apps/pwa/out/_next/static/{Q-7iofoxOEv1WX18S35of → Ev2jivDMTSoEc-5rIXdcy}/_ssgManifest.js +0 -0
|
@@ -48,11 +48,14 @@ export function runExportCheck(run: CommandRunner = runCommand, repoRoot = proce
|
|
|
48
48
|
const publicDir = join(out, 'export');
|
|
49
49
|
symlinkSync(join(modulesRoot, 'node_modules'), join(publicDir, 'node_modules'), 'dir');
|
|
50
50
|
const pwaModules = join(modulesRoot, 'apps', 'pwa', 'node_modules');
|
|
51
|
-
if (existsSync(pwaModules)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
if (!existsSync(pwaModules)) result = failed('build', 'PWA dependencies missing; run bun install --cwd apps/pwa in the release-loop repository');
|
|
52
|
+
else {
|
|
53
|
+
if (existsSync(join(publicDir, 'apps', 'pwa'))) symlinkSync(pwaModules, join(publicDir, 'apps', 'pwa', 'node_modules'), 'dir');
|
|
54
|
+
const built = run('bun', ['bin/elanous.mjs', 'nexus', 'build'], publicDir);
|
|
55
|
+
result = built.status === 0 && existsSync(join(publicDir, 'apps', 'pwa', 'out', 'index.html'))
|
|
56
|
+
? { outcome: 'ok', verdict: 'pass', summary: `public export and PWA build ${commit.slice(0, 12)}`, step: 'build', tail: '' }
|
|
57
|
+
: failed('build', 'PWA build or out/index.html missing', built);
|
|
58
|
+
}
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// GATE-REUSE-BY-DIFF (0.2.25 · 10-12): which test files can a code change reach? A test whose whole module closure
|
|
2
|
+
// (static ⊕ literal dynamic imports ⊕ requires ⊕ literal repo paths it reads or spawns) avoids every changed file runs
|
|
3
|
+
// the same code at both commits, so its prior gate result is reusable. Everything else — and every test whose closure we
|
|
4
|
+
// cannot read for sure («opaque») — is re-run. Doubt never shrinks the set: a closure we cannot build is a full gate.
|
|
5
|
+
//
|
|
6
|
+
// Read from the tip commit's git objects, never from the working tree (the resume can run from any checkout).
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { dirname, join, normalize } from 'node:path/posix';
|
|
9
|
+
import ts from 'typescript';
|
|
10
|
+
import { sourceCandidates } from '../../src/self-implement/importer-test-index.js';
|
|
11
|
+
|
|
12
|
+
/** Files the closure follows (code). Changing anything else that is not a test or inert Markdown is a full gate. */
|
|
13
|
+
export const CODE_FILE = /\.(?:[cm]?[jt]sx?)$/;
|
|
14
|
+
const TEST_FILE = /^(?:[\w.-]+\/)*[\w.-]+\.test\.(?:tsx?|jsx?|mts|cts)$/;
|
|
15
|
+
/** Test-side helpers: their filesystem scans and unresolved code paths make a test opaque (production code is trusted to
|
|
16
|
+
* reach code only through imports and the literal paths below). */
|
|
17
|
+
const TEST_SIDE_DIR = /(?:^|\/)(?:test|tests|__tests__|__mocks__|fixtures|__fixtures__|test-helpers|testing)\//;
|
|
18
|
+
const TEST_SIDE_NAME = /(?:^|[-_.])(?:test-?utils?|test-?helpers?|test-?support|testing|fixtures?|mocks?)$/i;
|
|
19
|
+
const isTestSide = (file: string): boolean => TEST_FILE.test(file) || TEST_SIDE_DIR.test(file)
|
|
20
|
+
|| TEST_SIDE_NAME.test((file.split('/').pop() ?? file).replace(CODE_FILE, ''));
|
|
21
|
+
/** A test-side file that lists directories or tracked files reads paths we cannot see. */
|
|
22
|
+
const FS_SCAN = /\b(?:readdirSync|readdir|opendirSync|opendir|globSync|Glob|walkSync)\b|\bls-files\b/;
|
|
23
|
+
/** `import(x)` / `require(x)` with a non-literal argument loads a module we cannot name. */
|
|
24
|
+
const NON_LITERAL_LOAD = /(?<![.\w$])(?:import|require)\s*\(\s*(?!['"`)\s])([^)]*)\)?/g;
|
|
25
|
+
/** `const NAME = "literal"` bindings — `import(NAME)` with such a NAME loads that literal. */
|
|
26
|
+
const CONST_STRING = /\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*(?:process\.env\.[\w$]+\s*(?:\?\?|\|\|)\s*)?(['"`])((?:(?!\2)[^\n\\$])*)\2\s*[;,\n]/g;
|
|
27
|
+
/** `import(["..", "x", "y.js"].join("/"))` — a specifier spelled as joined literals. */
|
|
28
|
+
const JOINED_IMPORT = /(?<![.\w$])import\s*\(\s*\[((?:\s*"[^"\n]*"\s*,?)+)\]\.join\(\s*"([^"\n]*)"\s*\)\s*\)/g;
|
|
29
|
+
/** Process launches — a file that has one may run a repo path it names. */
|
|
30
|
+
const SPAWNS = /(?<![\w$])(?:spawn|spawnSync|exec|execSync|execFile|execFileSync|fork|Bun\.spawn|Bun\.spawnSync|Bun\.\$)\s*[(`]/;
|
|
31
|
+
const LITERAL = /(['"`])((?:(?!\1)[^\n\\])*)\1/g;
|
|
32
|
+
/** An npm package specifier (`x`, `x/sub`, `@scope/x/sub`). */
|
|
33
|
+
const PACKAGE_NAME = /^(?:@[a-z0-9][\w.~-]*\/)?[a-z0-9][\w.~-]*(?:\/[^\s]*)?$/i;
|
|
34
|
+
const SUFFIXES = ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'];
|
|
35
|
+
|
|
36
|
+
export interface ClosureTree {
|
|
37
|
+
/** Every tracked path at the tip. */
|
|
38
|
+
files: readonly string[];
|
|
39
|
+
/** Content at the tip (code files only are asked for); undefined = unreadable. */
|
|
40
|
+
read: (path: string) => string | undefined;
|
|
41
|
+
/** bun test preload files (bunfig.toml `[test].preload`), repo-relative. */
|
|
42
|
+
preload: readonly string[];
|
|
43
|
+
/** tsconfig `paths` aliases: specifier prefix → repo dir, scoped to importers under `scope`. */
|
|
44
|
+
aliases?: readonly { scope: string; prefix: string; target: string }[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type ClosureVerdict =
|
|
48
|
+
| { ok: true; tests: string[]; reached: number; opaque: number; opaqueFiles: string[] }
|
|
49
|
+
| { ok: false; reason: string };
|
|
50
|
+
|
|
51
|
+
function loaderOf(file: string): 'ts' | 'tsx' | 'js' {
|
|
52
|
+
return /\.[jt]sx$/.test(file) ? 'tsx' : /\.[cm]?ts$/.test(file) ? 'ts' : 'js';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ClosureOptions {
|
|
56
|
+
/** CLI entry files a production literal does not make a dependency of (help text, child command builders). */
|
|
57
|
+
cliEntries?: readonly string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface ClosureGraph {
|
|
61
|
+
/** Every dependency a file can load: imports of any kind ⊕ literal repo paths. */
|
|
62
|
+
forward: Map<string, string[]>;
|
|
63
|
+
/** Only top-level `import`/`export … from` — what loading the file evaluates before any of its code runs. */
|
|
64
|
+
staticImports: Map<string, string[]>;
|
|
65
|
+
opaque: Map<string, string>;
|
|
66
|
+
tracked: Set<string>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Forward dependency edges of every code file at the tip ⊕ the files whose dependencies we cannot fully name. */
|
|
70
|
+
function closureGraph(tree: ClosureTree, options: ClosureOptions = {}): ClosureGraph {
|
|
71
|
+
const tracked = new Set(tree.files);
|
|
72
|
+
const dirs = new Set<string>();
|
|
73
|
+
for (const file of tree.files) for (let at = file.indexOf('/'); at > 0; at = file.indexOf('/', at + 1)) dirs.add(file.slice(0, at));
|
|
74
|
+
const resolveFrom = (from: string, spec: string): string | undefined => {
|
|
75
|
+
for (const candidate of sourceCandidates(from, spec)) if (tracked.has(candidate)) return candidate;
|
|
76
|
+
const base = normalize(join(dirname(from), spec.replace(/[?#].*$/, '')));
|
|
77
|
+
if (tracked.has(base)) return base;
|
|
78
|
+
for (const suffix of SUFFIXES) {
|
|
79
|
+
if (tracked.has(`${base}${suffix}`)) return `${base}${suffix}`;
|
|
80
|
+
if (tracked.has(`${base}/index${suffix}`)) return `${base}/index${suffix}`;
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
};
|
|
84
|
+
/** A literal is a file name, not a module specifier: exact (or emitted `.js` → `.ts`), extensionless relative ones get
|
|
85
|
+
* a source suffix (`mock.module('../y')`) — never a directory index (`'src/'` is not `src/index.ts`). */
|
|
86
|
+
const resolveLiteral = (from: string, value: string): string | undefined => {
|
|
87
|
+
const relative = value.startsWith('./') || value.startsWith('../');
|
|
88
|
+
const bases = relative ? [from] : [from, '_'];
|
|
89
|
+
if (/\.[A-Za-z0-9]+$/.test(value.split('/').pop() ?? '')) {
|
|
90
|
+
for (const base of bases) for (const candidate of sourceCandidates(base, value)) if (tracked.has(candidate)) return candidate;
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
if (!relative) return undefined;
|
|
94
|
+
const base = normalize(join(dirname(from), value));
|
|
95
|
+
for (const suffix of SUFFIXES) if (tracked.has(`${base}${suffix}`)) return `${base}${suffix}`;
|
|
96
|
+
return undefined;
|
|
97
|
+
};
|
|
98
|
+
const transpilers = { ts: new Bun.Transpiler({ loader: 'ts' }), tsx: new Bun.Transpiler({ loader: 'tsx' }), js: new Bun.Transpiler({ loader: 'js' }) };
|
|
99
|
+
const forward = new Map<string, string[]>();
|
|
100
|
+
const staticImports = new Map<string, string[]>();
|
|
101
|
+
const opaque = new Map<string, string>();
|
|
102
|
+
const blindAs = (file: string, why: string): void => { if (!opaque.has(file)) opaque.set(file, why); };
|
|
103
|
+
for (const file of tree.files) {
|
|
104
|
+
if (!CODE_FILE.test(file)) continue;
|
|
105
|
+
const text = tree.read(file);
|
|
106
|
+
if (text === undefined) { blindAs(file, 'unreadable'); continue; }
|
|
107
|
+
const edges = new Set<string>();
|
|
108
|
+
const testSide = isTestSide(file);
|
|
109
|
+
let specs: { path: string; kind: string }[];
|
|
110
|
+
// The transpiled text has no comments or types: literals and loads are read from code only (a path in a comment is
|
|
111
|
+
// not a dependency).
|
|
112
|
+
let code: string;
|
|
113
|
+
try {
|
|
114
|
+
const source = text.replace(/^#![^\n]*/, '');
|
|
115
|
+
specs = [...transpilers[loaderOf(file)].scanImports(source)];
|
|
116
|
+
code = transpilers[loaderOf(file)].transformSync(source).replace(JOINED_IMPORT, (_all, items: string, separator: string) => {
|
|
117
|
+
const joined = [...items.matchAll(/"([^"\n]*)"/g)].map((item) => item[1]).join(separator);
|
|
118
|
+
specs.push({ path: joined, kind: 'dynamic-import' });
|
|
119
|
+
return `import(${JSON.stringify(joined)})`;
|
|
120
|
+
});
|
|
121
|
+
} catch { blindAs(file, 'unparsable'); continue; }
|
|
122
|
+
// A load whose argument is not a literal: resolvable only when it is one identifier bound to a string constant in
|
|
123
|
+
// this file (`const M = './pitch-api.js'; import(M)`); anything else loads a module we cannot name.
|
|
124
|
+
const bindings = new Map<string, string>();
|
|
125
|
+
for (const match of code.matchAll(CONST_STRING)) bindings.set(match[1]!, match[3]!);
|
|
126
|
+
const codeOnly = code.replace(LITERAL, '""');
|
|
127
|
+
for (const match of codeOnly.matchAll(NON_LITERAL_LOAD)) {
|
|
128
|
+
const bound = bindings.get((match[1] ?? '').trim());
|
|
129
|
+
if (bound === undefined) { blindAs(file, 'non-literal import/require'); continue; }
|
|
130
|
+
if (!bound.startsWith('.')) continue; // a package name (optional dependency)
|
|
131
|
+
const hit = resolveFrom(file, bound);
|
|
132
|
+
if (hit) specs.push({ path: bound, kind: 'dynamic-import' }); else blindAs(file, `unresolved import ${bound}`);
|
|
133
|
+
}
|
|
134
|
+
if (testSide && FS_SCAN.test(code)) blindAs(file, `scans (${FS_SCAN.exec(code)?.[0]})`);
|
|
135
|
+
const eager = new Set<string>();
|
|
136
|
+
for (const { path: spec, kind } of specs) {
|
|
137
|
+
let hit: string | undefined;
|
|
138
|
+
if (spec.startsWith('.') || spec.startsWith('/')) {
|
|
139
|
+
hit = spec.startsWith('/') ? undefined : resolveFrom(file, spec);
|
|
140
|
+
if (!hit) { blindAs(file, `unresolved import ${spec}`); continue; }
|
|
141
|
+
} else {
|
|
142
|
+
const alias = tree.aliases?.find((entry) => (entry.scope === '' || file.startsWith(`${entry.scope}/`)) && spec.startsWith(entry.prefix));
|
|
143
|
+
if (!alias) {
|
|
144
|
+
// Not a package name (`@/x`, `~/x`, `#x`): an alias we do not know — never assume it is outside the repo.
|
|
145
|
+
if (!/^(?:node:|bun:)/.test(spec) && !PACKAGE_NAME.test(spec)) blindAs(file, `unresolved alias ${spec}`);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
hit = resolveFrom(`${alias.target}/_`, `./${spec.slice(alias.prefix.length)}`);
|
|
149
|
+
if (!hit) { blindAs(file, `unresolved alias ${spec}`); continue; }
|
|
150
|
+
}
|
|
151
|
+
edges.add(hit);
|
|
152
|
+
if (kind === 'import-statement') eager.add(hit);
|
|
153
|
+
// Anything else is a package (node_modules / builtin) — lockfile and package.json changes are full gates.
|
|
154
|
+
}
|
|
155
|
+
// Literal repo paths: a test that reads `src/x.ts`, spawns `bin/elanous.mjs` or mocks `../y` depends on that file.
|
|
156
|
+
for (const match of code.matchAll(LITERAL)) {
|
|
157
|
+
const value = match[2]!;
|
|
158
|
+
if (!value || value.length > 300 || value.includes('${') || /\s/.test(value) || /^[a-z]+:/i.test(value)) continue;
|
|
159
|
+
const pathLike = value.startsWith('./') || value.startsWith('../') || (value.includes('/') && /^[\w@.-]+(?:\/[\w@.-]+)+\/?$/.test(value)) || CODE_FILE.test(value);
|
|
160
|
+
if (!pathLike) continue;
|
|
161
|
+
const plain = value.replace(/\/$/, '');
|
|
162
|
+
const hit = resolveLiteral(file, plain);
|
|
163
|
+
// A path naming another test file is runner input (a list, a fixture string), not code this file loads.
|
|
164
|
+
if (hit) {
|
|
165
|
+
// A production file that names the CLI entry but spawns nothing only prints it (help text, a goal's command line).
|
|
166
|
+
const printsOnly = (options.cliEntries ?? []).includes(hit) && !SPAWNS.test(code);
|
|
167
|
+
if (!TEST_FILE.test(hit) && (testSide || !printsOnly)) edges.add(hit);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (!testSide) continue;
|
|
171
|
+
const rootDir = normalize(plain.startsWith('.') ? join(dirname(file), plain) : plain);
|
|
172
|
+
// A test-side literal naming a tracked directory scans it; one naming a code file we cannot find is a path we miss.
|
|
173
|
+
if (dirs.has(rootDir) || (plain.startsWith('.') && dirs.has(normalize(join(dirname(file), plain))))) blindAs(file, `names directory ${plain}`);
|
|
174
|
+
// With or without a slash: `join(dir, '..', 'oauth', 'store.ts')` builds the path from pieces, so the bare file name
|
|
175
|
+
// is all we see — and a code file we cannot find is a path we miss.
|
|
176
|
+
else if (CODE_FILE.test(plain) && !TEST_FILE.test(plain)) blindAs(file, `unresolved path ${plain}`);
|
|
177
|
+
}
|
|
178
|
+
edges.delete(file);
|
|
179
|
+
eager.delete(file);
|
|
180
|
+
forward.set(file, [...edges]);
|
|
181
|
+
staticImports.set(file, [...eager]);
|
|
182
|
+
}
|
|
183
|
+
return { forward, staticImports, opaque, tracked };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The tests among `tests` that can reach any of `changed` (code files) — or whose closure is opaque. */
|
|
187
|
+
export function testsReachingChanged(tree: ClosureTree, changed: readonly string[], tests: readonly string[], options: ClosureOptions = {}): ClosureVerdict {
|
|
188
|
+
const { forward, staticImports, opaque, tracked } = closureGraph(tree, options);
|
|
189
|
+
const reverse = new Map<string, string[]>();
|
|
190
|
+
for (const [from, edges] of forward) for (const to of edges) {
|
|
191
|
+
const list = reverse.get(to);
|
|
192
|
+
if (list) list.push(from); else reverse.set(to, [from]);
|
|
193
|
+
}
|
|
194
|
+
const reachedFrom = (seeds: Iterable<string>): Set<string> => {
|
|
195
|
+
const seen = new Set<string>(seeds);
|
|
196
|
+
const queue = [...seen];
|
|
197
|
+
while (queue.length) for (const importer of reverse.get(queue.pop()!) ?? []) if (!seen.has(importer)) { seen.add(importer); queue.push(importer); }
|
|
198
|
+
return seen;
|
|
199
|
+
};
|
|
200
|
+
// Preload runs before every test file: what it evaluates on load (its static import closure) is a dependency of all
|
|
201
|
+
// of them — changed, missing or unreadable there is a full gate.
|
|
202
|
+
const preloadClosure = new Set<string>();
|
|
203
|
+
const queue = [...tree.preload];
|
|
204
|
+
while (queue.length) {
|
|
205
|
+
const file = queue.pop()!;
|
|
206
|
+
if (preloadClosure.has(file)) continue;
|
|
207
|
+
preloadClosure.add(file);
|
|
208
|
+
queue.push(...(staticImports.get(file) ?? []));
|
|
209
|
+
}
|
|
210
|
+
const changedSet = new Set(changed);
|
|
211
|
+
for (const file of preloadClosure) {
|
|
212
|
+
if (!tracked.has(file)) return { ok: false, reason: `bun test preload closure file ${file} is missing — full gate` };
|
|
213
|
+
if (changedSet.has(file)) return { ok: false, reason: `bun test preload closure file ${file} changed — full gate` };
|
|
214
|
+
// Any load we cannot name here — even one that looks lazy — may run before every test: a full gate (#26516 review).
|
|
215
|
+
const why = opaque.get(file);
|
|
216
|
+
if (why) return { ok: false, reason: `bun test preload closure file ${file} is opaque (${why}) — full gate` };
|
|
217
|
+
}
|
|
218
|
+
const reached = reachedFrom(changed);
|
|
219
|
+
const blind = reachedFrom([...opaque.keys()].filter((file) => !preloadClosure.has(file)));
|
|
220
|
+
const selected = tests.filter((test) => reached.has(test) || blind.has(test)).sort();
|
|
221
|
+
return { ok: true, tests: selected, reached: tests.filter((test) => reached.has(test)).length, opaque: tests.filter((test) => blind.has(test) && !reached.has(test)).length,
|
|
222
|
+
opaqueFiles: [...opaque].filter(([file]) => !preloadClosure.has(file)).map(([file, why]) => `${file}: ${why}`).sort() };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** `[test].preload` of a bunfig.toml text (repo-relative). Null when the file has a preload we cannot parse. */
|
|
226
|
+
export function bunfigPreload(text: string | undefined): string[] | null {
|
|
227
|
+
if (text === undefined) return [];
|
|
228
|
+
const section = /^\s*\[test\]\s*$([\s\S]*?)(?=^\s*\[|(?![\s\S]))/m.exec(text)?.[1] ?? '';
|
|
229
|
+
const line = /^\s*preload\s*=\s*(.+)$/m.exec(section.replace(/#[^\n]*/g, ''));
|
|
230
|
+
if (!line) return /preload/.test(section.replace(/#[^\n]*/g, '')) ? null : [];
|
|
231
|
+
const list = /^\[([^\]]*)\]\s*$/.exec(line[1]!.trim()) ?? /^(["'][^"']*["'])\s*$/.exec(line[1]!.trim());
|
|
232
|
+
if (!list) return null;
|
|
233
|
+
return [...list[1]!.matchAll(/["']([^"']+)["']/g)].map((entry) => normalize(entry[1]!).replace(/^\.\//, ''));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The tip tree from git objects (`ls-tree` ⊕ one `cat-file --batch`). Throws on any git failure. */
|
|
237
|
+
export function gitClosureTree(repo: string, commit: string): ClosureTree {
|
|
238
|
+
if (!/^[0-9a-f]{40}$/i.test(commit)) throw new Error(`not a full sha: ${commit}`);
|
|
239
|
+
// git-spawn-allow: read-only object reads of one release commit.
|
|
240
|
+
const listed = spawnSync('git', ['ls-tree', '-r', '-z', '--name-only', commit], { cwd: repo, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 });
|
|
241
|
+
if (listed.status !== 0 || listed.error) throw new Error(`git ls-tree failed: ${(listed.stderr || listed.error || '').toString().trim()}`);
|
|
242
|
+
const files = listed.stdout.split('\0').filter(Boolean);
|
|
243
|
+
const wanted = files.filter((file) => CODE_FILE.test(file) || file === 'bunfig.toml' || /(?:^|\/)tsconfig[\w.-]*\.json$/.test(file));
|
|
244
|
+
// git-spawn-allow: read-only object reads of one release commit.
|
|
245
|
+
const batch = spawnSync('git', ['cat-file', '--batch'], { cwd: repo, input: wanted.map((file) => `${commit}:${file}`).join('\n') + '\n', maxBuffer: 2 * 1024 * 1024 * 1024 });
|
|
246
|
+
if (batch.status !== 0 || batch.error) throw new Error(`git cat-file failed: ${(batch.stderr || batch.error || '').toString().trim()}`);
|
|
247
|
+
const out = batch.stdout as Buffer;
|
|
248
|
+
const contents = new Map<string, string>();
|
|
249
|
+
let at = 0;
|
|
250
|
+
for (const file of wanted) {
|
|
251
|
+
const end = out.indexOf(10, at);
|
|
252
|
+
if (end < 0) throw new Error('git cat-file output truncated');
|
|
253
|
+
const header = out.subarray(at, end).toString('utf8');
|
|
254
|
+
const size = /^[0-9a-f]+ blob (\d+)$/.exec(header);
|
|
255
|
+
if (!size) { at = end + 1; continue; } // «missing» (a submodule or symlink target) — read() answers undefined → opaque
|
|
256
|
+
const length = Number(size[1]);
|
|
257
|
+
contents.set(file, out.subarray(end + 1, end + 1 + length).toString('utf8'));
|
|
258
|
+
at = end + 1 + length + 1;
|
|
259
|
+
}
|
|
260
|
+
const preload = bunfigPreload(contents.get('bunfig.toml'));
|
|
261
|
+
if (preload === null) throw new Error('bunfig.toml [test].preload is unreadable');
|
|
262
|
+
return { files, read: (path) => contents.get(path), preload, aliases: tsconfigAliases(contents) };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** `compilerOptions.paths` entries of the form `"<prefix>*": ["<dir>/*"]` — scoped to the tsconfig's directory. */
|
|
266
|
+
function tsconfigAliases(contents: ReadonlyMap<string, string>): { scope: string; prefix: string; target: string }[] {
|
|
267
|
+
const aliases: { scope: string; prefix: string; target: string }[] = [];
|
|
268
|
+
for (const [file, text] of contents) {
|
|
269
|
+
if (!/(?:^|\/)tsconfig[\w.-]*\.json$/.test(file)) continue;
|
|
270
|
+
// TypeScript's own JSONC reader (comments, trailing commas, `src/**/*.ts` globs). An alias we cannot read would
|
|
271
|
+
// silently look like a package — so an unreadable tsconfig is an error (full gate), never skipped.
|
|
272
|
+
const parsed = ts.parseConfigFileTextToJson(file, text);
|
|
273
|
+
if (parsed.error) throw new Error(`tsconfig unreadable: ${file}`);
|
|
274
|
+
const options = (parsed.config as { compilerOptions?: { paths?: unknown; baseUrl?: unknown } } | undefined)?.compilerOptions;
|
|
275
|
+
const paths = options?.paths;
|
|
276
|
+
const baseUrl = options?.baseUrl;
|
|
277
|
+
if (!paths || typeof paths !== 'object') continue;
|
|
278
|
+
const scope = dirname(file) === '.' ? '' : dirname(file);
|
|
279
|
+
const base = normalize(join(scope || '.', typeof baseUrl === 'string' ? baseUrl : '.'));
|
|
280
|
+
for (const [key, targets] of Object.entries(paths as Record<string, unknown>)) {
|
|
281
|
+
if (key === '*' || !key.endsWith('*') || !Array.isArray(targets) || typeof targets[0] !== 'string' || !targets[0].endsWith('*')) continue;
|
|
282
|
+
aliases.push({ scope, prefix: key.slice(0, -1), target: normalize(join(base, targets[0].slice(0, -1))).replace(/\/$/, '') });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return aliases;
|
|
286
|
+
}
|
|
@@ -14,12 +14,13 @@ import { loadEnvKnownFailures, splitKnownEnv } from './env-known-failures';
|
|
|
14
14
|
import { baselineFromCutLogs } from './baseline-from-logs';
|
|
15
15
|
import { mergePartialFailures, readPartialPlan, testFileOfId } from './gate-partial';
|
|
16
16
|
import { logPlanWalltime, planDurations, planShards, readFileMemory } from './shard-plan';
|
|
17
|
+
import { readJunitFileSeconds } from './gate-method';
|
|
17
18
|
import { deriveCdpTestPatterns } from '../test-deterministic';
|
|
18
19
|
import { emitNodeResult, readGraphContext } from './node-verdict.js';
|
|
19
20
|
import { isGateExecutor, type GateExecutor } from './gate-executor.js';
|
|
20
21
|
import { runPodCommand, parsePodCpu, resolvePodCpu, type RunPodCommandOptions, type PodCommandResult, type PodCpu } from '../../src/task-orchestrator/surfaces/pod-command-job.js';
|
|
21
22
|
import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from '../../src/task-orchestrator/surfaces/pod-bun-cache.js';
|
|
22
|
-
import { PodPoolScheduler, parsePodPool, checkPodPool } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
23
|
+
import { PodPoolScheduler, parsePodPool, checkPodPool, defaultRemoteRun, type PodPoolMember } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
23
24
|
import { POD_INSTALL_SLOTS_DEFAULT, POD_INSTALL_SLOTS_HOST_PATH, installSlotScript } from '../../src/task-orchestrator/surfaces/pod-install-slots.js';
|
|
24
25
|
import { gateDepsInstallScript, parseGateDeps } from '../../src/task-orchestrator/surfaces/pod-gate-deps.js';
|
|
25
26
|
import { gateShardsPath, recordGateShardsVerdict, writeGateShards, type GateShard } from '../../src/release-loop/gate-shards.js';
|
|
@@ -76,7 +77,13 @@ export interface GateOptions {
|
|
|
76
77
|
/** How often the stall watch reads the live log (ms); omitted = {@link GATE_POD_STALL_POLL_MS}. */
|
|
77
78
|
stallPollMs?: number;
|
|
78
79
|
/** GATE-LIVE-OBS — the shard state file (`gateShardsPath(version)`) this sweep keeps current; absent = not written. */
|
|
79
|
-
shardsFile?: { path: string; version: string }
|
|
80
|
+
shardsFile?: { path: string; version: string };
|
|
81
|
+
/** GATE-MULTI-HOST — is this pool member reachable (kubectl ns ⊕ ssh)? Injected by tests; absent = the real probe on
|
|
82
|
+
* the gate's own pool · an injected pool without it never marks a member down (unchanged). */
|
|
83
|
+
probeMember?: (member: PodPoolMember) => boolean;
|
|
84
|
+
/** GATE-MULTI-HOST — seconds a placed shard's Job status may stay unreadable before the shard counts as lost with its
|
|
85
|
+
* member; omitted = {@link GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT}. Only a pool of two or more members uses it. */
|
|
86
|
+
memberUnreadableSeconds?: number };
|
|
80
87
|
}
|
|
81
88
|
export interface GateResult {
|
|
82
89
|
outcome: 'ok' | 'regression' | 'error';
|
|
@@ -696,6 +703,18 @@ export const GATE_ADMISSION_WAIT_SECONDS_DEFAULT = 300;
|
|
|
696
703
|
export type GateAdmissionOutcome = 'granted' | 'admission-timeout' | 'admission-error' | 'off';
|
|
697
704
|
export interface GateAdmissionResult { outcome: GateAdmissionOutcome; waitedMs: number; reason?: string; release: () => void }
|
|
698
705
|
export const gateAdmissionWaitText = (reading: string | null) => `메모리 여유 대기(admission) · ${reading ?? '측정 전'}`.slice(0, 160);
|
|
706
|
+
/**
|
|
707
|
+
* GATE-MULTI-HOST (0.2.25 · 대표) — the gate pool is a host roster (`release.loop.gatePodPool` =
|
|
708
|
+
* `pool-node-b@node-b:N,pool-node-c@node-c:M,…`); shards spread over it by free share (selfCapped). A member that fails a
|
|
709
|
+
* shard's launch or goes silent mid-Job is probed; if it is unreachable it leaves the roster for the rest of this gate and
|
|
710
|
+
* the shard runs again on the others — one dead host no longer stops the gate. One-member pools keep the old behaviour.
|
|
711
|
+
*/
|
|
712
|
+
export const GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT = 600;
|
|
713
|
+
export function defaultGateMemberProbe(member: PodPoolMember): boolean {
|
|
714
|
+
if (!checkPodPool([member], defaultKubectl).ok) return false;
|
|
715
|
+
return !member.sshHost || defaultRemoteRun(member.sshHost, 'true').status === 0;
|
|
716
|
+
}
|
|
717
|
+
|
|
699
718
|
export function poolGateAdmission(pool: PodPoolScheduler): GateAdmission {
|
|
700
719
|
return { acquire: (signal) => pool.acquireAdmission(signal, undefined, { gate: true }), waitReason: () => pool.waitReason() };
|
|
701
720
|
}
|
|
@@ -840,9 +859,30 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
840
859
|
});
|
|
841
860
|
const stallWatch = stallSeconds > 0 && !!liveLog;
|
|
842
861
|
debug.log('release-loop.gate', 'pod-stall-watch', { stallSeconds, source: pod.stallSeconds !== undefined ? 'config' : 'default', enabled: stallWatch });
|
|
862
|
+
// GATE-MULTI-HOST: members found unreachable during this gate — skipped for placement while any other member is left.
|
|
863
|
+
const downMembers = new Set<string>();
|
|
864
|
+
const multiHost = poolScheduler.members.length > 1;
|
|
865
|
+
const probeMember = pod.probeMember ?? (poolOverride ? undefined : defaultGateMemberProbe);
|
|
866
|
+
const memberUnreadableSeconds = pod.memberUnreadableSeconds ?? GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT;
|
|
867
|
+
if (!Number.isFinite(memberUnreadableSeconds) || memberUnreadableSeconds < 0) throw new Error('invalid pod sweep options');
|
|
868
|
+
debug.log('release-loop.gate', 'pod-roster', { members: poolScheduler.members.map((m) => ({ context: m.context, host: m.sshHost ?? null, capacity: m.capacity })),
|
|
869
|
+
dropped: ready?.dropped ?? [], multiHost, probe: !!probeMember });
|
|
870
|
+
/** True when the shard's member is (now) known unreachable — a newly found one is logged and leaves the roster. */
|
|
871
|
+
const memberLost = (context: string, shard: number, error: string): boolean => {
|
|
872
|
+
if (downMembers.has(context)) return true;
|
|
873
|
+
const member = poolScheduler.members.find((m) => m.context === context);
|
|
874
|
+
if (!multiHost || !probeMember || !member) return false;
|
|
875
|
+
let reachable: boolean;
|
|
876
|
+
try { reachable = probeMember(member); } catch { reachable = false; }
|
|
877
|
+
if (reachable) return false;
|
|
878
|
+
downMembers.add(context);
|
|
879
|
+
debug.log('release-loop.gate', 'pod-member-down', { member: context, host: member.sshHost ?? null, shard, error: error.slice(0, 300),
|
|
880
|
+
remaining: poolScheduler.members.filter((m) => !downMembers.has(m.context)).map((m) => m.context) }, { level: 'warn' });
|
|
881
|
+
return true;
|
|
882
|
+
};
|
|
843
883
|
const jobLogTail = (job: PodCommandResult): string => {
|
|
844
884
|
if (podLogTail) return podLogTail(job);
|
|
845
|
-
for (const member of poolScheduler.members) {
|
|
885
|
+
for (const member of poolScheduler.members.filter((m) => !downMembers.has(m.context))) {
|
|
846
886
|
const logs = defaultKubectl(['--context', member.context, '-n', 'elanous-test', 'logs', `job/${job.job}`, '-c', 'child', '--tail=200']);
|
|
847
887
|
if (logs.status === 0 && logs.stdout) return logs.stdout;
|
|
848
888
|
}
|
|
@@ -882,16 +922,18 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
882
922
|
// Pending/Running in the cluster and the next admission reading counts it, so holding the lease would count it twice.
|
|
883
923
|
const trackedPool = (shard: number, retry: boolean, placed: (member: NonNullable<Awaited<ReturnType<PodPoolScheduler['tryAcquire']>>>) => void = () => {}): PodPoolScheduler => ({
|
|
884
924
|
members: poolScheduler.members,
|
|
885
|
-
tryAcquire: async (...
|
|
886
|
-
|
|
925
|
+
tryAcquire: async (...[skip, onEmpty, mode]: Parameters<PodPoolScheduler['tryAcquire']>) => {
|
|
926
|
+
// GATE-MULTI-HOST: a lost member is skipped while another member is left (all lost = the old behaviour).
|
|
927
|
+
const anyLeft = poolScheduler.members.some((m) => !downMembers.has(m.context));
|
|
928
|
+
const member = await poolScheduler.tryAcquire(downMembers.size && anyLeft ? new Set([...(skip ?? []), ...downMembers]) : skip, onEmpty, mode);
|
|
887
929
|
if (member) placed(member);
|
|
888
|
-
if (member) markShard(shard, { state: retry ? 'retry' : 'running', startedAt: board[shard]?.startedAt ?? new Date().toISOString() }, ['waitReason']);
|
|
930
|
+
if (member) markShard(shard, { state: retry ? 'retry' : 'running', startedAt: board[shard]?.startedAt ?? new Date().toISOString(), host: member.context }, ['waitReason']);
|
|
889
931
|
else markShard(shard, { waitReason: 'Pod 자리 대기' });
|
|
890
932
|
return member;
|
|
891
933
|
},
|
|
892
934
|
release: (member: Parameters<PodPoolScheduler['release']>[0]) => poolScheduler.release(member),
|
|
893
935
|
}) as unknown as PodPoolScheduler;
|
|
894
|
-
const runShard = async (paths: string[], shard: number, depth = 0, branch = '', retriedNoOutput = false, fresh = pod.freshShards === true, parentAttempt?: string): Promise<{ runs: ShardRun[]; stalled: NonNullable<GateResult['stalledShards']> }> => {
|
|
936
|
+
const runShard = async (paths: string[], shard: number, depth = 0, branch = '', retriedNoOutput = false, fresh = pod.freshShards === true, parentAttempt?: string, hostRetries = 0): Promise<{ runs: ShardRun[]; stalled: NonNullable<GateResult['stalledShards']> }> => {
|
|
895
937
|
// 파일별 소요는 junit 으로 남긴다 — 콘솔 요약(판정 원천)은 그대로이고, 느린 시험 목록(K10 D4)·계층 분리(D2)의 자가 된다.
|
|
896
938
|
// GATE-TIMEOUT-HEAL: parts (each with its own junit) under a soft deadline inside this shard's own Job deadline.
|
|
897
939
|
const shardDeadline = shardDeadlineSeconds(estimated(paths), deadlineSeconds);
|
|
@@ -910,6 +952,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
910
952
|
let jobExitCode: number | undefined;
|
|
911
953
|
let lastFile: string | undefined;
|
|
912
954
|
let jobFailed = false;
|
|
955
|
+
let jobError = '';
|
|
956
|
+
/** GATE-MULTI-HOST — the member this shard's Job was placed on (recorded in the shard json / log / board). */
|
|
957
|
+
let placedOn: string | undefined;
|
|
913
958
|
let timedOut = false;
|
|
914
959
|
let finished: string[] = [];
|
|
915
960
|
let unfinishedLog: string | undefined;
|
|
@@ -960,7 +1005,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
960
1005
|
} }) : undefined;
|
|
961
1006
|
const placed = admitted.release;
|
|
962
1007
|
const job = await podCommand({
|
|
963
|
-
pool: pod.pool, poolScheduler: trackedPool(shard, depth > 0 || branch !== '', (member) => { jobContext = member.context; placed(); }), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
|
|
1008
|
+
pool: pod.pool, poolScheduler: trackedPool(shard, depth > 0 || branch !== '', (member) => { jobContext = member.context; placedOn = member.context; placed(); }), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
|
|
1009
|
+
...(multiHost ? { statusUnreadableMs: memberUnreadableSeconds * 1000 } : {}),
|
|
964
1010
|
...(bunCache ? { bunCache } : {}),
|
|
965
1011
|
...(installSlots > 0 ? { installSlots: POD_INSTALL_SLOTS_HOST_PATH } : {}),
|
|
966
1012
|
...(pod.cpu ? { cpu: pod.cpu } : {}),
|
|
@@ -982,12 +1028,16 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
982
1028
|
try { lastFile = lastStartedTestFile(jobLogTail(job).split(/\r?\n/).slice(-200).join('\n'), paths); }
|
|
983
1029
|
catch { /* Log retrieval is diagnostic; do not hide the stalled shard. */ }
|
|
984
1030
|
}
|
|
985
|
-
} catch {
|
|
1031
|
+
} catch (error) {
|
|
986
1032
|
jobFailed = true;
|
|
1033
|
+
jobError = error instanceof Error ? error.message : String(error);
|
|
987
1034
|
}
|
|
988
1035
|
// A launch that failed before placement still gives its admission back (release is idempotent).
|
|
989
1036
|
admitted?.release();
|
|
990
1037
|
const durationMs = Date.now() - start;
|
|
1038
|
+
const fileSeconds = junit === undefined ? undefined : Object.fromEntries(readJunitFileSeconds(junit));
|
|
1039
|
+
const testSeconds = fileSeconds === undefined ? undefined : Object.values(fileSeconds).reduce((sum, seconds) => sum + seconds, 0);
|
|
1040
|
+
const podOverheadSeconds = testSeconds === undefined ? undefined : Math.max(0, durationMs / 1000 - testSeconds);
|
|
991
1041
|
// A reused shard keeps its files as they are — rewriting would replace the measured durationMs the next release plans by.
|
|
992
1042
|
if (logDir && !reused && !descent) {
|
|
993
1043
|
mkdirSync(logDir, { recursive: true });
|
|
@@ -1004,12 +1054,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1004
1054
|
if (unfinishedLog !== undefined) writeFileSync(unfinishedDestination, unfinishedLog, { mode: 0o600 });
|
|
1005
1055
|
else rmSync(unfinishedDestination, { force: true });
|
|
1006
1056
|
writeFileSync(join(logDir, `pod-${shard}${branch}.json`), JSON.stringify({ durationMs, rc: rc ?? null,
|
|
1007
|
-
...(stall ? { reason: 'stall', finishedFiles: 0, stallSeconds: stall.seconds, sinceCheckoutMs: stall.sinceCheckoutMs } : timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}), files: paths, plannedSeconds: estimated(paths), deadlineSeconds: shardDeadline, shardCount: shards.length, commit, runKey, attemptId, ...(parentAttempt ? { parentAttempt } : {}), jobExitCode: jobExitCode ?? null, jobFailed,
|
|
1057
|
+
...(stall ? { reason: 'stall', finishedFiles: 0, stallSeconds: stall.seconds, sinceCheckoutMs: stall.sinceCheckoutMs } : timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}), files: paths, plannedSeconds: estimated(paths), deadlineSeconds: shardDeadline, shardCount: shards.length, commit, runKey, attemptId, ...(parentAttempt ? { parentAttempt } : {}), jobExitCode: jobExitCode ?? null, jobFailed, ...(placedOn ? { host: placedOn } : {}),
|
|
1058
|
+
...(fileSeconds !== undefined ? { fileSeconds, testSeconds, podOverheadSeconds } : {}),
|
|
1008
1059
|
...(admitted && admitted.outcome !== 'off' ? { admission: admitted.outcome, admissionWaitMs: admitted.waitedMs } : {}) }) + '\n', { mode: 0o600 });
|
|
1009
1060
|
}
|
|
1010
1061
|
const installSeconds = parseInstallSeconds(output ?? '');
|
|
1011
1062
|
// GATE-TIMEOUT-HEAL ③: a cut shard is «reason: timeout» (with the files that still finished), not a bare rc=null.
|
|
1012
|
-
if (!reused && !descent) debug.log('release-loop.gate', 'pod-shard', { shard, files: paths, durationMs, rc: rc ?? null, attempt: depth + 1, installSeconds, gateDeps: parseGateDeps(output ?? ''), deadlineSeconds: shardDeadline, parts: parts.length,
|
|
1063
|
+
if (!reused && !descent) debug.log('release-loop.gate', 'pod-shard', { shard, files: paths, durationMs, rc: rc ?? null, attempt: depth + 1, installSeconds, gateDeps: parseGateDeps(output ?? ''), deadlineSeconds: shardDeadline, parts: parts.length, ...(placedOn ? { host: placedOn } : {}),
|
|
1064
|
+
...(testSeconds !== undefined ? { testSeconds } : {}),
|
|
1013
1065
|
...(stall ? { reason: 'stall', finishedFiles: 0, stallSeconds: stall.seconds } : timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}) });
|
|
1014
1066
|
if (!reused && !descent) markShard(shard, { ...(installSeconds !== null ? { installSec: installSeconds } : {}), ...(rc !== undefined ? { rc } : {}), ...(timedOut ? { state: 'timeout' as const } : {}) });
|
|
1015
1067
|
let clean = output?.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
@@ -1064,6 +1116,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
1064
1116
|
debug.log('release-loop.gate', 'pod-shard-resume-miss', { shard, branch, reason });
|
|
1065
1117
|
return runShard(paths, shard, depth, branch, retriedNoOutput, true, parentAttempt);
|
|
1066
1118
|
}
|
|
1119
|
+
// GATE-MULTI-HOST: the Job failed because its member is gone (launch failed · status unreadable) — not the shard's
|
|
1120
|
+
// fault, so no split: the same files run again on the members left. Bounded by the roster size.
|
|
1121
|
+
if (jobFailed && placedOn && hostRetries < poolScheduler.members.length && memberLost(placedOn, shard, jobError)
|
|
1122
|
+
&& poolScheduler.members.some((m) => !downMembers.has(m.context))) {
|
|
1123
|
+
debug.log('release-loop.gate', 'pod-shard-host-retry', { shard, branch, files: paths.length, lostMember: placedOn, attempt: hostRetries + 1 }, { level: 'warn' });
|
|
1124
|
+
return runShard(paths, shard, depth, `${branch}-h${hostRetries}`, retriedNoOutput, undefined, attemptId, hostRetries + 1);
|
|
1125
|
+
}
|
|
1067
1126
|
// GATE-TIMEOUT-HEAL ④: a Pod that died before any test ran (no outbox · jobExitCode≠0 — 0.2.19: isolated retries
|
|
1068
1127
|
// ending with exit 5, the source checkout) gets the same one fresh retry as «no output» instead of stalling the sweep.
|
|
1069
1128
|
// 137 (killed · OOM) keeps the isolation path: re-running an OOM file only re-measures the same kill.
|
|
@@ -1436,8 +1495,9 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
1436
1495
|
if (trustedKnown) knownFailures = [...trustedKnown.failures, ...(trustedKnown.errors ?? []), ...loadEnvKnownFailures(repo)];
|
|
1437
1496
|
} catch (error) { debug.log('release-loop.gate', 'known-failures-unavailable', { error: String(error) }); }
|
|
1438
1497
|
}
|
|
1498
|
+
const durationSource = join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut');
|
|
1439
1499
|
const cutRun = await runner.sweep(cutTree, join(root, 'release', opts.version, 'gate-logs', partial ? 'cut-partial' : 'cut'),
|
|
1440
|
-
opts.pod ? { ...opts.pod, durationSource
|
|
1500
|
+
opts.pod ? { ...opts.pod, durationSource, ...(knownFailures.length ? { knownFailures } : {}),
|
|
1441
1501
|
// GATE-LIVE-OBS: the release ledger's shards.json (UX #24782) — never the run tree's universe; an explicit
|
|
1442
1502
|
// --ledger-root (tests) keeps it in that ledger.
|
|
1443
1503
|
shardsFile: { path: opts.ledgerRoot ? gateShardsPath(opts.version, opts.ledgerRoot) : gateShardsPath(opts.version), version: opts.version } } : undefined,
|
|
@@ -1526,7 +1586,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
1526
1586
|
// K9b — the baseline is swept on the same Pod pool as the cut, not on this host (0.2.6: an hour of local baseline
|
|
1527
1587
|
// after a 23-minute Pod cut).
|
|
1528
1588
|
const baseRun = await runner.sweep(baseTree!, join(root, 'release', opts.version, 'gate-logs', 'baseline'),
|
|
1529
|
-
opts.pod ? { ...opts.pod, durationSource
|
|
1589
|
+
opts.pod ? { ...opts.pod, durationSource } : undefined,
|
|
1530
1590
|
present);
|
|
1531
1591
|
baseline = await localStalled(baseRun, baseTree!, baseSha, 'baseline');
|
|
1532
1592
|
} else baseline = { failures: [], errors: [] };
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
// changed — not the whole 24-shard sweep again (0.2.18: two timeout fixes on top of an 84-minute gate meant another full
|
|
3
3
|
// gate). The plan is opt-in (`release resume --from gate --partial`) and fail-closed: any non-test source change, a prior
|
|
4
4
|
// gate record for another commit, or a missing record means «full gate», never a guess.
|
|
5
|
+
// GATE-REUSE-BY-DIFF (0.2.25): a code change no longer forces the full sweep — with the tip's import closure
|
|
6
|
+
// (gate-closure.ts) the plan adds every test that can reach a changed file, and every test whose closure is opaque.
|
|
7
|
+
// `release resume --from gate` now plans this by default when the tip moved (`--no-partial` = full gate).
|
|
5
8
|
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
9
|
import { spawnSync } from 'node:child_process';
|
|
7
10
|
import { dirname, join } from 'node:path';
|
|
11
|
+
import { CODE_FILE, type ClosureVerdict } from './gate-closure';
|
|
8
12
|
|
|
9
13
|
export interface PartialPlan {
|
|
10
14
|
version: string;
|
|
@@ -12,7 +16,7 @@ export interface PartialPlan {
|
|
|
12
16
|
priorCommit: string;
|
|
13
17
|
/** The commit this plan is for — a gate on any other commit ignores it. */
|
|
14
18
|
forCommit: string;
|
|
15
|
-
/** Test files re-run: changed test files ⊕ files of the prior failures. */
|
|
19
|
+
/** Test files re-run: changed test files ⊕ files of the prior failures ⊕ tests reaching a changed code file. */
|
|
16
20
|
files: string[];
|
|
17
21
|
priorFailures: string[];
|
|
18
22
|
priorErrors: string[];
|
|
@@ -37,19 +41,43 @@ export function testFileOfId(id: string): string {
|
|
|
37
41
|
|
|
38
42
|
/** Decide whether a partial re-gate is safe for `changedFiles` (prior → for). Never guesses: any doubt is a full gate. */
|
|
39
43
|
export function planPartialRegate(input: { version: string; priorCommit: string; forCommit: string; changedFiles: readonly string[];
|
|
40
|
-
priorFailures: readonly string[]; priorErrors: readonly string[]
|
|
41
|
-
|
|
44
|
+
priorFailures: readonly string[]; priorErrors: readonly string[];
|
|
45
|
+
/** GATE-REUSE-BY-DIFF: the tests reaching these changed code files (seeds include changed tests). Without it any
|
|
46
|
+
* non-test change is a full gate. */
|
|
47
|
+
closure?: (changedCode: string[]) => ClosureVerdict;
|
|
48
|
+
/** Tracked at the tip — a deleted changed test is not re-run (nothing to run); a deleted prior-failure file is a full
|
|
49
|
+
* gate (its failure can be neither re-measured nor carried). Without it every changed test is planned. */
|
|
50
|
+
existsAtTip?: (file: string) => boolean }):
|
|
51
|
+
{ ok: true; plan: PartialPlan; closure?: { reached: number; opaque: number } } | { ok: false; reason: string } {
|
|
42
52
|
if (!/^[0-9a-f]{40}$/i.test(input.priorCommit) || !/^[0-9a-f]{40}$/i.test(input.forCommit)) return { ok: false, reason: 'commit is not a full sha' };
|
|
43
53
|
if (input.priorCommit === input.forCommit) return { ok: false, reason: 'no new commit since the prior gate' };
|
|
44
54
|
const source = input.changedFiles.filter((file) => !TEST_FILE.test(file) && !isInert(file));
|
|
45
|
-
|
|
55
|
+
const list = (files: string[]) => `${files.slice(0, 5).join(', ')}${files.length > 5 ? ` (+${files.length - 5})` : ''}`;
|
|
56
|
+
// Only code is followed through the closure: a changed fixture, JSON, lockfile, config or script is a full gate.
|
|
57
|
+
const notCode = source.filter((file) => !CODE_FILE.test(file));
|
|
58
|
+
if (source.length && (!input.closure || notCode.length)) return { ok: false, reason: `non-test change → full gate: ${list(notCode.length ? notCode : source)}` };
|
|
59
|
+
let reaching: string[] = [];
|
|
60
|
+
let closure: { reached: number; opaque: number } | undefined;
|
|
61
|
+
if (source.length) {
|
|
62
|
+
let verdict: ClosureVerdict;
|
|
63
|
+
try { verdict = input.closure!(input.changedFiles.filter((file) => CODE_FILE.test(file))); }
|
|
64
|
+
catch (error) { return { ok: false, reason: `import closure unreadable → full gate: ${String(error instanceof Error ? error.message : error).slice(0, 200)}` }; }
|
|
65
|
+
if (!verdict.ok) return { ok: false, reason: verdict.reason };
|
|
66
|
+
const unsafe = verdict.tests.filter((file) => !TEST_FILE.test(file) || file.split('/').includes('..'));
|
|
67
|
+
if (unsafe.length) return { ok: false, reason: `closure selected a test path the plan cannot carry → full gate: ${list(unsafe)}` };
|
|
68
|
+
reaching = verdict.tests;
|
|
69
|
+
closure = { reached: verdict.reached, opaque: verdict.opaque };
|
|
70
|
+
}
|
|
46
71
|
let failureFiles: string[];
|
|
47
72
|
try { failureFiles = [...input.priorFailures, ...input.priorErrors].map(testFileOfId); }
|
|
48
73
|
catch (error) { return { ok: false, reason: String(error instanceof Error ? error.message : error) }; }
|
|
49
|
-
const
|
|
74
|
+
const exists = input.existsAtTip ?? (() => true);
|
|
75
|
+
const gone = failureFiles.filter((file) => !exists(file));
|
|
76
|
+
if (gone.length) return { ok: false, reason: `prior failure file deleted at the tip → full gate: ${list(gone)}` };
|
|
77
|
+
const files = [...new Set([...input.changedFiles.filter((file) => TEST_FILE.test(file) && exists(file)), ...failureFiles, ...reaching])].sort();
|
|
50
78
|
if (!files.length) return { ok: false, reason: 'nothing to re-run' };
|
|
51
79
|
return { ok: true, plan: { version: input.version, priorCommit: input.priorCommit, forCommit: input.forCommit, files,
|
|
52
|
-
priorFailures: [...input.priorFailures], priorErrors: [...input.priorErrors] } };
|
|
80
|
+
priorFailures: [...input.priorFailures], priorErrors: [...input.priorErrors] }, ...(closure ? { closure } : {}) };
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
export function writePartialPlan(root: string, plan: PartialPlan): string {
|
|
@@ -48,9 +48,14 @@ export function lastResult(run: CommandResult): Record<string, unknown> | undefi
|
|
|
48
48
|
} catch { return undefined; }
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export function finishNode(node: string, version: string, result: Record<string, unknown> & { outcome: NodeOutcome; verdict: NodeVerdict; summary: string }): number {
|
|
51
|
+
export function finishNode(node: string, version: string, result: Record<string, unknown> & { outcome: NodeOutcome; verdict: NodeVerdict; summary: string; tail?: string }): number {
|
|
52
52
|
debug.log(`release-loop.${node}`, 'result', { version, outcome: result.outcome });
|
|
53
|
-
|
|
53
|
+
if (result.outcome === 'fail') {
|
|
54
|
+
const summary = result.summary.replace(/[\r\n]+/g, ' ').trim();
|
|
55
|
+
const tail = result.tail?.trim() ? result.tail : summary;
|
|
56
|
+
debug.log('release.node', 'failed', { node, summary, tail });
|
|
57
|
+
emitNodeResult({ ...result, summary, tail });
|
|
58
|
+
} else emitNodeResult(result);
|
|
54
59
|
return result.outcome === 'ok' ? 0 : result.outcome === 'fail' ? 1 : 2;
|
|
55
60
|
}
|
|
56
61
|
|