elanous 0.2.12 → 0.2.14
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/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9906-482c6619b3fdda20.js → 8060-cc008cc6fedfa818.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-a67e11ba962337b5.js → page-15374909d8f1aebd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-793fa536b3a6ef37.js → page-2ce5471926761fdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-e297866051595d81.js → page-274703bc41b71333.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
- package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +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/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/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -0
- package/apps/pwa/out/loops/index.txt +30 -0
- 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/examples/teams/personas/backend.yaml +4 -0
- package/examples/teams/personas/pm.yaml +4 -0
- package/examples/teams/personas/qa.yaml +4 -0
- package/examples/teams/small-product.yaml +20 -0
- package/graphs/lecture/lecture-note.yaml +28 -0
- package/graphs/lecture/recipes.yaml +17 -0
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -1
- package/graphs/rhythm/daily.yaml +38 -0
- package/graphs/rhythm/recipes.yaml +18 -0
- package/graphs/rhythm/weekly.yaml +39 -0
- package/graphs/video/recipes.yaml +3 -0
- package/graphs/video/youtube-summary.yaml +17 -0
- package/package.json +2 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +186 -0
- package/scripts/context-hooks/emit.ts +36 -14
- package/scripts/coord-channel-watch.sh +1 -0
- package/scripts/coord-post.sh +11 -1
- package/scripts/discord-seat-probe.ts +144 -0
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +163 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/hq-standby.sh +65 -0
- package/scripts/hq/standby-promote.ts +173 -0
- package/scripts/hq/standby-role.ts +59 -0
- package/scripts/hq/standby-snapshot.ts +226 -0
- package/scripts/hq/standby-verify.ts +223 -0
- package/scripts/lecture-note/node.ts +256 -0
- package/scripts/lessons/handbook-page.ts +85 -0
- package/scripts/release-loop/gate-node.ts +62 -15
- package/scripts/release-loop/ops-upgrade-node.ts +17 -5
- package/scripts/release-loop/publish-node.ts +14 -1
- package/scripts/release-loop/unattended-release.ts +23 -1
- package/scripts/release-loop/vault-note-node.ts +196 -0
- package/scripts/release-story/draft.ts +172 -0
- package/scripts/rhythm/daily.ts +383 -0
- package/scripts/rhythm/weekly.ts +294 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/test-deterministic.ts +57 -11
- package/scripts/test-diet/effectiveness.ts +195 -0
- package/scripts/test-diet/lib.ts +16 -8
- package/scripts/test-diet/node.ts +77 -9
- package/scripts/ux/demo-rehearsal.ts +2 -2
- package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
- package/scripts/video/youtube-summary.ts +133 -0
- package/skills/diagram-master/SKILL.md +42 -49
- package/skills/diagram-master/references/pyproject.toml +4 -1
- package/skills/diagram-master/references/routing-guide.md +4 -2
- package/skills/grill-me/SKILL.md +1 -0
- package/skills/omni-crawl/SKILL.md +16 -16
- package/skills/omni-crawl/package-lock.json +1 -544
- package/skills/omni-crawl/package.json +3 -4
- package/skills/omni-crawl/scripts/main.ts +13 -13
- package/skills/omni-crawl/scripts/monitor.ts +8 -8
- package/skills/omni-crawl/src/apify.ts +13 -2
- package/skills/omni-crawl/src/capture.ts +6 -0
- package/skills/omni-crawl/src/registry.ts +2 -2
- package/skills/omni-crawl/src/stdout-json.ts +4 -0
- package/skills/omni-digest/SKILL.md +10 -11
- package/skills/omni-digest/package-lock.json +0 -545
- package/skills/omni-digest/package.json +2 -5
- package/skills/omni-digest/scripts/main.ts +3 -3
- package/skills/omni-digest/src/delegate.ts +5 -5
- package/skills/omni-digest/src/fetch.ts +2 -2
- package/skills/omni-digest/src/media.ts +25 -11
- package/skills/omni-digest/src/visual.ts +3 -2
- package/skills/omni-market/scripts/main.ts +1 -1
- package/skills/omni-market/scripts/stdout-json.ts +4 -0
- package/skills/youtube-master/SKILL.md +13 -13
- package/skills/youtube-master/package-lock.json +0 -545
- package/skills/youtube-master/package.json +2 -5
- package/skills/youtube-master/scripts/main.ts +2 -2
- package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
- package/skills/youtube-master/src/util.ts +30 -7
- package/src/agent/identity-env.ts +11 -0
- package/src/agent-mission/driver.ts +15 -10
- package/src/agent-mission/duo.ts +94 -0
- package/src/agent-mission/pty-decision.ts +1 -1
- package/src/autopilot/build/llm-conflict-merge.ts +45 -8
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +4 -1
- package/src/boot/daemon-prompt-request.ts +3 -0
- package/src/boot/daemon-prompt-turn.ts +6 -2
- package/src/boot/daemon-runtime.ts +48 -16
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +157 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +84 -20
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- package/src/cli/lesson-cli.ts +108 -0
- package/src/cli/logs-cli.ts +104 -2
- package/src/cli/logs-model-input.ts +131 -0
- package/src/cli/outputs-cli.ts +92 -0
- package/src/cli/persona-cli.ts +29 -0
- package/src/cli/pod-cli.ts +10 -3
- package/src/cli/pr-cli.ts +44 -2
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +248 -45
- package/src/cli/rfc-status.ts +91 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +189 -0
- package/src/context-bus/context-day.ts +82 -0
- package/src/context-bus/context-now-public.ts +86 -0
- package/src/context-bus/context-now-surfaces.ts +70 -4
- package/src/context-bus/context-now.ts +4 -3
- package/src/context-bus/long-term-memory.ts +201 -24
- package/src/context-bus/nightly-condense.ts +1 -1
- package/src/dashboard/index.ts +75 -22
- package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
- package/src/dashboard/slash-runtime/loops-table.ts +77 -0
- package/src/decisions/decision-cards.ts +13 -7
- package/src/decisions/decision-ledger.ts +83 -18
- package/src/discord/sprint21-runtime.ts +16 -0
- package/src/discord-self-message.ts +8 -0
- package/src/discord-slash-wire.ts +31 -2
- package/src/domains/outbound-alert.ts +1 -1
- package/src/elanous-config-dir.ts +29 -4
- package/src/execution-loop/launch-gate.ts +29 -14
- package/src/flow/cards.ts +239 -0
- package/src/graph-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-approval-card.ts +2 -2
- package/src/graph-runner/graph-grow.ts +64 -11
- package/src/graph-runner/graph-tick.ts +13 -2
- package/src/graph-runner/runner.ts +111 -22
- package/src/harness/harness-cli-command.ts +178 -27
- package/src/harness/harness-incidents.ts +43 -12
- package/src/harness/harness-pod-dispatch.ts +2 -0
- package/src/harness/harness-queue-child.ts +37 -0
- package/src/harness/harness-queue.ts +494 -0
- package/src/harness/helper-scan.ts +139 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/hooks/github-shadow.ts +72 -2
- package/src/hooks/receiver.ts +4 -2
- package/src/hq/hq.ts +318 -0
- package/src/hq/lease.ts +210 -0
- package/src/hq/ledger-write-target.ts +41 -0
- package/src/hq/promote.ts +301 -0
- package/src/index.ts +281 -27
- package/src/intake-plane/discord-seat-work.ts +13 -6
- package/src/intake-plane/linear-wish.ts +72 -0
- package/src/intake-plane/persona-answer.ts +5 -0
- package/src/intake-plane/telegram-seat-work.ts +13 -6
- package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
- package/src/intake-plane/wish-card.ts +53 -2
- package/src/intake-plane/wish-folder.ts +5 -2
- package/src/intake-plane/wish-reply.ts +72 -0
- package/src/lessons/lesson-import.ts +106 -0
- package/src/lessons/lesson-ledger.ts +206 -0
- package/src/loops/activity.ts +203 -0
- package/src/loops/budget.ts +32 -0
- package/src/loops/checker.ts +180 -0
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +72 -6
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +59 -0
- package/src/loops/orchestrator/author-depth.ts +164 -0
- package/src/loops/orchestrator/author-input-check.ts +77 -0
- package/src/loops/orchestrator/author-ledger.ts +230 -0
- package/src/loops/orchestrator/finish-rate.ts +175 -0
- package/src/loops/orchestrator/overlap.ts +180 -0
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/tick.ts +302 -0
- package/src/loops/orchestrator/traffic.ts +165 -0
- package/src/loops/package/verify.ts +125 -0
- package/src/loops/registry.ts +454 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +34 -0
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +8 -5
- package/src/memory.ts +37 -4
- package/src/nexus/api/card-followup-route.ts +118 -0
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/decisions-route.ts +68 -0
- package/src/nexus/api/http-server.ts +65 -2
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/personas.ts +2 -1
- package/src/nexus/api/schedules-read.ts +5 -1
- package/src/nexus/api/skill-exec-route.ts +97 -0
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/persona/loader.ts +18 -0
- package/src/persona/persona-todo.ts +14 -9
- package/src/persona/presets.ts +2 -1
- package/src/persona/team.ts +105 -0
- package/src/persona/types.ts +4 -0
- package/src/pod-lease/host-lease.ts +130 -20
- package/src/release-loop/auto-start.ts +48 -0
- package/src/release-loop/checklist.ts +13 -4
- package/src/release-loop/feature-store.ts +28 -16
- package/src/release-loop/landed-but-yellow.ts +12 -0
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/placement.ts +242 -0
- package/src/release-loop/release-schedule.ts +3 -1
- package/src/seat-dispatch/ceo-commands.ts +2 -2
- package/src/seat-dispatch/seat-ask.ts +73 -15
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-loop/seat-loop.ts +495 -46
- package/src/seat-loop/seat-pack.ts +127 -0
- package/src/self-dev/ask-launch-flow.ts +3 -0
- package/src/self-dev/dev-pipeline.ts +6 -2
- package/src/self-dev/draft-sweep.ts +76 -0
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +8 -0
- package/src/self-dev/self-orchestrate-runtime.ts +5 -1
- package/src/self-implement/budget-gate.ts +29 -0
- package/src/self-implement/expose-gate.ts +47 -0
- package/src/self-implement/frozen-merges.ts +294 -0
- package/src/self-implement/gate-cli.ts +10 -4
- package/src/self-implement/gate-scope.ts +46 -9
- package/src/self-implement/goal-archive.ts +222 -0
- package/src/self-implement/goal-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +79 -0
- package/src/self-implement/host-regate.ts +25 -2
- package/src/self-implement/l8-merge-queue.ts +157 -1
- package/src/self-implement/orchestrator.ts +114 -10
- package/src/self-implement/revert-guard.ts +62 -0
- package/src/self-implement/run-ledger.ts +1 -1
- package/src/self-implement/seams.ts +28 -8
- package/src/session/index.ts +2 -0
- package/src/steward/launch.ts +5 -1
- package/src/steward/readiness.ts +125 -0
- package/src/steward/rescue.ts +75 -0
- package/src/steward/triage.ts +48 -9
- package/src/task-cards/card-cli.ts +86 -0
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +23 -8
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +112 -18
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-dispatch-forward.ts +1 -1
- package/src/telegram-fabric-plan.ts +90 -0
- package/src/telegram-loops-command.ts +44 -0
- package/src/telegram-project-command.ts +10 -4
- package/src/telegram.ts +5 -0
- package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
- package/src/user-config.ts +204 -16
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +4 -0
- package/src/workflow-runtime/node-catalog.ts +13 -0
- package/src/workflow-runtime/nodes/knowledge.ts +38 -0
- package/src/workflow-runtime/schema.ts +17 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/src/workflow-runtime/types.ts +13 -1
- package/apps/pwa/out/_next/static/-6XrAoD3eU7NyMHAE4xPS/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-ee3795b112907122.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3992-2e522a8ac5f2afde.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
- package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8605-0259994816604c49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-cb169620945d1952.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cefe8169e36cf62a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5c05b0da4bc1392c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-813c4a52c9d05ca8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-a022b257950f39e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-a2ffc717e04b8b0a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-b59ee4e70fc18fcf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-1efd0cb9e0ed9d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d5dc7ff69a37b4cf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-dfca9b7d2de0b135.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6dd3bfbbde2a5c2c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-77447356baad75d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-6d604227e1664b71.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-4048543b4b7ad5e0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4c3a3126abed551a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-64b0aecb1ca75e75.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9547b5cec7cf76bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-2cddeb9e2aeedc55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-baba2e047872fb0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-e895da4ec9cac345.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
- /package/apps/pwa/out/_next/static/{-6XrAoD3eU7NyMHAE4xPS → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
# 환경 노브: CH_PR · CH_REPO · CH_INTERVAL(기본 60) · CH_BODY_CHARS(기본 1500) · CH_WATCH_BODY(기본 1)
|
|
48
48
|
|
|
49
49
|
set -u
|
|
50
|
+
set -o pipefail
|
|
50
51
|
|
|
51
52
|
PR_NUM="${CH_PR:-23032}" # ⛔ 2026-10-03: #20798 가 2,500 상한 → 채널 «6»(OP) # ⛔ 2026-09-27: #16815 가 2,500 상한에 닿아 채널 «5» 로 옮김(🅞 · 벽에 «닿은 뒤» — 미리 못 옮겼다)
|
|
52
53
|
# 📏 이력: #5730(07-28~08-12) → #8328(08-11~08-25) → #12577(08-25~09-10) → #16815(09-10~09-27) → #20798(09-27~10-03) → #23032.
|
package/scripts/coord-post.sh
CHANGED
|
@@ -220,7 +220,17 @@ fi
|
|
|
220
220
|
echo "[coord-post] 발신 성공$VIA · 채널 #$PR · 신원 [$ID] · 시각 $TS" >&2
|
|
221
221
|
# Only a successfully sent post is recorded; never pass the body or later lines to the ledger.
|
|
222
222
|
POST_URL=$(printf '%s\n' "$POST_OUTPUT" | grep -oE 'https://github\.com/[^[:space:]]+/issues/[0-9]+#issuecomment-[0-9]+|https://github\.com/[^[:space:]]+/pull/[0-9]+#issuecomment-[0-9]+' | head -1 || true)
|
|
223
|
-
|
|
223
|
+
# The channel is a production surface, so its record belongs in the production ledger, not this tree's
|
|
224
|
+
# test universe (OP 10-04 18:00). An explicit ELANOUS_STATE_DIR is the caller's chosen universe and wins;
|
|
225
|
+
# otherwise prefer the installed CLI (resolves prod from any tree), else pin the source CLI to prod.
|
|
226
|
+
if [ -n "${ELANOUS_STATE_DIR:-}" ]; then
|
|
227
|
+
RECORD=(bun bin/elanous.mjs coord event record)
|
|
228
|
+
elif command -v eln >/dev/null 2>&1; then
|
|
229
|
+
RECORD=(eln coord event record)
|
|
230
|
+
else
|
|
231
|
+
RECORD=(env ELANOUS_STATE_DIR="$HOME/.elanous" bun bin/elanous.mjs --config-dir "$HOME/.elanous" coord event record)
|
|
232
|
+
fi
|
|
233
|
+
if ! "${RECORD[@]}" --seat "$ID" --header "$(head -1 "$TMP")" --url "$POST_URL" >/dev/null 2>&1; then
|
|
224
234
|
echo '⚠️ 맥락 원장 기록 실패(발신은 됐다)' >&2
|
|
225
235
|
fi
|
|
226
236
|
exit 0
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { discordDecisionOwner } from '../src/decisions/discord-decision-cards.js';
|
|
5
|
+
import { handleDiscordSeatWork, type DiscordSeatWorkDeps } from '../src/intake-plane/discord-seat-work.js';
|
|
6
|
+
import { submitIntakeWork } from '../src/intake-plane/submit-intake-work.js';
|
|
7
|
+
import type { MsgStore } from '../src/msg/msg-store.js';
|
|
8
|
+
import { dispatchCeoTask } from '../src/seat-dispatch/ceo-commands.js';
|
|
9
|
+
import { resolveSeat } from '../src/seat-address/seat-address.js';
|
|
10
|
+
import { getUserConfig, type UserConfig } from '../src/user-config.js';
|
|
11
|
+
|
|
12
|
+
export type ProbeResult = {
|
|
13
|
+
text: string;
|
|
14
|
+
ms: number;
|
|
15
|
+
route: 'dispatch' | 'ask' | 'submit' | 'none';
|
|
16
|
+
seat: string | null;
|
|
17
|
+
body: string | null;
|
|
18
|
+
reply: string | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type ProbeOptions = { config?: UserConfig; dm?: boolean; live?: boolean; liveDeps?: DiscordSeatWorkDeps; messageId?: string };
|
|
22
|
+
|
|
23
|
+
/** Execute the real Discord seat router; dry dependencies never open a live store or delivery channel. */
|
|
24
|
+
export async function probeDiscordSeatWork(text: string, options: ProbeOptions = {}): Promise<ProbeResult> {
|
|
25
|
+
const config = options.config ?? getUserConfig();
|
|
26
|
+
const ownerId = discordDecisionOwner(config);
|
|
27
|
+
if (options.dm && !ownerId) throw new Error('Discord 소유자 ID가 없어 DM을 재현할 수 없습니다.');
|
|
28
|
+
const messageId = options.messageId ?? 'probe-1';
|
|
29
|
+
const msg = { channelId: options.dm ? `probe-dm-${ownerId}` : 'probe-channel', messageId,
|
|
30
|
+
...(ownerId ? { userId: ownerId } : {}), isDm: options.dm ?? false };
|
|
31
|
+
let route: ProbeResult['route'] = 'none';
|
|
32
|
+
let seat: string | null = null;
|
|
33
|
+
let body: string | null = null;
|
|
34
|
+
const capture = (path: ProbeResult['route'], seatId: string | null, forwarded: string) => {
|
|
35
|
+
route = path;
|
|
36
|
+
seat = seatId;
|
|
37
|
+
body = Array.from(forwarded).slice(0, 80).join('');
|
|
38
|
+
};
|
|
39
|
+
const dry: DiscordSeatWorkDeps = {
|
|
40
|
+
config,
|
|
41
|
+
// An unknown address must not cause registry I/O or a persona/LLM answer in a dry run.
|
|
42
|
+
personaSource: { list: () => [], get: () => undefined },
|
|
43
|
+
personaAnswer: async () => { throw new Error('dry persona answer unavailable'); },
|
|
44
|
+
answer: async () => null,
|
|
45
|
+
commandDeps: { ownerId, replyTarget: null, runGh: async () => { throw new Error('dry channel delivery unavailable'); },
|
|
46
|
+
append: () => { throw new Error('dry seat delivery unavailable'); } },
|
|
47
|
+
dispatch: async (seatId, forwarded) => {
|
|
48
|
+
capture('dispatch', seatId, forwarded);
|
|
49
|
+
return { channel: 'unknown', reply: `마른 실행 — ${seatId}에 맡길 예정 (실제 맡김 없음).` };
|
|
50
|
+
},
|
|
51
|
+
submit: async (input) => {
|
|
52
|
+
capture('submit', resolveSeat(input.text.split(/\s+/)[0] ?? '')?.id ?? null, input.text);
|
|
53
|
+
return { ok: true, track: 'graph', acceptanceId: 'DRY-RUN (모의 · 실제 접수 없음)' };
|
|
54
|
+
},
|
|
55
|
+
askDeps: {
|
|
56
|
+
open: () => { const db = new Database(':memory:'); return { db, close: () => db.close() } as unknown as MsgStore; },
|
|
57
|
+
dispatch: async (seatId, forwarded) => {
|
|
58
|
+
capture('ask', seatId, forwarded);
|
|
59
|
+
return { channel: 'unknown', reply: '마른 실행 — 질문을 맡기지 않았습니다.' };
|
|
60
|
+
},
|
|
61
|
+
send: async () => { throw new Error('dry Discord delivery unavailable'); },
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const live: DiscordSeatWorkDeps = {
|
|
65
|
+
...options.liveDeps,
|
|
66
|
+
config,
|
|
67
|
+
dispatch: async (seatId, forwarded, deps, extra) => {
|
|
68
|
+
capture('dispatch', seatId, forwarded);
|
|
69
|
+
return (options.liveDeps?.dispatch ?? dispatchCeoTask)(seatId, forwarded, deps, extra);
|
|
70
|
+
},
|
|
71
|
+
submit: async (input, deps) => {
|
|
72
|
+
capture('submit', resolveSeat(input.text.split(/\s+/)[0] ?? '')?.id ?? null, input.text);
|
|
73
|
+
return (options.liveDeps?.submit ?? submitIntakeWork)(input, deps);
|
|
74
|
+
},
|
|
75
|
+
askDeps: {
|
|
76
|
+
...options.liveDeps?.askDeps,
|
|
77
|
+
dispatch: async (seatId, forwarded, deps, extra) => {
|
|
78
|
+
capture('ask', seatId, forwarded);
|
|
79
|
+
return (options.liveDeps?.askDeps?.dispatch ?? dispatchCeoTask)(seatId, forwarded, deps, extra);
|
|
80
|
+
},
|
|
81
|
+
// This CLI has no Discord transport: even live mode must not post to Discord.
|
|
82
|
+
send: async () => { throw new Error('probe does not send Discord messages'); },
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const start = performance.now();
|
|
86
|
+
const reply = await handleDiscordSeatWork(text, msg, options.live ? live : dry);
|
|
87
|
+
return { text: Array.from(text).slice(0, 80).join(''), ms: Math.round((performance.now() - start) * 100) / 100,
|
|
88
|
+
route, seat, body, reply: reply === null ? null : Array.from(reply).slice(0, 120).join('') };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Extract only the '치는 문장' column of a Markdown runbook table, in row order. */
|
|
92
|
+
export function runbookTexts(markdown: string): string[] {
|
|
93
|
+
const lines = markdown.split(/\r?\n/);
|
|
94
|
+
const header = lines.findIndex((line) => /^\|.*\|\s*치는 문장(?:\s*\([^|]*\))?\s*\|/.test(line));
|
|
95
|
+
if (header < 0) throw new Error('대본의 «치는 문장» 표를 찾지 못했습니다.');
|
|
96
|
+
const columns = lines[header]!.split('|').slice(1, -1).map((cell) => cell.trim());
|
|
97
|
+
const index = columns.findIndex((cell) => cell.startsWith('치는 문장'));
|
|
98
|
+
const texts: string[] = [];
|
|
99
|
+
for (const line of lines.slice(header + 2)) {
|
|
100
|
+
if (!line.startsWith('|')) break;
|
|
101
|
+
const cells = line.split('|').slice(1, -1);
|
|
102
|
+
if (cells.length !== columns.length) throw new Error('대본 표의 열 수가 맞지 않습니다.');
|
|
103
|
+
const cell = cells[index]?.trim() ?? '';
|
|
104
|
+
if (!cell.startsWith('`') || !cell.endsWith('`')) throw new Error('치는 문장 칸이 백틱으로 둘러싸여 있지 않습니다.');
|
|
105
|
+
texts.push(cell.slice(1, -1));
|
|
106
|
+
}
|
|
107
|
+
if (!texts.length) throw new Error('대본에 치는 문장이 없습니다.');
|
|
108
|
+
return texts;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function runProbeCli(argv: string[], config: UserConfig = getUserConfig()): Promise<ProbeResult[]> {
|
|
112
|
+
let text: string | undefined;
|
|
113
|
+
let runbook: string | undefined;
|
|
114
|
+
let dm = false;
|
|
115
|
+
let live = false;
|
|
116
|
+
let json = false;
|
|
117
|
+
for (let i = 0; i < argv.length; i++) {
|
|
118
|
+
const flag = argv[i];
|
|
119
|
+
if (flag === '--text' || flag === '--runbook') {
|
|
120
|
+
const value = argv[++i];
|
|
121
|
+
if (!value || value.startsWith('--')) throw new Error(`${flag} 값이 필요합니다.`);
|
|
122
|
+
if (flag === '--text') text = value;
|
|
123
|
+
else runbook = value;
|
|
124
|
+
} else if (flag === '--dm') dm = true;
|
|
125
|
+
else if (flag === '--live') live = true;
|
|
126
|
+
else if (flag === '--json') json = true;
|
|
127
|
+
else throw new Error(`알 수 없는 옵션: ${flag}`);
|
|
128
|
+
}
|
|
129
|
+
if (Boolean(text) === Boolean(runbook)) throw new Error('--text 또는 --runbook 중 하나만 지정하세요.');
|
|
130
|
+
const texts = runbook ? runbookTexts(readFileSync(resolve(runbook), 'utf8')) : [text!];
|
|
131
|
+
const results: ProbeResult[] = [];
|
|
132
|
+
for (const [index, line] of texts.entries()) {
|
|
133
|
+
const result = await probeDiscordSeatWork(line, { config, dm, live, messageId: `probe-${index + 1}` });
|
|
134
|
+
results.push(result);
|
|
135
|
+
if (!json) console.log(`글: ${result.text} | ${result.ms}ms | 길: ${result.route} | 자리: ${result.seat ?? '-'} | 본문: ${result.body ?? '-'} | 봇 답: ${result.reply ?? '-'}`);
|
|
136
|
+
}
|
|
137
|
+
if (json) console.log(JSON.stringify(results));
|
|
138
|
+
return results;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (import.meta.main) {
|
|
142
|
+
try { await runProbeCli(process.argv.slice(2)); }
|
|
143
|
+
catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; }
|
|
144
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// 공개 문서에 적힌 `elanous …` 명령·하위 명령·플래그가 «실제 CLI» 에 있나 — 문서가 실제와 어긋나는 1순위 원인을 잡는다.
|
|
3
3
|
// bun scripts/docs-cli-check.ts [--json] [files…] (기본 = release/public/내부 문서 `*` ⊕ README.md)
|
|
4
4
|
// 🩸 계기(2026-09-25 🅢 RFC 피드백 ③): README·install.md 가 거짓이 된 원인이 전부 «설치기·doctor 가 바뀌었는데 문서가 모름»이었다.
|
|
5
|
-
// 자 = `bun bin/elanous.mjs <cmd> [<sub>] --help` 산출(원천) — 문서의 코드 블록과 인라인 코드에서 `elanous ` 로 시작하는 것만 본다.
|
|
5
|
+
// 자 = `bun bin/elanous.mjs <cmd> [<sub>] [<subsub>] --help` 산출(원천) — 문서의 코드 블록과 인라인 코드에서 `elanous ` 로 시작하는 것만 본다.
|
|
6
6
|
// ⛔ 못 본 것은 «없다»로 적지 않는다: `--help` 가 실패하면 그 명령은 `unmeasured` 로 따로 센다.
|
|
7
7
|
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
8
8
|
import { join, relative, resolve } from 'node:path';
|
|
@@ -10,7 +10,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
10
10
|
|
|
11
11
|
const REPO = resolve(import.meta.dir, '..');
|
|
12
12
|
|
|
13
|
-
export interface DocCommand { file: string; line: number; text: string; cmd: string; sub?: string; flags: string[] }
|
|
13
|
+
export interface DocCommand { file: string; line: number; text: string; cmd: string; sub?: string; subsub?: string; flags: string[] }
|
|
14
14
|
export interface Finding { kind: 'unknown-command' | 'unknown-subcommand' | 'unknown-flag' | 'unmeasured'; ref: DocCommand; detail: string }
|
|
15
15
|
|
|
16
16
|
/** 코드 블록(``` … ```) 줄과 인라인 코드(`elanous …`)에서 elanous 호출을 뽑는다. 자리표(`<x>`)·셸 변수는 인자로 보고 넘긴다. */
|
|
@@ -24,15 +24,17 @@ export function extractElanousCommands(file: string, text: string): DocCommand[]
|
|
|
24
24
|
for (const m of raw.matchAll(/`(elanous [^`]+)`/g)) candidates.push(m[1]!);
|
|
25
25
|
for (const c of candidates) {
|
|
26
26
|
for (const seg of c.split(/&&|\|\||;|\|/)) {
|
|
27
|
-
const m =
|
|
27
|
+
const m = /^(?:(?:\$\s+|>\s+|sudo\s+|[A-Za-z_][A-Za-z0-9_]*=\S+\s+))*elanous\s+(.+)$/.exec(seg.trim());
|
|
28
28
|
if (!m) continue;
|
|
29
29
|
const tokens = m[1]!.trim().split(/\s+/).filter(Boolean);
|
|
30
30
|
const cmd = tokens[0];
|
|
31
31
|
if (!cmd || !/^[a-z][a-z0-9:-]*$/.test(cmd)) continue;
|
|
32
32
|
const second = tokens[1];
|
|
33
33
|
const sub = second && /^[a-z][a-z0-9-]*$/.test(second) ? second : undefined;
|
|
34
|
+
const third = tokens[2];
|
|
35
|
+
const subsub = sub && third && /^[a-z][a-z0-9-]*$/.test(third) ? third : undefined;
|
|
34
36
|
const flags = tokens.filter((t) => /^--[a-z]/.test(t)).map((t) => t.replace(/[=,.)].*$/, ''));
|
|
35
|
-
out.push({ file, line: i + 1, text: seg.trim(), cmd, ...(sub ? { sub } : {}), flags });
|
|
37
|
+
out.push({ file, line: i + 1, text: seg.trim(), cmd, ...(sub ? { sub } : {}), ...(subsub ? { subsub } : {}), flags });
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
});
|
|
@@ -63,13 +65,28 @@ export function checkCommands(refs: readonly DocCommand[], help: HelpRunner = de
|
|
|
63
65
|
const cmdHelp = get([ref.cmd]);
|
|
64
66
|
if (!cmdHelp.ok) { findings.push({ kind: 'unmeasured', ref, detail: `elanous ${ref.cmd} --help 실패` }); continue; }
|
|
65
67
|
let flagsHelp = cmdHelp.out;
|
|
68
|
+
let flagsPath = `elanous ${ref.cmd}`;
|
|
66
69
|
if (ref.sub && /\nCommands:\n/.test(cmdHelp.out)) {
|
|
67
70
|
if (!listed(cmdHelp.out, ref.sub)) { findings.push({ kind: 'unknown-subcommand', ref, detail: `elanous ${ref.cmd} ${ref.sub}` }); continue; }
|
|
71
|
+
flagsPath = `elanous ${ref.cmd} ${ref.sub}`;
|
|
68
72
|
const subHelp = get([ref.cmd, ref.sub]);
|
|
69
|
-
if (subHelp.ok)
|
|
73
|
+
if (!subHelp.ok) {
|
|
74
|
+
findings.push({ kind: 'unmeasured', ref, detail: `${flagsPath} --help 실패` });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
flagsHelp = subHelp.out;
|
|
78
|
+
if (ref.subsub && listed(subHelp.out, ref.subsub)) {
|
|
79
|
+
flagsPath += ` ${ref.subsub}`;
|
|
80
|
+
const subsubHelp = get([ref.cmd, ref.sub, ref.subsub]);
|
|
81
|
+
if (!subsubHelp.ok) {
|
|
82
|
+
findings.push({ kind: 'unmeasured', ref, detail: `${flagsPath} --help 실패` });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
flagsHelp = subsubHelp.out;
|
|
86
|
+
}
|
|
70
87
|
}
|
|
71
88
|
for (const f of ref.flags) {
|
|
72
|
-
if (!new RegExp(`(^|[\\s,])${f}(\\b|[\\s,=<\\[])`, 'm').test(flagsHelp)) findings.push({ kind: 'unknown-flag', ref, detail: `${f} (
|
|
89
|
+
if (!new RegExp(`(^|[\\s,])${f}(\\b|[\\s,=<\\[])`, 'm').test(flagsHelp)) findings.push({ kind: 'unknown-flag', ref, detail: `${f} (${flagsPath})` });
|
|
73
90
|
}
|
|
74
91
|
}
|
|
75
92
|
return findings;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { parse } from 'yaml';
|
|
5
|
+
import { debug } from '../src/debug/log.js';
|
|
6
|
+
|
|
7
|
+
const DOCS = 'release/public/docs';
|
|
8
|
+
const LEDGER = 'release/public/expose-rubric.yaml';
|
|
9
|
+
const CRITERIA = ['maturity', 'value', 'reproducible', 'evidence', 'confidential', 'brand', 'support'] as const;
|
|
10
|
+
type Criterion = { status: 'pass' | 'fail' | 'n/a'; reason: string };
|
|
11
|
+
type Entry = {
|
|
12
|
+
path: string;
|
|
13
|
+
verdict: 'public' | 'beta' | 'internal';
|
|
14
|
+
criteria: Record<(typeof CRITERIA)[number], Criterion>;
|
|
15
|
+
judge: string;
|
|
16
|
+
techReview?: string;
|
|
17
|
+
at: string;
|
|
18
|
+
};
|
|
19
|
+
type RubricResult = { docs: number; missing: string[]; orphan: string[]; failing: Array<{ path: string; criteria: string[]; verdict?: 'beta' }>; internal: string[]; unreviewed: string[]; unjudged: string[] };
|
|
20
|
+
|
|
21
|
+
function validEntry(value: unknown): value is Entry {
|
|
22
|
+
if (!value || typeof value !== 'object') return false;
|
|
23
|
+
const entry = value as Partial<Entry>;
|
|
24
|
+
if (typeof entry.path !== 'string' || !/^release\/public\/docs\/[^/]+\.md$/.test(entry.path)
|
|
25
|
+
|| !['public', 'beta', 'internal'].includes(entry.verdict ?? '')
|
|
26
|
+
// The exposure judge is the CMO seat (EXPOSE-RUBRIC); any other non-empty name is not a judgement.
|
|
27
|
+
|| entry.judge !== 'MK'
|
|
28
|
+
|| (entry.techReview !== undefined && (typeof entry.techReview !== 'string' || !entry.techReview.trim()))
|
|
29
|
+
|| typeof entry.at !== 'string' || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(entry.at)
|
|
30
|
+
|| !Number.isFinite(Date.parse(entry.at)) || !entry.criteria || typeof entry.criteria !== 'object') return false;
|
|
31
|
+
const criteria = entry.criteria as Record<string, unknown>;
|
|
32
|
+
if (Object.keys(criteria).length !== CRITERIA.length) return false;
|
|
33
|
+
return CRITERIA.every((key) => {
|
|
34
|
+
const item = criteria[key] as Partial<Criterion> | undefined;
|
|
35
|
+
return item && ['pass', 'fail', 'n/a'].includes(item.status ?? '')
|
|
36
|
+
&& typeof item.reason === 'string' && !!item.reason.trim() && !item.reason.includes('\n');
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function selectedPath(input: string, root: string): string | undefined {
|
|
41
|
+
const path = relative(root, resolve(root, input)).split(sep).join('/');
|
|
42
|
+
return /^release\/public\/docs\/[^/]+\.md$/.test(path) ? path : undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function checkExposeRubric(root: string, files?: readonly string[]): RubricResult {
|
|
46
|
+
const data = parse(readFileSync(join(root, LEDGER), 'utf8')) as unknown;
|
|
47
|
+
if (!data || typeof data !== 'object' || !('entries' in data) || !Array.isArray(data.entries)) throw new Error(`invalid ledger: ${LEDGER}`);
|
|
48
|
+
const entries = new Map<string, Entry>();
|
|
49
|
+
for (const value of data.entries as unknown[]) {
|
|
50
|
+
if (!validEntry(value) || entries.has(value.path)) throw new Error(`invalid or duplicate ledger entry: ${JSON.stringify(value)}`);
|
|
51
|
+
entries.set(value.path, value);
|
|
52
|
+
}
|
|
53
|
+
const scope = files === undefined ? undefined : new Set(files.map((file) => selectedPath(file, root)).filter((path): path is string => path !== undefined));
|
|
54
|
+
const docs = readdirSync(join(root, DOCS)).filter((name) => name.endsWith('.md'))
|
|
55
|
+
.map((name) => `${DOCS}/${name}`).filter((path) => !scope || scope.has(path)).sort();
|
|
56
|
+
const missing = docs.filter((path) => !entries.has(path));
|
|
57
|
+
const orphan: string[] = [];
|
|
58
|
+
const failing: RubricResult['failing'] = [];
|
|
59
|
+
const internal: string[] = [];
|
|
60
|
+
const unreviewed: string[] = [];
|
|
61
|
+
const unjudged: string[] = [];
|
|
62
|
+
for (const entry of entries.values()) {
|
|
63
|
+
if (scope && !scope.has(entry.path)) continue;
|
|
64
|
+
if (!existsSync(join(root, entry.path))) {
|
|
65
|
+
orphan.push(entry.path);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (entry.verdict === 'internal') internal.push(entry.path);
|
|
69
|
+
if (entry.verdict === 'public' || entry.verdict === 'beta') {
|
|
70
|
+
const failed = entry.verdict === 'public'
|
|
71
|
+
? CRITERIA.filter((key) => entry.criteria[key].status === 'fail')
|
|
72
|
+
: (entry.criteria.confidential.status === 'fail' ? ['confidential'] : []);
|
|
73
|
+
if (failed.length) failing.push({ path: entry.path, criteria: failed, ...(entry.verdict === 'beta' ? { verdict: 'beta' as const } : {}) });
|
|
74
|
+
if (entry.verdict === 'public' && entry.techReview !== 'TC') unreviewed.push(entry.path);
|
|
75
|
+
// An outward verdict with no criterion actually passed is a placeholder, not a judgement (all n/a = never checked).
|
|
76
|
+
if (!CRITERIA.some((key) => entry.criteria[key].status === 'pass')) unjudged.push(entry.path);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
orphan.sort();
|
|
80
|
+
failing.sort((a, b) => a.path.localeCompare(b.path));
|
|
81
|
+
internal.sort();
|
|
82
|
+
unreviewed.sort();
|
|
83
|
+
unjudged.sort();
|
|
84
|
+
const result = { docs: docs.length, missing, orphan, failing, internal, unreviewed, unjudged };
|
|
85
|
+
debug.log('expose.rubric', 'checked', { docs: result.docs, missing: missing.length, orphan: orphan.length, failing: failing.length, unjudged: unjudged.length });
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (import.meta.main) {
|
|
90
|
+
try {
|
|
91
|
+
const args = process.argv.slice(2);
|
|
92
|
+
if (args.includes('--help')) {
|
|
93
|
+
console.log('Usage: bun scripts/expose-rubric-check.ts [--strict] [--json] [--files <path...>]');
|
|
94
|
+
} else {
|
|
95
|
+
const options = new Set(['--strict', '--json', '--files']);
|
|
96
|
+
const index = args.indexOf('--files');
|
|
97
|
+
const beforeFiles = args.slice(0, index < 0 ? args.length : index);
|
|
98
|
+
const files = index < 0 ? undefined : args.slice(index + 1);
|
|
99
|
+
if (beforeFiles.some((arg) => !options.has(arg) || arg === '--files')
|
|
100
|
+
|| (files && (!files.length || files.some((file) => file.startsWith('--'))))) throw new Error('invalid arguments: use --files <path...> last');
|
|
101
|
+
const result = checkExposeRubric(process.cwd(), files);
|
|
102
|
+
if (args.includes('--json')) console.log(JSON.stringify(result));
|
|
103
|
+
else {
|
|
104
|
+
for (const path of result.missing) console.error(`경고: 원장 없음: ${path}`);
|
|
105
|
+
for (const path of result.orphan) console.error(`경고: 파일 없음: ${path}`);
|
|
106
|
+
for (const item of result.failing) console.error(`경고: ${item.verdict ?? 'public'} 인데 fail (${item.criteria.join(', ')}): ${item.path}`);
|
|
107
|
+
for (const path of result.internal) console.error(`경고: internal 인데 공개 경로: ${path}`);
|
|
108
|
+
for (const path of result.unreviewed) console.error(`경고: public 기술 정확성 TC 미검토: ${path}`);
|
|
109
|
+
for (const path of result.unjudged) console.error(`경고: 미판정(일곱 기준 중 pass 0): ${path}`);
|
|
110
|
+
console.error(`expose-rubric — docs=${result.docs} missing=${result.missing.length} orphan=${result.orphan.length} failing=${result.failing.length} internal=${result.internal.length} unreviewed=${result.unreviewed.length} unjudged=${result.unjudged.length}`);
|
|
111
|
+
}
|
|
112
|
+
if (args.includes('--strict') && (result.missing.length || result.orphan.length || result.failing.length || result.internal.length || result.unjudged.length)) process.exitCode = 1;
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
116
|
+
process.exitCode = 2;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/** Machine-measured suggestions only; MK judges and merges the exposure ledger. */
|
|
3
|
+
import { readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { join, relative, resolve, sep } from 'node:path';
|
|
6
|
+
import { stringify } from 'yaml';
|
|
7
|
+
import { debug } from '../src/debug/log.js';
|
|
8
|
+
import { checkBrand } from './brand/check.js';
|
|
9
|
+
import { checkCommands, extractElanousCommands, type HelpRunner } from './docs-cli-check.js';
|
|
10
|
+
import { scanLeaks } from './public-export.js';
|
|
11
|
+
|
|
12
|
+
const DOCS = 'release/public/docs';
|
|
13
|
+
const LEDGER = 'release/public/expose-rubric.yaml';
|
|
14
|
+
const KEYS = ['reproducible', 'confidential', 'brand'] as const;
|
|
15
|
+
type Criterion = { status: 'pass' | 'fail' | 'n/a'; reason: string };
|
|
16
|
+
type Suggestion = { path: string; criteria: Record<(typeof KEYS)[number], Criterion> };
|
|
17
|
+
|
|
18
|
+
function unmeasured(error: unknown): Criterion {
|
|
19
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
20
|
+
return { status: 'n/a', reason: `측정 불가: ${detail.split(/\r?\n/)[0] || '알 수 없는 오류'}` };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function measure(run: () => Criterion): Criterion {
|
|
24
|
+
try { return run(); } catch (error) { return unmeasured(error); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Files are repository-relative paths (or absolute paths within root); the ledger is never read or written. */
|
|
28
|
+
export function suggestExposeRubric(root: string, files?: readonly string[], help?: HelpRunner): Suggestion[] {
|
|
29
|
+
const names = files ?? readdirSync(join(root, DOCS)).filter((name) => name.endsWith('.md')).map((name) => `${DOCS}/${name}`);
|
|
30
|
+
const paths = [...new Set(names.map((name) => relative(root, resolve(root, name)).split(sep).join('/')))];
|
|
31
|
+
if (paths.some((path) => !/^release\/public\/docs\/[^/]+\.md$/.test(path))) throw new Error('문서 경로는 release/public/docs/*.md 이어야 합니다');
|
|
32
|
+
type HelpResult = ReturnType<HelpRunner> & { failure?: string };
|
|
33
|
+
let helpRuns = 0;
|
|
34
|
+
const rootHelp: (args: string[]) => HelpResult = help ?? ((args) => {
|
|
35
|
+
helpRuns++;
|
|
36
|
+
const result = spawnSync('bun', ['bin/elanous.mjs', '--test', ...args, '--help'], {
|
|
37
|
+
cwd: resolve(import.meta.dir, '..'), encoding: 'utf8', timeout: 60_000, env: { ...process.env, NO_COLOR: '1' },
|
|
38
|
+
});
|
|
39
|
+
const error = result.error as NodeJS.ErrnoException | undefined;
|
|
40
|
+
return {
|
|
41
|
+
ok: result.status === 0 && !error,
|
|
42
|
+
out: `${result.stdout ?? ''}\n${result.stderr ?? ''}`,
|
|
43
|
+
failure: error?.code === 'ETIMEDOUT' ? 'timeout'
|
|
44
|
+
: error ? error.message
|
|
45
|
+
: result.status !== 0 ? `exit ${result.status ?? 'unknown'}` : undefined,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
const helpCache = new Map<string, { ok: true; out: string } | { ok: false; reason: string }>();
|
|
49
|
+
let helpFailures = 0;
|
|
50
|
+
const checkedHelp: HelpRunner = (args) => {
|
|
51
|
+
const key = args.join(' ');
|
|
52
|
+
let cached = helpCache.get(key);
|
|
53
|
+
if (!cached) {
|
|
54
|
+
const runOnce = (): { ok: true; out: string } | { ok: false; reason: string } => {
|
|
55
|
+
try {
|
|
56
|
+
const result = rootHelp(args);
|
|
57
|
+
if (result.ok && result.out.trim()) return { ok: true, out: result.out };
|
|
58
|
+
helpFailures++;
|
|
59
|
+
return { ok: false, reason: result.failure ?? (result.out.trim() ? '실행 실패' : '빈 출력') };
|
|
60
|
+
} catch (error) {
|
|
61
|
+
helpFailures++;
|
|
62
|
+
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
cached = runOnce();
|
|
66
|
+
if (!cached.ok) cached = runOnce();
|
|
67
|
+
helpCache.set(key, cached);
|
|
68
|
+
}
|
|
69
|
+
if (!cached.ok) throw new Error(`elanous ${key ? `${key} ` : ''}--help 실패(${cached.reason.split(/\r?\n/)[0] || '알 수 없는 오류'})`);
|
|
70
|
+
return cached;
|
|
71
|
+
};
|
|
72
|
+
const results = paths.sort().map((path): Suggestion => {
|
|
73
|
+
const absolute = join(root, path);
|
|
74
|
+
let text: string;
|
|
75
|
+
try { text = readFileSync(absolute, 'utf8'); }
|
|
76
|
+
catch (error) {
|
|
77
|
+
const failed = unmeasured(error);
|
|
78
|
+
return { path, criteria: { reproducible: failed, confidential: failed, brand: failed } };
|
|
79
|
+
}
|
|
80
|
+
const reproducible = measure(() => {
|
|
81
|
+
const refs = extractElanousCommands(path, text);
|
|
82
|
+
if (!refs.length) return { status: 'n/a', reason: '문서에 elanous 명령 없음' };
|
|
83
|
+
const findings = checkCommands(refs, checkedHelp);
|
|
84
|
+
const unable = findings.find((finding) => finding.kind === 'unmeasured');
|
|
85
|
+
if (unable) return unmeasured(unable.detail);
|
|
86
|
+
const missing = [...new Set(findings.map((finding) => finding.detail))].slice(0, 3);
|
|
87
|
+
return missing.length
|
|
88
|
+
? { status: 'fail', reason: `없는 명령: ${missing.join(', ')}` }
|
|
89
|
+
: { status: 'pass', reason: `명령 ${refs.length}개 모두 --help 에 있음` };
|
|
90
|
+
});
|
|
91
|
+
const confidential = measure(() => {
|
|
92
|
+
const hits = scanLeaks(root, [path], undefined, new Map([[path, text]]));
|
|
93
|
+
const markers = [...new Set(hits.map((hit) => hit.marker))].slice(0, 3);
|
|
94
|
+
return markers.length
|
|
95
|
+
? { status: 'fail', reason: `누출 표식: ${markers.join(', ')}` }
|
|
96
|
+
: { status: 'pass', reason: '누출 표식 없음' };
|
|
97
|
+
});
|
|
98
|
+
const brand = measure(() => {
|
|
99
|
+
const result = checkBrand('public-docs', [absolute]);
|
|
100
|
+
if (result.missing || result.files !== 1 || result.rules === 0) return unmeasured('브랜드 규칙 없음 또는 파일 검사 실패');
|
|
101
|
+
const ids = [...new Set(result.findings.map((finding) => finding.id))].slice(0, 3);
|
|
102
|
+
return ids.length
|
|
103
|
+
? { status: 'fail', reason: `브랜드 규칙: ${ids.join(', ')}` }
|
|
104
|
+
: { status: 'pass', reason: '브랜드 규칙 위반 없음' };
|
|
105
|
+
});
|
|
106
|
+
return { path, criteria: { reproducible, confidential, brand } };
|
|
107
|
+
});
|
|
108
|
+
const statuses = results.flatMap((item) => KEYS.map((key) => item.criteria[key].status));
|
|
109
|
+
debug.log('expose.rubric', 'suggested', {
|
|
110
|
+
docs: results.length, pass: statuses.filter((status) => status === 'pass').length,
|
|
111
|
+
fail: statuses.filter((status) => status === 'fail').length,
|
|
112
|
+
unmeasured: statuses.filter((status) => status === 'n/a').length,
|
|
113
|
+
helpRuns, helpFailures,
|
|
114
|
+
});
|
|
115
|
+
return results;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function run(argv: readonly string[], root = process.cwd(), help?: HelpRunner): number {
|
|
119
|
+
const json = argv.includes('--json');
|
|
120
|
+
let files: string[] | undefined;
|
|
121
|
+
let out: string | undefined;
|
|
122
|
+
for (let i = 0; i < argv.length; i++) {
|
|
123
|
+
const arg = argv[i]!;
|
|
124
|
+
if (arg === '--json') continue;
|
|
125
|
+
if (arg === '--yaml-patch') {
|
|
126
|
+
out = argv[++i];
|
|
127
|
+
if (!out || out.startsWith('--')) throw new Error('--yaml-patch 에 출력 경로가 필요합니다');
|
|
128
|
+
} else if (arg === '--files') {
|
|
129
|
+
files = [];
|
|
130
|
+
while (argv[i + 1] && !argv[i + 1]!.startsWith('--')) files.push(argv[++i]!);
|
|
131
|
+
if (!files.length) throw new Error('--files 에 문서 경로가 필요합니다');
|
|
132
|
+
} else throw new Error(`알 수 없는 옵션: ${arg}`);
|
|
133
|
+
}
|
|
134
|
+
const results = suggestExposeRubric(root, files, help);
|
|
135
|
+
if (out) {
|
|
136
|
+
const target = resolve(root, out);
|
|
137
|
+
const ledger = resolve(root, LEDGER);
|
|
138
|
+
if (target === ledger || realpathOrSelf(target) === realpathOrSelf(ledger) || sameFile(target, ledger)) throw new Error('원장 파일은 덮어쓸 수 없습니다');
|
|
139
|
+
writeFileSync(target, stringify({ entries: results }));
|
|
140
|
+
}
|
|
141
|
+
if (json) console.log(JSON.stringify(results));
|
|
142
|
+
else {
|
|
143
|
+
for (const { path, criteria } of results) console.log(`${path} · ${KEYS.map((key) => `${key} ${criteria[key].status}${criteria[key].status === 'fail' ? `(${criteria[key].reason.split(': ').slice(1).join(': ') || criteria[key].reason})` : ''}`).join(' · ')}`);
|
|
144
|
+
const statuses = results.flatMap((item) => KEYS.map((key) => item.criteria[key].status));
|
|
145
|
+
console.log(`합계 문서 ${results.length} · pass ${statuses.filter((status) => status === 'pass').length} · fail ${statuses.filter((status) => status === 'fail').length} · n/a ${statuses.filter((status) => status === 'n/a').length} · maturity/value/evidence/support 사람 판정 (MK)`);
|
|
146
|
+
}
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function realpathOrSelf(path: string): string {
|
|
151
|
+
try { return realpathSync(path); } catch { return path; }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function sameFile(target: string, ledger: string): boolean {
|
|
155
|
+
const a = statSync(target, { throwIfNoEntry: false });
|
|
156
|
+
const b = statSync(ledger, { throwIfNoEntry: false });
|
|
157
|
+
return !!a && !!b && a.dev === b.dev && a.ino === b.ino;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (import.meta.main) {
|
|
161
|
+
try { process.exitCode = run(process.argv.slice(2)); }
|
|
162
|
+
catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 2; }
|
|
163
|
+
}
|
package/scripts/heal/run-role.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { readFileSync } from 'node:fs';
|
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { debug } from '../../src/debug/log.js';
|
|
8
|
+
import { readFailureInbox } from '../../src/self-implement/heal-intake.js';
|
|
8
9
|
import {
|
|
9
10
|
fillMissingGateFacts,
|
|
10
11
|
markDeletedUnverified,
|
|
@@ -101,6 +102,7 @@ export interface HealRoleDeps {
|
|
|
101
102
|
loadLedger?: (runId: string) => RunLedgerEntry[] | null;
|
|
102
103
|
ledgerDir?: string;
|
|
103
104
|
search?: (query: string) => unknown;
|
|
105
|
+
stateRoot?: string;
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
function gitHeadExists(worktree: string, path: string): PathExists {
|
|
@@ -243,6 +245,18 @@ export function roleResult(role: string, ctx: GraphContext, deps: HealRoleDeps =
|
|
|
243
245
|
const signature = failureOf(ctx);
|
|
244
246
|
switch (role) {
|
|
245
247
|
case 'observe-ledger': {
|
|
248
|
+
const selector = asRecord(input.failureEvent);
|
|
249
|
+
if (selector) {
|
|
250
|
+
if (typeof selector.source !== 'string' || typeof selector.ref !== 'string' || !selector.source || !selector.ref) {
|
|
251
|
+
return withAddedFacts({ outcome: 'fail', reason: 'invalid-failure-event-selector' }, []);
|
|
252
|
+
}
|
|
253
|
+
const since = typeof input.since === 'string' ? input.since : undefined;
|
|
254
|
+
const event = readFailureInbox({ since }, deps.stateRoot)
|
|
255
|
+
.filter(row => row.source === selector.source && row.ref === selector.ref)
|
|
256
|
+
.sort((a, b) => Date.parse(b.at) - Date.parse(a.at)).at(0);
|
|
257
|
+
if (!event) return withAddedFacts({ outcome: 'empty', reason: 'inbox-event-not-found' }, []);
|
|
258
|
+
return withAddedFacts({ outcome: 'ok', entries: [{ errorText: event.summary }], event }, ['errorText']);
|
|
259
|
+
}
|
|
246
260
|
if (!asRecord(input.failure) && Object.keys(failure).length === 0) return withAddedFacts({ outcome: 'empty' }, []);
|
|
247
261
|
const worktree = typeof input.worktree === 'string' ? input.worktree : '';
|
|
248
262
|
const paths = signature.unverified ?? [];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# HQ-REP replication body — the same file runs on both HQ hosts (OP 10-04 10:09 · «되돌리기 방향»).
|
|
3
|
+
# The lease holder is the source, the other host is the standby: mbp → node-b today, node-b → mbp once node-b holds the lease.
|
|
4
|
+
# Called by each host's short cron shim (~/.elanous/bin/hq-standby.sh: lock ⊕ fetch ⊕ detach origin/main) with:
|
|
5
|
+
# HQ_REP_PEER=<the other host's ssh name> HQ_REP_DEFAULT_SOURCE=<lease names of the pre-lease source, comma list>
|
|
6
|
+
# [HQ_REP_SURVIVAL=<host>,s3://bucket/prefix] [HQ_REP_STATE_DIR=<canonical ledgers · default ~/.elanous>]
|
|
7
|
+
# sh scripts/hq/hq-standby.sh core|big|obs|vault
|
|
8
|
+
# HQ_REP_STATE_DIR (OP 10-04 11:27): on node-b the promoted HQ state lives in ~/.elanous-hqstate — its live ~/.elanous is the
|
|
9
|
+
# Pod host's own state and must never be copied as HQ ledgers. The copy refuses a state dir without release/features.sqlite
|
|
10
|
+
# (an empty or wrong root would otherwise become the standby's newest generation).
|
|
11
|
+
# Direction comes from scripts/hq/standby-role.ts: `fenced` → the copy runs under `elanous hq fence --role cron`
|
|
12
|
+
# (TC HQ-FENCE decides · skip exits 0), `direct` → no lease yet and this is the default source, `skip` → no lease, not it.
|
|
13
|
+
MODE="$1"
|
|
14
|
+
ts() { date '+%Y-%m-%dT%H:%M:%S%z'; }
|
|
15
|
+
# Production ledgers on every host — a source-tree command resolves the test universe otherwise.
|
|
16
|
+
# HQ_REP_CONFIG_DIR (TC drill step 0 · 10-04 11:5x): node-b keeps its hq settings (hq.hostName=node-b) in ~/.elanous-hqcfg
|
|
17
|
+
# because its live ~/.elanous is the Pod host's; the role check and `hq fence` must read the same lease name as HQ-HB.
|
|
18
|
+
# ⚠️ The config dir moves only by the `--config-dir` flag (src/elanous-config-dir.ts) — an ELANOUS_CONFIG_DIR env is ignored.
|
|
19
|
+
export ELANOUS_STATE_DIR="$HOME/.elanous"
|
|
20
|
+
CFG="${HQ_REP_CONFIG_DIR:-$HOME/.elanous}"
|
|
21
|
+
STATE="${HQ_REP_STATE_DIR:-$HOME/.elanous}"
|
|
22
|
+
case "$MODE" in core|big|obs|vault) ;; *) echo "$(ts) hq-standby skip: unknown mode $MODE"; exit 2 ;; esac
|
|
23
|
+
[ -n "$HQ_REP_PEER" ] || { echo "$(ts) hq-standby skip($MODE): HQ_REP_PEER not set"; exit 2; }
|
|
24
|
+
[ -n "$HQ_REP_DEFAULT_SOURCE" ] || { echo "$(ts) hq-standby skip($MODE): HQ_REP_DEFAULT_SOURCE not set"; exit 2; }
|
|
25
|
+
verify_received() {
|
|
26
|
+
# Verification is observational: a broken or not-yet-received copy must not fail replication's cron exit status.
|
|
27
|
+
bun scripts/hq/standby-verify.ts --root "$HOME/.elanous-standby" --tier core,big --max-age-min core=30,big=400 || :
|
|
28
|
+
}
|
|
29
|
+
ROLE=$(bun scripts/hq/standby-role.ts --config-dir "$CFG" --default-source "$HQ_REP_DEFAULT_SOURCE" 2>/dev/null) || ROLE=fenced
|
|
30
|
+
case "$ROLE" in
|
|
31
|
+
skip) if [ "$MODE" = core ]; then verify_received; else echo "$(ts) hq-standby skip($MODE): standby — no lease yet and this host is not the default source"; fi; exit 0 ;;
|
|
32
|
+
direct) set -- ;;
|
|
33
|
+
*) ROLE=fenced; set -- bun bin/elanous.mjs --config-dir "$CFG" hq fence --role cron -- ;;
|
|
34
|
+
esac
|
|
35
|
+
echo "$(ts) hq-standby run($MODE) role=$ROLE state=$STATE → $HQ_REP_PEER at $(git rev-parse --short HEAD 2>/dev/null)"
|
|
36
|
+
if [ "$MODE" = vault ]; then
|
|
37
|
+
# Obsidian vault: one-way copy to the standby (no --delete; workspace state and trash stay local; --update keeps newer files there).
|
|
38
|
+
V="$HOME/Obsidian/ElanvitalAI/"
|
|
39
|
+
"$@" rsync -a --update --exclude '.obsidian/workspace*' --exclude '.trash/' "$V" "$HQ_REP_PEER:$V" 2>&1 \
|
|
40
|
+
&& echo "$(ts) hq-standby vault done rc=0 (a fence skip line above means nothing was copied)" || { echo "$(ts) hq-standby vault FAILED"; exit 1; }
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
# In core fenced mode the guard runs inside the fence: a non-holder need not have HQ source ledgers.
|
|
44
|
+
if [ "$MODE" != core ] || [ "$ROLE" = direct ]; then
|
|
45
|
+
[ -f "$STATE/release/features.sqlite" ] || { echo "$(ts) hq-standby skip($MODE): $STATE has no release/features.sqlite — not an HQ state dir, nothing copied"; exit 1; }
|
|
46
|
+
fi
|
|
47
|
+
SURV=""
|
|
48
|
+
[ "$MODE" = core ] && [ -n "$HQ_REP_SURVIVAL" ] && SURV="--survival $HQ_REP_SURVIVAL"
|
|
49
|
+
# The fence exits 0 both after copying and after skipping. Mark execution inside the fence,
|
|
50
|
+
# not by parsing its diagnostic text: only the skipped host owns a received copy to verify.
|
|
51
|
+
if [ "$MODE" = core ] && [ "$ROLE" = fenced ]; then
|
|
52
|
+
FENCE_RAN=$(mktemp) || { echo "$(ts) hq-standby FAILED: cannot track fence execution"; exit 1; }
|
|
53
|
+
# shellcheck disable=SC2086 — SURV is either empty or two words.
|
|
54
|
+
COPY_OUT=$("$@" sh -c '
|
|
55
|
+
[ -f "$1/release/features.sqlite" ] || { echo "hq-standby skip(core): $1 has no release/features.sqlite — not an HQ state dir, nothing copied"; exit 1; }
|
|
56
|
+
printf "ran\n" > "$2"; shift 2; exec "$@"
|
|
57
|
+
' sh "$STATE" "$FENCE_RAN" env ELANOUS_STATE_DIR="$STATE" bun scripts/hq/standby-snapshot.ts --tier "$MODE" --push "$HQ_REP_PEER" $SURV 2>&1)
|
|
58
|
+
COPY_RC=$?
|
|
59
|
+
[ -z "$COPY_OUT" ] || printf '%s\n' "$COPY_OUT"
|
|
60
|
+
if [ "$COPY_RC" -eq 0 ] && [ ! -s "$FENCE_RAN" ]; then verify_received; fi
|
|
61
|
+
rm -f "$FENCE_RAN"
|
|
62
|
+
exit "$COPY_RC"
|
|
63
|
+
fi
|
|
64
|
+
# shellcheck disable=SC2086 — SURV is either empty or two words.
|
|
65
|
+
"$@" env ELANOUS_STATE_DIR="$STATE" bun scripts/hq/standby-snapshot.ts --tier "$MODE" --push "$HQ_REP_PEER" $SURV 2>&1
|