elanous 0.2.13 → 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/8060-cc008cc6fedfa818.js +1 -0
- 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-f14987d5850a31e0.js → page-1abfe3aec3be6bdc.js} +1 -1
- 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-15374909d8f1aebd.js +1 -0
- 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-66c27608f651b866.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-78b40a344ed22f24.js → page-e1e47d8d940aba3d.js} +1 -1
- 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-63d14a4d8fe1e1c1.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/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 +1 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +186 -0
- package/scripts/coord-post.sh +11 -1
- 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/standby-verify.ts +11 -7
- package/scripts/lessons/handbook-page.ts +85 -0
- package/scripts/release-loop/gate-node.ts +9 -0
- 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 +4 -8
- 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/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-mission/driver.ts +3 -3
- package/src/autopilot/build/llm-conflict-merge.ts +45 -8
- package/src/autopilot/build/merge-intent.ts +53 -0
- 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 +35 -3
- 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/pr-cli.ts +29 -1
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +144 -21
- package/src/cli/rfc-status.ts +91 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +86 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +4 -3
- package/src/context-bus/long-term-memory.ts +92 -11
- 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-ledger.ts +40 -1
- 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-runner/graph-tick.ts +4 -0
- package/src/graph-runner/runner.ts +30 -5
- package/src/harness/harness-cli-command.ts +152 -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 +3 -3
- package/src/harness/harness-queue.ts +216 -28
- package/src/harness/helper-scan.ts +139 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/hq/hq.ts +1 -1
- package/src/hq/promote.ts +301 -0
- package/src/index.ts +83 -21
- package/src/intake-plane/linear-wish.ts +39 -11
- 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 +5 -3
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +36 -4
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +10 -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 +51 -12
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/tick.ts +302 -0
- package/src/loops/orchestrator/traffic.ts +18 -3
- package/src/loops/registry.ts +454 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +17 -5
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +7 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +18 -2
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +5 -1
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- 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/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 +16 -3
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-loop/seat-loop.ts +406 -31
- 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/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/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +80 -16
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/user-config.ts +120 -16
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.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-fe5c06d6b31f8b25.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/5250-c78ca295585bbf95.js +0 -1
- 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/6511-ff8360be3d65aaa4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.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/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.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-9da4678f8a310d18.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/9906-e7b8b5310a169dfe.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-e5b4e1b256f41076.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.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-cb2eb0fd809980da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.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-d92e32d51ab5157e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.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/{D51l8jUBplJrIpWvENsDK → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
|
@@ -10,6 +10,9 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
|
|
10
10
|
import { runGitCommand } from '../../git-fs/runner.js';
|
|
11
11
|
import { countTestDeclarations } from '../../self-implement/test-declarations.js';
|
|
12
12
|
import { NEXT_MD_PATH, resolveNextMdConflict } from '../../release-loop/next-md-merge.js';
|
|
13
|
+
import { getUserConfig } from '../../user-config.js';
|
|
14
|
+
import { debug } from '../../debug/log.js';
|
|
15
|
+
import { collectMergeIntent, defaultIntentGit, type IntentGit } from './merge-intent.js';
|
|
13
16
|
|
|
14
17
|
function deterministicNextMd(git: MergeGitSeam, worktreePath: string, file: string): string | null {
|
|
15
18
|
try {
|
|
@@ -328,27 +331,61 @@ export function defaultGitMergeSeam(): MergeGitSeam {
|
|
|
328
331
|
}
|
|
329
332
|
|
|
330
333
|
/** 실 LLM 충돌 해결 어댑터(streamLLM·sol). 코드펜스/설명 제거해 완결 파일만. */
|
|
331
|
-
export async function defaultLlmResolve(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
334
|
+
export async function defaultLlmResolve(
|
|
335
|
+
filePath: string, conflicted: string, mergeTarget: string,
|
|
336
|
+
options: { worktreePath?: string; mode?: 'off' | 'shadow' | 'on'; git?: IntentGit; stream?: typeof import('../../llm.js')['streamLLM'] } = {},
|
|
337
|
+
): Promise<string> {
|
|
338
|
+
let rawMode: unknown = options.mode;
|
|
339
|
+
if (rawMode === undefined) {
|
|
340
|
+
try { rawMode = (getUserConfig().raw.selfImplement as { mergeIntent?: unknown } | undefined)?.mergeIntent; }
|
|
341
|
+
catch { /* unreadable config keeps the old resolver */ }
|
|
342
|
+
}
|
|
343
|
+
const mode = rawMode === 'shadow' || rawMode === 'on' ? rawMode : 'off';
|
|
344
|
+
const streamLLM = options.stream ?? (await import('../../llm.js')).streamLLM;
|
|
345
|
+
const resolve = async (intent?: { ours: string | null; theirs: string[] }): Promise<string> => {
|
|
346
|
+
const out = await streamLLM([{ role: 'user', content: conflictResolvePrompt(filePath, conflicted, mergeTarget, intent) }], () => {}, { model: process.env.ELANOUS_CONFLICT_MODEL || tierModel('better'), reasoningEffort: 'medium' });
|
|
347
|
+
// ⛔ 라우터는 공급자가 전부 막히면 던지지 않고 오류 문구를 돌려준다 — 그것은 파일 내용이 아니다.
|
|
348
|
+
if (PROVIDER_FAILURE_TEXT.test(out)) throw new Error(`conflict resolve: LLM provider failure for ${filePath}`);
|
|
349
|
+
return `${out.replace(/^```[\w.-]*\n?/, '').replace(/\n?```\s*$/, '').trimEnd()}\n`;
|
|
350
|
+
};
|
|
351
|
+
if (mode === 'off') return resolve();
|
|
352
|
+
const collect = () => collectMergeIntent({ worktreePath: options.worktreePath ?? process.cwd(), filePath, mergeTarget, git: options.git ?? defaultIntentGit });
|
|
353
|
+
if (mode === 'on') return resolve(collect());
|
|
354
|
+
const plain = await resolve();
|
|
355
|
+
try {
|
|
356
|
+
const intent = collect();
|
|
357
|
+
const withIntent = await resolve(intent);
|
|
358
|
+
debug.log('self-dev.merge', 'intent-shadow', {
|
|
359
|
+
file: filePath, same: plain === withIntent,
|
|
360
|
+
plainMarkers: hasConflictMarkers(plain), intentMarkers: hasConflictMarkers(withIntent),
|
|
361
|
+
plainLines: lineCount(plain), intentLines: lineCount(withIntent),
|
|
362
|
+
oursIntent: intent.ours !== null, theirsCount: intent.theirs.length,
|
|
363
|
+
});
|
|
364
|
+
} catch (error) {
|
|
365
|
+
try { debug.log('self-dev.merge', 'intent-shadow-failed', { file: filePath, error: String(error) }); }
|
|
366
|
+
catch { /* shadow observation must not affect the adopted result */ }
|
|
367
|
+
}
|
|
368
|
+
return plain;
|
|
337
369
|
}
|
|
338
370
|
|
|
339
371
|
/** 편의 — 실 git+LLM 으로 호출부가 해석한 ref를 worktree 에 지능 정합한다. */
|
|
340
372
|
export async function mergeMainIntoWorktreeWithLlm(worktreePath: string, mergeTarget: string): Promise<LlmMergeOutcome> {
|
|
341
|
-
return mergeMainWithLlmResolve(worktreePath, mergeTarget, (filePath, conflicted) => defaultLlmResolve(filePath, conflicted, mergeTarget), defaultGitMergeSeam());
|
|
373
|
+
return mergeMainWithLlmResolve(worktreePath, mergeTarget, (filePath, conflicted) => defaultLlmResolve(filePath, conflicted, mergeTarget, { worktreePath }), defaultGitMergeSeam());
|
|
342
374
|
}
|
|
343
375
|
|
|
344
376
|
/** LLM 충돌 해결 프롬프트(순수·테스트) — ours(walker)·theirs(호출자가 전달한 정합 대상) 종합 지시. */
|
|
345
|
-
export function conflictResolvePrompt(filePath: string, conflictedContent: string, mergeTarget: string): string {
|
|
377
|
+
export function conflictResolvePrompt(filePath: string, conflictedContent: string, mergeTarget: string, intent?: { ours: string | null; theirs: string[] }): string {
|
|
346
378
|
return [
|
|
347
379
|
'너는 git merge 충돌을 지능적으로 해결하는 엔지니어다. 아래 파일은 3-way merge 충돌 마커를 포함한다:',
|
|
348
380
|
' <<<<<<< ours = 현재 브랜치(walker 가 이 미션에서 만든 산출물)',
|
|
349
381
|
' ======= 사이 = 양쪽 버전',
|
|
350
382
|
` >>>>>>> theirs = ${mergeTarget}(호출자가 해석해 전달한 정합 대상)`,
|
|
351
383
|
'',
|
|
384
|
+
...(intent ? [
|
|
385
|
+
`ours 의 의도: ${intent.ours ?? '(확인 불가)'}`,
|
|
386
|
+
`theirs 에 먼저 착지한 변경: ${intent.theirs.length ? intent.theirs.join(' · ') : '(확인 불가)'}`,
|
|
387
|
+
'',
|
|
388
|
+
] : []),
|
|
352
389
|
'해결 원칙:',
|
|
353
390
|
'- 양쪽의 의도를 **모두 보존**하며 종합한다(한쪽을 통째로 버리지 않는다).',
|
|
354
391
|
`- 같은 목적의 중복(예: 같은 테스트·같은 함수)은 **theirs(${mergeTarget}) 버전을 채택**하고 ours 의 중복은 제거.`,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { runGitCommand } from '../../git-fs/runner.js';
|
|
4
|
+
|
|
5
|
+
export interface MergeIntent {
|
|
6
|
+
ours: string | null;
|
|
7
|
+
theirs: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type IntentGit = (worktreePath: string, args: string[]) => { status: number | null; stdout: string };
|
|
11
|
+
|
|
12
|
+
export const defaultIntentGit: IntentGit = (worktreePath, args) => {
|
|
13
|
+
const result = runGitCommand(worktreePath, args, { encoding: 'utf8' });
|
|
14
|
+
return { status: result.status, stdout: result.stdout ?? '' };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Read only commits introduced on each side of the merge base; a missing ref/document never blocks resolution. */
|
|
18
|
+
export function collectMergeIntent({ worktreePath, filePath, mergeTarget, git }: {
|
|
19
|
+
worktreePath: string;
|
|
20
|
+
filePath: string;
|
|
21
|
+
mergeTarget: string;
|
|
22
|
+
git: IntentGit;
|
|
23
|
+
}): MergeIntent {
|
|
24
|
+
const read = (...args: string[]): string | null => {
|
|
25
|
+
try {
|
|
26
|
+
const result = git(worktreePath, args);
|
|
27
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
28
|
+
} catch { return null; }
|
|
29
|
+
};
|
|
30
|
+
const base = read('merge-base', 'HEAD', mergeTarget);
|
|
31
|
+
if (!base) return { ours: null, theirs: [] };
|
|
32
|
+
|
|
33
|
+
let ours: string | null = null;
|
|
34
|
+
const added = read('log', '--reverse', '--diff-filter=A', '--format=', '--name-only', '-z', `${base}..HEAD`, '--', 'docs/goals/');
|
|
35
|
+
for (const goal of added?.split('\0').map((file) => file.trim()).filter((file) => /^docs\/goals\/[^/]+$/.test(file)) ?? []) {
|
|
36
|
+
try {
|
|
37
|
+
const content = readFileSync(join(worktreePath, goal), 'utf8');
|
|
38
|
+
const lines = content.split(/\r?\n/);
|
|
39
|
+
const title = lines.find((line) => /^#\s+\S/.test(line))?.replace(/^#\s+/, '').trim()
|
|
40
|
+
?? lines.find((line) => line.trim().length > 0)?.trim();
|
|
41
|
+
const situation = lines.find((line) => /^Situation:/.test(line))?.trim();
|
|
42
|
+
const complication = lines.find((line) => /^Complication:/.test(line))?.trim();
|
|
43
|
+
ours = [title, situation, complication].filter(Boolean).join('\n') || null;
|
|
44
|
+
if (ours) break;
|
|
45
|
+
} catch { /* missing goal: try the next added document */ }
|
|
46
|
+
}
|
|
47
|
+
if (!ours) {
|
|
48
|
+
const subjects = read('log', '-3', '--format=%s', `${base}..HEAD`);
|
|
49
|
+
ours = subjects || null;
|
|
50
|
+
}
|
|
51
|
+
const subjects = read('log', '-5', '--format=%s', `${base}..${mergeTarget}`, '--', filePath);
|
|
52
|
+
return { ours, theirs: subjects ? subjects.split('\n') : [] };
|
|
53
|
+
}
|
package/src/chat/index.ts
CHANGED
|
@@ -452,6 +452,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
|
|
452
452
|
{ name: 'reasoning', aliases: ['r', 'think'], description: 'Reasoning level (codex effort + summary, anthropic extended-thinking budget) — /reasoning [off|low|medium|high|xhigh] (cycle when no arg · xhigh 는 모델 상한이 xhigh 이상일 때만 wire 에 실린다, 아니면 high 로 깎인다)', subcommands: ['off', 'low', 'medium', 'high', 'xhigh'] },
|
|
453
453
|
{ name: 'local', aliases: ['ll'], description: 'Local OpenAI-compatible LLM — ping / models / test / use', subcommands: ['ping', 'models', 'test', 'use', 'status'] },
|
|
454
454
|
{ name: 'session', aliases: ['sess'], description: 'Session resume — list / load / sync / new (handoff from mobile)', subcommands: ['list', 'load', 'sync', 'new'] },
|
|
455
|
+
{ name: 'persona', aliases: [], description: '이 대화의 페르소나 보기·고르기·빼기 — /persona [이름|-]', subcommands: ['-'] },
|
|
455
456
|
{ name: 'fork', aliases: [], description: 'Copy this session into a new one' },
|
|
456
457
|
{ name: 'rewind', aliases: [], description: 'Rewind — 과거 user 턴 픽커로 되감기 (원본 보존 · 새 세션 분기 · /rewind <n> 숏컷)' },
|
|
457
458
|
// TUI 부활 C-a (2026-07-12): autopilot 미션 TUI 표면 — CLI 동등 단일 창구 소비.
|
|
@@ -463,6 +464,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
|
|
463
464
|
{ name: 'plugin', aliases: ['plugins'], description: 'Manage plugins', subcommands: ['list', 'activate', 'deactivate', 'reload'] },
|
|
464
465
|
{ name: 'widget', aliases: ['widgets'], description: 'Manage widgets', subcommands: ['list', 'reload', 'instances'] },
|
|
465
466
|
{ name: 'log', aliases: [], description: 'Chat Log controls — size / clear / filter / search / freeze / solo / turn / fold / help', subcommands: ['size', 'clear', 'filter', 'search', 'freeze', 'solo', 'turn', 'fold', 'help'] },
|
|
467
|
+
{ name: 'loops', aliases: [], description: '루프와 크론 상태 표 — /loops [owner]' },
|
|
466
468
|
{ name: 'memory', aliases: ['mem'], description: 'Memory ops — list / show / search / add / delete (see `elanous memory --help`)', subcommands: ['list', 'show', 'search', 'add', 'delete', 'index'] },
|
|
467
469
|
{ name: 'export', aliases: [], description: 'Export this conversation transcript to a markdown file — /export [path] (default ~/temp/elanous-transcript-<stamp>.md)' },
|
|
468
470
|
{ name: 'delta', aliases: ['diffs'], description: 'Source delta browser — open the latest turn-scoped file diff popup', subcommands: ['open', 'help'] },
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { chmodSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
|
+
import { debug } from '../debug/log.js';
|
|
6
|
+
|
|
7
|
+
export type ClaimStatus = 'draft' | 'verified' | 'public' | 'stale' | 'retracted';
|
|
8
|
+
|
|
9
|
+
export class ClaimsInputError extends Error {
|
|
10
|
+
override name = 'ClaimsInputError';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const claimsTable = (name: 'claims' | 'claims_new') => `CREATE TABLE ${name} (
|
|
14
|
+
id TEXT PRIMARY KEY, claim TEXT NOT NULL, audience TEXT NOT NULL,
|
|
15
|
+
status TEXT NOT NULL CHECK(status IN ('draft','verified','public','stale','retracted')),
|
|
16
|
+
owner TEXT NOT NULL, contrast TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)`;
|
|
17
|
+
export interface ClaimRow {
|
|
18
|
+
id: string;
|
|
19
|
+
claim: string;
|
|
20
|
+
audience: string;
|
|
21
|
+
status: ClaimStatus;
|
|
22
|
+
owner: string;
|
|
23
|
+
contrast: string | null;
|
|
24
|
+
created_at: string;
|
|
25
|
+
updated_at: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ClaimEvidence { claim_id: string; value: string; command: string; source: string | null; measured_at: string; valid_until: string }
|
|
28
|
+
export interface ClaimLink { claim_id: string; cell: string; version: string | null }
|
|
29
|
+
export interface ClaimHistory { claim_id: string; at: string; by: string; event: 'add' | 'verify' | 'publish' | 'link' | 'stale' | 'retract'; detail: string }
|
|
30
|
+
export interface ClaimDetail extends ClaimRow { evidence: ClaimEvidence[]; links: ClaimLink[]; history: ClaimHistory[] }
|
|
31
|
+
export interface ClaimsLedgerOptions { stateDir?: string; now?: () => Date }
|
|
32
|
+
|
|
33
|
+
function required(value: string, field: string): string {
|
|
34
|
+
if (typeof value !== 'string' || !value.trim()) throw new ClaimsInputError(`${field} is required`);
|
|
35
|
+
return value.trim();
|
|
36
|
+
}
|
|
37
|
+
function iso(value: string, field: string): string {
|
|
38
|
+
if (typeof value !== 'string' || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(value)) throw new ClaimsInputError(`invalid ${field}: ${value}`);
|
|
39
|
+
const date = new Date(value);
|
|
40
|
+
if (!Number.isFinite(date.getTime())) throw new ClaimsInputError(`invalid ${field}: ${value}`);
|
|
41
|
+
return date.toISOString();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class ClaimsLedger {
|
|
45
|
+
readonly path: string;
|
|
46
|
+
private readonly now: () => Date;
|
|
47
|
+
|
|
48
|
+
constructor(options: ClaimsLedgerOptions = {}) {
|
|
49
|
+
this.path = join(options.stateDir ?? elanousStateRoot(), 'claims', 'claims.sqlite');
|
|
50
|
+
this.now = options.now ?? (() => new Date());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private open(): Database {
|
|
54
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
55
|
+
const db = new Database(this.path, { create: true, strict: true });
|
|
56
|
+
try {
|
|
57
|
+
chmodSync(this.path, 0o600);
|
|
58
|
+
db.exec('PRAGMA busy_timeout = 10000');
|
|
59
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
60
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
61
|
+
db.exec(`${claimsTable('claims').replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS')};
|
|
62
|
+
CREATE TABLE IF NOT EXISTS evidence (
|
|
63
|
+
claim_id TEXT NOT NULL REFERENCES claims(id), value TEXT NOT NULL, command TEXT NOT NULL,
|
|
64
|
+
source TEXT, measured_at TEXT NOT NULL, valid_until TEXT NOT NULL);
|
|
65
|
+
CREATE TABLE IF NOT EXISTS links (
|
|
66
|
+
claim_id TEXT NOT NULL REFERENCES claims(id), cell TEXT NOT NULL, version TEXT);
|
|
67
|
+
CREATE TABLE IF NOT EXISTS history (
|
|
68
|
+
claim_id TEXT NOT NULL REFERENCES claims(id), at TEXT NOT NULL, by TEXT NOT NULL,
|
|
69
|
+
event TEXT NOT NULL, detail TEXT NOT NULL);`);
|
|
70
|
+
const schema = (db.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'claims'").get() as { sql: string }).sql;
|
|
71
|
+
if (!schema.includes("'retracted'")) {
|
|
72
|
+
db.exec('PRAGMA foreign_keys = OFF');
|
|
73
|
+
try {
|
|
74
|
+
db.exec('BEGIN IMMEDIATE');
|
|
75
|
+
try {
|
|
76
|
+
// Another opener may have migrated while this connection waited for the write lock.
|
|
77
|
+
const current = (db.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'claims'").get() as { sql: string }).sql;
|
|
78
|
+
if (!current.includes("'retracted'")) {
|
|
79
|
+
db.exec(claimsTable('claims_new'));
|
|
80
|
+
db.exec('INSERT INTO claims_new SELECT * FROM claims');
|
|
81
|
+
db.exec('DROP TABLE claims');
|
|
82
|
+
db.exec('ALTER TABLE claims_new RENAME TO claims');
|
|
83
|
+
if ((db.query('PRAGMA foreign_key_check').all() as unknown[]).length) throw new Error('claims migration foreign key check failed');
|
|
84
|
+
}
|
|
85
|
+
db.exec('COMMIT');
|
|
86
|
+
} catch (error) { db.exec('ROLLBACK'); throw error; }
|
|
87
|
+
} finally { db.exec('PRAGMA foreign_keys = ON'); }
|
|
88
|
+
}
|
|
89
|
+
return db;
|
|
90
|
+
} catch (error) { db.close(); throw error; }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private using<T>(work: (db: Database) => T): T {
|
|
94
|
+
const db = this.open();
|
|
95
|
+
try { return work(db); } finally { db.close(); }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private transaction<T>(db: Database, work: () => T): T {
|
|
99
|
+
db.exec('BEGIN IMMEDIATE');
|
|
100
|
+
try { const result = work(); db.exec('COMMIT'); return result; }
|
|
101
|
+
catch (error) { db.exec('ROLLBACK'); throw error; }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private row(db: Database, id: string): ClaimRow {
|
|
105
|
+
const row = db.query('SELECT * FROM claims WHERE id = ?').get(id) as ClaimRow | null;
|
|
106
|
+
if (!row) throw new ClaimsInputError(`claim not found: ${id}`);
|
|
107
|
+
return row;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private latest(db: Database, id: string): ClaimEvidence | null {
|
|
111
|
+
return db.query('SELECT claim_id, value, command, source, measured_at, valid_until FROM evidence WHERE claim_id = ? ORDER BY measured_at DESC, rowid DESC LIMIT 1').get(id) as ClaimEvidence | null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private record(db: Database, id: string, at: string, by: string, event: ClaimHistory['event'], detail: string): void {
|
|
115
|
+
db.query('INSERT INTO history (claim_id, at, by, event, detail) VALUES (?, ?, ?, ?, ?)').run(id, at, by, event, detail);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private refresh(db: Database, row: ClaimRow, at: string): ClaimRow {
|
|
119
|
+
if (row.status === 'stale' || row.status === 'draft' || row.status === 'retracted') return row;
|
|
120
|
+
const evidence = this.latest(db, row.id);
|
|
121
|
+
if (!evidence || evidence.valid_until > at) return row;
|
|
122
|
+
db.query("UPDATE claims SET status = 'stale', updated_at = ? WHERE id = ?").run(at, row.id);
|
|
123
|
+
this.record(db, row.id, at, 'system', 'stale', `evidence expired: ${evidence.valid_until}`);
|
|
124
|
+
debug.log('claims.ledger', 'stale', { id: row.id, status: 'stale' });
|
|
125
|
+
return { ...row, status: 'stale', updated_at: at };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
add(input: { id: string; claim: string; audience: string; owner: string; contrast?: string }): ClaimRow {
|
|
129
|
+
const id = required(input.id, 'id');
|
|
130
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(id)) throw new ClaimsInputError('invalid claim id');
|
|
131
|
+
const claim = required(input.claim, 'claim');
|
|
132
|
+
const audience = required(input.audience, 'audience').split(',').map(part => required(part, 'audience')).join(',');
|
|
133
|
+
const owner = required(input.owner, 'owner');
|
|
134
|
+
const contrast = input.contrast === undefined ? null : required(input.contrast, 'contrast');
|
|
135
|
+
return this.using(db => this.transaction(db, () => {
|
|
136
|
+
if (db.query('SELECT 1 FROM claims WHERE id = ?').get(id)) throw new ClaimsInputError(`claim already exists: ${id}`);
|
|
137
|
+
const at = this.now().toISOString();
|
|
138
|
+
db.query('INSERT INTO claims (id, claim, audience, status, owner, contrast, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)')
|
|
139
|
+
.run(id, claim, audience, 'draft', owner, contrast, at, at);
|
|
140
|
+
this.record(db, id, at, owner, 'add', claim);
|
|
141
|
+
debug.log('claims.ledger', 'add', { id, status: 'draft' });
|
|
142
|
+
return this.row(db, id);
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
verify(id: string, input: { value: string; command: string; source?: string; measuredAt: string; validUntil: string; by: string }): ClaimRow {
|
|
147
|
+
const value = required(input.value, 'value');
|
|
148
|
+
const command = required(input.command, 'command');
|
|
149
|
+
const by = required(input.by, 'by');
|
|
150
|
+
const measuredAt = iso(input.measuredAt, 'measuredAt');
|
|
151
|
+
const validUntil = iso(input.validUntil, 'validUntil');
|
|
152
|
+
if (validUntil <= measuredAt) throw new ClaimsInputError('validUntil must be after measuredAt');
|
|
153
|
+
const source = input.source === undefined ? null : required(input.source, 'source');
|
|
154
|
+
return this.using(db => this.transaction(db, () => {
|
|
155
|
+
if (this.row(db, id).status === 'retracted') throw new ClaimsInputError(`claim retracted: ${id}`);
|
|
156
|
+
const at = this.now().toISOString();
|
|
157
|
+
db.query('INSERT INTO evidence (claim_id, value, command, source, measured_at, valid_until) VALUES (?, ?, ?, ?, ?, ?)')
|
|
158
|
+
.run(id, value, command, source, measuredAt, validUntil);
|
|
159
|
+
db.query("UPDATE claims SET status = 'verified', updated_at = ? WHERE id = ?").run(at, id);
|
|
160
|
+
this.record(db, id, at, by, 'verify', value);
|
|
161
|
+
debug.log('claims.ledger', 'verify', { id, status: 'verified' });
|
|
162
|
+
return this.row(db, id);
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
publish(id: string, by: string): ClaimRow {
|
|
167
|
+
const actor = required(by, 'by');
|
|
168
|
+
return this.using(db => {
|
|
169
|
+
const published = this.transaction(db, () => {
|
|
170
|
+
const at = this.now().toISOString();
|
|
171
|
+
const row = this.refresh(db, this.row(db, id), at);
|
|
172
|
+
const evidence = this.latest(db, id);
|
|
173
|
+
if (row.status !== 'verified' || !evidence || evidence.valid_until <= at) return null;
|
|
174
|
+
db.query("UPDATE claims SET status = 'public', updated_at = ? WHERE id = ?").run(at, id);
|
|
175
|
+
this.record(db, id, at, actor, 'publish', evidence.value);
|
|
176
|
+
return this.row(db, id);
|
|
177
|
+
});
|
|
178
|
+
if (!published) throw new ClaimsInputError(`claim requires verified, valid evidence: ${id}`);
|
|
179
|
+
debug.log('claims.ledger', 'publish', { id, status: 'public' });
|
|
180
|
+
return published;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
link(id: string, input: { cell: string; version?: string }): ClaimRow {
|
|
185
|
+
const cell = required(input.cell, 'cell');
|
|
186
|
+
const version = input.version === undefined ? null : required(input.version, 'version');
|
|
187
|
+
return this.using(db => this.transaction(db, () => {
|
|
188
|
+
const row = this.row(db, id);
|
|
189
|
+
const at = this.now().toISOString();
|
|
190
|
+
db.query('INSERT INTO links (claim_id, cell, version) VALUES (?, ?, ?)').run(id, cell, version);
|
|
191
|
+
db.query('UPDATE claims SET updated_at = ? WHERE id = ?').run(at, id);
|
|
192
|
+
this.record(db, id, at, 'system', 'link', version ? `${cell} (${version})` : cell);
|
|
193
|
+
debug.log('claims.ledger', 'link', { id, status: row.status });
|
|
194
|
+
return this.row(db, id);
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
retract(id: string, input: { reason: string; by: string }): ClaimRow {
|
|
199
|
+
const reason = required(input.reason, 'reason');
|
|
200
|
+
const by = required(input.by, 'by');
|
|
201
|
+
return this.using(db => this.transaction(db, () => {
|
|
202
|
+
if (this.row(db, id).status === 'retracted') throw new ClaimsInputError(`claim already retracted: ${id}`);
|
|
203
|
+
const at = this.now().toISOString();
|
|
204
|
+
db.query("UPDATE claims SET status = 'retracted', updated_at = ? WHERE id = ?").run(at, id);
|
|
205
|
+
this.record(db, id, at, by, 'retract', reason);
|
|
206
|
+
debug.log('claims.ledger', 'retract', { id, status: 'retracted' });
|
|
207
|
+
return this.row(db, id);
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
list(filters: { status?: ClaimStatus; audience?: string } = {}): ClaimRow[] {
|
|
212
|
+
if (filters.status && !['draft', 'verified', 'public', 'stale', 'retracted'].includes(filters.status)) throw new ClaimsInputError('invalid status');
|
|
213
|
+
return this.using(db => this.transaction(db, () => {
|
|
214
|
+
const at = this.now().toISOString();
|
|
215
|
+
return (db.query('SELECT * FROM claims ORDER BY created_at, id').all() as ClaimRow[])
|
|
216
|
+
.map(row => this.refresh(db, row, at))
|
|
217
|
+
.filter(row => (!filters.status || row.status === filters.status)
|
|
218
|
+
&& (!filters.audience || row.audience.split(',').includes(filters.audience)));
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
get(id: string): ClaimDetail {
|
|
223
|
+
return this.using(db => this.transaction(db, () => {
|
|
224
|
+
const row = this.refresh(db, this.row(db, id), this.now().toISOString());
|
|
225
|
+
return { ...row,
|
|
226
|
+
evidence: db.query('SELECT claim_id, value, command, source, measured_at, valid_until FROM evidence WHERE claim_id = ? ORDER BY measured_at, rowid').all(id) as ClaimEvidence[],
|
|
227
|
+
links: db.query('SELECT claim_id, cell, version FROM links WHERE claim_id = ? ORDER BY rowid').all(id) as ClaimLink[],
|
|
228
|
+
history: db.query('SELECT claim_id, at, by, event, detail FROM history WHERE claim_id = ? ORDER BY rowid').all(id) as ClaimHistory[],
|
|
229
|
+
};
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { appendFileSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { checkBrand } from '../../scripts/brand/check.js';
|
|
6
|
+
import { debug } from '../debug/log.js';
|
|
7
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
8
|
+
import type { ClaimsLedger, ClaimEvidence } from './claims-ledger.js';
|
|
9
|
+
|
|
10
|
+
export type ClaimSurface = 'deck' | 'site' | 'notice';
|
|
11
|
+
export interface RenderedClaims {
|
|
12
|
+
markdown: string;
|
|
13
|
+
included: string[];
|
|
14
|
+
excluded: Array<{ id: string; reason: 'stale' | 'not-public' | 'retracted' | 'expired' | 'brand' | 'ambiguous-value' }>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const headings: Record<ClaimSurface, string> = {
|
|
18
|
+
deck: '## 왜 엘라누스인가',
|
|
19
|
+
site: '## 엘라누스가 하는 일',
|
|
20
|
+
notice: '### 이번 판에서 확인된 것',
|
|
21
|
+
};
|
|
22
|
+
const scopes: Record<ClaimSurface, 'deck' | 'site' | 'release-notes'> = {
|
|
23
|
+
deck: 'deck', site: 'site', notice: 'release-notes',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function latest(evidence: ClaimEvidence[]): ClaimEvidence | undefined {
|
|
27
|
+
return evidence.reduce<ClaimEvidence | undefined>((best, item) =>
|
|
28
|
+
!best || item.measured_at >= best.measured_at ? item : best, undefined);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function commentValue(value: string): string {
|
|
32
|
+
return value.replace(/<!--|-->|\r|\n/g, ' ').replace(/\s+/g, ' ');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function firstSentence(claim: string): string {
|
|
36
|
+
const sentence = claim.split(/\r?\n|(?<=[.!?。!?])\s+/u)[0];
|
|
37
|
+
return sentence?.trim() ?? '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The big number to show is the one measured value that carries a unit (420만+ 줄 · 37% · 12배 …).
|
|
42
|
+
* Dates, times and bare numbers are not values; the first unit-bearing number wins, none is ambiguous — exclude
|
|
43
|
+
* rather than publish the wrong one (review round 3: «측정일 2026-10-03, 결과 420만+ 줄» used to print 2026).
|
|
44
|
+
*/
|
|
45
|
+
export function representativeNumber(value: string): string | undefined {
|
|
46
|
+
const withoutDates = value.replace(/\b\d{4}-\d{2}-\d{2}(?:[T ][\d:.]+Z?)?\b/g, ' ').replace(/\b\d{1,2}:\d{2}(?::\d{2})?\b/g, ' ');
|
|
47
|
+
const matches = [...withoutDates.matchAll(/\d[\d,.]*\s*(?:만\+?\s*줄|만\+|만|억|%|배|명|건|줄|개월|lines?\b|PRs?\b|x\b)\+?/gi)].map(match => match[0].trim());
|
|
48
|
+
return matches[0];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function renderClaims(ledger: ClaimsLedger, options: { surface: ClaimSurface; audience?: string }): RenderedClaims {
|
|
52
|
+
const { surface, audience } = options;
|
|
53
|
+
if (!(surface in headings)) throw new Error(`invalid claims surface: ${surface}`);
|
|
54
|
+
const excluded: RenderedClaims['excluded'] = [];
|
|
55
|
+
const candidates: Array<{ id: string; line: string }> = [];
|
|
56
|
+
for (const row of ledger.list()) {
|
|
57
|
+
if (audience && !row.audience.split(',').includes(audience)) continue;
|
|
58
|
+
if (row.status !== 'public') {
|
|
59
|
+
excluded.push({ id: row.id, reason: row.status === 'stale' ? 'stale' : row.status === 'retracted' ? 'retracted' : 'not-public' });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const detail = ledger.get(row.id);
|
|
63
|
+
if (detail.status !== 'public') {
|
|
64
|
+
excluded.push({ id: row.id, reason: 'stale' });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const evidence = latest(detail.evidence);
|
|
68
|
+
if (!evidence) {
|
|
69
|
+
excluded.push({ id: row.id, reason: 'expired' });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const claim = firstSentence(row.claim);
|
|
73
|
+
const number = representativeNumber(evidence.value);
|
|
74
|
+
if (!number) {
|
|
75
|
+
excluded.push({ id: row.id, reason: 'ambiguous-value' });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!claim) {
|
|
79
|
+
excluded.push({ id: row.id, reason: 'brand' });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
candidates.push({ id: row.id, line: `- ${claim} — ${number} <!-- claim:${row.id} measured:${evidence.measured_at} cmd:${commentValue(evidence.command)}${evidence.source ? ` source:${commentValue(evidence.source)}` : ''} --> <!-- src:${commentValue(evidence.source ?? evidence.command)} -->` });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const temp = mkdtempSync(join(tmpdir(), 'claims-render-'));
|
|
86
|
+
const file = join(temp, 'claims.md');
|
|
87
|
+
try {
|
|
88
|
+
const probe = (items: typeof candidates): Set<string> => {
|
|
89
|
+
writeFileSync(file, [headings[surface], '', ...items.map(item => item.line)].join('\n').trimEnd() + '\n');
|
|
90
|
+
const result = checkBrand(scopes[surface], [file]);
|
|
91
|
+
if (result.missing) throw new Error('brand rules missing');
|
|
92
|
+
const bad = new Set<string>();
|
|
93
|
+
for (const finding of result.findings) {
|
|
94
|
+
const owner = items[finding.line - 3];
|
|
95
|
+
if (!owner) throw new Error('brand finding outside claim lines');
|
|
96
|
+
bad.add(owner.id);
|
|
97
|
+
}
|
|
98
|
+
return bad;
|
|
99
|
+
};
|
|
100
|
+
let remaining = candidates;
|
|
101
|
+
while (remaining.length) {
|
|
102
|
+
const bad = probe(remaining);
|
|
103
|
+
if (!bad.size) break;
|
|
104
|
+
const next = remaining.filter(item => {
|
|
105
|
+
if (!bad.has(item.id)) return true;
|
|
106
|
+
excluded.push({ id: item.id, reason: 'brand' });
|
|
107
|
+
return false;
|
|
108
|
+
});
|
|
109
|
+
remaining = next;
|
|
110
|
+
}
|
|
111
|
+
const included = remaining.map(item => item.id);
|
|
112
|
+
const markdown = [headings[surface], '', ...remaining.map(item => item.line)].join('\n').trimEnd() + '\n';
|
|
113
|
+
debug.log('claims.render', 'rendered', { surface, included, excluded });
|
|
114
|
+
return { markdown, included, excluded };
|
|
115
|
+
} finally { rmSync(temp, { recursive: true, force: true }); }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function queueStaleRechecks(ledger: ClaimsLedger, options: { root?: string; now?: Date } = {}): number {
|
|
119
|
+
const stale = ledger.list({ status: 'stale' });
|
|
120
|
+
if (!stale.length) {
|
|
121
|
+
debug.log('claims.recheck', 'queued', { count: 0 });
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
const now = options.now ?? new Date();
|
|
125
|
+
const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(now);
|
|
126
|
+
const part = (type: string) => parts.find(p => p.type === type)!.value;
|
|
127
|
+
const day = `${part('year')}-${part('month')}-${part('day')}`;
|
|
128
|
+
const path = join(options.root ?? effectiveInstanceRoot(), 'seat-requests', 'requests.jsonl');
|
|
129
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
130
|
+
const lock = new Database(`${path}.loop-checker.lock.sqlite`);
|
|
131
|
+
try {
|
|
132
|
+
lock.exec('PRAGMA busy_timeout = 10000');
|
|
133
|
+
lock.exec('BEGIN EXCLUSIVE');
|
|
134
|
+
try {
|
|
135
|
+
const existing = new Set<string>();
|
|
136
|
+
if (existsSync(path)) for (const line of readFileSync(path, 'utf8').split('\n')) {
|
|
137
|
+
if (line.trim()) {
|
|
138
|
+
const row = JSON.parse(line) as { key?: string };
|
|
139
|
+
if (row.key) existing.add(row.key);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
let count = 0;
|
|
143
|
+
for (const claim of stale) {
|
|
144
|
+
const key = `claims-recheck:${claim.id}:${day}`;
|
|
145
|
+
if (existing.has(key)) continue;
|
|
146
|
+
const command = latest(ledger.get(claim.id).evidence)?.command ?? '없음';
|
|
147
|
+
appendFileSync(path, JSON.stringify({ key, seat: claim.owner, text: `${claim.id} 근거 낡음 — 재측: ${command}`,
|
|
148
|
+
source: 'claims', status: 'queued', queuedAt: now.toISOString() }) + '\n');
|
|
149
|
+
existing.add(key);
|
|
150
|
+
count++;
|
|
151
|
+
}
|
|
152
|
+
lock.exec('COMMIT');
|
|
153
|
+
debug.log('claims.recheck', 'queued', { count });
|
|
154
|
+
return count;
|
|
155
|
+
} catch (error) { lock.exec('ROLLBACK'); throw error; }
|
|
156
|
+
} finally { lock.close(); }
|
|
157
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { ClaimsInputError, ClaimsLedger, type ClaimsLedgerOptions, type ClaimRow, type ClaimDetail } from '../claims/claims-ledger.js';
|
|
3
|
+
import { renderClaims, queueStaleRechecks, type ClaimSurface } from '../claims/claims-render.js';
|
|
4
|
+
|
|
5
|
+
function actor(explicit?: string): string {
|
|
6
|
+
const by = explicit?.trim() || process.env.AI_AGENT?.trim();
|
|
7
|
+
if (!by) throw new ClaimsInputError('--by or AI_AGENT is required');
|
|
8
|
+
return by;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function validUntil(raw: string, now: Date): string {
|
|
12
|
+
const days = /^\+(\d+)d$/.exec(raw);
|
|
13
|
+
const parsed = days ? new Date(now.getTime() + Number(days[1]) * 86400000) : new Date(raw);
|
|
14
|
+
if (!Number.isFinite(parsed.getTime()) || (!days && !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(raw))) throw new ClaimsInputError(`invalid --valid-until: ${raw}`);
|
|
15
|
+
return parsed.toISOString();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function handle<Args extends unknown[]>(command: string, action: (...args: Args) => void): (...args: Args) => void {
|
|
19
|
+
return (...args) => {
|
|
20
|
+
try { action(...args); }
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (!(error instanceof ClaimsInputError)) throw error;
|
|
23
|
+
const message = error.message.replace(/\r/g, '\\r').replace(/\n/g, '\\n')
|
|
24
|
+
.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
25
|
+
process.stderr.write(`claims ${command}: ${message}\n`);
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function format(row: ClaimRow): string {
|
|
32
|
+
return `${row.id} · ${row.status} · ${row.claim} · ${row.audience} · ${row.owner}`;
|
|
33
|
+
}
|
|
34
|
+
function detail(row: ClaimDetail): string {
|
|
35
|
+
return [format(row), ...(row.contrast ? [`대비: ${row.contrast}`] : []),
|
|
36
|
+
'근거:', ...row.evidence.map(e => ` ${e.value} · ${e.command} · 측정 ${e.measured_at} · 유효 ${e.valid_until}${e.source ? ` · ${e.source}` : ''}`),
|
|
37
|
+
'연결:', ...row.links.map(l => ` ${l.cell}${l.version ? ` · ${l.version}` : ''}`),
|
|
38
|
+
'이력:', ...row.history.map(h => ` ${h.at} · ${h.event} · ${h.by} · ${h.detail}`)].join('\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function registerClaimsCommands(program: Command, options: ClaimsLedgerOptions & { instanceRoot?: string } = {}, out: Pick<Console, 'log'> = console): void {
|
|
42
|
+
const root = program.command('claims').description('소구점 · 실측 근거 원장');
|
|
43
|
+
const ledger = () => new ClaimsLedger(options);
|
|
44
|
+
const emit = (value: unknown, json: boolean | undefined, text: string) => out.log(json ? JSON.stringify(value) : text);
|
|
45
|
+
root.command('add <id>').requiredOption('--claim <text>').requiredOption('--audience <list>').requiredOption('--owner <seat>')
|
|
46
|
+
.option('--contrast <text>').action(handle('add', (id: string, o: { claim: string; audience: string; owner: string; contrast?: string }) => {
|
|
47
|
+
const row = ledger().add({ id, claim: o.claim, audience: o.audience, owner: o.owner, ...(o.contrast ? { contrast: o.contrast } : {}) });
|
|
48
|
+
emit(row, false, format(row));
|
|
49
|
+
}));
|
|
50
|
+
root.command('verify <id>').requiredOption('--value <value>').requiredOption('--command <command>')
|
|
51
|
+
.requiredOption('--valid-until <ISO|+Nd>').option('--source <url>').option('--by <name>')
|
|
52
|
+
.action(handle('verify', (id: string, o: { value: string; command: string; validUntil: string; source?: string; by?: string }) => {
|
|
53
|
+
const by = actor(o.by);
|
|
54
|
+
const now = options.now?.() ?? new Date();
|
|
55
|
+
const row = ledger().verify(id, { value: o.value, command: o.command, measuredAt: now.toISOString(), validUntil: validUntil(o.validUntil, now), by, ...(o.source ? { source: o.source } : {}) });
|
|
56
|
+
emit(row, false, format(row));
|
|
57
|
+
}));
|
|
58
|
+
root.command('publish <id>').option('--by <name>').action(handle('publish', (id: string, o: { by?: string }) => {
|
|
59
|
+
const row = ledger().publish(id, actor(o.by));
|
|
60
|
+
emit(row, false, format(row));
|
|
61
|
+
}));
|
|
62
|
+
root.command('link <id>').requiredOption('--cell <cell>').option('--version <version>')
|
|
63
|
+
.action(handle('link', (id: string, o: { cell: string; version?: string }) => {
|
|
64
|
+
const row = ledger().link(id, { cell: o.cell, ...(o.version ? { version: o.version } : {}) });
|
|
65
|
+
emit(row, false, format(row));
|
|
66
|
+
}));
|
|
67
|
+
root.command('retract <id>').requiredOption('--reason <text>').option('--by <name>')
|
|
68
|
+
.action(handle('retract', (id: string, o: { reason: string; by?: string }) => {
|
|
69
|
+
const row = ledger().retract(id, { reason: o.reason, by: actor(o.by) });
|
|
70
|
+
emit(row, false, format(row));
|
|
71
|
+
}));
|
|
72
|
+
root.command('list').option('--status <status>').option('--audience <audience>').option('--json')
|
|
73
|
+
.action(handle('list', (o: { status?: ClaimRow['status']; audience?: string; json?: boolean }) => {
|
|
74
|
+
const rows = ledger().list({ status: o.status, audience: o.audience });
|
|
75
|
+
emit(rows, o.json, rows.length ? rows.map(format).join('\n') : '소구점 0건');
|
|
76
|
+
}));
|
|
77
|
+
root.command('show <id>').option('--json').action(handle('show', (id: string, o: { json?: boolean }) => {
|
|
78
|
+
const row = ledger().get(id);
|
|
79
|
+
emit(row, o.json, detail(row));
|
|
80
|
+
}));
|
|
81
|
+
root.command('render').requiredOption('--surface <surface>', 'deck | site | notice')
|
|
82
|
+
.option('--audience <audience>').option('--json')
|
|
83
|
+
.action(handle('render', (o: { surface: ClaimSurface; audience?: string; json?: boolean }) => {
|
|
84
|
+
const result = renderClaims(ledger(), { surface: o.surface, ...(o.audience ? { audience: o.audience } : {}) });
|
|
85
|
+
// Review follow-up: rendering is where stale claims surface — queue their re-measurement in the same act.
|
|
86
|
+
const rechecks = result.excluded.some(item => item.reason === 'stale')
|
|
87
|
+
? queueStaleRechecks(ledger(), { root: options.instanceRoot, now: options.now?.() }) : 0;
|
|
88
|
+
emit({ ...result, rechecks }, o.json, rechecks ? `${result.markdown}\n<!-- 재측 요청 ${rechecks}건 -->` : result.markdown);
|
|
89
|
+
}));
|
|
90
|
+
root.command('recheck').option('--json').action(handle('recheck', (o: { json?: boolean }) => {
|
|
91
|
+
const count = queueStaleRechecks(ledger(), { root: options.instanceRoot, now: options.now?.() });
|
|
92
|
+
emit({ count }, o.json, `재측 요청 ${count}건`);
|
|
93
|
+
}));
|
|
94
|
+
}
|