elanous 0.2.20 → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/MObU8ujkMciAKr6ljAV05/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
- package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
- package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
- package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
- package/apps/pwa/out/_next/static/chunks/{1307-77fad80cf54477c6.js → 1307-c97eba9fb72a9a5e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
- package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
- package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
- package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
- package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
- package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
- package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
- package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
- package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
- package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
- package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
- package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
- package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
- package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
- package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-59fedea50d13c762.js +1 -0
- package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
- package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
- package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
- package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
- package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
- package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
- package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
- package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
- package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
- package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
- package/apps/pwa/out/_next/static/chunks/8857-f1762140ef9a96b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
- package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
- package/apps/pwa/out/_next/static/chunks/9247-7581825d511a64c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
- package/apps/pwa/out/_next/static/chunks/9497-b0e758d1cd4ae8d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
- package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-937c92292a4ab4f0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d781be3e3a9cfd74.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-b0a80997903852d8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-2b1263165630d9c8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-7a852956c74015a3.js → page-ea998f7f5d156bea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-33c328b64ca508f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-e2d6bed48ac20120.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-111cabe4d1fd69ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-544dd4bd4847eeb7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-3ee191319bf97d87.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e4bcb2c503dab04b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-300897dcbbd5ed76.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-a449f1ed472b81c2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-9045094d6d3b5cd0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-c06673596849ef45.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-103fa3191c1b2e08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-9f81233f29dcd409.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-be8b4615c919e36c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-c685b104e0dfa609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-8d693331cfbb6add.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-cef4f30a4c22fae1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66ea1b54fc83fa15.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-7a12f61be4a579d1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-c85b0589591a99c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-725a4da02dd129eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-f6055495e9ed7ea8.js → page-8cc707bd09a497d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-90b55be1f15ce0e8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cb16606e08be65db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-16dc64b74ed6a40a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-21bec4afeb2f0917.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-dc39ee7a36d26385.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-84ae759f76053672.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-3db58ddb61fe2be9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c5315e30f3528dc4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-2fe368efb6de3b6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-c26fa10aac6c21c4.js → page-8d04132dd0ac5e10.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-68793c6e10331d66.js → page-f23484de467c9ea3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-c69762987b751174.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5456692bda39aafb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-be1c4e8697f22360.js → page-d75f7cd65845ce98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-5603f5e568b25ce3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-d2cc0f4e89f42028.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-0fd895fab9b62c1b.js → page-a6eb73b8a4840f1c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c0a3f4d10edb83fa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-c6e4364d9de8f717.js → page-6c0b88068c143f15.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-7f8d7fcdf058a845.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-22163556cd38bc88.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-141436fd26dd56fc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
- package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
- package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
- package/apps/pwa/out/_next/static/css/e81df11490b4413b.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/live-run/index.html +1 -0
- package/apps/pwa/out/live-run/index.txt +35 -0
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/drafts/recipes.yaml +4 -4
- package/graphs/implement-loop.yaml +277 -5
- package/package.json +1 -1
- package/scripts/coord-channel-watch.sh +31 -4
- package/scripts/docs-lint.ts +4 -1
- package/scripts/marketing/generate-ax-brief.ts +18 -1
- package/scripts/measure-nl-routing-corpus.ts +11 -3
- package/scripts/release-loop/gate-node.ts +5 -5
- package/scripts/release-loop/shard-plan.ts +50 -5
- package/scripts/rhythm/daily.ts +2 -1
- package/src/browser-cdp/client.ts +10 -5
- package/src/cli/ops-cli.ts +11 -0
- package/src/cli/ops-overview-cli.ts +50 -0
- package/src/cli/pod-cli.ts +1 -0
- package/src/cli/tasks-cli.ts +106 -0
- package/src/dashboard/index.ts +40 -4
- package/src/esc-abort-gate.ts +3 -1
- package/src/graph-runner/graph-cli.ts +57 -0
- package/src/graph-runner/graph-run-api.ts +13 -0
- package/src/graph-wizard/archetypes.ts +88 -0
- package/src/graph-wizard/generate.ts +545 -0
- package/src/graph-wizard/saved-steps.ts +120 -0
- package/src/graph-wizard/steps.ts +193 -0
- package/src/harness/harness-cli-command.ts +68 -12
- package/src/index.ts +7 -4
- package/src/intake-plane/digest.ts +18 -11
- package/src/maturity/feature-maturity.ts +2 -1
- package/src/nexus/api/draft-metrics.ts +114 -0
- package/src/nexus/api/graph-wizard-api.ts +53 -0
- package/src/nexus/api/graphs-api.ts +50 -12
- package/src/nexus/api/harness-traversal.ts +205 -0
- package/src/nexus/api/http-server.ts +31 -0
- package/src/nexus/outbound/router.ts +28 -8
- package/src/onboarding/first-run.ts +3 -0
- package/src/ops/overview-collect.ts +345 -0
- package/src/ops/overview-seat-era.ts +90 -0
- package/src/ops/overview.ts +223 -0
- package/src/release-loop/gate-shards.ts +7 -2
- package/src/self-dev/ask-launch-flow.ts +4 -2
- package/src/self-dev/ask-launch-io.ts +3 -1
- package/src/self-dev/graph-journey-nodes.ts +157 -0
- package/src/self-dev/graph-journey-route.ts +105 -0
- package/src/self-dev/launch-preflight.ts +43 -3
- package/src/self-dev/release-path-guard.ts +7 -4
- package/src/self-implement/frozen-merges.ts +23 -0
- package/src/self-implement/goal-author.ts +14 -0
- package/src/self-implement/heal-intake.ts +3 -0
- package/src/self-implement/orchestrator.ts +16 -1
- package/src/self-implement/seams.ts +3 -0
- package/src/self-implement/self-implement-runtime.ts +12 -0
- package/src/session-runtime/index.ts +5 -3
- package/src/shell-env-bootstrap.ts +17 -3
- package/src/task-agent/card-evidence.ts +6 -0
- package/src/task-agent/parent-host.ts +315 -0
- package/src/task-agent/task-hand.ts +9 -2
- package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
- package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
- package/src/tool-runtime/browser-runtime.ts +87 -3
- package/src/tool-runtime/index.ts +1 -0
- package/src/user-config.ts +20 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
- package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
- package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-c855c24de7db74fe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-f9ccb1b1adbea14d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
- package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
- /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → MObU8ujkMciAKr6ljAV05}/_ssgManifest.js +0 -0
- /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAPH-WIZARD-SAVE-RECIPES — a wizard graph saved from the editor keeps its runnable steps.
|
|
3
|
+
*
|
|
4
|
+
* The wizard answers with `steps` (node id → {label, step, arg, retries}) and a `recipes` text. The editor
|
|
5
|
+
* saves through `POST /v1/graphs` / `PUT …/yaml`, which store YAML only, so the steps used to be lost and
|
|
6
|
+
* the graph could not really run.
|
|
7
|
+
*
|
|
8
|
+
* ⛔ We do NOT store the client's `recipes` text: `<mine>/` is HTTP-writable, and a recipes file there would
|
|
9
|
+
* make «run my edited graph» into «run my edited command» (TC 10-07 19:31 — graph-run-api.ts header).
|
|
10
|
+
* Instead the save stores the *structured* steps in a sidecar (`<mine>/.steps/<id>.json`), every field
|
|
11
|
+
* validated against the step library (WIZARD_STEPS); at run time the server itself turns them into
|
|
12
|
+
* commands with `recipesYamlFor` — step names from the whitelist, the argument shell-quoted.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { parse as parseYaml } from 'yaml';
|
|
17
|
+
import { WIZARD_MAX_RETRIES, WIZARD_STEP_IDS, recipesYamlFor, type WizardNodeStep } from './steps.js';
|
|
18
|
+
|
|
19
|
+
const NODE_ID = /^[a-z0-9][a-z0-9_-]*$/;
|
|
20
|
+
const GRAPH_ID = /^[a-z0-9-]+$/;
|
|
21
|
+
const MAX_NODES = 200;
|
|
22
|
+
const MAX_LABEL = 80;
|
|
23
|
+
const MAX_ARG = 1000;
|
|
24
|
+
|
|
25
|
+
export function savedStepsFile(mineDir: string, graphId: string): string {
|
|
26
|
+
return join(mineDir, '.steps', `${graphId}.json`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function nodeIdsOf(yaml: string): Set<string> {
|
|
30
|
+
try {
|
|
31
|
+
const raw = parseYaml(yaml) as { nodes?: Array<{ node_id?: unknown }> } | null;
|
|
32
|
+
return new Set((raw?.nodes ?? []).map((node) => node?.node_id).filter((id): id is string => typeof id === 'string'));
|
|
33
|
+
} catch { return new Set(); }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Validate the client's steps against the library and the graph's own node ids. Unknown fields are dropped. */
|
|
37
|
+
export function parseWizardSteps(raw: unknown, yaml: string): { ok: true; steps: Record<string, WizardNodeStep> } | { ok: false; reason: string } {
|
|
38
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return { ok: false, reason: 'steps must be an object of node id → {label, step, arg, retries}' };
|
|
39
|
+
const entries = Object.entries(raw as Record<string, unknown>);
|
|
40
|
+
if (entries.length > MAX_NODES) return { ok: false, reason: `steps: at most ${MAX_NODES} nodes` };
|
|
41
|
+
const ids = nodeIdsOf(yaml);
|
|
42
|
+
const steps: Record<string, WizardNodeStep> = {};
|
|
43
|
+
for (const [nodeId, value] of entries) {
|
|
44
|
+
if (!NODE_ID.test(nodeId)) return { ok: false, reason: `steps: invalid node id '${nodeId.slice(0, 40)}'` };
|
|
45
|
+
if (!ids.has(nodeId)) continue; // a node removed by hand since the wizard turn — nothing to keep
|
|
46
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return { ok: false, reason: `steps.${nodeId} must be an object` };
|
|
47
|
+
const { label, step, arg, retries } = value as Record<string, unknown>;
|
|
48
|
+
const out: WizardNodeStep = {};
|
|
49
|
+
if (label !== undefined) {
|
|
50
|
+
if (typeof label !== 'string' || label.length > MAX_LABEL) return { ok: false, reason: `steps.${nodeId}.label must be a string ≤ ${MAX_LABEL}` };
|
|
51
|
+
if (label.trim()) out.label = label.trim();
|
|
52
|
+
}
|
|
53
|
+
if (step !== undefined) {
|
|
54
|
+
if (typeof step !== 'string' || !WIZARD_STEP_IDS.has(step)) return { ok: false, reason: `steps.${nodeId}.step must be one of ${[...WIZARD_STEP_IDS].join(', ')}` };
|
|
55
|
+
out.step = step;
|
|
56
|
+
}
|
|
57
|
+
if (arg !== undefined) {
|
|
58
|
+
if (typeof arg !== 'string' || arg.length > MAX_ARG || arg.includes('\0')) return { ok: false, reason: `steps.${nodeId}.arg must be a string ≤ ${MAX_ARG}` };
|
|
59
|
+
if (arg.trim()) out.arg = arg;
|
|
60
|
+
}
|
|
61
|
+
if (retries !== undefined) {
|
|
62
|
+
if (typeof retries !== 'number' || !Number.isInteger(retries) || retries < 0 || retries > WIZARD_MAX_RETRIES) return { ok: false, reason: `steps.${nodeId}.retries must be 0..${WIZARD_MAX_RETRIES}` };
|
|
63
|
+
if (retries > 0) out.retries = retries;
|
|
64
|
+
}
|
|
65
|
+
steps[nodeId] = out;
|
|
66
|
+
}
|
|
67
|
+
return { ok: true, steps };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function writeWizardSteps(mineDir: string, graphId: string, steps: Record<string, WizardNodeStep>): void {
|
|
71
|
+
if (!GRAPH_ID.test(graphId)) throw new Error('invalid graph id');
|
|
72
|
+
const file = savedStepsFile(mineDir, graphId);
|
|
73
|
+
mkdirSync(join(mineDir, '.steps'), { recursive: true });
|
|
74
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
75
|
+
writeFileSync(temp, `${JSON.stringify(steps, null, 2)}\n`);
|
|
76
|
+
renameSync(temp, file);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Save the steps and the graph together: the sidecar is written first; if the graph write then throws, the
|
|
80
|
+
* sidecar goes back to what it was (or away), so a failed save changes neither. A sidecar failure stops the
|
|
81
|
+
* save before the graph is touched. */
|
|
82
|
+
export function withWizardSteps<T>(mineDir: string, graphId: string, steps: Record<string, WizardNodeStep> | null, writeGraph: () => T): T {
|
|
83
|
+
if (!steps) return writeGraph();
|
|
84
|
+
const file = savedStepsFile(mineDir, graphId);
|
|
85
|
+
const before = existsSync(file) ? readFileSync(file, 'utf8') : null;
|
|
86
|
+
writeWizardSteps(mineDir, graphId, steps);
|
|
87
|
+
try {
|
|
88
|
+
return writeGraph();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
try {
|
|
91
|
+
if (before === null) rmSync(file, { force: true }); else writeFileSync(file, before);
|
|
92
|
+
} catch { /* best effort — the graph write error is the one to report */ }
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Read the sidecar back — re-validated (the file may have been edited by hand), pruned to the graph's nodes. */
|
|
98
|
+
export function readWizardSteps(mineDir: string, graphId: string, yaml: string): Record<string, WizardNodeStep> | null {
|
|
99
|
+
if (!GRAPH_ID.test(graphId)) return null;
|
|
100
|
+
const file = savedStepsFile(mineDir, graphId);
|
|
101
|
+
if (!existsSync(file)) return null;
|
|
102
|
+
let raw: unknown;
|
|
103
|
+
try { raw = JSON.parse(readFileSync(file, 'utf8')); } catch { return null; }
|
|
104
|
+
const parsed = parseWizardSteps(raw, yaml);
|
|
105
|
+
return parsed.ok ? parsed.steps : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Recipes the server generates for the graph's `cmd:` nodes that carry a library step (approvals are not
|
|
109
|
+
* generated here — the editor run still refuses approval recipes). */
|
|
110
|
+
export function wizardRecipesFor(yaml: string, steps: Record<string, WizardNodeStep>, bin?: string): Record<string, unknown> {
|
|
111
|
+
let raw: { nodes?: Array<{ node_id?: unknown; recipe?: unknown }> } | null;
|
|
112
|
+
try { raw = parseYaml(yaml) as typeof raw; } catch { return {}; }
|
|
113
|
+
const nodes = (raw?.nodes ?? [])
|
|
114
|
+
.map((node) => ({ nodeId: String(node?.node_id ?? ''), recipe: typeof node?.recipe === 'string' ? node.recipe : '' }))
|
|
115
|
+
.filter((node) => node.recipe.startsWith('cmd:') && steps[node.nodeId]?.step);
|
|
116
|
+
if (!nodes.length) return {};
|
|
117
|
+
const text = bin === undefined ? recipesYamlFor(nodes, steps) : recipesYamlFor(nodes, steps, bin);
|
|
118
|
+
const parsed = parseYaml(text) as unknown;
|
|
119
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {};
|
|
120
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAPH-WIZARD 단계 서재 — 마법사가 짓는 그래프의 `cmd:` 노드가 «이름만»이 아니라 실제로 도는 부품.
|
|
3
|
+
*
|
|
4
|
+
* 노드 하나 = 서재의 단계 하나(⊕ 인자). recipes.yaml 은 `elanous graph step <단계> --arg …` 를 부른다.
|
|
5
|
+
* 단계는 기존 CLI 만 쓴다(research · ask --bare · notify · self review · gh) — 새 실행 경로를 만들지 않는다.
|
|
6
|
+
* 계약: 표준출력 마지막 줄 = JSON {outcome, text?} (graph runner 의 lastJsonObject 가 읽는다).
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { resolve } from 'node:path';
|
|
11
|
+
import { debug } from '../debug/log.js';
|
|
12
|
+
|
|
13
|
+
export interface WizardStepSpec {
|
|
14
|
+
id: string;
|
|
15
|
+
/** LLM 에게 보이는 한 줄 — 언제 쓰나 · arg 가 무엇인가. */
|
|
16
|
+
use: string;
|
|
17
|
+
/** 이 단계가 낼 수 있는 결과(간선 map 키). */
|
|
18
|
+
outcomes: string[];
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const WIZARD_STEPS: readonly WizardStepSpec[] = [
|
|
23
|
+
{ id: 'web-search', use: '웹/뉴스를 검색해 결과를 모은다. arg = 검색어(예: "오늘 AI 뉴스")', outcomes: ['ok', 'fail'], timeoutMs: 120_000 },
|
|
24
|
+
{ id: 'summarize', use: '앞 단계 결과를 요약한다. arg = 요약 지시(예: "핵심 5줄, 한국어")', outcomes: ['ok', 'fail'], timeoutMs: 300_000 },
|
|
25
|
+
{ id: 'llm', use: '앞 단계 결과를 LLM 으로 가공한다(표로 정리·분류·초안 작성 등). arg = 지시', outcomes: ['ok', 'fail'], timeoutMs: 300_000 },
|
|
26
|
+
{ id: 'check', use: '앞 단계 결과가 기준을 만족하는지 LLM 이 판정한다. arg = 기준. 결과 ok(통과) · rework(다시)', outcomes: ['ok', 'rework', 'fail'], timeoutMs: 300_000 },
|
|
27
|
+
{ id: 'telegram-send', use: '앞 단계 결과를 텔레그램(대표 채널)으로 보낸다. arg 없음', outcomes: ['ok', 'fail'], timeoutMs: 60_000 },
|
|
28
|
+
{ id: 'notify-me', use: '실패·포기 같은 사건을 나(대표)에게 알린다. arg = 알림 문구', outcomes: ['ok', 'fail'], timeoutMs: 60_000 },
|
|
29
|
+
{ id: 'gh-pr-review', use: 'GitHub PR 을 엘라누스 리뷰어로 리뷰한다. arg = PR 번호(없으면 실행 입력 input.pr). 결과 ok(must-fix 없음) · must-fix', outcomes: ['ok', 'must-fix', 'fail'], timeoutMs: 900_000 },
|
|
30
|
+
{ id: 'gh-pr-merge', use: 'GitHub PR 을 squash 머지한다. arg = PR 번호(없으면 input.pr)', outcomes: ['ok', 'fail'], timeoutMs: 300_000 },
|
|
31
|
+
{ id: 'custom', use: '위 단계로 안 되는 외부 연동(노션·슬랙 업로드 등). 실행하면 «아직 구현되지 않음»으로 실패한다. arg = 무엇을 해야 하는지', outcomes: ['ok', 'fail'], timeoutMs: 60_000 },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/** 재시도 상한 — 넘으면 생성 검증이 문제로 되먹인다(조용히 줄이지 않는다). */
|
|
35
|
+
export const WIZARD_MAX_RETRIES = 10;
|
|
36
|
+
|
|
37
|
+
export const WIZARD_STEP_IDS: ReadonlySet<string> = new Set(WIZARD_STEPS.map((s) => s.id));
|
|
38
|
+
|
|
39
|
+
export interface WizardNodeStep { label?: string; step?: string; arg?: string; /** 단계 안 재시도 횟수(실패 시 다시 · 노드·간선을 늘리지 않는다). */ retries?: number }
|
|
40
|
+
|
|
41
|
+
const ELANOUS_BIN = resolve(import.meta.dir, '../../bin/elanous.mjs');
|
|
42
|
+
|
|
43
|
+
function shellQuote(value: string): string {
|
|
44
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 노드 → recipes.yaml. cmd 노드는 서재 단계를, hitl 노드는 한국어 승인 문구를. */
|
|
48
|
+
/**
|
|
49
|
+
* 단계 명령의 머리 — 기본은 PATH 의 `elanous`(설치본 갱신을 따라간다 · 판 폴더 절대경로를 박지 않는다).
|
|
50
|
+
* 작업 트리에서 실물로 돌릴 땐 ELANOUS_WIZARD_BIN='bun <트리>/bin/elanous.mjs' 로 바꾼다.
|
|
51
|
+
*/
|
|
52
|
+
export function wizardStepBin(): string {
|
|
53
|
+
return process.env.ELANOUS_WIZARD_BIN?.trim() || 'elanous';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function recipesYamlFor(nodes: ReadonlyArray<{ nodeId: string; recipe: string }>, steps: Record<string, WizardNodeStep>, bin = wizardStepBin()): string {
|
|
57
|
+
const out: string[] = [];
|
|
58
|
+
const seen = new Set<string>();
|
|
59
|
+
for (const node of nodes) {
|
|
60
|
+
const match = /^(cmd|approval):([a-z0-9][a-z0-9-]*)$/.exec(node.recipe);
|
|
61
|
+
if (!match || seen.has(match[2]!)) continue;
|
|
62
|
+
seen.add(match[2]!);
|
|
63
|
+
const meta = steps[node.nodeId] ?? {};
|
|
64
|
+
if (match[1] === 'approval') {
|
|
65
|
+
out.push(`${match[2]}:\n approval: ${JSON.stringify(`${meta.label ?? node.nodeId} — 승인할까요?`)}`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const spec = WIZARD_STEPS.find((s) => s.id === meta.step) ?? WIZARD_STEPS.find((s) => s.id === 'custom')!;
|
|
69
|
+
const arg = meta.arg ?? (spec.id === 'custom' ? (meta.label ?? node.nodeId) : '');
|
|
70
|
+
const retries = meta.retries && meta.retries > 0 ? meta.retries : 0;
|
|
71
|
+
const command = `${bin} graph step ${spec.id}${arg ? ` --arg ${shellQuote(arg)}` : ''}${retries ? ` --retries ${retries}` : ''}`;
|
|
72
|
+
out.push(`${match[2]}:\n command: ${JSON.stringify(command)}\n timeout_ms: ${spec.timeoutMs * (retries + 1)}`);
|
|
73
|
+
}
|
|
74
|
+
return out.length ? `${out.join('\n')}\n` : '{}\n';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface GraphContext { graphId?: string; runId?: string; nodeId?: string; input?: unknown; outputs?: Record<string, unknown> }
|
|
78
|
+
|
|
79
|
+
function readContext(): GraphContext {
|
|
80
|
+
const path = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
81
|
+
if (!path || !existsSync(path)) return {};
|
|
82
|
+
try { return JSON.parse(readFileSync(path, 'utf8')) as GraphContext; } catch { return {}; }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 앞 단계 결과를 사람이 읽는 글로 — 각 결과의 text 를 우선, 아니면 JSON. */
|
|
86
|
+
export function previousText(ctx: GraphContext, limit = 12_000): string {
|
|
87
|
+
const parts = Object.entries(ctx.outputs ?? {}).map(([id, value]) => {
|
|
88
|
+
if (value && typeof value === 'object' && typeof (value as { text?: unknown }).text === 'string') return `[${id}]\n${(value as { text: string }).text}`;
|
|
89
|
+
if (typeof value === 'string') return `[${id}]\n${value}`;
|
|
90
|
+
return value == null ? '' : `[${id}]\n${JSON.stringify(value)}`;
|
|
91
|
+
}).filter(Boolean);
|
|
92
|
+
const text = parts.join('\n\n');
|
|
93
|
+
return text.length > limit ? text.slice(-limit) : text;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function runElanous(args: string[], input?: string, timeoutMs = 300_000): { code: number; stdout: string; stderr: string } {
|
|
97
|
+
const proc = spawnSync('bun', [ELANOUS_BIN, ...args], { input, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024 });
|
|
98
|
+
return { code: proc.status ?? 1, stdout: proc.stdout ?? '', stderr: proc.stderr ?? '' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function lastJson(stdout: string): Record<string, unknown> | undefined {
|
|
102
|
+
for (const line of stdout.split('\n').reverse()) {
|
|
103
|
+
const t = line.trim();
|
|
104
|
+
if (!t.startsWith('{')) continue;
|
|
105
|
+
try { return JSON.parse(t) as Record<string, unknown>; } catch { /* keep looking */ }
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function prNumber(arg: string | undefined, ctx: GraphContext): string | undefined {
|
|
111
|
+
const fromInput = ctx.input && typeof ctx.input === 'object' ? (ctx.input as { pr?: unknown }).pr : undefined;
|
|
112
|
+
const raw = (arg?.trim() || (fromInput === undefined ? '' : String(fromInput))).replace(/^#/, '');
|
|
113
|
+
return /^\d+$/.test(raw) ? raw : undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function ask(prompt: string): { ok: boolean; text: string; error?: string } {
|
|
117
|
+
const r = runElanous(['ask', '--bare', '--json', prompt]);
|
|
118
|
+
const reply = lastJson(r.stdout)?.reply;
|
|
119
|
+
return typeof reply === 'string' && r.code === 0 ? { ok: true, text: reply } : { ok: false, text: '', error: r.stderr.slice(-500) || 'ask failed' };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface StepResult { outcome: string; text?: string; error?: string }
|
|
123
|
+
|
|
124
|
+
/** 단계 안 재시도 — 실패면 최대 retries 번 더. 노드·간선을 늘리지 않고 «다시 시도»를 표현한다. */
|
|
125
|
+
export function runWizardStepWithRetries(step: string, arg: string | undefined, retries = 0, run: typeof runWizardStep = runWizardStep): StepResult & { tries: number } {
|
|
126
|
+
let tries = 0;
|
|
127
|
+
let result: StepResult = { outcome: 'fail' };
|
|
128
|
+
for (; tries <= Math.max(0, retries); ) {
|
|
129
|
+
tries += 1;
|
|
130
|
+
result = run(step, arg);
|
|
131
|
+
if (result.outcome !== 'fail') break;
|
|
132
|
+
debug.log('graph.wizard', 'step-retry', { step, tries, max: retries + 1 });
|
|
133
|
+
}
|
|
134
|
+
return { ...result, tries };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** `elanous graph step <id> --arg …` 의 몸. 부르는 쪽은 그래프 러너(cmd 노드)다. */
|
|
138
|
+
export function runWizardStep(step: string, arg: string | undefined, ctx: GraphContext = readContext()): StepResult {
|
|
139
|
+
const prev = previousText(ctx);
|
|
140
|
+
const started = Date.now();
|
|
141
|
+
let result: StepResult;
|
|
142
|
+
switch (step) {
|
|
143
|
+
case 'web-search': {
|
|
144
|
+
const query = arg?.trim() || (ctx.input && typeof ctx.input === 'object' && typeof (ctx.input as { query?: unknown }).query === 'string' ? (ctx.input as { query: string }).query : '');
|
|
145
|
+
if (!query) { result = { outcome: 'fail', error: 'web-search: 검색어(arg)가 없다' }; break; }
|
|
146
|
+
const r = runElanous(['research', '--json', '--limit', '5', query], undefined, 120_000);
|
|
147
|
+
const out = lastJson(r.stdout);
|
|
148
|
+
const total = (out?.metadata as { totalHits?: number } | undefined)?.totalHits ?? 0;
|
|
149
|
+
result = r.code === 0 && typeof out?.output === 'string' && total > 0 ? { outcome: 'ok', text: out.output } : { outcome: 'fail', error: `검색 결과 없음 (${r.stderr.slice(-300)})` };
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'summarize':
|
|
153
|
+
case 'llm': {
|
|
154
|
+
const instruction = arg?.trim() || (step === 'summarize' ? '핵심만 한국어로 간결하게 요약하라.' : '앞 결과를 정리하라.');
|
|
155
|
+
const r = ask(`${instruction}\n\n--- 입력 ---\n${prev || '(입력 없음)'}`);
|
|
156
|
+
result = r.ok ? { outcome: 'ok', text: r.text } : { outcome: 'fail', error: r.error };
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case 'check': {
|
|
160
|
+
const r = ask(`아래 결과가 기준을 만족하는지 판정하라. 기준: ${arg?.trim() || '내용이 충실하고 사실에 맞다'}\n첫 줄에 PASS 또는 REWORK 만, 둘째 줄에 이유 한 줄.\n\n--- 결과 ---\n${prev || '(없음)'}`);
|
|
161
|
+
result = !r.ok ? { outcome: 'fail', error: r.error } : { outcome: /^\s*PASS/i.test(r.text) ? 'ok' : 'rework', text: r.text };
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case 'telegram-send':
|
|
165
|
+
case 'notify-me': {
|
|
166
|
+
const body = step === 'notify-me' ? `${arg?.trim() || '그래프 알림'}${ctx.graphId ? ` (${ctx.graphId})` : ''}` : (prev || arg || '');
|
|
167
|
+
if (!body.trim()) { result = { outcome: 'fail', error: '보낼 내용이 없다' }; break; }
|
|
168
|
+
const r = runElanous(['notify', '--stdin', '--json', ...(step === 'notify-me' ? ['--kind', 'ops-alert'] : [])], body, 60_000);
|
|
169
|
+
result = lastJson(r.stdout)?.ok === true ? { outcome: 'ok', text: '보냄' } : { outcome: 'fail', error: '발송 실패 — elanous logs --category outbound.send' };
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case 'gh-pr-review': {
|
|
173
|
+
const pr = prNumber(arg, ctx);
|
|
174
|
+
if (!pr) { result = { outcome: 'fail', error: 'PR 번호가 없다(arg 또는 input.pr)' }; break; }
|
|
175
|
+
const r = runElanous(['self', 'review', pr, '--json'], undefined, 900_000);
|
|
176
|
+
const out = lastJson(r.stdout) as { mustFix?: unknown[]; verdict?: string } | undefined;
|
|
177
|
+
result = !out ? { outcome: 'fail', error: r.stderr.slice(-300) || 'review failed' }
|
|
178
|
+
: { outcome: (out.mustFix?.length ?? 0) > 0 ? 'must-fix' : 'ok', text: JSON.stringify({ verdict: out.verdict, mustFix: out.mustFix }) };
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case 'gh-pr-merge': {
|
|
182
|
+
const pr = prNumber(arg, ctx);
|
|
183
|
+
if (!pr) { result = { outcome: 'fail', error: 'PR 번호가 없다(arg 또는 input.pr)' }; break; }
|
|
184
|
+
const r = runElanous(['gh', 'pr', 'merge', pr, '--squash'], undefined, 300_000);
|
|
185
|
+
result = r.code === 0 ? { outcome: 'ok', text: `PR #${pr} 머지` } : { outcome: 'fail', error: r.stderr.slice(-300) };
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
default:
|
|
189
|
+
result = { outcome: 'fail', error: `아직 구현되지 않은 단계: ${arg ?? step} — recipes.yaml 의 이 줄을 실제 명령으로 바꾸라` };
|
|
190
|
+
}
|
|
191
|
+
debug.log('graph.wizard', 'step', { step, graphId: ctx.graphId, nodeId: ctx.nodeId, outcome: result.outcome, ms: Date.now() - started });
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
@@ -23,7 +23,7 @@ import { resolveChildLlmEffort, resolveImplementationChildModel } from '../self-
|
|
|
23
23
|
import { resolveHarnessTarget } from '../self-implement/harness-target-options.js';
|
|
24
24
|
import { queryRunningRuns } from '../self-implement/running-runs.js';
|
|
25
25
|
import { DevPipelineError } from '../self-dev/dev-pipeline.js';
|
|
26
|
-
import { DRAFT_SWEEP_CONCURRENCY, mapBounded, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
|
|
26
|
+
import { collectDraftMetrics, DRAFT_SWEEP_CONCURRENCY, mapBounded, runDraftSweep, sweepFailureReason, type DraftSweepAdapters, type DraftSweepResult, type SweepDraft, type SweepMergedPr, type SweepReviewGate } from '../self-dev/draft-sweep.js';
|
|
27
27
|
import { debug } from '../debug/log.js';
|
|
28
28
|
import { installHarnessSalvageCommand, installHarnessSalvageRetentionCommand } from './harness-salvage-cli.js';
|
|
29
29
|
import { decideNestedElanousLaunch, readNestedElanousDepth } from './nested-elanous-policy.js';
|
|
@@ -47,6 +47,8 @@ import { runHarnessPlanRfc } from './harness-plan-rfc.js';
|
|
|
47
47
|
import { classifyGarbage, isGarbageProcessTarget, type GarbageProcess } from './process-garbage.js';
|
|
48
48
|
import type { MissionSolveOutcome } from './mission-solve-loop.js';
|
|
49
49
|
import { formatAxis, formatAxisObservations, inspectAskMarkers, inspectUnpressedDecisionSignals } from '../../scripts/ask-marker-check.js';
|
|
50
|
+
import { enterJourneyNode, ensureJourneyKey, exitJourneyNode, JOURNEY_KEY_ENV, passJourneyNode, withJourneyNode } from '../self-dev/graph-journey-nodes.js';
|
|
51
|
+
import { intakeTakesQueue, readJourneyEdge, recordJourneyEdge } from '../self-dev/graph-journey-route.js';
|
|
50
52
|
|
|
51
53
|
let harnessPlanRfcForTesting: typeof runHarnessPlanRfc | undefined;
|
|
52
54
|
let harnessAskMarkerInspectorForTesting: ((ask: string) => readonly string[]) | undefined;
|
|
@@ -777,13 +779,17 @@ async function dispatchHarnessAskSay(
|
|
|
777
779
|
immediateLaunch?: HarnessQueueDeps['launch'],
|
|
778
780
|
immediateExit?: () => Promise<number>,
|
|
779
781
|
): Promise<void> {
|
|
780
|
-
|
|
782
|
+
// HARNESS-FULL-GRAPH — 여정 조인 키는 이 발사 동안만 환경에 싣는다(자식은 그동안 물려받는다).
|
|
783
|
+
const journeyPreset = Boolean(process.env[JOURNEY_KEY_ENV]?.trim());
|
|
784
|
+
try { await runInjectedHarnessHandler(async () => {
|
|
781
785
|
assertHarnessChildLlmModel(opts);
|
|
782
786
|
if (isHarnessDryRun(opts)) {
|
|
783
787
|
resolveLaunchSubstrate(opts);
|
|
784
788
|
printHarnessLaunchDryRun(dryRunPreview);
|
|
785
789
|
return;
|
|
786
790
|
}
|
|
791
|
+
ensureJourneyKey();
|
|
792
|
+
const journey = { provenance: dryRunPreview.entrance, ...(opts.goalType ? { goalType: opts.goalType as GoalType } : {}) };
|
|
787
793
|
// Resolve the launch directory once; descendants and the checkpoint retain the same identity.
|
|
788
794
|
const queued = process.env.ELANOUS_HARNESS_QUEUE_LAUNCH && process.env.ELANOUS_HARNESS_SEAT;
|
|
789
795
|
const queueSeat = queued === 'OP' || queued === 'TC' || queued === 'MK' || queued === 'UX' ? queued : undefined;
|
|
@@ -801,10 +807,21 @@ async function dispatchHarnessAskSay(
|
|
|
801
807
|
const podSeat = resolved.substrate === 'pod' && ['OP', 'TC', 'MK', 'UX'].includes(inheritedSeat ?? '')
|
|
802
808
|
? inheritedSeat as QueueSeat : undefined;
|
|
803
809
|
const stampedSeat = resolved.substrate === 'pod' ? opts.seat ?? queueSeat ?? podSeat ?? assigned : assigned;
|
|
810
|
+
// HARNESS-FULL-GRAPH 2판 — 대기열을 탈지는 «그래프»가 정한다. 코드는 상태 값(queued|direct)만 내고,
|
|
811
|
+
// 다음 노드는 YAML(⊕ 조건이 맞는 launch 오버레이)의 intake 간선이 고른다. 기본 YAML 은 오늘과 같은 길이다.
|
|
812
|
+
// ⛔ 대기열은 자리가 있어야 하고 대기열 자식·--no-queue 는 다시 줄 서지 않는다 — 그 셋은 그래프가 못 넘는다.
|
|
813
|
+
const intakeLabel = shouldQueue && stampedSeat ? 'queued' : 'direct';
|
|
814
|
+
// intake 는 판정만 하는 관측 노드 — 진입·출구를 한 자리에서.
|
|
815
|
+
passJourneyNode('intake', { ...journey, outcome: intakeLabel });
|
|
816
|
+
const intakeRoute = readJourneyEdge('intake', intakeLabel, { seat: stampedSeat ?? 'none', substrate: resolved.substrate });
|
|
817
|
+
const queueEligible = opts.queue !== false && !process.env.ELANOUS_HARNESS_QUEUE_LAUNCH && Boolean(stampedSeat);
|
|
818
|
+
const takeQueue = intakeTakesQueue(intakeLabel, intakeRoute, queueEligible);
|
|
819
|
+
// 실제로 탄 간선을 전제 검사 «뒤»에 남긴다(그래프 답과 다르면 honored=false · 이유).
|
|
820
|
+
recordJourneyEdge(intakeRoute, takeQueue ? 'queue' : 'launch-gate', intakeRoute.to === 'queue' && !takeQueue ? (opts.queue === false ? '--no-queue' : process.env.ELANOUS_HARNESS_QUEUE_LAUNCH ? 'queue-child' : 'no-seat') : undefined);
|
|
804
821
|
if (directSay && opts.queue === false) {
|
|
805
822
|
try { debug.log('harness.queue', 'bypass', { seat: stampedSeat ?? null, reason: '--no-queue' }); }
|
|
806
823
|
catch { /* Observation must not prevent an emergency launch. */ }
|
|
807
|
-
} else if (
|
|
824
|
+
} else if (takeQueue && stampedSeat) {
|
|
808
825
|
resolveNestedElanousOption(opts);
|
|
809
826
|
const flags: string[] = [];
|
|
810
827
|
const values: Array<[string, string | undefined]> = [
|
|
@@ -833,6 +850,7 @@ async function dispatchHarnessAskSay(
|
|
|
833
850
|
[opts.forceGate, '--force-gate'],
|
|
834
851
|
] as const) if (enabled) flags.push(flag);
|
|
835
852
|
let row: QueueItem;
|
|
853
|
+
enterJourneyNode('queue', journey);
|
|
836
854
|
try {
|
|
837
855
|
row = await addHarnessQueue({ seat: stampedSeat, launchCwd: process.cwd(), refuseDuplicate: true,
|
|
838
856
|
...(dryRunPreview.goalPath ? { ask: dryRunPreview.goalPath } : { say: dryRunPreview.input }),
|
|
@@ -842,12 +860,17 @@ async function dispatchHarnessAskSay(
|
|
|
842
860
|
}, queueDeps);
|
|
843
861
|
await queueDeps?.afterEnqueue?.(row);
|
|
844
862
|
} catch (error) {
|
|
845
|
-
if (!(error instanceof HarnessQueueDuplicateError))
|
|
863
|
+
if (!(error instanceof HarnessQueueDuplicateError)) {
|
|
864
|
+
exitJourneyNode('queue', { ...journey, outcome: 'error', data: { error: error instanceof Error ? error.message : String(error) } });
|
|
865
|
+
throw error;
|
|
866
|
+
}
|
|
846
867
|
console.error(`발사 거절 — ${error.message.replace(/[\r\n]+/g, ' ')}`);
|
|
868
|
+
exitJourneyNode('queue', { ...journey, outcome: 'refused' });
|
|
847
869
|
process.exitCode = 1;
|
|
848
870
|
return;
|
|
849
871
|
}
|
|
850
|
-
const result = await tickHarnessQueue({ ...queueDeps, ...(immediateLaunch ? { launch: immediateLaunch } : {}) }, row.id);
|
|
872
|
+
const result = await withJourneyNode('queue', journey, () => tickHarnessQueue({ ...queueDeps, ...(immediateLaunch ? { launch: immediateLaunch } : {}) }, row.id));
|
|
873
|
+
exitJourneyNode('queue', { ...journey, outcome: result.outcome === 'launched' && result.item?.id === row.id ? 'launched' : 'waiting', data: { queueItemId: row.id, queueTick: result.outcome } });
|
|
851
874
|
if (result.outcome === 'launched' && result.item?.id === row.id) {
|
|
852
875
|
if (immediateExit) process.exitCode = await immediateExit();
|
|
853
876
|
} else if (result.outcome === 'waiting') {
|
|
@@ -880,6 +903,8 @@ async function dispatchHarnessAskSay(
|
|
|
880
903
|
const previous = process.env.ELANOUS_HARNESS_SEAT;
|
|
881
904
|
if (stampedSeat) process.env.ELANOUS_HARNESS_SEAT = stampedSeat;
|
|
882
905
|
else delete process.env.ELANOUS_HARNESS_SEAT;
|
|
906
|
+
// 배치 노드 — 실행 칸(local | pod)이 그 출구 라벨이고, YAML 의 decompose 간선(on: substrate)이 그 값으로 갈린다.
|
|
907
|
+
passJourneyNode('dispatch', { ...journey, outcome: resolved.substrate });
|
|
883
908
|
try { await dispatch(resolved, stampedOpts); }
|
|
884
909
|
finally {
|
|
885
910
|
if (previous === undefined) delete process.env.ELANOUS_HARNESS_SEAT;
|
|
@@ -900,11 +925,16 @@ async function dispatchHarnessAskSay(
|
|
|
900
925
|
// CLI wiring tests time out on them. Gate tests inject deps or opt in with ELANOUS_LAUNCH_GATE_LIVE=1.
|
|
901
926
|
if (process.env.NODE_ENV === 'test' && Object.keys(gate).length === 0 && process.env.ELANOUS_LAUNCH_GATE_LIVE !== '1') {
|
|
902
927
|
try { debug.log('execution-loop.launch-gate', 'skipped-test-env', { goalId }); } catch { /* observation is fail-soft */ }
|
|
928
|
+
passJourneyNode('launch-gate', { ...journey, outcome: 'pass', data: { skipped: 'test-env' } });
|
|
903
929
|
await launch();
|
|
904
930
|
return;
|
|
905
931
|
}
|
|
906
|
-
|
|
907
|
-
const decision =
|
|
932
|
+
enterJourneyNode('launch-gate', journey);
|
|
933
|
+
const decision = await withJourneyNode('launch-gate', journey, async () => {
|
|
934
|
+
const budget = await (gate.readBudget ?? (() => readHarnessLaunchBudget(opts)))();
|
|
935
|
+
return preLaunchGate({ goalId, budget, forceLaunch: opts.forceGate === true }, gate);
|
|
936
|
+
});
|
|
937
|
+
exitJourneyNode('launch-gate', { ...journey, outcome: decision.action === 'proceed' ? 'pass' : 'refuse', data: { action: decision.action } });
|
|
908
938
|
if (decision.action !== 'proceed') {
|
|
909
939
|
console.error(`❌ launch gate: ${decision.action} — ${decision.reason.split(/\r?\n/, 1)[0]}`);
|
|
910
940
|
process.exitCode = 3;
|
|
@@ -916,7 +946,7 @@ async function dispatchHarnessAskSay(
|
|
|
916
946
|
];
|
|
917
947
|
if (warnings.length > 0) console.error(`⚠️ launch gate: ${warnings.join(' · ').split(/\r?\n/, 1)[0]}`);
|
|
918
948
|
await launch();
|
|
919
|
-
});
|
|
949
|
+
}); } finally { if (!journeyPreset) delete process.env[JOURNEY_KEY_ENV]; }
|
|
920
950
|
}
|
|
921
951
|
|
|
922
952
|
function normalizeHarnessPlanOptions(opts: HarnessPlanOptions): HarnessPlanOptions {
|
|
@@ -2420,15 +2450,16 @@ export function githubDraftSweepAdapters(execute: GhExecute = executeGh, git: Dr
|
|
|
2420
2450
|
},
|
|
2421
2451
|
getClaimOwner: async (repository, number) => claimCommentOwner(repository, number, execute),
|
|
2422
2452
|
getActiveClaimOwner: async (repository, number) => {
|
|
2453
|
+
// DRAFT-METRIC: the batched GraphQL comments (oldest first, proven complete) answer without a per-draft REST call.
|
|
2454
|
+
const batched = (await detailFor(repository, number))?.comments;
|
|
2455
|
+
if (batched) return activeClaimOwnerFromBodies(batched);
|
|
2423
2456
|
const pages = ghJson<Array<Array<{ body: string; created_at: string }>>>(['api', '--paginate', '--slurp',
|
|
2424
2457
|
`repos/${repository}/issues/${number}/comments?per_page=100`], execute);
|
|
2425
2458
|
if (!Array.isArray(pages) || pages.some((page) => !Array.isArray(page)
|
|
2426
2459
|
|| page.some((comment) => typeof comment.body !== 'string' || !Number.isFinite(Date.parse(comment.created_at)))))
|
|
2427
2460
|
throw new Error('Incomplete claim comments');
|
|
2428
|
-
|
|
2429
|
-
.map((comment) => comment.body)
|
|
2430
|
-
.find((body) => draftClaimOwner(body) !== undefined || body.startsWith('🔧 처리 끝 —'));
|
|
2431
|
-
return last ? draftClaimOwner(last) : undefined;
|
|
2461
|
+
return activeClaimOwnerFromBodies(pages.flat().sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))
|
|
2462
|
+
.map((comment) => comment.body));
|
|
2432
2463
|
},
|
|
2433
2464
|
getReviewGate: async (draft, repository) => reviewGateForDraft(draft, repository),
|
|
2434
2465
|
};
|
|
@@ -2464,6 +2495,12 @@ async function draftSweepRunTtl(repository: string, deps: HarnessDraftSweepDeps,
|
|
|
2464
2495
|
}
|
|
2465
2496
|
}
|
|
2466
2497
|
|
|
2498
|
+
/** Latest claim or release among comments in chronological order (oldest first); a release means no owner. */
|
|
2499
|
+
export function activeClaimOwnerFromBodies(bodies: readonly string[]): string | undefined {
|
|
2500
|
+
const last = [...bodies].reverse().find((body) => draftClaimOwner(body) !== undefined || body.startsWith('🔧 처리 끝 —'));
|
|
2501
|
+
return last ? draftClaimOwner(last) : undefined;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2467
2504
|
function draftClaimOwner(body: string): string | undefined {
|
|
2468
2505
|
return /^🔧 처리 중 — owner ([^\n·]+?) · /m.exec(body)?.[1]?.trim();
|
|
2469
2506
|
}
|
|
@@ -2520,6 +2557,25 @@ function installHarnessDraftSweepCommand(harnessCmd: Command, deps: HarnessDraft
|
|
|
2520
2557
|
process.exitCode = 1;
|
|
2521
2558
|
}
|
|
2522
2559
|
});
|
|
2560
|
+
drafts.command('metrics').description('draft 재고·최장 나이·needs-owner·48h 전환율 (읽기 전용 · PR 근거는 GraphQL 배치)')
|
|
2561
|
+
.option('--json', 'JSON 으로 출력')
|
|
2562
|
+
.option('--repo <owner/name>', '조회할 GitHub 저장소')
|
|
2563
|
+
.action(async (opts: { json?: boolean; repo?: string }) => {
|
|
2564
|
+
try {
|
|
2565
|
+
const execute = deps.execute ?? executeGh;
|
|
2566
|
+
const repository = resolveRepositoryName({ repo: opts.repo ?? deps.repository?.(), executeGh: (args) => execute(args) });
|
|
2567
|
+
if (!DRAFT_SWEEP_REPOSITORY.test(repository) || repository.includes('..')) throw new HarnessCliInputError(`invalid --repo (expected owner/name): ${repository}`);
|
|
2568
|
+
const started = Date.now();
|
|
2569
|
+
const metrics = await collectDraftMetrics(repository, deps.adapters ?? githubDraftSweepAdapters(deps.execute), deps.now?.() ?? new Date());
|
|
2570
|
+
try { debug.log('self-dev.draft-metrics', 'collected', { repository, ms: Date.now() - started, ...metrics }); } catch { /* fail-soft */ }
|
|
2571
|
+
const write = deps.write ?? console.log;
|
|
2572
|
+
if (opts.json) write(JSON.stringify({ repository, ...metrics }));
|
|
2573
|
+
else write(`draft 재고 ${metrics.inventory} · 최장 나이 ${metrics.oldestAgeHours === null ? '해당 없음' : `${metrics.oldestAgeHours.toFixed(1)}h`} · needs-owner ${metrics.needsOwner} · 48h 전환율 ${metrics.conversion48h === null ? '표본 없음' : `${(metrics.conversion48h * 100).toFixed(1)}% (${metrics.converted48h}/${metrics.cohort48h})`}`);
|
|
2574
|
+
} catch (error) {
|
|
2575
|
+
console.error(humanErrorLine(error));
|
|
2576
|
+
process.exitCode = 1;
|
|
2577
|
+
}
|
|
2578
|
+
});
|
|
2523
2579
|
return drafts.command('sweep').description('draft PR 을 조사한다 (기본: 변경 없음)')
|
|
2524
2580
|
.option('--apply', '판정한 라벨 변경과 종료를 적용')
|
|
2525
2581
|
.option('--json', '판정 결과를 JSON 으로 출력')
|
package/src/index.ts
CHANGED
|
@@ -4465,8 +4465,10 @@ selfCmd
|
|
|
4465
4465
|
};
|
|
4466
4466
|
try {
|
|
4467
4467
|
const { dispatchHeavyCheck } = await import('./self-implement/gate-remote.js');
|
|
4468
|
-
const {
|
|
4469
|
-
const
|
|
4468
|
+
const { runGitCommand } = await import('./git-fs/runner.js');
|
|
4469
|
+
const topLevel = runGitCommand(process.cwd(), ['rev-parse', '--show-toplevel'], { encoding: 'utf8' });
|
|
4470
|
+
if (topLevel.status !== 0) throw new Error(`git rev-parse --show-toplevel failed: ${topLevel.stderr.trim() || `rc=${topLevel.status}`}`);
|
|
4471
|
+
const repo = topLevel.stdout.trim();
|
|
4470
4472
|
// Only a committed --base range is reproducible on another host: the default mode measures uncommitted files, --pr
|
|
4471
4473
|
// reads files through gh on this host, and the remote checkout runs from the repository root — those stay local.
|
|
4472
4474
|
const { realpathSync } = await import('node:fs');
|
|
@@ -4891,7 +4893,7 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4891
4893
|
// ☸️ 풀이 «원격 노드뿐»이면 이 기계의 이미지는 아무도 안 쓴다 — 로컬 판정·굽기를 건너뛰고 기준 판 = HEAD 로 노드 동기화만 한다.
|
|
4892
4894
|
// 🩸 2026-09-26 실측: 원격 전용 풀인데 로컬 이미지가 없다고 로컬에서 굽다가 로컬 클러스터(elanous-h1)가 없어 런이 시작도 전에 죽었다.
|
|
4893
4895
|
const remoteOnlyPool = poolMembers.length > 0 && poolMembers.every((m) => Boolean(m.sshHost));
|
|
4894
|
-
const headCommit = remoteOnlyPool ? ((await import('
|
|
4896
|
+
const headCommit = remoteOnlyPool ? ((await import('./git-fs/runner.js')).runGitCommand(process.cwd(), ['rev-parse', 'HEAD'], { encoding: 'utf8' }).stdout.trim() || null) : null;
|
|
4895
4897
|
let image = remoteOnlyPool
|
|
4896
4898
|
? { imageCommit: headCommit, headCommit, fresh: true, reason: 'remote-only pool — 노드 쪽에서 맞춘다' }
|
|
4897
4899
|
: podImageFreshness();
|
|
@@ -4901,7 +4903,8 @@ const selfOrchestrateCmd = selfCmd
|
|
|
4901
4903
|
} else {
|
|
4902
4904
|
if (!opts.json) ui.info(`[pod] 이미지 다시 굽기 — ${image.reason} (~1분)`);
|
|
4903
4905
|
const { spawnSync } = await import('node:child_process');
|
|
4904
|
-
const
|
|
4906
|
+
const { runGitCommand } = await import('./git-fs/runner.js');
|
|
4907
|
+
const top = runGitCommand(process.cwd(), ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).stdout.trim() || process.cwd();
|
|
4905
4908
|
// bun 1.4 applies the 1 MiB default maxBuffer: a docker build log past it kills the child (ENOBUFS · status null).
|
|
4906
4909
|
const b = spawnSync('bash', [`${top}/docker/harness/build.sh`], { encoding: 'utf8', timeout: 900_000, maxBuffer: 64 * 1024 * 1024 });
|
|
4907
4910
|
if (b.status !== 0) { ui.error(`--substrate pod: 이미지 굽기 실패 rc=${b.status}${b.error ? ` (${(b.error as NodeJS.ErrnoException).code ?? b.error.message})` : ''}: ${(b.stdout + b.stderr).slice(-400)}`); process.exit(2); }
|
|
@@ -125,6 +125,19 @@ export function buildIntakeDigest(root: string, day: string, readFile: (p: strin
|
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
function digestNoteRef(e: DigestEntry): string {
|
|
129
|
+
return e.noteName ? `[[${e.noteName}]]` : e.note?.trim() || e.url?.trim() || e.id;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** 참고 줄에 이름을 다는 상한 — 많은 날에도 화면 한 장을 지킨다(M 은 그대로 센다). */
|
|
133
|
+
const DIGEST_REFERENCE_NAMED = 10;
|
|
134
|
+
|
|
135
|
+
function digestReferenceLine(entries: DigestEntry[]): string {
|
|
136
|
+
const named = entries.slice(0, DIGEST_REFERENCE_NAMED).map(digestNoteRef).join(', ');
|
|
137
|
+
const rest = entries.length - DIGEST_REFERENCE_NAMED;
|
|
138
|
+
return `참고 ${entries.length}편 — 노트: ${named}${rest > 0 ? ` 외 ${rest}편 · 원장 \`elanous intake items\`` : ''}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
128
141
|
/** 노트 절 — 축별 묶음 · 노트 링크 · 한 줄 요약 · 골 후보. */
|
|
129
142
|
export function renderDigestMarkdown(d: IntakeDigest): string {
|
|
130
143
|
const L: string[] = [`## 📰 오늘의 흡수 요약 (${d.absorbed.length})`, ''];
|
|
@@ -165,14 +178,9 @@ export function renderDigestMarkdown(d: IntakeDigest): string {
|
|
|
165
178
|
|
|
166
179
|
/** 텔레그램 — 흡수 렌즈가 대조한 항목만 SCQA 짧은 판으로 낸다. */
|
|
167
180
|
export function renderDigestTelegram(d: IntakeDigest, _opts: { vaultRoot?: string; notePath?: string } = {}): string {
|
|
168
|
-
if (d.seat && !d.absorbed.length) return '오늘 새 소식 없음';
|
|
169
|
-
if (d.seat) return [
|
|
170
|
-
`흡수 ${d.absorbed.length}`,
|
|
171
|
-
...d.absorbed.map((item) => `- ${item.oneLiner ?? item.noteName ?? item.id}${item.url ? ` · ${item.url}` : ''}`),
|
|
172
|
-
].join('\n');
|
|
173
181
|
// «Touching» is decided by the lens verdict alone; a missing note summary only changes how S reads (ACP must-fix).
|
|
174
182
|
const touching = d.absorbed.filter((e) => e.impact);
|
|
175
|
-
const L = [`흡수 ${d.absorbed.length} → 우리에게 닿는 것 ${touching.length}`];
|
|
183
|
+
const L = [`흡수 ${d.absorbed.length}편 → 우리에게 닿는 것 ${touching.length}`];
|
|
176
184
|
// 관심 뉴스는 최대 셋 — 기사마다 S(요약 첫 줄) · A(엘라누스 함의 첫 줄) · 링크를 한 덩어리로(NEWS-INTAKE).
|
|
177
185
|
for (const article of (d.news ?? []).slice(0, 3)) {
|
|
178
186
|
L.push(`📰 ${article.title}`);
|
|
@@ -181,9 +189,7 @@ export function renderDigestTelegram(d: IntakeDigest, _opts: { vaultRoot?: strin
|
|
|
181
189
|
L.push(article.url);
|
|
182
190
|
}
|
|
183
191
|
if (d.shadowSuggestions?.length) L.push(`뉴스 칸 제안 ${d.shadowSuggestions.length}건 (그림자·판 미등록): ${d.shadowSuggestions.slice(0, 3).map((s) => `[${s.verdict}] ${s.fact}${s.url ? ` ${s.url}` : ''}`).join(' · ')}`);
|
|
184
|
-
|
|
185
|
-
// At most three items are shown, each as its own S·C·A·link block so context and source stay paired;
|
|
186
|
-
// a later item sharing (verdict, target) says «위와 같은 행동» instead of repeating the A sentence (ACP must-fix).
|
|
192
|
+
// At most three items are shown, each as its own S·C·A·note block so context and source stay paired.
|
|
187
193
|
// S must be the note's own summary: items without one still count as touching but are not shown (ACP must-fix).
|
|
188
194
|
const shownItems = touching.filter((e) => e.oneLiner?.trim()).slice(0, 3);
|
|
189
195
|
const actionSeen = new Set<string>();
|
|
@@ -205,10 +211,11 @@ export function renderDigestTelegram(d: IntakeDigest, _opts: { vaultRoot?: strin
|
|
|
205
211
|
`S 무엇: ${e.oneLiner?.trim() || '노트 한 줄 요약 없음'}`,
|
|
206
212
|
`C 우리에게 왜: ${impact.why}`,
|
|
207
213
|
`A 그래서 무엇을 하나: ${action}`,
|
|
208
|
-
`🔗
|
|
214
|
+
`🔗 노트: ${digestNoteRef(e)}`,
|
|
209
215
|
);
|
|
210
216
|
}
|
|
211
217
|
if (touching.length > shownItems.length) L.push('', `닿는 것 ${touching.length - shownItems.length}건 더(요약 없는 것 포함) · 원장 \`elanous intake items\``);
|
|
212
|
-
|
|
218
|
+
const reference = d.absorbed.filter((e) => !e.impact);
|
|
219
|
+
if (reference.length) L.push('', digestReferenceLine(reference));
|
|
213
220
|
return L.join('\n');
|
|
214
221
|
}
|
|
@@ -16,10 +16,11 @@ export const FEATURE_MATURITY = {
|
|
|
16
16
|
'/today': { pwa: 'stable' },
|
|
17
17
|
'/term': { pwa: 'stable' },
|
|
18
18
|
'/live': { pwa: 'stable' },
|
|
19
|
+
'/live-run': { pwa: 'beta' }, // HARNESS-RUN-LIVE-GRAPH (0.2.21) — 하니스 런 하나가 도는 장면
|
|
19
20
|
'/market': { pwa: 'stable' },
|
|
20
21
|
'/sessions': { pwa: 'stable' },
|
|
21
22
|
'/settings': { pwa: 'beta' },
|
|
22
|
-
'/editor': { pwa: '
|
|
23
|
+
'/editor': { pwa: 'stable' }, // EDITOR-NAV-PUBLIC (0.2.21) — 데모·0순위 피처: 역할을 안 고른 기기에도 «만들기 › 편집기»
|
|
23
24
|
'/intake': { pwa: 'beta' },
|
|
24
25
|
'/trace': { pwa: 'beta' },
|
|
25
26
|
'/inside': { pwa: 'beta' },
|