elanous 0.2.17 → 0.2.19
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/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
- package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/decisions/index.html +1 -0
- package/apps/pwa/out/decisions/index.txt +30 -0
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +25 -24
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +24 -23
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +28 -27
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/package.json +2 -1
- package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
- package/samples/workflows/templates/chat-driven-research.yaml +35 -0
- package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
- package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
- package/samples/workflows/templates/manual-quickstart.yaml +21 -0
- package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
- package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
- package/scripts/ci-public-export-import-gate.ts +7 -2
- package/scripts/ci-typecheck-changed.ts +21 -6
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +6 -3
- package/scripts/docs/generate-cli-anatomy.ts +162 -0
- package/scripts/goals/authoring-metrics.ts +73 -0
- package/scripts/landing-heal/must-fix-match.ts +7 -7
- package/scripts/landing-heal/run-mission.ts +3 -2
- package/scripts/lib/pch-regress-map.ts +23 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/public-export-test-run.ts +37 -2
- package/scripts/release-loop/checklist-gate-node.ts +13 -11
- package/scripts/release-loop/cut-branch.ts +2 -1
- package/scripts/release-loop/cutoff.ts +4 -2
- package/scripts/release-loop/docs-land-node.ts +8 -1
- package/scripts/release-loop/docs-node.ts +1 -1
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +436 -59
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +120 -0
- package/scripts/release-loop/node-verdict.ts +2 -1
- package/scripts/release-loop/npm-publish-node.ts +11 -7
- package/scripts/release-loop/ops-upgrade-node.ts +8 -1
- package/scripts/release-loop/preflight.ts +246 -0
- package/scripts/release-loop/publish-node.ts +8 -1
- package/scripts/release-loop/release-readiness.ts +11 -6
- package/scripts/release-loop/release-version.ts +48 -0
- package/scripts/release-loop/resume-release.ts +174 -0
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +45 -7
- package/scripts/release-loop/version-node.ts +145 -40
- package/scripts/rhythm/daily.ts +25 -4
- package/scripts/skills/public-check.ts +62 -12
- package/scripts/test-deterministic.ts +51 -33
- package/scripts/test-diet/effectiveness.ts +62 -1
- package/scripts/test-diet/lib.ts +1 -1
- package/scripts/test-diet/node.ts +8 -1
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/agent-mission/review-loop.ts +4 -3
- package/src/agent-substrate/pr-comment-meta.ts +20 -0
- package/src/autopilot/build/llm-conflict-merge.ts +28 -8
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +255 -0
- package/src/briefing/brief-items.ts +86 -17
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/autopilot-cli.ts +30 -11
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +85 -7
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/provider-cli.ts +12 -2
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +128 -19
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +403 -4
- package/src/context-bus/condense.ts +19 -3
- package/src/context-bus/context-day.ts +77 -24
- package/src/context-bus/context-first.ts +33 -0
- package/src/context-bus/context-now-public.ts +1 -1
- package/src/context-bus/context-now-surfaces.ts +16 -6
- package/src/context-bus/context-now.ts +97 -3
- package/src/core-turn/short-question.ts +100 -0
- package/src/dashboard/dashboard-status-lines.ts +24 -4
- package/src/dashboard/index.ts +50 -5
- package/src/dashboard/input/slash-executor.ts +6 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/discord-self-message.ts +20 -2
- package/src/discord-slash-wire.ts +1 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +244 -40
- package/src/domains/outbound-log-probe.ts +16 -1
- package/src/domains/release-tool.ts +2 -2
- package/src/domains/schedule-registry.ts +23 -0
- package/src/domains/telegram-kind-route.ts +39 -0
- package/src/domains/watch-default-scheduler.ts +95 -0
- package/src/esc-abort-gate.ts +44 -0
- package/src/execution-loop/gates.ts +11 -2
- package/src/flow-loop/split.ts +8 -4
- package/src/git-fs/worktree.ts +23 -3
- package/src/graph-runner/runner.ts +25 -5
- package/src/harness/harness-cli-command.ts +12 -2
- package/src/harness/harness-queue.ts +388 -33
- package/src/harness/harness-salvage-cli.ts +78 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/helper-scan.ts +10 -3
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/hq.ts +6 -1
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +185 -18
- package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
- package/src/instance/test-write-guard.ts +200 -0
- package/src/intake-plane/adopt-gate.ts +49 -0
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/collect-github.ts +57 -25
- package/src/intake-plane/digest.ts +39 -15
- package/src/intake-plane/discord-seat-work.ts +8 -2
- package/src/intake-plane/harness-target.ts +4 -3
- package/src/intake-plane/intake-sources.ts +1 -0
- package/src/intake-plane/items.ts +1 -0
- package/src/intake-plane/requirement-funnel.ts +316 -0
- package/src/knowledge/know-find.ts +10 -1
- package/src/loops/budget.ts +36 -0
- package/src/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +106 -4
- package/src/loops/orchestrator/grid-place.ts +11 -0
- package/src/loops/orchestrator/tick.ts +450 -54
- package/src/maturity/feature-maturity.ts +8 -5
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- package/src/nexus/api/decisions-route.ts +11 -1
- package/src/nexus/api/http-server.ts +16 -5
- package/src/nexus/api/loop-edges.ts +352 -23
- package/src/nexus/api/ops-api.ts +96 -28
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/nexus/index.ts +7 -0
- package/src/oauth/codex.ts +38 -0
- package/src/onboarding/first-chat-gate.ts +29 -0
- package/src/release-loop/checklist.ts +106 -6
- package/src/release-loop/feature-store.ts +64 -16
- package/src/release-loop/landing-freeze.ts +6 -4
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +64 -20
- package/src/release-loop/rubric.ts +136 -0
- package/src/seat-dispatch/seat-ask.ts +79 -16
- package/src/seat-loop/seat-loop.ts +12 -6
- package/src/self-dev/ask-launch-flow.ts +24 -3
- package/src/self-dev/ask-path-candidates.ts +68 -0
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/dev-pipeline.ts +4 -0
- package/src/self-dev/draft-triage-rules.ts +1 -1
- package/src/self-dev/orchestrate-cli.ts +22 -0
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +179 -0
- package/src/self-dev/round-pr-supersede.ts +244 -0
- package/src/self-dev/run-supervisor.ts +14 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-author.ts +8 -3
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +61 -13
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +101 -7
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +203 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +49 -5
- package/src/task-agent/live-moves.ts +176 -0
- package/src/task-agent/mission.ts +449 -0
- package/src/task-agent/shadow.ts +258 -0
- package/src/task-agent/task-hand.ts +337 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
- package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +88 -20
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +11 -0
- package/src/telegram-loops-command.ts +1 -1
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/telegram.ts +5 -20
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +196 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-aac7332576c4d6fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.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/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +0 -2
- package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3143b07a817d1da9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-17ba086ef82d339b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-6e5c3a867557298b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4187666132fc98c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-09328d499162bae4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-29ff3a8f84f03b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-80ec7401bc83f511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-fc7d86467291ae38.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-043a9111e74f6b24.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-23a4601ceb7f452e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-10d958605a77b3bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-12916918988ac97e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-7f49fe645d5132f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-424659bacfb8cdd7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d4fef9ccd7d406b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
package/src/index.ts
CHANGED
|
@@ -51,6 +51,7 @@ import { registerRoleCommands } from './cli/role-cli.js';
|
|
|
51
51
|
import { registerMachineCommands } from './cli/machine-cli.js';
|
|
52
52
|
import { registerAgentEnvCommands } from './cli/agent-env-cli.js';
|
|
53
53
|
import { registerScheduleCommands } from './cli/schedule-cli.js';
|
|
54
|
+
import { registerGuardianCommands } from './cli/guardian-cli.js';
|
|
54
55
|
export { runSchedule, scheduleCreatePlan } from './cli/schedule-cli.js';
|
|
55
56
|
export type { ScheduleDispatch } from './cli/schedule-cli.js';
|
|
56
57
|
import { registerReviewLoopOptions, buildReviewLoopOpts } from './agent-mission/review-loop-cli.js';
|
|
@@ -74,6 +75,8 @@ import { registerModelWatchCommand } from './cli/model-watch-cli.js';
|
|
|
74
75
|
import { registerIntakeCommands } from './cli/intake-cli.js';
|
|
75
76
|
import { registerTasksCommands } from './cli/tasks-cli.js';
|
|
76
77
|
import { registerBriefCommands } from './cli/brief-cli.js';
|
|
78
|
+
import { registerNotifyCommand } from './cli/notify-cli.js';
|
|
79
|
+
import { registerAwayCommand } from './cli/away-cli.js';
|
|
77
80
|
import { registerLabelsCommands } from './cli/labels-cli.js';
|
|
78
81
|
import { registerMsgCommands } from './cli/msg-cli.js';
|
|
79
82
|
import { registerDoctorCommand } from './cli/doctor-cli.js';
|
|
@@ -135,6 +138,7 @@ import type { ReviewResult } from './agent-substrate/pr-reviewer.js';
|
|
|
135
138
|
import type { ReviewVerdict } from './agent-mission/review-loop.js';
|
|
136
139
|
import type { SyncMode } from './types.js';
|
|
137
140
|
import { runOnboarding, runOnboardingStep, runOnboardingNonInteractive, needsOnboarding, needsFirstRun, handleOnboardingRefusal, type OnboardingStepId } from './onboarding.js';
|
|
141
|
+
import { firstChatGate } from './onboarding/first-chat-gate.js';
|
|
138
142
|
import {
|
|
139
143
|
getUserConfig, reloadUserConfig, userConfigPath, saveUserConfig,
|
|
140
144
|
backupUserConfig, backupConfigPath, addRotationEntry, type RotationEntry,
|
|
@@ -213,6 +217,23 @@ export { cliVersion, setInstallMetadataRootForTesting };
|
|
|
213
217
|
export const program = new Command();
|
|
214
218
|
program.enablePositionalOptions();
|
|
215
219
|
|
|
220
|
+
program.command('preview <target>')
|
|
221
|
+
.description('Isolated PR/branch-head TUI + PWA preview; publish the TUI-COMFORT friction table to a PR')
|
|
222
|
+
.option('--scenario <id>', 'TUI-COMFORT scenario (default S2)', 'S2')
|
|
223
|
+
.option('--flag <path>', 'Enable an existing config feature flag in main-based preview (repeatable)', (value: string, all: string[]) => [...all, value], [] as string[])
|
|
224
|
+
.option('--comment-pr <number>', 'PR to receive a branch/main preview report', (value: string) => Number(value))
|
|
225
|
+
.action(async (target: string, opts: { scenario: string; flag: string[]; commentPr?: number }) => {
|
|
226
|
+
try {
|
|
227
|
+
const { runPrPreview } = await import('./self-dev/pr-preview.js');
|
|
228
|
+
const result = await runPrPreview({ target, repoRoot: process.cwd(), scenario: opts.scenario, flags: opts.flag, ...(opts.commentPr === undefined ? {} : { commentPr: opts.commentPr }) });
|
|
229
|
+
console.log(`preview ${result.status} · ${result.head} · PR #${result.prNumber} · comment posted`);
|
|
230
|
+
if (result.status !== 'passed') process.exitCode = 1;
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
233
|
+
process.exitCode = 1;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
216
237
|
export { SELF_SEND_RECENT_FRAME_WINDOW_MS, SELF_SEND_STALE_HEARTBEAT_MS, formatSelfSendCandidateDisplay } from './harness/self-send-decide.js';
|
|
217
238
|
export type { SelfSendCandidate, SelfSendCandidateDisplay } from './harness/self-send-decide.js';
|
|
218
239
|
import { decideSelfSend, type SelfSendCandidate } from './harness/self-send-decide.js';
|
|
@@ -888,6 +909,8 @@ registerLiveCommands(program);
|
|
|
888
909
|
registerResearchCommand(program);
|
|
889
910
|
registerReleaseCommands(program);
|
|
890
911
|
registerKnowCommand(program);
|
|
912
|
+
registerNotifyCommand(program);
|
|
913
|
+
registerAwayCommand(program);
|
|
891
914
|
registerFreezeCommands(program);
|
|
892
915
|
registerFlowCommands(program);
|
|
893
916
|
// HQ-HB · HQ-FENCE (10-04): one writer across mbp · node-b · cloud-vm — lease on the arbiter, 2-of-3 quorum, generation fencing.
|
|
@@ -985,6 +1008,37 @@ hqCmd.command('seen').description('이 호스트가 본 가장 높은 임대 세
|
|
|
985
1008
|
process.exitCode = result.seen ? 0 : 3;
|
|
986
1009
|
});
|
|
987
1010
|
registerHqMovePlanCommand(hqCmd);
|
|
1011
|
+
hqCmd.command('init').description('한 저장소의 bare 미러와 집 원장을 만들고 5분 fetch 크론 줄만 출력')
|
|
1012
|
+
.option('--root <dir>', 'HQ 폴더 (기본 ~/elanous-hq)')
|
|
1013
|
+
.action(async (opts: { root?: string }) => {
|
|
1014
|
+
try {
|
|
1015
|
+
const { hqInit } = await import('./hq/seats.js');
|
|
1016
|
+
const { getElanousConfigDir } = await import('./elanous-config-dir.js');
|
|
1017
|
+
const result = hqInit({ ...opts, home: getElanousConfigDir() });
|
|
1018
|
+
console.log(`hq init: ${result.created ? 'created' : 'already exists'} ${result.root}\n# OP installs this line; nothing was installed:\n${result.cron}`);
|
|
1019
|
+
} catch (error) { console.error(`hq init: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
1020
|
+
});
|
|
1021
|
+
hqCmd.command('seat <role>').description('OP|MK|TC|UX 자리 워크트리 (깨끗할 때만 갱신)')
|
|
1022
|
+
.option('--refresh', 'origin/main으로 깨끗한 자리만 최신화')
|
|
1023
|
+
.option('--root <dir>', 'HQ 폴더')
|
|
1024
|
+
.action(async (role: string, opts: { refresh?: boolean; root?: string }) => {
|
|
1025
|
+
try {
|
|
1026
|
+
const { hqSeat } = await import('./hq/seats.js');
|
|
1027
|
+
const result = hqSeat(role, opts);
|
|
1028
|
+
if (result.outcome === 'dirty') console.warn(`hq seat ${role}: dirty — refresh skipped; move edits to hq work new`);
|
|
1029
|
+
else console.log(`hq seat ${role}: ${result.outcome} ${result.path}`);
|
|
1030
|
+
} catch (error) { console.error(`hq seat: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
1031
|
+
});
|
|
1032
|
+
const hqWorkCmd = hqCmd.command('work').description('고칠 때만 작업 워크트리를 만들고 병합 후 회수');
|
|
1033
|
+
for (const action of ['new', 'done'] as const) hqWorkCmd.command(`${action} <role> <name>`)
|
|
1034
|
+
.option('--root <dir>', 'HQ 폴더')
|
|
1035
|
+
.action(async (role: string, name: string, opts: { root?: string }) => {
|
|
1036
|
+
try {
|
|
1037
|
+
const { hqWork } = await import('./hq/seats.js');
|
|
1038
|
+
const result = hqWork(action, role, name, opts);
|
|
1039
|
+
console.log(`hq work ${action}: ${result.outcome} ${result.path}`);
|
|
1040
|
+
} catch (error) { console.error(`hq work ${action}: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
1041
|
+
});
|
|
988
1042
|
const fenceWrapperCmd = hqCmd.command('fence-wrapper').description('설치본 HQ 울타리 크론 래퍼');
|
|
989
1043
|
fenceWrapperCmd.command('install').description('래퍼 제안 또는 백업 후 원자적 설치')
|
|
990
1044
|
.option('--dry-run', '변경 내용만 출력 (기본)')
|
|
@@ -1042,6 +1096,40 @@ hqCmd.command('fence').description('이 호스트가 임대를 쥐었을 때만
|
|
|
1042
1096
|
});
|
|
1043
1097
|
const seatCmd = program.command('seat').description('분배된 자리의 하루 루프와 보고');
|
|
1044
1098
|
registerSeatRequestsCommands(seatCmd);
|
|
1099
|
+
seatCmd.command('asks').description('최근 CTO 되묻기 왕복과 10분 판정')
|
|
1100
|
+
.option('--since <duration>', '조회 창 (예: 24h)', '24h')
|
|
1101
|
+
.option('--json', 'JSON 출력')
|
|
1102
|
+
.action(async (opts: { since: string; json?: boolean }) => {
|
|
1103
|
+
try {
|
|
1104
|
+
const duration = /^(\d+)([mhd])$/.exec(opts.since);
|
|
1105
|
+
if (!duration || !Number.isSafeInteger(Number(duration[1]))) throw new Error('--since: 24h, 30m, 7d 형식 필요');
|
|
1106
|
+
const ms = Number(duration[1]) * ({ m: 60_000, h: 3_600_000, d: 86_400_000 }[duration[2]!] ?? 0);
|
|
1107
|
+
if (!Number.isSafeInteger(ms) || ms <= 0) throw new Error('--since: 양의 시간 필요');
|
|
1108
|
+
const { defaultMsgStorePath } = await import('./msg/msg-store.js');
|
|
1109
|
+
const path = defaultMsgStorePath();
|
|
1110
|
+
if (!(await import('node:fs')).existsSync(path)) {
|
|
1111
|
+
if (opts.json) await writeStdoutJson('[]\n');
|
|
1112
|
+
else console.log(`원장 없음 — ${path}`);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
const { listSeatAskRoundTrips } = await import('./seat-dispatch/seat-ask.js');
|
|
1116
|
+
const now = Date.now();
|
|
1117
|
+
const rows = listSeatAskRoundTrips({ since: now - ms, now });
|
|
1118
|
+
if (!rows.length) {
|
|
1119
|
+
if (opts.json) await writeStdoutJson('[]\n');
|
|
1120
|
+
else console.log('이 창에 되묻기 0건');
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (opts.json) { await writeStdoutJson(`${JSON.stringify(rows)}\n`); return; }
|
|
1124
|
+
const clock = (value: number | null) => value === null ? '-' : new Intl.DateTimeFormat('ko-KR', {
|
|
1125
|
+
timeZone: 'Asia/Seoul', hour: '2-digit', minute: '2-digit', hourCycle: 'h23',
|
|
1126
|
+
}).format(value);
|
|
1127
|
+
console.log('id · 표면 · 물음 HH:MM KST · 답 · 도착 · 왕복 분 · 판정');
|
|
1128
|
+
for (const row of rows) console.log(`${row.id} · ${row.surface} · ${clock(row.askedAt)} KST · ${clock(row.answeredAt)} · ${clock(row.deliveredAt)} · ${row.roundTripMs === null ? '-' : (row.roundTripMs / 60_000).toFixed(1)}분 · ${row.verdict}`);
|
|
1129
|
+
const count = (verdict: string) => rows.filter(row => row.verdict === verdict).length;
|
|
1130
|
+
console.log(`10분 안 ${count('10분 안')} / 넘음 ${count('10분 넘음')} / 미답 ${count('미답')} / 대기 ${count('답 대기') + count('전달 대기')} / 미상 ${count('시각 미상')}`);
|
|
1131
|
+
} catch (error) { console.error(`seat asks: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
|
|
1132
|
+
});
|
|
1045
1133
|
seatCmd.command('answer <askId> <answer>').description('자리 질문에 답을 남기고 원래 표면으로 회신 예약')
|
|
1046
1134
|
.action(async (askId: string, answer: string) => {
|
|
1047
1135
|
try {
|
|
@@ -3299,11 +3387,20 @@ contextCmd.command('day')
|
|
|
3299
3387
|
.description('자리 넷과 외부 세션의 최근 맥락 이벤트 수·마지막 시각 (읽기 전용)')
|
|
3300
3388
|
.option('--since <hours>', '최근 시간 창 (예: 24h)', '24h')
|
|
3301
3389
|
.option('--json', '구조화된 JSON 출력')
|
|
3302
|
-
.
|
|
3390
|
+
.option('--timeline', '시간순 사건 한 줄씩 (KST)')
|
|
3391
|
+
.option('--seat <source>', '한 출처만 (OP|TC|MK|UX|claude-code|codex-agent-mission|harness-child)')
|
|
3392
|
+
.action(async (opts: { since: string; json?: boolean; timeline?: boolean; seat?: string }) => {
|
|
3303
3393
|
try {
|
|
3304
|
-
const { contextDay, renderContextDay } = await import('./context-bus/context-day.js');
|
|
3305
|
-
|
|
3306
|
-
|
|
3394
|
+
const { contextDay, contextDayTimeline, renderContextDay, renderContextDayTimeline, CONTEXT_DAY_SOURCES } = await import('./context-bus/context-day.js');
|
|
3395
|
+
if (opts.seat && !CONTEXT_DAY_SOURCES.some(source => source === opts.seat)) throw new Error(`unknown --seat: ${opts.seat}`);
|
|
3396
|
+
if (opts.seat && !opts.timeline) throw new Error('--seat requires --timeline');
|
|
3397
|
+
if (opts.timeline) {
|
|
3398
|
+
const report = contextDayTimeline(opts.since, { seat: opts.seat as typeof CONTEXT_DAY_SOURCES[number] | undefined });
|
|
3399
|
+
await writeStdoutJson(`${opts.json ? JSON.stringify(report) : renderContextDayTimeline(report)}\n`);
|
|
3400
|
+
} else {
|
|
3401
|
+
const report = contextDay(opts.since);
|
|
3402
|
+
await writeStdoutJson(`${opts.json ? JSON.stringify(report) : renderContextDay(report)}\n`);
|
|
3403
|
+
}
|
|
3307
3404
|
} catch (error) {
|
|
3308
3405
|
console.error(`context day: ${error instanceof Error ? error.message : String(error)}`);
|
|
3309
3406
|
process.exitCode = 2;
|
|
@@ -4337,16 +4434,52 @@ selfCmd
|
|
|
4337
4434
|
.option('--base <ref>', '커밋됐지만 PR 없는 변경의 비교 기준(ref...HEAD)')
|
|
4338
4435
|
.option('--pr <number>', '열린 PR 번호에서 gh pr view files로 변경 파일을 읽는다')
|
|
4339
4436
|
.option('--shards [number]', '메모리 예산에 맞춰 시험을 별도 프로세스로 분할한다 (기본 2)')
|
|
4340
|
-
.
|
|
4437
|
+
.option('--remote [host]', 'GATE-REMOTE — 같은 커밋을 원격 호스트(기본 config gateRemote.host=node-b)에서 돌리고 결과만 가져온다(깨끗한 트리 · --pr 은 로컬)')
|
|
4438
|
+
.option('--local', '원격 자동 분배(로컬 1분 부하 > gateRemote.loadThreshold=20)를 끄고 로컬에서 돌린다')
|
|
4439
|
+
.action(async (opts: { changed?: boolean; base?: string; pr?: string; shards?: string | boolean; remote?: string | boolean; local?: boolean }) => {
|
|
4341
4440
|
if (!opts.changed && opts.shards === undefined) { ui.error('self gate requires --changed or --shards'); process.exitCode = 2; return; }
|
|
4441
|
+
// The root strips `--local` from argv anywhere (remote-bookmark attach · src/cli/remote-resolve.ts) before Commander
|
|
4442
|
+
// sees it, so this subcommand reads it from the untouched process.argv.
|
|
4443
|
+
opts.local = !!opts.local || process.argv.slice(2).includes('--local');
|
|
4444
|
+
if (opts.remote !== undefined && opts.local) { ui.error('self gate: --remote and --local cannot be combined'); process.exitCode = 2; return; }
|
|
4445
|
+
if (typeof opts.remote === 'string') {
|
|
4446
|
+
const { isValidGateHost } = await import('./self-implement/gate-remote.js');
|
|
4447
|
+
if (!isValidGateHost(opts.remote)) { ui.error(`self gate: invalid --remote host: ${opts.remote}`); process.exitCode = 2; return; }
|
|
4448
|
+
}
|
|
4449
|
+
const shards = opts.shards === undefined ? undefined : opts.shards === true ? 2 : Number(opts.shards);
|
|
4450
|
+
const runLocal = () => {
|
|
4451
|
+
try {
|
|
4452
|
+
const { runSelfGateCli } = require('./self-implement/gate-cli.js') as typeof import('./self-implement/gate-cli.js');
|
|
4453
|
+
const result = runSelfGateCli(process.cwd(), { base: opts.base, pr: opts.pr, shards });
|
|
4454
|
+
console.log(result.lines.join('\n'));
|
|
4455
|
+
return result.exitCode;
|
|
4456
|
+
} catch (error) {
|
|
4457
|
+
ui.error(`self gate failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4458
|
+
return 1;
|
|
4459
|
+
}
|
|
4460
|
+
};
|
|
4342
4461
|
try {
|
|
4343
|
-
const {
|
|
4344
|
-
const
|
|
4345
|
-
|
|
4346
|
-
|
|
4462
|
+
const { dispatchHeavyCheck } = await import('./self-implement/gate-remote.js');
|
|
4463
|
+
const { execFileSync } = await import('node:child_process');
|
|
4464
|
+
const repo = execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim();
|
|
4465
|
+
// Only a committed --base range is reproducible on another host: the default mode measures uncommitted files, --pr
|
|
4466
|
+
// reads files through gh on this host, and the remote checkout runs from the repository root — those stay local.
|
|
4467
|
+
const { realpathSync } = await import('node:fs');
|
|
4468
|
+
const localOnlyReason = opts.pr ? 'self gate --pr reads PR files through gh on this host'
|
|
4469
|
+
: !opts.base ? 'self gate without --base measures uncommitted files on this host'
|
|
4470
|
+
: realpathSync(repo) !== realpathSync(process.cwd()) ? 'self gate run from a sub-directory' : undefined;
|
|
4471
|
+
// origin/main is pinned to this machine's origin/main on the host; any other base is passed as its resolved sha.
|
|
4472
|
+
const remoteArgv = (sha: (ref: string) => string) => ['bun', 'bin/elanous.mjs', 'self', 'gate', '--local', ...(opts.changed ? ['--changed'] : []),
|
|
4473
|
+
...(opts.base ? ['--base', opts.base === 'origin/main' ? opts.base : sha(opts.base)] : []), ...(shards !== undefined ? ['--shards', String(shards)] : [])];
|
|
4474
|
+
process.exitCode = await dispatchHeavyCheck({
|
|
4475
|
+
tool: 'self-gate', repo, installPwa: true, remoteArgv, refs: opts.base ? [opts.base] : [],
|
|
4476
|
+
localOnlyReason,
|
|
4477
|
+
flags: { remote: typeof opts.remote === 'string' ? opts.remote : opts.remote ? true : undefined, local: !!opts.local },
|
|
4478
|
+
runLocal,
|
|
4479
|
+
});
|
|
4347
4480
|
} catch (error) {
|
|
4348
|
-
ui.error(`self gate failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4349
|
-
process.exitCode =
|
|
4481
|
+
ui.error(`self gate dispatch failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4482
|
+
process.exitCode = runLocal();
|
|
4350
4483
|
}
|
|
4351
4484
|
});
|
|
4352
4485
|
|
|
@@ -5866,6 +5999,7 @@ program
|
|
|
5866
5999
|
});
|
|
5867
6000
|
|
|
5868
6001
|
registerScheduleCommands(program);
|
|
6002
|
+
registerGuardianCommands(program);
|
|
5869
6003
|
|
|
5870
6004
|
/** `elanous decide*` 공통 — 보낼 곳과 자격을 정한다(설정 decide.endpoint > ELANOUS_JEV_ENDPOINT > Typesafe). 없으면 안내하고 rc 2. */
|
|
5871
6005
|
async function loadJevAccessOrExit(): Promise<import('./decide/jev.js').JevAccess> {
|
|
@@ -7060,7 +7194,7 @@ const selfDevCmd = program
|
|
|
7060
7194
|
const members = parsePodPool(devAskSubstrate.pool!);
|
|
7061
7195
|
const host = podPoolHostLease(members);
|
|
7062
7196
|
while (!authoringLease) {
|
|
7063
|
-
const status = recommendConcurrency(measurePoolLease(members), { capacity: members.reduce((n, m) => n + m.capacity, 0), accounts: 0, perAccount: 0 });
|
|
7197
|
+
const status = recommendConcurrency(measurePoolLease(members, { measureUsage: true }), { capacity: members.reduce((n, m) => n + m.capacity, 0), accounts: 0, perAccount: 0 });
|
|
7064
7198
|
if (status.recommended !== null && status.recommended > 0) {
|
|
7065
7199
|
const { leaseHasPendingPod } = await import('./pod-lease/host-lease.js');
|
|
7066
7200
|
const pendingJobs = status.pendingJobs ?? [];
|
|
@@ -7215,7 +7349,23 @@ const selfDevCmd = program
|
|
|
7215
7349
|
if (hasExplicitReviewerContext && loaded.failed.length > 0) throw new DevPipelineError(contextStatus);
|
|
7216
7350
|
if (loaded.items.length > 0) spec = { ...spec, reviewerContext: loaded.items };
|
|
7217
7351
|
}
|
|
7218
|
-
|
|
7352
|
+
// ⭐ TUI-ONE-WORD-LAUNCH — 명시 격리 우주에서 `--cwd`·`--worktree` 둘 다 없으면 «자동» 갈래로
|
|
7353
|
+
// 같은 `prepareDevWorktree` 를 탄다(새 생성 경로 없음). 명시 `--cwd` 는 그대로 이긴다.
|
|
7354
|
+
const autoWorktreeDecision = await (async () => {
|
|
7355
|
+
if (!devOpts.elanous || devOpts.worktree === true || devOpts.cwd !== undefined) {
|
|
7356
|
+
return devCli.decideAutoWorktree({ explicitlyIsolated: false, ...devOpts });
|
|
7357
|
+
}
|
|
7358
|
+
const [{ resolveCurrentInstance }, { isExplicitlyIsolated }] = await Promise.all([
|
|
7359
|
+
import('./instance/current.js'),
|
|
7360
|
+
import('./cli/pty-drive-cli.js'),
|
|
7361
|
+
]);
|
|
7362
|
+
return devCli.decideAutoWorktree({
|
|
7363
|
+
explicitlyIsolated: isExplicitlyIsolated(resolveCurrentInstance(), devOpts.isolatedRoot),
|
|
7364
|
+
...devOpts,
|
|
7365
|
+
});
|
|
7366
|
+
})();
|
|
7367
|
+
debug.log('dev-pipeline', 'auto-worktree-decision', { runId: devRunId, decision: autoWorktreeDecision });
|
|
7368
|
+
if (autoWorktreeDecision === 'requested' || autoWorktreeDecision === 'auto') {
|
|
7219
7369
|
const { prepareDevWorktree, renderPreparedDevWorktree } = await import('./harness/harness-worktree-auto.js');
|
|
7220
7370
|
const goal = 'file' in input
|
|
7221
7371
|
? await (async () => {
|
|
@@ -7225,13 +7375,25 @@ const selfDevCmd = program
|
|
|
7225
7375
|
return { id: goalId, file: input.file };
|
|
7226
7376
|
})()
|
|
7227
7377
|
: undefined;
|
|
7228
|
-
const prepared =
|
|
7378
|
+
const prepared = (() => {
|
|
7379
|
+
try {
|
|
7380
|
+
return prepareDevWorktree(process.cwd(), devRunId, invokedAsDrive ? 'drive' : 'dev', goal);
|
|
7381
|
+
} catch (error) {
|
|
7382
|
+
if (autoWorktreeDecision !== 'auto') throw error;
|
|
7383
|
+
debug.log('dev-pipeline', 'auto-worktree-failed', { runId: devRunId, cwd: process.cwd(), msg: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
|
|
7384
|
+
throw new DevPipelineError(devCli.formatAutoWorktreeFailure(error));
|
|
7385
|
+
}
|
|
7386
|
+
})();
|
|
7229
7387
|
preparedWorktree = prepared;
|
|
7230
7388
|
if (spec.elanous) spec = { ...spec, elanous: { ...spec.elanous, cwd: prepared.worktree.path } };
|
|
7231
7389
|
else if (spec.drive) spec = { ...spec, drive: { ...spec.drive, cwd: prepared.worktree.path } };
|
|
7232
7390
|
else throw new DevPipelineError('--worktree 는 elanous 또는 셸 drive child에만 유효');
|
|
7233
7391
|
if (!opts.json) console.log(renderPreparedDevWorktree(prepared).join('\n'));
|
|
7234
7392
|
debug.log('dev-pipeline', 'auto-worktree-prepared', { runId: devRunId, ...prepared.environment, worktree: prepared.worktree });
|
|
7393
|
+
if (autoWorktreeDecision === 'auto') {
|
|
7394
|
+
if (!opts.json) console.log(devCli.formatAutoWorktreeNotice(prepared.worktree.path));
|
|
7395
|
+
debug.log('dev-pipeline', 'auto-worktree-implied', { runId: devRunId, worktree: prepared.worktree.path });
|
|
7396
|
+
}
|
|
7235
7397
|
}
|
|
7236
7398
|
// ⛔ 종전의 `const plan = planDevPipeline(spec)` 를 지운다(무인 리뷰 must-fix) — 쓰이지 않는
|
|
7237
7399
|
// 데다 **잔여 조회보다 먼저 던질 수 있어** 관측 자체를 건너뛰게 만든다. 계획은 어차피
|
|
@@ -8599,10 +8761,15 @@ program
|
|
|
8599
8761
|
}
|
|
8600
8762
|
if (opts.tools) announceChatToolsCompatibility();
|
|
8601
8763
|
const cfg = getUserConfig();
|
|
8602
|
-
if (
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8764
|
+
if (await firstChatGate(cfg, {
|
|
8765
|
+
isTTY: process.stdin.isTTY === true,
|
|
8766
|
+
runFirstRun: async (deps) => (await import('./onboarding/first-run.js')).runFirstRun(deps),
|
|
8767
|
+
runInteractive: () => {
|
|
8768
|
+
if (cfg.onboarding.webFirst === false) ui.info('No config yet — launching setup wizard first.');
|
|
8769
|
+
return runFirstSetupForCli(cfg, true);
|
|
8770
|
+
},
|
|
8771
|
+
print: (line) => console.log(line),
|
|
8772
|
+
}) === 'stop') return;
|
|
8606
8773
|
const refreshed = reloadUserConfig();
|
|
8607
8774
|
// ★ 관측갭 수리(2026-07-21·제1원칙·트랙A) — self-implement 자식 goal-loop(`chat --goal-loop`)은 데몬과
|
|
8608
8775
|
// 별개 독립 프로세스라 nexus StoreSink 를 상속 안 한다. 부모 `self implement`(위 self 커맨드)는 sink 를
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Child fixture for test-write-guard.test.ts: a bare spy installed BEFORE outbound-alert is imported must not
|
|
2
|
+
// make the captured «real» curl look like a fake. Prints {refused, curlCalls}.
|
|
3
|
+
import { spyOn } from 'bun:test';
|
|
4
|
+
import * as childProcess from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
const bare = spyOn(childProcess, 'execFileSync');
|
|
7
|
+
const { sendTelegramDirect } = await import('../domains/outbound-alert.js');
|
|
8
|
+
const { getUserConfig } = await import('../user-config.js');
|
|
9
|
+
const base = getUserConfig();
|
|
10
|
+
const config = { ...base, telegram: { ...base.telegram, channels: undefined, botToken: '2:fake', homeChannel: 2 } } as typeof base;
|
|
11
|
+
let refused = '';
|
|
12
|
+
try { sendTelegramDirect('private-alert-body', 'ops-alert', { config }); } catch (e) { refused = e instanceof Error ? e.name : String(e); }
|
|
13
|
+
console.log(JSON.stringify({ refused, curlCalls: bare.mock.calls.length }));
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Last line of defense: a test process never writes ops state or sends real alerts, even without the test preload.
|
|
2
|
+
// 10-07 incident: the public-export tree ran `bun test` without bunfig.toml (#24629), so tests wrote the ops deferred
|
|
3
|
+
// queue, ops logs.db and sent real Telegram messages. This guard does not depend on the preload or on HOME.
|
|
4
|
+
import { lstatSync, readFileSync, readlinkSync } from 'node:fs';
|
|
5
|
+
import { homedir, userInfo } from 'node:os';
|
|
6
|
+
import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
7
|
+
import { debug } from '../debug/log.js';
|
|
8
|
+
|
|
9
|
+
export class TestOpsWriteRefusedError extends Error {
|
|
10
|
+
constructor(what: string, path: string, signal: string) {
|
|
11
|
+
super(`[test-guard] refused: a test process (${signal}) tried to ${what} in the operational root (${path}). `
|
|
12
|
+
+ 'Point the test at an isolated root (ELANOUS_STATE_DIR / --config-dir tmp) or inject a fake sender.');
|
|
13
|
+
this.name = 'TestOpsWriteRefusedError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TEST_FILE = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
18
|
+
|
|
19
|
+
/** Which signal marks this process as a test, or null for a real run.
|
|
20
|
+
* Measured under bun 1.4.2: `bun test` sets NODE_ENV=test unless the caller set another value, and `Bun.main` is the
|
|
21
|
+
* running test file (argv carries no «test» word). ELANOUS_TEST_HOME is set by the deterministic runner and inherited
|
|
22
|
+
* by children it spawns. A real CLI/daemon has none of the three. */
|
|
23
|
+
export function testProcessSignal(env: NodeJS.ProcessEnv = process.env, main: string | undefined = bunMain()): string | null {
|
|
24
|
+
if (env.NODE_ENV === 'test') return 'NODE_ENV=test';
|
|
25
|
+
if (env.ELANOUS_TEST_HOME?.trim()) return 'ELANOUS_TEST_HOME';
|
|
26
|
+
if (main && TEST_FILE.test(main)) return 'Bun.main=test-file';
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isTestProcess(): boolean { return testProcessSignal() !== null; }
|
|
31
|
+
|
|
32
|
+
function bunMain(): string | undefined {
|
|
33
|
+
try { return (globalThis as { Bun?: { main?: string } }).Bun?.main; } catch { return undefined; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Real path of `path` with the kernel's lookup order: components left to right, each symlink (live or dangling)
|
|
37
|
+
* expanded before a later `..` applies — `alias/../x` with `alias → ops/child` is `ops/x`. Missing tails are kept. */
|
|
38
|
+
export function physicalPath(path: string): string {
|
|
39
|
+
const pending = (isAbsolute(path) ? path : `${process.cwd()}/${path}`).split('/').filter(Boolean);
|
|
40
|
+
let current = '/';
|
|
41
|
+
let links = 0;
|
|
42
|
+
while (pending.length) {
|
|
43
|
+
const part = pending.shift()!;
|
|
44
|
+
if (part === '.') continue;
|
|
45
|
+
if (part === '..') { current = dirname(current); continue; }
|
|
46
|
+
const next = current === '/' ? `/${part}` : `${current}/${part}`;
|
|
47
|
+
let target: string | null = null;
|
|
48
|
+
try { if (lstatSync(next).isSymbolicLink()) target = readlinkSync(next); } catch { /* missing: keep the name */ }
|
|
49
|
+
if (target !== null && links++ < 40) {
|
|
50
|
+
pending.unshift(...target.split('/').filter(Boolean));
|
|
51
|
+
if (isAbsolute(target)) current = '/';
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
current = next;
|
|
55
|
+
}
|
|
56
|
+
return current;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type SpawnSync = (cmd: string[]) => { exitCode: number; stdout: { toString(): string } };
|
|
60
|
+
|
|
61
|
+
/** The OS account's home from the account database, not HOME — test runners redirect HOME. Null when unresolvable. */
|
|
62
|
+
function lookupAccountHome(): string | null {
|
|
63
|
+
try {
|
|
64
|
+
const spawnSync = (globalThis as { Bun?: { spawnSync: SpawnSync } }).Bun?.spawnSync;
|
|
65
|
+
const user = userInfo().username;
|
|
66
|
+
const uid = process.getuid?.();
|
|
67
|
+
const valid = (home: string | undefined): string | null => (home && isAbsolute(home) ? home : null);
|
|
68
|
+
if (process.platform === 'darwin' && spawnSync) {
|
|
69
|
+
const r = spawnSync(['/usr/bin/dscl', '.', '-read', `/Users/${user}`, 'NFSHomeDirectory']);
|
|
70
|
+
const home = valid(r.exitCode === 0 ? r.stdout.toString().trim().split(/\s+/).at(-1) : undefined);
|
|
71
|
+
if (home) return home;
|
|
72
|
+
}
|
|
73
|
+
if (process.platform !== 'darwin' && uid !== undefined) {
|
|
74
|
+
try {
|
|
75
|
+
const home = valid(readFileSync('/etc/passwd', 'utf8').split('\n')
|
|
76
|
+
.map((line) => line.split(':')).find((f) => Number(f[2]) === uid)?.[5]);
|
|
77
|
+
if (home) return home;
|
|
78
|
+
} catch { /* try getent */ }
|
|
79
|
+
if (spawnSync) {
|
|
80
|
+
const r = spawnSync(['getent', 'passwd', String(uid)]);
|
|
81
|
+
const home = valid(r.exitCode === 0 ? r.stdout.toString().trim().split(':')[5] : undefined);
|
|
82
|
+
if (home) return home;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch { /* unresolvable */ }
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let accountHomeLookup: () => string | null = lookupAccountHome;
|
|
90
|
+
let opsRootsOverride: string[] | null = null; // added roots, never a replacement
|
|
91
|
+
let cachedOpsRoots: string[] | null = null;
|
|
92
|
+
|
|
93
|
+
/** Tests add «ops-like» roots to prove the refusal; the real account root stays protected. null clears them. */
|
|
94
|
+
export function setOpsRootsForTesting(roots: string[] | null): void { opsRootsOverride = roots ? roots.map(physicalPath) : null; }
|
|
95
|
+
|
|
96
|
+
/** Tests inject a failing account lookup to prove the fallback; null restores the real one. Clears the cache. */
|
|
97
|
+
export function setAccountHomeLookupForTesting(lookup: (() => string | null) | null): void {
|
|
98
|
+
accountHomeLookup = lookup ?? lookupAccountHome;
|
|
99
|
+
cachedOpsRoots = null;
|
|
100
|
+
accountHomeUnresolved = false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The operational instance root(s) the guard protects — the account's real `~/.elanous`.
|
|
104
|
+
* When the account database cannot be read, fail toward protection: every conventional home of this user plus HOME. */
|
|
105
|
+
export function opsRoots(): string[] {
|
|
106
|
+
// Widening only: injected roots and the env list add to the real account root; nothing can remove it.
|
|
107
|
+
const extra = (process.env[EXTRA_OPS_ROOTS_ENV] ?? '').split(delimiter).filter((p) => isAbsolute(p)).map(physicalPath);
|
|
108
|
+
const added = [...(opsRootsOverride ?? []), ...extra];
|
|
109
|
+
return added.length ? [...new Set([...accountOpsRoots(), ...added])] : accountOpsRoots();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Extra roots the guard protects in this process (path-delimited). Used to prove the guard in a real child entry. */
|
|
113
|
+
export const EXTRA_OPS_ROOTS_ENV = 'ELANOUS_TEST_GUARD_EXTRA_OPS_ROOTS';
|
|
114
|
+
|
|
115
|
+
function accountOpsRoots(): string[] {
|
|
116
|
+
if (cachedOpsRoots) return cachedOpsRoots;
|
|
117
|
+
const home = accountHomeLookup();
|
|
118
|
+
let homes: string[];
|
|
119
|
+
if (home) homes = [home];
|
|
120
|
+
else {
|
|
121
|
+
let user = '';
|
|
122
|
+
try { user = userInfo().username; } catch { /* unknown user */ }
|
|
123
|
+
homes = [...(user ? [`/Users/${user}`, `/home/${user}`] : []), ...(process.getuid?.() === 0 ? ['/root'] : []), homedir()];
|
|
124
|
+
try { debug.log('test-guard', 'account-home-unresolved', { protected: homes.length }); } catch { /* observation only */ }
|
|
125
|
+
}
|
|
126
|
+
accountHomeUnresolved = !home;
|
|
127
|
+
cachedOpsRoots = [...new Set(homes.map((h) => physicalPath(join(h, '.elanous'))))];
|
|
128
|
+
return cachedOpsRoots;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let accountHomeUnresolved = false;
|
|
132
|
+
|
|
133
|
+
export function isInsideOpsRoot(path: string, roots: string[] = opsRoots()): boolean {
|
|
134
|
+
const target = physicalPath(path);
|
|
135
|
+
// Unknown account home (non-standard, unreadable): any `<dir>/.elanous` tree may be the ops root — protect them all.
|
|
136
|
+
// Isolated test roots are temp dirs or `<root>/state`, never a directory named `.elanous` under a home.
|
|
137
|
+
// Both spellings: a `.elanous` that is itself a symlink to an oddly named directory is still that tree.
|
|
138
|
+
if (accountHomeUnresolved && [resolve(path), target].some((p) => p.split(sep).includes('.elanous'))) return true;
|
|
139
|
+
return roots.some((root) => {
|
|
140
|
+
const rel = relative(root, target);
|
|
141
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function refuse(what: string, path: string, signal: string): never {
|
|
146
|
+
try { debug.log('test-guard', 'ops-write-refused', { what, path, signal }); } catch { /* the refusal stands */ }
|
|
147
|
+
throw new TestOpsWriteRefusedError(what, path, signal);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Throw when a test process is about to write `path` inside the operational root. No-op in real runs. */
|
|
151
|
+
export function assertNotTestWritingOps(path: string, what: string): void {
|
|
152
|
+
const signal = testProcessSignal();
|
|
153
|
+
if (!signal) return;
|
|
154
|
+
if (path === ':memory:' || !isInsideOpsRoot(path)) return;
|
|
155
|
+
refuse(what, path, signal);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Throw when a test process is about to perform a network send it must not make.
|
|
159
|
+
* - Routing (config dir · state root · conatus dir · acp-token dir) inside the ops root: always refused, faked or
|
|
160
|
+
* not — a test that resolved the ops universe is the leak itself, whatever its transport does.
|
|
161
|
+
* - `external` (a third-party bot API — Telegram, Discord): refused unless the transport is a test double that
|
|
162
|
+
* replaces the real one (`transportFaked`), whatever root the credentials came from. A double that deliberately
|
|
163
|
+
* calls the original is an explicit choice to send and is out of this guard's reach. */
|
|
164
|
+
export function assertNoRealSendFromTest(what: string, opts: {
|
|
165
|
+
transportFaked: boolean; routingRoots: string[]; external?: boolean;
|
|
166
|
+
}): void {
|
|
167
|
+
const signal = testProcessSignal();
|
|
168
|
+
if (!signal) return;
|
|
169
|
+
const opsRoot = opts.routingRoots.find((root) => root && isInsideOpsRoot(root));
|
|
170
|
+
if (opsRoot) refuse(what, opsRoot, signal);
|
|
171
|
+
if (opts.external && !opts.transportFaked) refuse(what, 'a real third-party bot API', signal);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** True when `fn` is a bun:test/jest double whose behaviour replaces `real`. A bare `spyOn` keeps calling the
|
|
175
|
+
* original (its implementation IS `real`), so it is not a fake. */
|
|
176
|
+
export function isTestDouble(fn: unknown, real?: unknown): boolean {
|
|
177
|
+
if (!isMockFn(fn)) return false;
|
|
178
|
+
const impl = fn.getMockImplementation?.();
|
|
179
|
+
return real === undefined || impl !== unwrapMock(real);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
type MockFn = { mock: object; getMockImplementation?: () => unknown };
|
|
183
|
+
function isMockFn(fn: unknown): fn is MockFn {
|
|
184
|
+
return typeof fn === 'function' && typeof (fn as { mock?: unknown }).mock === 'object' && (fn as { mock?: unknown }).mock !== null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The function under a chain of spies — a «real» captured after a bare spy was installed is the spy itself.
|
|
188
|
+
* Unwrapping a fake yields the fake, so a capture taken under a fake refuses rather than allows. */
|
|
189
|
+
function unwrapMock(fn: unknown): unknown {
|
|
190
|
+
let current = fn;
|
|
191
|
+
for (let i = 0; i < 8 && isMockFn(current); i++) {
|
|
192
|
+
const impl: unknown = current.getMockImplementation?.();
|
|
193
|
+
if (typeof impl !== 'function' || impl === (current as unknown)) break;
|
|
194
|
+
current = impl;
|
|
195
|
+
}
|
|
196
|
+
return current;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Resolve the account home while no test has mocked anything yet; real runs never pay the lookup.
|
|
200
|
+
try { if (typeof process !== 'undefined' && isTestProcess()) opsRoots(); } catch { /* resolved lazily */ }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { debug } from '../debug/log.js';
|
|
2
|
+
|
|
3
|
+
export interface AdoptionCandidate {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly grade: 'P1' | 'P2' | 'P3' | 'P4';
|
|
6
|
+
readonly risk: number;
|
|
7
|
+
readonly money: number;
|
|
8
|
+
readonly security: number;
|
|
9
|
+
readonly license: string | null;
|
|
10
|
+
readonly touchesPatentCandidate: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type AdoptShadowReason = 'grade' | 'risk' | 'money' | 'security' | 'license' | 'patent' | 'daily-cap';
|
|
14
|
+
|
|
15
|
+
export interface AdoptShadowVerdict {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly adopt: boolean;
|
|
18
|
+
readonly reasons: readonly AdoptShadowReason[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ALLOWED_LICENSES: ReadonlySet<string> = new Set([
|
|
22
|
+
'MIT', 'Apache-2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'ISC', 'MPL-2.0',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
export function judgeAdoption(candidate: AdoptionCandidate, adoptedToday: number): AdoptShadowVerdict {
|
|
26
|
+
const reasons: AdoptShadowReason[] = [];
|
|
27
|
+
if (candidate.grade !== 'P1' && candidate.grade !== 'P2') reasons.push('grade');
|
|
28
|
+
if (!(candidate.risk <= 1)) reasons.push('risk');
|
|
29
|
+
if (candidate.money !== 0) reasons.push('money');
|
|
30
|
+
if (candidate.security !== 0) reasons.push('security');
|
|
31
|
+
if (candidate.license === null || !ALLOWED_LICENSES.has(candidate.license)) reasons.push('license');
|
|
32
|
+
if (candidate.touchesPatentCandidate !== false) reasons.push('patent');
|
|
33
|
+
if (!(adoptedToday < 5)) reasons.push('daily-cap');
|
|
34
|
+
return { id: candidate.id, adopt: reasons.length === 0, reasons };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function shadowAdopt(candidates: readonly AdoptionCandidate[]): AdoptShadowVerdict[] {
|
|
38
|
+
const verdicts: AdoptShadowVerdict[] = [];
|
|
39
|
+
let adoptedToday = 0;
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
const verdict = judgeAdoption(candidate, adoptedToday);
|
|
42
|
+
if (verdict.adopt) adoptedToday++;
|
|
43
|
+
verdicts.push(verdict);
|
|
44
|
+
debug.log('intake.adopt', 'shadow-verdict', {
|
|
45
|
+
id: verdict.id, adopt: verdict.adopt, reasons: verdict.reasons,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return verdicts;
|
|
49
|
+
}
|
|
@@ -21,6 +21,7 @@ import { parse as parseYaml } from 'yaml';
|
|
|
21
21
|
import { debug } from '../debug/log.js';
|
|
22
22
|
import { openSurfaceEventsDb, recallEvents } from '../domains/surface-events.js';
|
|
23
23
|
import { extractJsonBlock, skeletonFallback } from './decompose.js';
|
|
24
|
+
import type { AdoptShadowVerdict } from './adopt-gate.js';
|
|
24
25
|
|
|
25
26
|
export const INTAKE_CHECK_MODE = 'check' as const;
|
|
26
27
|
|
|
@@ -110,6 +111,7 @@ export interface IntakeCheckReport {
|
|
|
110
111
|
/** 비교·시너지 제안 수(근거 없음 포함). */
|
|
111
112
|
readonly proposalCount?: number;
|
|
112
113
|
readonly proposals?: readonly IntakeCompareProposal[];
|
|
114
|
+
readonly adoptShadow?: readonly AdoptShadowVerdict[];
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
/** 선가공 렌즈 여섯. 🅞 원장 · PR #20009. */
|
|
@@ -1826,6 +1828,7 @@ export function intakeCheckReportJson(report: IntakeCheckReport): Record<string,
|
|
|
1826
1828
|
proposalCount: report.proposalCount,
|
|
1827
1829
|
proposals: report.proposals,
|
|
1828
1830
|
} : {}),
|
|
1831
|
+
...(report.adoptShadow !== undefined ? { adoptShadow: report.adoptShadow } : {}),
|
|
1829
1832
|
};
|
|
1830
1833
|
}
|
|
1831
1834
|
|