elanous 0.2.20 → 0.2.22
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/aBi6EyMtLsrl7J8eH4kF7/_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-08b716476f1d50af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
- package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
- package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
- package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
- package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/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/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
- 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/3857-5985236d9307f676.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/5633-d6852385000738ef.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-5b9e8a45a5b0c823.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-aa1cc4ac90af4d40.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-55bcfbb0e8057f59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -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-c855c24de7db74fe.js → page-88459e0903da2803.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d85c43f46f58716b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-4e645600579ae952.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-87edbfd19117823b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-cbaf66b1e1804adf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-80ed511e02c81412.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-9f5610caaa06178c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-ec8f3575e339de74.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-2a19c5cd3b562580.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-b6a09cde2e07521a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-35bfec2e631d3eae.js → page-abd86cd832aaa011.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-f9ccb1b1adbea14d.js → page-8c3f168fc3f91939.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-786fb4504d0f2fa9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-7ec8e289b4c14df6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-8c8ac17d52be2a79.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-d359ab048d63a9d9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-f151cc10ad026940.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-bf495a1e284f4e00.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-cf0a4b4abead71ea.js → page-9abcfac07e02a542.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-69cb93d1a2e57e0e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-72ec543928d67c3e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.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/f2bd5257e6af88d8.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/catalog/catalog.yaml +1 -1
- package/graphs/drafts/recipes.yaml +6 -6
- package/graphs/implement-loop.yaml +277 -5
- package/graphs/test-diet/recipes.yaml +3 -0
- package/graphs/test-diet/test-diet.yaml +3 -1
- package/package.json +1 -1
- package/scripts/coord-channel-watch.sh +31 -4
- package/scripts/docs-lint.ts +4 -1
- package/scripts/f-session-monitor.ts +69 -0
- package/scripts/hq/standby-snapshot.ts +7 -3
- package/scripts/knowledge-compress.ts +11 -0
- package/scripts/marketing/generate-ax-brief.ts +18 -1
- package/scripts/measure-nl-routing-corpus.ts +11 -3
- package/scripts/nightly-docops.ts +90 -3
- package/scripts/release-loop/gate-node.ts +5 -5
- package/scripts/release-loop/shard-plan.ts +50 -5
- package/scripts/research-coo-admin-dates.ts +39 -0
- package/scripts/rhythm/daily.ts +2 -1
- package/scripts/skills/public-check.ts +7 -2
- package/scripts/smart-merge/resync-shadow.ts +5 -3
- package/scripts/test-diet/lib.ts +81 -1
- package/scripts/test-diet/node.ts +48 -2
- package/scripts/verify-coo-admin-evidence.ts +83 -0
- package/src/acp/client.ts +4 -2
- package/src/agent/run-context.ts +4 -0
- package/src/agent-env/profile.ts +25 -5
- package/src/agent-mission/driver.ts +26 -12
- package/src/agent-mission/supervisor-metrics.ts +17 -2
- package/src/agent-substrate/acp-reviewer.ts +12 -5
- package/src/agent-substrate/pr-reviewer.ts +11 -4
- package/src/agent-substrate/review-intent.ts +102 -3
- package/src/autopilot/build/llm-conflict-merge.ts +22 -8
- package/src/autopilot/pr-manager.ts +18 -8
- package/src/away/release-watch.ts +47 -8
- package/src/briefing/brief-items.ts +104 -11
- package/src/browser-cdp/client.ts +10 -5
- package/src/chat/index.ts +1 -0
- package/src/cli/brief-cli.ts +20 -2
- package/src/cli/config-classify.ts +83 -0
- package/src/cli/control-cli.ts +14 -0
- package/src/cli/decisions-cli.ts +26 -16
- package/src/cli/logs-model-input.ts +1 -1
- package/src/cli/ops-cli.ts +11 -0
- package/src/cli/ops-overview-cli.ts +50 -0
- package/src/cli/pod-cli.ts +1 -0
- package/src/cli/pr-cli.ts +65 -8
- package/src/cli/release-cli.ts +21 -4
- package/src/cli/tasks-cli.ts +214 -1
- package/src/connectors/types.ts +6 -6
- package/src/context-bus/context-now-surfaces.ts +18 -0
- package/src/context-bus/coord-events.ts +32 -4
- package/src/context-bus/long-term-memory.ts +23 -6
- package/src/context-bus/nightly-condense.ts +169 -21
- package/src/control/control-status.ts +87 -0
- package/src/control-plane/light-member.ts +5 -3
- package/src/control-plane/member.ts +172 -7
- package/src/core-turn/run-goal-loop.ts +22 -1
- package/src/dashboard/index.ts +40 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
- package/src/decisions/decision-cards.ts +20 -1
- package/src/decisions/decision-ledger.ts +11 -8
- package/src/decisions/web-push-decision-cards.ts +19 -3
- package/src/discord-self-message.ts +2 -2
- package/src/domains/coo-admin-dates.evidence.json +876 -0
- package/src/domains/coo-admin-tool.ts +48 -14
- package/src/domains/ops-status.ts +53 -0
- package/src/domains/outbound-alert.ts +24 -1
- package/src/esc-abort-gate.ts +3 -1
- package/src/exec-requests/planner.ts +6 -0
- package/src/graph-kinds/registry.ts +6 -6
- package/src/graph-runner/graph-cli.ts +91 -2
- package/src/graph-runner/graph-run-api.ts +20 -0
- package/src/graph-runner/graph-tick.ts +73 -12
- package/src/graph-runner/runner.ts +43 -11
- package/src/graph-unify/wf-to-graph.ts +79 -0
- package/src/graph-wizard/archetypes.ts +88 -0
- package/src/graph-wizard/generate.ts +577 -0
- package/src/graph-wizard/saved-steps.ts +120 -0
- package/src/graph-wizard/steps.ts +206 -0
- package/src/harness/harness-cli-command.ts +186 -32
- package/src/harness/harness-queue.ts +58 -14
- package/src/harness/launch-advice.ts +23 -0
- package/src/index.ts +43 -11
- package/src/intake-plane/digest.ts +18 -11
- package/src/knowledge/conclusion-compress.ts +93 -0
- package/src/knowledge/know-find.ts +9 -2
- package/src/knowledge/query.ts +20 -0
- package/src/llm.ts +26 -16
- package/src/loops/neighbor-runtime.ts +88 -0
- package/src/loops/orchestrator/author-depth.ts +29 -11
- package/src/loops/orchestrator/overlap.ts +7 -1
- package/src/loops/orchestrator/resync-shadow.ts +1 -1
- package/src/loops/orchestrator/tick.ts +35 -2
- package/src/loops/orchestrator/traffic.ts +2 -0
- package/src/loops/registry.ts +5 -0
- package/src/market/publish.ts +17 -1
- package/src/maturity/feature-maturity.ts +4 -2
- package/src/mss/logging/redaction.ts +17 -0
- package/src/nexus/api/draft-metrics.ts +128 -0
- package/src/nexus/api/graph-peer-edit.ts +77 -0
- package/src/nexus/api/graph-wizard-api.ts +61 -0
- package/src/nexus/api/graphs-api.ts +193 -17
- package/src/nexus/api/harness-traversal.ts +205 -0
- package/src/nexus/api/http-server.ts +122 -6
- package/src/nexus/api/plugins-market.ts +24 -0
- package/src/nexus/index.ts +3 -2
- package/src/nexus/outbound/router.ts +28 -8
- package/src/oauth/claude-pty-maturity.ts +24 -0
- package/src/oauth/fallback-chain.ts +27 -14
- 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/plugins/maker/market-bundle.ts +9 -0
- package/src/plugins/maker/plugin-maker.ts +9 -3
- package/src/plugins/maker/wizard-generate.ts +73 -9
- package/src/policy/llm-policy.ts +11 -6
- package/src/release-loop/checklist-board.ts +27 -0
- package/src/release-loop/checklist.ts +38 -0
- package/src/release-loop/gate-shards.ts +7 -2
- package/src/seat-address/seat-address.ts +1 -0
- package/src/seat-loop/seat-loop.ts +174 -20
- package/src/seat-loop/seat-report.ts +29 -1
- package/src/self-dev/ask-launch-flow.ts +4 -2
- package/src/self-dev/ask-launch-io.ts +3 -1
- package/src/self-dev/dev-cli.ts +90 -8
- package/src/self-dev/dev-pipeline.ts +20 -1
- package/src/self-dev/draft-sweep.ts +45 -8
- 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/orchestrate.ts +3 -0
- package/src/self-dev/release-path-guard.ts +7 -4
- package/src/self-dev/review-provider-fallback.ts +83 -0
- package/src/self-dev/run-supervisor.ts +5 -1
- package/src/self-implement/frozen-merges.ts +23 -0
- package/src/self-implement/goal-author.ts +14 -0
- package/src/self-implement/harness-policy-gates.ts +7 -5
- package/src/self-implement/heal-intake.ts +3 -0
- package/src/self-implement/host-regate.ts +28 -14
- package/src/self-implement/l8-merge-queue.ts +80 -8
- package/src/self-implement/orchestrator.ts +84 -28
- package/src/self-implement/seams.ts +8 -3
- package/src/self-implement/self-implement-cli.ts +4 -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 +35 -6
- package/src/steward/triage.ts +25 -8
- package/src/storage/file-lock.ts +33 -4
- package/src/task-agent/card-evidence.ts +6 -0
- package/src/task-agent/completion-evidence.ts +31 -3
- package/src/task-agent/cover.ts +61 -0
- package/src/task-agent/live-moves.ts +126 -3
- package/src/task-agent/parent-host.ts +315 -0
- package/src/task-agent/shadow.ts +19 -4
- package/src/task-agent/task-hand.ts +11 -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/pod-pool.ts +138 -15
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
- package/src/telegram-commands.ts +8 -0
- package/src/telegram-project-command.ts +6 -0
- package/src/telegram.ts +2 -2
- package/src/tool-runtime/browser-runtime.ts +87 -3
- package/src/tool-runtime/index.ts +1 -0
- package/src/typecheck-ratchet.ts +2 -1
- package/src/user-config.ts +55 -8
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +24 -2
- package/src/workflow-runtime/triggers/registry.ts +27 -0
- package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
- package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.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/3857-995ea9f5ef33d71e.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/bots/page-7a852956c74015a3.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/market/page-9ca21f9ad3208a61.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/checklist/page-f6055495e9ed7ea8.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/setup/done/page-c26fa10aac6c21c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.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/tasks/page-be1c4e8697f22360.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/trace/page-0fd895fab9b62c1b.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/workflows/chat-ui/page-c6e4364d9de8f717.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 → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
- /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
|
@@ -156,8 +156,8 @@ type MergeStateReading =
|
|
|
156
156
|
| { status: 'not-merged'; state: string }
|
|
157
157
|
| { status: 'unreadable' };
|
|
158
158
|
|
|
159
|
-
function readPrMergeState(run: CmdRunner, prNumber: number): MergeStateReading {
|
|
160
|
-
const state = run('gh', ['pr', 'view', String(prNumber), '--json', 'state']);
|
|
159
|
+
function readPrMergeState(run: CmdRunner, prNumber: number, cwd?: string): MergeStateReading {
|
|
160
|
+
const state = run('gh', ['pr', 'view', String(prNumber), '--json', 'state'], cwd ? { cwd } : undefined);
|
|
161
161
|
if (!state.ok) return { status: 'unreadable' };
|
|
162
162
|
try {
|
|
163
163
|
const parsed = JSON.parse(state.out) as { state?: unknown };
|
|
@@ -189,7 +189,7 @@ export interface PrManager {
|
|
|
189
189
|
/** PR squash 머지(+브랜치 삭제). 완료 리뷰 후 clean PR 반영. */
|
|
190
190
|
mergePr(prUrl: string): boolean;
|
|
191
191
|
/** 머지 exit 실패 시 GH 상태를 재조회한 구조화 판정. */
|
|
192
|
-
mergePrOutcome(prUrl: string): MergePrOutcome;
|
|
192
|
+
mergePrOutcome(prUrl: string, expectedHead?: string, cwd?: string): MergePrOutcome;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
/** "이미 커밋된 산출이 있나"를 판정할 비교 base 를 해석한다(순수 아님 — run 주입).
|
|
@@ -553,11 +553,21 @@ export function makePrManager(runner: CmdRunner = defaultCmdRunner): PrManager {
|
|
|
553
553
|
mergePr(prUrl) {
|
|
554
554
|
return this.mergePrOutcome(prUrl).ok;
|
|
555
555
|
},
|
|
556
|
-
mergePrOutcome(prUrl) {
|
|
556
|
+
mergePrOutcome(prUrl, expectedHead, cwd) {
|
|
557
557
|
const n = extractPrNumber(prUrl);
|
|
558
558
|
if (n === null) return { ok: false, kind: 'state-read-failed' };
|
|
559
|
+
if (expectedHead !== undefined) {
|
|
560
|
+
const pinned = run('gh', ['pr', 'view', String(n), '--json', 'headRefOid,state,isDraft'], cwd ? { cwd } : undefined);
|
|
561
|
+
try {
|
|
562
|
+
if (!pinned.ok) return { ok: false, kind: 'state-read-failed' };
|
|
563
|
+
const view = JSON.parse(pinned.out) as { headRefOid?: unknown; state?: unknown; isDraft?: unknown };
|
|
564
|
+
if (view.headRefOid !== expectedHead || view.state !== 'OPEN' || view.isDraft !== false) {
|
|
565
|
+
return { ok: false, kind: 'state-read-failed' };
|
|
566
|
+
}
|
|
567
|
+
} catch { return { ok: false, kind: 'state-read-failed' }; }
|
|
568
|
+
}
|
|
559
569
|
const deleteRemoteHead = (kind: 'merge-exit-0' | 'merged-after-nonzero'): MergePrOutcome => {
|
|
560
|
-
const head = run('gh', ['pr', 'view', String(n), '--json', 'headRefName,headRepository']);
|
|
570
|
+
const head = run('gh', ['pr', 'view', String(n), '--json', 'headRefName,headRepository'], cwd ? { cwd } : undefined);
|
|
561
571
|
if (!head.ok) return { ok: true, kind, remoteBranchDeletion: { detail: head.err || head.out || 'PR head branch 조회 실패' } };
|
|
562
572
|
try {
|
|
563
573
|
const { headRefName, headRepository } = JSON.parse(head.out) as {
|
|
@@ -568,7 +578,7 @@ export function makePrManager(runner: CmdRunner = defaultCmdRunner): PrManager {
|
|
|
568
578
|
if (typeof headRefName !== 'string' || !headRefName || typeof repository !== 'string' || !repository) {
|
|
569
579
|
return { ok: true, kind, remoteBranchDeletion: { detail: 'PR head branch 또는 저장소를 읽지 못했습니다' } };
|
|
570
580
|
}
|
|
571
|
-
const deleted = run('gh', ['api', '--method', 'DELETE', `repos/${repository}/git/refs/heads/${encodeURIComponent(headRefName)}`]);
|
|
581
|
+
const deleted = run('gh', ['api', '--method', 'DELETE', `repos/${repository}/git/refs/heads/${encodeURIComponent(headRefName)}`], cwd ? { cwd } : undefined);
|
|
572
582
|
return deleted.ok
|
|
573
583
|
? { ok: true, kind }
|
|
574
584
|
: { ok: true, kind, remoteBranchDeletion: { detail: deleted.err || deleted.out || `원격 브랜치 ${headRefName} 삭제 실패` } };
|
|
@@ -576,13 +586,13 @@ export function makePrManager(runner: CmdRunner = defaultCmdRunner): PrManager {
|
|
|
576
586
|
return { ok: true, kind, remoteBranchDeletion: { detail: 'PR head branch 응답을 읽지 못했습니다' } };
|
|
577
587
|
}
|
|
578
588
|
};
|
|
579
|
-
const merged = run('gh', ['pr', 'merge', String(n), '--squash']);
|
|
589
|
+
const merged = run('gh', ['pr', 'merge', String(n), '--squash', ...(expectedHead ? ['--match-head-commit', expectedHead] : [])], cwd ? { cwd } : undefined);
|
|
580
590
|
if (merged.ok) return deleteRemoteHead('merge-exit-0');
|
|
581
591
|
let lastNotMerged: string | undefined;
|
|
582
592
|
let notMergedCount = 0;
|
|
583
593
|
let sawUnreadable = false;
|
|
584
594
|
for (let i = 0; i < MERGE_UNMERGED_CONFIRMATIONS; i++) {
|
|
585
|
-
const reading = readPrMergeState(run, n);
|
|
595
|
+
const reading = readPrMergeState(run, n, cwd);
|
|
586
596
|
if (reading.status === 'merged') return deleteRemoteHead('merged-after-nonzero');
|
|
587
597
|
if (reading.status === 'unreadable') {
|
|
588
598
|
sawUnreadable = true;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
3
4
|
import { debug } from '../debug/log.js';
|
|
4
5
|
import { deliver, inQuietHours, sendOutbound } from '../domains/outbound-alert.js';
|
|
5
6
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
@@ -53,6 +54,39 @@ function endedNodes(run: ReleaseRunView): ReleaseRunNodeView[] {
|
|
|
53
54
|
return run.nodes.filter((node) => node.ok !== null && !TERMINAL.has(node.nodeId));
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
/** 분모는 원장 path(지나온 자리)가 아니라 이 런에 확인된 그래프 선언의 전체 노드 수다. */
|
|
58
|
+
function graphTotal(file: string, run: ReleaseRunView): number | undefined {
|
|
59
|
+
try {
|
|
60
|
+
const raw: unknown = parseYaml(readFileSync(file, 'utf8'));
|
|
61
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;
|
|
62
|
+
const graph = raw as Record<string, unknown>;
|
|
63
|
+
if (graph.graph_id !== 'release-loop' || !Array.isArray(graph.nodes) || !Array.isArray(graph.terminal_nodes)) return undefined;
|
|
64
|
+
const terminal = graph.terminal_nodes;
|
|
65
|
+
if (!terminal.length || !terminal.every((id): id is string => typeof id === 'string')) return undefined;
|
|
66
|
+
const ids = graph.nodes.map((node: unknown) => node && typeof node === 'object' && !Array.isArray(node)
|
|
67
|
+
? (node as Record<string, unknown>).node_id : undefined);
|
|
68
|
+
if (!ids.length || !ids.every((id): id is string => typeof id === 'string') || new Set(ids).size !== ids.length
|
|
69
|
+
|| !terminal.every((id) => ids.includes(id)) || !run.nodes.every((node) => ids.includes(node.nodeId))) return undefined;
|
|
70
|
+
// 분자(endedNodes)가 종결 자리표를 빼므로 분모도 뺀다 — 끝난 판이 «23/25» 로 영영 덜 끝나 보이지 않게.
|
|
71
|
+
return ids.filter((id) => !terminal.includes(id)).length;
|
|
72
|
+
} catch { return undefined; }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 이 런이 실제로 쓴 그래프 정의 — 그래프 실행기가 런마다 떠 두는 스냅샷 `<런>.json.graph/graph.yaml`.
|
|
77
|
+
* 없거나 runId 가 경로로 못 쓰는 모양이면 undefined(분모 생략) — 현재 선언 파일로 추측하지 않는다.
|
|
78
|
+
*/
|
|
79
|
+
export function releaseGraphFile(run: ReleaseRunView, root: string = effectiveInstanceRoot()): string | undefined {
|
|
80
|
+
if (!/^[A-Za-z0-9-]+$/.test(run.runId)) return undefined;
|
|
81
|
+
return join(root, 'graph-runs', 'release-loop', `${run.runId}.json.graph`, 'graph.yaml');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function completedCount(run: ReleaseRunView, graphFile?: string): string {
|
|
85
|
+
const ended = endedNodes(run).length;
|
|
86
|
+
const total = graphFile ? graphTotal(graphFile, run) : undefined;
|
|
87
|
+
return `끝난 노드 ${ended}${total !== undefined ? `/${total}` : ''}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
56
90
|
function runningNode(run: ReleaseRunView): ReleaseRunNodeView | undefined {
|
|
57
91
|
return run.status === 'running' ? run.nodes.find((node) => node.ok === null) : undefined;
|
|
58
92
|
}
|
|
@@ -88,7 +122,7 @@ function blockedLine(run: ReleaseRunView): string {
|
|
|
88
122
|
}
|
|
89
123
|
|
|
90
124
|
/** 직전 표지와 지금 런을 견주어 보낼 줄을 만든다. 순수 함수 — 시험이 이것을 문다. */
|
|
91
|
-
export function diffRun(prev: RunMark | undefined, run: ReleaseRunView, now: number): { lines: string[]; urgent: boolean; changed: boolean } {
|
|
125
|
+
export function diffRun(prev: RunMark | undefined, run: ReleaseRunView, now: number, graphFile?: string): { lines: string[]; urgent: boolean; changed: boolean } {
|
|
92
126
|
const ended = endedNodes(run);
|
|
93
127
|
const running = runningNode(run);
|
|
94
128
|
const lines: string[] = [];
|
|
@@ -97,11 +131,10 @@ export function diffRun(prev: RunMark | undefined, run: ReleaseRunView, now: num
|
|
|
97
131
|
// 이미 끝난 판은 조용히 기준만 잡는다 — 외출을 켤 때 지난 판이 쏟아지지 않게.
|
|
98
132
|
if (run.status === 'done') return { lines, urgent, changed: true };
|
|
99
133
|
// 처음 보는 런 — 지난 노드를 다시 쏟지 않고 «지금 어디인가» 한 줄로 시작한다.
|
|
100
|
-
const total = run.path.filter((step) => !TERMINAL.has(step)).length || run.nodes.length;
|
|
101
134
|
const where = run.status === 'running'
|
|
102
135
|
? `지금 ${running?.nodeId ?? '다음 노드 준비'}${running?.startedAt ? `(${minutesSince(running.startedAt, now)}분째)` : ''}`
|
|
103
136
|
: `상태 ${run.status}`;
|
|
104
|
-
lines.push(`📦 ${label(run)} 발행 따라가기 시작 ·
|
|
137
|
+
lines.push(`📦 ${label(run)} 발행 따라가기 시작 · ${completedCount(run, graphFile)} · ${where}`);
|
|
105
138
|
if (BLOCKED.has(run.status)) { lines.push(blockedLine(run)); urgent = true; }
|
|
106
139
|
return { lines, urgent, changed: true };
|
|
107
140
|
}
|
|
@@ -129,7 +162,7 @@ export function diffRun(prev: RunMark | undefined, run: ReleaseRunView, now: num
|
|
|
129
162
|
}
|
|
130
163
|
if (!lines.length && run.status === 'running' && now - Date.parse(prev.sentAt) >= ALIVE_EVERY_MS) {
|
|
131
164
|
const mins = minutesSince(running?.startedAt, now);
|
|
132
|
-
lines.push(`💓 ${label(run)} 발행 진행 중 · 지금 ${running?.nodeId ?? '다음 노드 준비'}${mins !== null ? `(${mins}분째)` : ''} ·
|
|
165
|
+
lines.push(`💓 ${label(run)} 발행 진행 중 · 지금 ${running?.nodeId ?? '다음 노드 준비'}${mins !== null ? `(${mins}분째)` : ''} · ${completedCount(run, graphFile)}`
|
|
133
166
|
+ (run.gateShards ? `\n ${shardsLine(run.gateShards.summary)}` : ''));
|
|
134
167
|
}
|
|
135
168
|
const changed = lines.length > 0 || prev.ended !== ended.length || prev.status !== run.status || prev.running !== running?.nodeId
|
|
@@ -182,6 +215,8 @@ export interface TickDeps {
|
|
|
182
215
|
/** 막힘·실패 — 야간 무음을 뚫는다. */
|
|
183
216
|
sendUrgent?: (text: string) => boolean;
|
|
184
217
|
now?: () => number;
|
|
218
|
+
/** 이 런이 쓴 그래프 정의 파일 — 기본은 런 원장 옆 스냅샷(`releaseGraphFile`). */
|
|
219
|
+
graphFile?: (run: ReleaseRunView) => string | undefined;
|
|
185
220
|
}
|
|
186
221
|
|
|
187
222
|
export type TickOutcome =
|
|
@@ -228,7 +263,8 @@ export function releaseWatchTick(deps: TickDeps = {}): TickOutcome {
|
|
|
228
263
|
let changed = false;
|
|
229
264
|
for (const run of [...runs].reverse()) {
|
|
230
265
|
const prev = state.runs[run.runId];
|
|
231
|
-
const
|
|
266
|
+
const graphFile = (deps.graphFile ?? releaseGraphFile)(run);
|
|
267
|
+
const diff = diffRun(prev, run, now, graphFile);
|
|
232
268
|
lines.push(...diff.lines);
|
|
233
269
|
urgent ||= diff.urgent;
|
|
234
270
|
changed ||= diff.changed;
|
|
@@ -248,14 +284,17 @@ export function releaseWatchTick(deps: TickDeps = {}): TickOutcome {
|
|
|
248
284
|
}
|
|
249
285
|
|
|
250
286
|
/** /release · away status 가 쓰는 «지금» 요약 — 가장 최근 런 하나. */
|
|
251
|
-
export function releaseNowText(
|
|
287
|
+
export function releaseNowText(
|
|
288
|
+
runs: ReleaseRunView[] | 'unavailable',
|
|
289
|
+
now: number = Date.now(),
|
|
290
|
+
graphFileOf: (run: ReleaseRunView) => string | undefined = releaseGraphFile,
|
|
291
|
+
): string {
|
|
252
292
|
if (runs === 'unavailable') return '발행 런 원장을 못 읽었다(권한·디스크) — 「없다」가 아니다';
|
|
253
293
|
const run = runs[0];
|
|
254
294
|
if (!run) return `발행 런 기록 없음\n↗ ${RELEASE_SCREEN_URL}`;
|
|
255
295
|
const ended = endedNodes(run);
|
|
256
296
|
const running = runningNode(run);
|
|
257
|
-
const
|
|
258
|
-
const head = `📦 ${label(run)} · ${run.status} · 끝난 노드 ${ended.length}${total ? `/${total}` : ''}`;
|
|
297
|
+
const head = `📦 ${label(run)} · ${run.status} · ${completedCount(run, graphFileOf(run))}`;
|
|
259
298
|
const lines = [head];
|
|
260
299
|
if (running) {
|
|
261
300
|
const mins = minutesSince(running.startedAt, now);
|
|
@@ -9,6 +9,13 @@ import { readRequirementFunnel, type RequirementFunnelResult } from '../intake-p
|
|
|
9
9
|
export const BRIEF_DOMAINS = ['운영', '판', '흡수', '시장', '행정', '코나투스'] as const;
|
|
10
10
|
export const BRIEF_PRIORITIES = ['P0', 'P1', 'P2'] as const;
|
|
11
11
|
export const BRIEF_SLOTS = ['08:30', 'after-release', '22:00'] as const;
|
|
12
|
+
export const BRIEF_REACTIONS = ['열람', '버튼', '답장', '정정', '무시'] as const;
|
|
13
|
+
|
|
14
|
+
export type BriefReaction = (typeof BRIEF_REACTIONS)[number];
|
|
15
|
+
export interface BriefActionCount { acted: number; total: number; rate: number }
|
|
16
|
+
export interface BriefWeeklyActions extends BriefActionCount {
|
|
17
|
+
byKind: Record<BriefDomain, BriefActionCount>;
|
|
18
|
+
}
|
|
12
19
|
|
|
13
20
|
export type BriefDomain = (typeof BRIEF_DOMAINS)[number];
|
|
14
21
|
export type BriefPriority = (typeof BRIEF_PRIORITIES)[number];
|
|
@@ -36,6 +43,9 @@ export interface AddBriefItem {
|
|
|
36
43
|
evidence?: string | null;
|
|
37
44
|
source: string;
|
|
38
45
|
createdAt?: string;
|
|
46
|
+
/** Optional normalized claim identity and time window for repeat observations. */
|
|
47
|
+
dedupeKey?: string;
|
|
48
|
+
dedupeWithinMs?: number;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
export interface BriefItemsOptions {
|
|
@@ -98,8 +108,10 @@ export function composeBriefMarkdown(items: readonly BriefItem[], slot: BriefSlo
|
|
|
98
108
|
export function composeBriefPage(items: readonly BriefItem[], slot: BriefSlot, now: Date): { markdown: string; ids: number[] } {
|
|
99
109
|
const today = dateKey(now, { timeZone: KST });
|
|
100
110
|
// A duplicate left out of a delivered page stays unsent in the ledger, but must not reappear on a later page.
|
|
111
|
+
// A new undeliverable observation after a sent page is a new occurrence, not an old duplicate.
|
|
101
112
|
const deliveredClaims = new Set(items.filter(item => item.sent_at !== null).map(item => normalizeClaim(item.text)));
|
|
102
|
-
const eligible = items.filter(item => item.sent_at === null && beforeSlot(item.created_at, slot, now)
|
|
113
|
+
const eligible = items.filter(item => item.sent_at === null && beforeSlot(item.created_at, slot, now)
|
|
114
|
+
&& (item.source === 'outbound.undeliverable' || !deliveredClaims.has(normalizeClaim(item.text))));
|
|
103
115
|
// Duplicate claims keep the copy that needs a decision today (P0 or due today), then the higher priority, then the earliest (ACP must-fix).
|
|
104
116
|
const rank = (item: BriefItem) => [needsDecision(item, today) ? 0 : 1, BRIEF_PRIORITIES.indexOf(item.priority), item.id];
|
|
105
117
|
const better = (a: BriefItem, b: BriefItem) => { const x = rank(a), y = rank(b); return x[0]! - y[0]! || x[1]! - y[1]! || x[2]! - y[2]!; };
|
|
@@ -210,7 +222,19 @@ export class BriefItemsLedger {
|
|
|
210
222
|
item_id INTEGER NOT NULL REFERENCES items(id),
|
|
211
223
|
slot TEXT NOT NULL,
|
|
212
224
|
sent_at TEXT NOT NULL
|
|
213
|
-
)
|
|
225
|
+
);
|
|
226
|
+
CREATE TABLE IF NOT EXISTS reactions (
|
|
227
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
228
|
+
item_id INTEGER NOT NULL REFERENCES items(id),
|
|
229
|
+
reaction TEXT NOT NULL CHECK(reaction IN ('열람', '버튼', '답장', '정정', '무시')),
|
|
230
|
+
reacted_at TEXT NOT NULL
|
|
231
|
+
);
|
|
232
|
+
CREATE INDEX IF NOT EXISTS reactions_item_time ON reactions(item_id, reacted_at);
|
|
233
|
+
CREATE INDEX IF NOT EXISTS sends_time ON sends(sent_at);`);
|
|
234
|
+
if (!(db.query("SELECT name FROM pragma_table_info('items')").all() as { name: string }[]).some(column => column.name === 'dedupe_key')) {
|
|
235
|
+
db.exec('ALTER TABLE items ADD COLUMN dedupe_key TEXT');
|
|
236
|
+
}
|
|
237
|
+
db.exec('CREATE INDEX IF NOT EXISTS items_dedupe ON items(source, dedupe_key, created_at)');
|
|
214
238
|
return work(db);
|
|
215
239
|
} finally {
|
|
216
240
|
db.close();
|
|
@@ -236,16 +260,85 @@ export class BriefItemsLedger {
|
|
|
236
260
|
const evidence = optional(input.evidence, 'evidence');
|
|
237
261
|
const createdAt = input.createdAt ?? this.now().toISOString();
|
|
238
262
|
if (!Number.isFinite(Date.parse(createdAt))) throw new BriefItemsInputError('invalid createdAt');
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
).
|
|
263
|
+
const dedupeKey = input.dedupeKey === undefined ? null : normalizeClaim(required(input.dedupeKey, 'dedupeKey'));
|
|
264
|
+
const windowMs = input.dedupeWithinMs;
|
|
265
|
+
if (windowMs !== undefined && (!Number.isFinite(windowMs) || windowMs <= 0 || dedupeKey === null)) {
|
|
266
|
+
throw new BriefItemsInputError('invalid dedupe window');
|
|
267
|
+
}
|
|
268
|
+
const selected = this.using(db => {
|
|
269
|
+
const select = 'SELECT id, text, domain, priority, deadline, evidence, source, created_at, (SELECT MIN(sent_at) FROM sends WHERE sends.item_id = items.id) AS sent_at FROM items WHERE id = ?';
|
|
270
|
+
db.exec('BEGIN IMMEDIATE');
|
|
271
|
+
try {
|
|
272
|
+
if (dedupeKey !== null && windowMs !== undefined) {
|
|
273
|
+
const existing = db.query(`SELECT id FROM items WHERE source = ? AND dedupe_key = ?
|
|
274
|
+
AND NOT EXISTS (SELECT 1 FROM sends WHERE sends.item_id = items.id)
|
|
275
|
+
AND julianday(created_at) >= julianday(?) - ? / 86400000.0
|
|
276
|
+
AND julianday(created_at) <= julianday(?) ORDER BY id DESC LIMIT 1`)
|
|
277
|
+
.get(source, dedupeKey, createdAt, windowMs, createdAt) as { id: number } | null;
|
|
278
|
+
if (existing) {
|
|
279
|
+
const item = db.query(select).get(existing.id) as BriefItem;
|
|
280
|
+
db.exec('COMMIT');
|
|
281
|
+
return { item, added: false };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const result = db.query(
|
|
285
|
+
'INSERT INTO items (text, domain, priority, deadline, evidence, source, created_at, sent_at, dedupe_key) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?)',
|
|
286
|
+
).run(text, input.domain, input.priority, deadline, evidence, source, createdAt, dedupeKey);
|
|
287
|
+
const item = db.query(select).get(result.lastInsertRowid) as BriefItem;
|
|
288
|
+
db.exec('COMMIT');
|
|
289
|
+
return { item, added: true };
|
|
290
|
+
} catch (cause) {
|
|
291
|
+
db.exec('ROLLBACK');
|
|
292
|
+
throw cause;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
if (selected.added) this.log('briefing.items', 'added', { id: selected.item.id, domain: selected.item.domain, priority: selected.item.priority, source: selected.item.source });
|
|
296
|
+
return selected.item;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Append a reaction without changing the original item or its send history. */
|
|
300
|
+
recordReaction(itemId: number, reaction: BriefReaction): void {
|
|
301
|
+
if (!Number.isSafeInteger(itemId) || itemId < 1) throw new BriefItemsInputError('invalid item id');
|
|
302
|
+
if (!(BRIEF_REACTIONS as readonly string[]).includes(reaction)) throw new BriefItemsInputError('invalid reaction');
|
|
303
|
+
this.using(db => {
|
|
304
|
+
const at = this.now().toISOString();
|
|
305
|
+
if (!db.query('SELECT 1 FROM sends WHERE item_id = ? AND sent_at <= ? LIMIT 1').get(itemId, at)) {
|
|
306
|
+
throw new BriefItemsInputError('item was not sent');
|
|
307
|
+
}
|
|
308
|
+
db.query('INSERT INTO reactions (item_id, reaction, reacted_at) VALUES (?, ?, ?)')
|
|
309
|
+
.run(itemId, reaction, at);
|
|
310
|
+
});
|
|
311
|
+
this.log('briefing.items', 'reaction-recorded', { itemId, reaction });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** KST Monday through now: distinct sent items acted on (button/reply/correction), grouped by domain. */
|
|
315
|
+
weeklyActions(): BriefWeeklyActions {
|
|
316
|
+
const now = this.now();
|
|
317
|
+
const today = dateKey(now, { timeZone: KST });
|
|
318
|
+
const day = new Date(`${today}T00:00:00Z`);
|
|
319
|
+
day.setUTCDate(day.getUTCDate() - ((day.getUTCDay() + 6) % 7));
|
|
320
|
+
const since = new Date(`${day.toISOString().slice(0, 10)}T00:00:00+09:00`).toISOString();
|
|
321
|
+
const until = now.toISOString();
|
|
322
|
+
return this.using(db => {
|
|
323
|
+
const rows = db.query(`SELECT items.domain AS kind,
|
|
324
|
+
COUNT(*) AS total,
|
|
325
|
+
SUM(CASE WHEN EXISTS (
|
|
326
|
+
SELECT 1 FROM reactions r WHERE r.item_id = items.id
|
|
327
|
+
AND r.reaction IN ('버튼', '답장', '정정') AND r.reacted_at >= ? AND r.reacted_at <= ?
|
|
328
|
+
) THEN 1 ELSE 0 END) AS acted
|
|
329
|
+
FROM items WHERE EXISTS (
|
|
330
|
+
SELECT 1 FROM sends s WHERE s.item_id = items.id AND s.sent_at >= ? AND s.sent_at <= ?
|
|
331
|
+
) GROUP BY items.domain`).all(since, until, since, until) as Array<{ kind: BriefDomain; total: number; acted: number }>;
|
|
332
|
+
const byKind = Object.fromEntries(BRIEF_DOMAINS.map(kind => [kind, { acted: 0, total: 0, rate: 0 }])) as Record<BriefDomain, BriefActionCount>;
|
|
333
|
+
let acted = 0;
|
|
334
|
+
let total = 0;
|
|
335
|
+
for (const row of rows) {
|
|
336
|
+
byKind[row.kind] = { acted: row.acted, total: row.total, rate: row.acted / row.total };
|
|
337
|
+
acted += row.acted;
|
|
338
|
+
total += row.total;
|
|
339
|
+
}
|
|
340
|
+
return { acted, total, rate: total === 0 ? 0 : acted / total, byKind };
|
|
246
341
|
});
|
|
247
|
-
this.log('briefing.items', 'added', { id: item.id, domain: item.domain, priority: item.priority, source: item.source });
|
|
248
|
-
return item;
|
|
249
342
|
}
|
|
250
343
|
|
|
251
344
|
/** Insertion order. Optional domain filter. Item text is the stored original. */
|
|
@@ -7,11 +7,9 @@
|
|
|
7
7
|
// `{id, method, params}` messages, receive `{id, result}` or
|
|
8
8
|
// `{id, error}`.
|
|
9
9
|
//
|
|
10
|
-
// Scope kept narrow:
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// describes. Full Puppeteer feature parity is explicitly out of
|
|
14
|
-
// scope.
|
|
10
|
+
// Scope kept narrow: navigation, capture, evaluation and CDP input.
|
|
11
|
+
// Approval of mutating actions belongs to the caller, not this transport.
|
|
12
|
+
// Full Puppeteer feature parity is explicitly out of scope.
|
|
15
13
|
|
|
16
14
|
import { CHROME_NO_KEYCHAIN_FLAGS } from './chrome-flags.js';
|
|
17
15
|
import { spawn, type ChildProcess } from 'node:child_process';
|
|
@@ -126,6 +124,8 @@ export interface CdpClient {
|
|
|
126
124
|
setScriptExecutionDisabled(value: boolean): Promise<void>;
|
|
127
125
|
/** Dispatch one primary-button click at viewport coordinates. */
|
|
128
126
|
click?(coordinates: { x: number; y: number }): Promise<void>;
|
|
127
|
+
/** Insert text at the focused element (approval is the caller's responsibility). */
|
|
128
|
+
input?(text: string): Promise<void>;
|
|
129
129
|
/** Dispose — closes Chrome + WebSocket. */
|
|
130
130
|
close(): Promise<void>;
|
|
131
131
|
readonly isAlive: boolean;
|
|
@@ -513,6 +513,11 @@ async function buildClientFromTransport(
|
|
|
513
513
|
await runOnPage('Input.dispatchMouseEvent', { type: 'mousePressed', ...params });
|
|
514
514
|
await runOnPage('Input.dispatchMouseEvent', { type: 'mouseReleased', ...params });
|
|
515
515
|
},
|
|
516
|
+
async input(text) {
|
|
517
|
+
// Newlines can submit a form even without an Enter key event.
|
|
518
|
+
if (/[\r\n]/.test(text)) throw new Error('CDP input refuses newlines');
|
|
519
|
+
await runOnPage('Input.insertText', { text });
|
|
520
|
+
},
|
|
516
521
|
async close() {
|
|
517
522
|
if (!alive) return;
|
|
518
523
|
alive = false;
|
package/src/chat/index.ts
CHANGED
|
@@ -466,6 +466,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
|
|
466
466
|
{ name: 'widget', aliases: ['widgets'], description: 'Manage widgets', subcommands: ['list', 'reload', 'instances'] },
|
|
467
467
|
{ name: 'log', aliases: [], description: 'Chat Log controls — size / clear / filter / search / freeze / solo / turn / fold / help', subcommands: ['size', 'clear', 'filter', 'search', 'freeze', 'solo', 'turn', 'fold', 'help'] },
|
|
468
468
|
{ name: 'loops', aliases: [], description: '루프와 크론 상태 표 — /loops [owner]' },
|
|
469
|
+
{ name: 'board', aliases: [], description: '판 체크리스트 — /board [판]' },
|
|
469
470
|
{ name: 'memory', aliases: ['mem'], description: 'Memory ops — list / show / search / add / delete (see `elanous memory --help`)', subcommands: ['list', 'show', 'search', 'add', 'delete', 'index'] },
|
|
470
471
|
{ name: 'export', aliases: [], description: 'Export this conversation transcript to a markdown file — /export [path] (default ~/temp/elanous-transcript-<stamp>.md)' },
|
|
471
472
|
{ name: 'delta', aliases: ['diffs'], description: 'Source delta browser — open the latest turn-scoped file diff popup', subcommands: ['open', 'help'] },
|
package/src/cli/brief-cli.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import { BRIEF_DOMAINS, BRIEF_PRIORITIES, BRIEF_SLOTS, BriefItemsInputError, BriefItemsLedger, type BriefDomain, type BriefPriority, type BriefSlot } from '../briefing/brief-items.js';
|
|
2
|
+
import { BRIEF_DOMAINS, BRIEF_PRIORITIES, BRIEF_REACTIONS, BRIEF_SLOTS, BriefItemsInputError, BriefItemsLedger, type BriefDomain, type BriefPriority, type BriefReaction, type BriefSlot } from '../briefing/brief-items.js';
|
|
3
3
|
import { kindRouteTarget } from '../domains/telegram-kind-route.js';
|
|
4
4
|
import { sendTelegramReturningId } from '../autopilot/mission-notify.js';
|
|
5
5
|
import { getUserConfig } from '../user-config.js';
|
|
@@ -24,7 +24,13 @@ export function registerBriefCommands(program: Command, deps: BriefCliDeps = {})
|
|
|
24
24
|
error(`brief ${verb}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
25
25
|
process.exitCode = cause instanceof BriefItemsInputError ? 2 : 1;
|
|
26
26
|
};
|
|
27
|
-
const brief = program.command('brief').description('대표 브리핑 항목 원장과 슬롯 발송')
|
|
27
|
+
const brief = program.command('brief').description('대표 브리핑 항목 원장과 슬롯 발송')
|
|
28
|
+
.action(() => {
|
|
29
|
+
try {
|
|
30
|
+
const { acted, total } = ledger().weeklyActions();
|
|
31
|
+
output(`이번 주 브리핑 중 대표가 움직인 것 ${acted}/${total}`);
|
|
32
|
+
} catch (cause) { fail('', cause); }
|
|
33
|
+
});
|
|
28
34
|
|
|
29
35
|
brief.command('add')
|
|
30
36
|
.description('주장 한 줄을 원장에 추가한다 (원문 그대로)')
|
|
@@ -51,6 +57,18 @@ export function registerBriefCommands(program: Command, deps: BriefCliDeps = {})
|
|
|
51
57
|
} catch (cause) { fail('add', cause); }
|
|
52
58
|
});
|
|
53
59
|
|
|
60
|
+
brief.command('react')
|
|
61
|
+
.description('발송된 브리핑 항목에 대한 대표 반응을 기록한다')
|
|
62
|
+
.requiredOption('--item-id <id>', '발송 항목 id')
|
|
63
|
+
.requiredOption('--reaction <reaction>', `반응 (${BRIEF_REACTIONS.join('|')})`)
|
|
64
|
+
.action((opts: { itemId: string; reaction: string }) => {
|
|
65
|
+
try {
|
|
66
|
+
const id = Number(opts.itemId);
|
|
67
|
+
ledger().recordReaction(id, opts.reaction as BriefReaction);
|
|
68
|
+
output(`reacted ${id} ${opts.reaction}`);
|
|
69
|
+
} catch (cause) { fail('react', cause); }
|
|
70
|
+
});
|
|
71
|
+
|
|
54
72
|
brief.command('list')
|
|
55
73
|
.description('원장 항목을 추가 순으로 본다')
|
|
56
74
|
.option('--domain <domain>', `도메인 필터 (${BRIEF_DOMAINS.join('|')})`)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// CFG-CLASSIFY — 파일의 잎만 분류한다. 운영 config 변경·시험 우주 동기화 없음.
|
|
2
|
+
import { isAbsolute } from 'node:path';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import { flattenConfig, isSecretPath, isSecretValue, MASK } from './config-drift.js';
|
|
5
|
+
|
|
6
|
+
export type ConfigClass = 'secret' | 'product-default' | 'machine-fact' | 'operational';
|
|
7
|
+
export interface ClassifiedConfigLeaf {
|
|
8
|
+
key: string;
|
|
9
|
+
class: ConfigClass;
|
|
10
|
+
rule: string;
|
|
11
|
+
overridesDefault: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ConfigClassifyReport {
|
|
15
|
+
totalLeaves: number;
|
|
16
|
+
overridesDefault: number;
|
|
17
|
+
counts: Record<ConfigClass, number>;
|
|
18
|
+
leaves: ClassifiedConfigLeaf[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface RuleInput {
|
|
22
|
+
key: string;
|
|
23
|
+
value: unknown;
|
|
24
|
+
matchesDefault: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 순서가 우선순위다. 비밀은 기본값·경로보다 먼저, 기본값 복사는 기계 사실보다 먼저 판정한다.
|
|
28
|
+
export const CLASSIFY_RULES: ReadonlyArray<{
|
|
29
|
+
class: ConfigClass;
|
|
30
|
+
rule: string;
|
|
31
|
+
matches: (input: RuleInput) => boolean;
|
|
32
|
+
}> = [
|
|
33
|
+
{ class: 'secret', rule: '비밀 경로 또는 비밀 값', matches: ({ key, value }) => isSecretPath(key) || isSecretValue(value) },
|
|
34
|
+
{ class: 'product-default', rule: '코드 기본값 복사', matches: ({ matchesDefault }) => matchesDefault },
|
|
35
|
+
{
|
|
36
|
+
class: 'machine-fact', rule: '기계 경로·주소·포트',
|
|
37
|
+
matches: ({ key, value }) => key.split('.').some((part) => /(?:^|[_-])(?:paths?|dirs?|directories|roots?|ports?|hosts?|urls?|bins?|sockets?|cwd)(?:$|[_-])/i.test(part)
|
|
38
|
+
|| /(?:Path|Dir|Directory|Root|Port|Host|URL|Url|Bin|Socket|Cwd)$/.test(part))
|
|
39
|
+
|| (typeof value === 'string' && (isAbsolute(value) || /^[a-z][a-z0-9+.-]*:\/\//i.test(value))),
|
|
40
|
+
},
|
|
41
|
+
{ class: 'operational', rule: '나머지 운영 정책·상태', matches: () => true },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/** raw 파일의 잎 수를 분모로 한다. 기본값에 키가 없으면 (값이 undefined 여도) 덮어쓰기다. */
|
|
45
|
+
export function classifyConfig(raw: Record<string, unknown>, codeDefaults: Record<string, unknown>): ConfigClassifyReport {
|
|
46
|
+
const defaults = flattenConfig(codeDefaults);
|
|
47
|
+
const leaves = [...flattenConfig(raw)].sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => {
|
|
48
|
+
const matchesDefault = defaults.has(key) && isDeepStrictEqual(value, defaults.get(key));
|
|
49
|
+
const rule = CLASSIFY_RULES.find((candidate) => candidate.matches({ key, value, matchesDefault }))!;
|
|
50
|
+
return { key, class: rule.class, rule: rule.rule, overridesDefault: !matchesDefault };
|
|
51
|
+
});
|
|
52
|
+
const counts: ConfigClassifyReport['counts'] = {
|
|
53
|
+
secret: 0, 'product-default': 0, 'machine-fact': 0, operational: 0,
|
|
54
|
+
};
|
|
55
|
+
for (const leaf of leaves) counts[leaf.class]++;
|
|
56
|
+
return {
|
|
57
|
+
totalLeaves: leaves.length,
|
|
58
|
+
overridesDefault: leaves.filter((leaf) => leaf.overridesDefault).length,
|
|
59
|
+
counts, leaves,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function renderClassify(report: ConfigClassifyReport): string {
|
|
64
|
+
const labels: Record<ConfigClass, string> = {
|
|
65
|
+
secret: '③ 비밀', 'product-default': '① 제품 기본값',
|
|
66
|
+
'machine-fact': '② 기계 사실', operational: '④ 운영 정책·상태',
|
|
67
|
+
};
|
|
68
|
+
const lines = [`config classify — 잎 ${report.totalLeaves} · 운영 덮어쓰기 ${report.overridesDefault}`];
|
|
69
|
+
for (const { class: kind } of CLASSIFY_RULES) {
|
|
70
|
+
const leaves = report.leaves.filter((leaf) => leaf.class === kind);
|
|
71
|
+
lines.push(` ${labels[kind]} ${report.counts[kind]}`);
|
|
72
|
+
const topKeys = new Map<string, number>();
|
|
73
|
+
for (const leaf of leaves) {
|
|
74
|
+
const top = leaf.key.split('.')[0]!;
|
|
75
|
+
topKeys.set(top, (topKeys.get(top) ?? 0) + 1);
|
|
76
|
+
}
|
|
77
|
+
const leaders = [...topKeys].sort(([a, ac], [b, bc]) => bc - ac || a.localeCompare(b)).slice(0, 10);
|
|
78
|
+
if (leaders.length) lines.push(` 상위 키: ${leaders.map(([key, count]) => `${key} × ${count}`).join(' · ')}`);
|
|
79
|
+
for (const leaf of leaves.slice(0, 10)) lines.push(` ${leaf.key}${kind === 'secret' ? ` = ${MASK}` : ''}`);
|
|
80
|
+
if (leaves.length > 10) lines.push(` … ${leaves.length - 10}개 더 (--json)`);
|
|
81
|
+
}
|
|
82
|
+
return lines.join('\n');
|
|
83
|
+
}
|
package/src/cli/control-cli.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { RESOURCE_KINDS, type ResourceKind } from '../control-plane/ledger.js';
|
|
|
13
13
|
import { runControlJoin, type ControlJoinOptions } from './control-join-cli.js';
|
|
14
14
|
import type { ResourceView } from '../control-plane/ledger.js';
|
|
15
15
|
import { issueMemberToken, revokeMemberToken, listMemberTokens } from '../control-plane/member-tokens.js';
|
|
16
|
+
import { collectControlStatus } from '../control/control-status.js';
|
|
16
17
|
|
|
17
18
|
function controlPort(flag?: string): number {
|
|
18
19
|
const raw = flag ?? (process.env.ELANOUS_CONTROL_PORT?.trim() || undefined);
|
|
@@ -152,6 +153,19 @@ export async function runControlMember(options: { resource?: string[]; interval?
|
|
|
152
153
|
|
|
153
154
|
export function registerControlCommands(program: Command): void {
|
|
154
155
|
const control = program.command('control').description('독립 관제부');
|
|
156
|
+
control.command('status').description('집 하나의 읽기 전용 관제 현황')
|
|
157
|
+
.option('--json', '일곱 줄을 JSON으로 출력')
|
|
158
|
+
.action(async (options: { json?: boolean }) => {
|
|
159
|
+
const rows = await collectControlStatus();
|
|
160
|
+
if (options.json) console.log(JSON.stringify({ rows }));
|
|
161
|
+
else for (const { row, state, text, reason } of rows) {
|
|
162
|
+
const label = state === 'ok' ? '정상' : state === 'warn' ? '주의' : '못 쟀다';
|
|
163
|
+
console.log(`${row} · ${label} · ${text}${reason ? ` — ${reason}` : ''}`
|
|
164
|
+
.replace(/\r/g, '\\r').replace(/\n/g, '\\n')
|
|
165
|
+
// Other C0/C1 controls (ESC, backspace, ...) and line separators must not repaint the terminal table.
|
|
166
|
+
.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, (ch) => `\\u${ch.charCodeAt(0).toString(16).padStart(4, '0')}`));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
155
169
|
control.command('serve').description('관제부를 포그라운드에서 실행')
|
|
156
170
|
.option('--port <n>', '리스닝 포트(0=임시 포트)')
|
|
157
171
|
.option('--host <addr>', '루프백 또는 tailnet 바인드 주소')
|