elanous 0.2.25 → 0.2.28
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/{1454-37fd16f479f5554a.js → 1454-29d1d773d5996a49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-24453f6a302816b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-6c131d30bb1cf871.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12149c51f7d04b1.js → page-fa46d4588e8442c2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-dc0772b11861af51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-256ffd365d32e100.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-dea4b795741126fd.js → page-e0de78ae7b99f01c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-4c741eb760f2f77c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-e9ba98f5f1440a21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-dafc901bb1b72b5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-2f6f303fa114ec24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-1634081c5272006c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-ced0bcfc6542b3fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-bb1f05cd51c38944.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-646b50f63b6fe732.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-f736324afa2117c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-6f131b51193a36f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-435f5e2faf02bc01.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-6a14333d1b1b3967.js → page-497e0eedf20e7890.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-8c91a9a66c9ec90a.js → page-a8d28fdf995e16ea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8966452228753c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-66a67eb19ebd976b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-c71267e7681da982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a21d1e1797172692.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-cb249e81ed339318.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-781cfc5114e097da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-478157214db6a023.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-86141f12cf090b7f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-89cb33bced824e4a.js → page-7256c73bb1c36751.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-979d318a534e92da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-21050928a697ed7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-601fb77340e63c9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-05a4b2c9b91034aa.js → page-11bf9e5725a72c22.js} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/graphs/implement-loop.yaml +38 -1
- package/graphs/steward/recipes.yaml +6 -0
- package/graphs/steward/release-day.yaml +23 -0
- package/graphs/steward/steward.yaml +9 -1
- package/graphs/work/code-pr.yaml +23 -0
- package/package.json +1 -1
- package/scripts/browser-observe-cycle.ts +76 -2
- package/scripts/ci-typecheck-changed.ts +119 -7
- package/scripts/coord-msg-bridge.ts +209 -0
- package/scripts/coord-post.sh +40 -7
- package/scripts/coord-requests-bridge.ts +10 -5
- package/scripts/detect-test-interference.ts +31 -1
- package/scripts/expose-rubric-suggest.ts +1 -1
- package/scripts/measure-tsc-affected-roots.ts +10 -1
- package/scripts/release-loop/docs-node.ts +51 -22
- package/scripts/release-loop/export-check-node.ts +8 -5
- package/scripts/release-loop/gate-node.ts +37 -4
- package/scripts/release-loop/node-verdict.ts +10 -3
- package/scripts/release-loop/publish-node.ts +3 -0
- package/scripts/release-loop/tui-node-checks.ts +5 -2
- package/scripts/skills/public-check.ts +5 -2
- package/scripts/test-deterministic.ts +520 -37
- package/scripts/ux-sim.ts +7 -1
- package/scripts/verify-kpack6-live.ts +230 -0
- package/src/acp/codex-plugins.ts +17 -0
- package/src/agent/task-notification.ts +32 -2
- package/src/agent-mission/in-agent-plugin.ts +8 -3
- package/src/agent-substrate/review-observation.ts +9 -2
- package/src/agent-substrate/self-review-cli.ts +62 -16
- package/src/boot/daemon-tools/self-implement.ts +72 -4
- package/src/briefing/brief-items.ts +151 -35
- package/src/budget/llm-cost.ts +30 -3
- package/src/budget/usage-outcomes.ts +32 -0
- package/src/claims/claims-ledger.ts +23 -0
- package/src/claims/claims-render.ts +22 -2
- package/src/cli/brief-cli.ts +131 -10
- package/src/cli/claims-cli.ts +24 -4
- package/src/cli/control-cli.ts +29 -0
- package/src/cli/doctor-cli.ts +90 -8
- package/src/cli/msg-cli.ts +177 -11
- package/src/cli/plugin-cli.ts +11 -3
- package/src/cli/pr-cli.ts +133 -18
- package/src/cli/pr-land-overlap-evidence.ts +18 -10
- package/src/cli/pr-land-refusal.ts +20 -0
- package/src/cli/release-cli.ts +34 -5
- package/src/cli/rfc-cli.ts +24 -0
- package/src/cli/seat-cli.ts +121 -0
- package/src/cli/self-utterances-cli.ts +140 -0
- package/src/cli/tailscale-serve.ts +5 -8
- package/src/cli/tasks-cli.ts +528 -31
- package/src/cli/usage-cli.ts +9 -4
- package/src/context-card/cell.ts +40 -0
- package/src/dashboard/boot-timing.ts +35 -0
- package/src/dashboard/first-screen-band.ts +63 -13
- package/src/dashboard/index.ts +94 -27
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
- package/src/dashboard/skill-detection-runtime.ts +24 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
- package/src/debug/log.ts +1 -1
- package/src/domains/morning-report.ts +3 -1
- package/src/domains/release-tool.ts +29 -12
- package/src/domains/surface-events.ts +16 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/fleet/host-doctor.ts +73 -2
- package/src/github/pr-labels.ts +10 -4
- package/src/graph-runner/runner.ts +104 -15
- package/src/harness/harness-clean.ts +21 -3
- package/src/harness/harness-cli-command.ts +68 -10
- package/src/harness/harness-pod-dispatch.ts +11 -4
- package/src/harness/harness-stop.ts +11 -7
- package/src/hq/seats.ts +17 -3
- package/src/index.ts +69 -9
- package/src/instance/current.ts +2 -6
- package/src/instance/resolve.ts +5 -6
- package/src/intelligence-map/model-bench-scores.ts +102 -0
- package/src/llm/openai-sampling-fields.ts +89 -0
- package/src/llm/runtime-model-family.ts +61 -0
- package/src/llm/zai-coding-plan.ts +27 -1
- package/src/llm.ts +13 -89
- package/src/loops/orchestrator/inbox.ts +73 -0
- package/src/loops/orchestrator/resync-shadow.ts +100 -12
- package/src/loops/orchestrator/tick.ts +73 -2
- package/src/machines/machine-ledger.ts +31 -3
- package/src/maturity/cli-maturity.ts +13 -3
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/msg/msg-sse-server.ts +141 -0
- package/src/msg/msg-store.ts +149 -17
- package/src/ops/overview-collect.ts +3 -2
- package/src/ops/overview-owner.ts +29 -0
- package/src/patents/ledger.ts +128 -0
- package/src/plugin-hooks/shell-hook.ts +17 -2
- package/src/plugin-missions/evaluator-runner.ts +18 -2
- package/src/plugins/install/market-fetch.ts +32 -1
- package/src/plugins/install/plugin-install.ts +11 -1
- package/src/pod-lease/admission.ts +116 -41
- package/src/prompt-enhance/enhance.ts +11 -0
- package/src/release-loop/checklist.ts +52 -4
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/merged-pr-checklist.ts +24 -5
- package/src/release-loop/rubric.ts +70 -0
- package/src/rfc-registry/registry.ts +88 -0
- package/src/seat-dispatch/seat-request-ledger.ts +4 -0
- package/src/seat-loop/knowhow-lint.ts +233 -0
- package/src/seat-loop/seat-loop.ts +5 -1
- package/src/seat-loop/seat-pack.ts +16 -4
- package/src/seat-loop/traffic-signal.ts +7 -0
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/ask-path-candidates.ts +69 -14
- package/src/self-dev/decompose-proposal.ts +43 -1
- package/src/self-dev/draft-sweep.ts +23 -14
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/launch-preflight-text.ts +5 -1
- package/src/self-dev/launch-preflight.ts +143 -10
- package/src/self-dev/orchestrate.ts +6 -2
- package/src/self-dev/run-supervisor.ts +35 -1
- package/src/self-implement/gate-cli.ts +6 -2
- package/src/self-implement/goal-author.ts +5 -28
- package/src/self-implement/goal-command-execution.ts +26 -0
- package/src/self-implement/goal-new-paths.ts +4 -5
- package/src/self-implement/graph-authority.ts +17 -3
- package/src/self-implement/graph-yaml.ts +15 -7
- package/src/self-implement/headless-elanous-driver.ts +16 -1
- package/src/self-implement/host-regate.ts +44 -9
- package/src/self-implement/orchestrator.ts +5 -11
- package/src/self-implement/pwa-import-graph.ts +194 -7
- package/src/self-implement/rework-policy.ts +80 -2
- package/src/self-implement/self-implement-runtime.ts +126 -21
- package/src/self-implement/speed-claims.ts +145 -6
- package/src/session/index.ts +14 -4
- package/src/session-runtime/index.ts +55 -11
- package/src/skills/tools/persistent-grounding.ts +59 -4
- package/src/skills/tools/shell.ts +79 -1
- package/src/steward/rejudge-tick.ts +12 -1
- package/src/steward/release-day.ts +158 -0
- package/src/steward/resync-tick.ts +123 -0
- package/src/task-agent/actions.ts +1 -0
- package/src/task-agent/adopt-pr.ts +140 -0
- package/src/task-agent/board.ts +11 -1
- package/src/task-agent/card-evidence.ts +37 -5
- package/src/task-agent/cover.ts +105 -19
- package/src/task-agent/failure-notify.ts +61 -0
- package/src/task-agent/green-sweep.ts +170 -0
- package/src/task-agent/land-worktree.ts +52 -5
- package/src/task-agent/live-move-history.ts +1 -0
- package/src/task-agent/live-moves.ts +56 -17
- package/src/task-agent/parent-host.ts +1 -1
- package/src/task-agent/postland-card.ts +139 -0
- package/src/task-agent/rejudge-on-head.ts +321 -25
- package/src/task-agent/remote-rejudge-bind.ts +62 -0
- package/src/task-agent/repair-branch-cas.ts +182 -0
- package/src/task-agent/repair-cycle.ts +275 -0
- package/src/task-agent/repair-next-move.ts +31 -0
- package/src/task-agent/repair-review.ts +24 -0
- package/src/task-agent/repair-worker.ts +87 -0
- package/src/task-agent/review-result-capture.ts +66 -2
- package/src/task-agent/shadow.ts +150 -29
- package/src/task-agent/task-hand.ts +133 -8
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +71 -12
- package/src/telegram.ts +30 -1
- package/src/tool-runtime/tool-search-runtime.ts +25 -0
- package/src/tui.ts +20 -1
- package/src/user-config.ts +11 -60
- package/src/ux-sim/fault-sqlite-busy.ts +50 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-9360b1285b2a346e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-44eaad486bc98a45.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-97fa6cf3d34c7eb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-74366ae2693a7d95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-b9cc8a10bc6078b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-b008aff96be56654.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-852170303c6cd755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-97c7361cc2a19319.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-8c6901833f897496.js +0 -1
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { Database } from 'bun:sqlite';
|
|
4
|
+
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
5
|
+
import { assertNotTestWritingOps } from '../instance/test-write-guard.js';
|
|
6
|
+
|
|
7
|
+
/** The RFC ledger is independent of the release checklist ledger. Callers own and close the handle. */
|
|
8
|
+
export function openRfcRegistry(root = releaseLedgerRoot()): Database {
|
|
9
|
+
const path = join(root, 'rfc', 'registry.sqlite');
|
|
10
|
+
assertNotTestWritingOps(path, 'open the RFC registry');
|
|
11
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
12
|
+
const db = new Database(path, { create: true, strict: true });
|
|
13
|
+
try {
|
|
14
|
+
chmodSync(path, 0o600);
|
|
15
|
+
db.exec('PRAGMA busy_timeout = 10000; PRAGMA foreign_keys = ON');
|
|
16
|
+
db.exec(`CREATE TABLE IF NOT EXISTS rfc (
|
|
17
|
+
id TEXT PRIMARY KEY CHECK(length(id) = 8 AND id GLOB 'RFC-[0-9][0-9][0-9][0-9]'),
|
|
18
|
+
title TEXT NOT NULL CHECK(length(trim(title)) > 0),
|
|
19
|
+
status TEXT NOT NULL,
|
|
20
|
+
doc_path TEXT,
|
|
21
|
+
created_at TEXT NOT NULL
|
|
22
|
+
);
|
|
23
|
+
CREATE TABLE IF NOT EXISTS rfc_item (
|
|
24
|
+
id TEXT PRIMARY KEY CHECK(length(id) = 12 AND id GLOB 'RFC-[0-9][0-9][0-9][0-9].I[0-9][0-9]'),
|
|
25
|
+
rfc_id TEXT NOT NULL REFERENCES rfc(id),
|
|
26
|
+
title TEXT NOT NULL CHECK(length(trim(title)) > 0),
|
|
27
|
+
status TEXT NOT NULL,
|
|
28
|
+
created_at TEXT NOT NULL,
|
|
29
|
+
CHECK(substr(id, 1, 8) = rfc_id)
|
|
30
|
+
);
|
|
31
|
+
CREATE TABLE IF NOT EXISTS rfc_item_link (
|
|
32
|
+
item_id TEXT NOT NULL REFERENCES rfc_item(id),
|
|
33
|
+
target_item_id TEXT NOT NULL REFERENCES rfc_item(id),
|
|
34
|
+
PRIMARY KEY(item_id, target_item_id),
|
|
35
|
+
CHECK(item_id <> target_item_id)
|
|
36
|
+
);
|
|
37
|
+
CREATE TABLE IF NOT EXISTS rfc_event (
|
|
38
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
39
|
+
rfc_id TEXT NOT NULL REFERENCES rfc(id),
|
|
40
|
+
item_id TEXT REFERENCES rfc_item(id),
|
|
41
|
+
at TEXT NOT NULL,
|
|
42
|
+
actor TEXT NOT NULL,
|
|
43
|
+
event TEXT NOT NULL,
|
|
44
|
+
payload TEXT
|
|
45
|
+
);
|
|
46
|
+
CREATE TRIGGER IF NOT EXISTS rfc_id_immutable BEFORE UPDATE OF id ON rfc
|
|
47
|
+
BEGIN SELECT RAISE(ABORT, 'RFC identifier is immutable'); END;
|
|
48
|
+
CREATE TRIGGER IF NOT EXISTS rfc_no_delete BEFORE DELETE ON rfc
|
|
49
|
+
BEGIN SELECT RAISE(ABORT, 'RFC identifier is immutable'); END;
|
|
50
|
+
CREATE TRIGGER IF NOT EXISTS rfc_item_id_immutable BEFORE UPDATE OF id, rfc_id ON rfc_item
|
|
51
|
+
BEGIN SELECT RAISE(ABORT, 'RFC item identifier is immutable'); END;
|
|
52
|
+
CREATE TRIGGER IF NOT EXISTS rfc_item_no_delete BEFORE DELETE ON rfc_item
|
|
53
|
+
BEGIN SELECT RAISE(ABORT, 'RFC item identifier is immutable'); END;
|
|
54
|
+
CREATE TRIGGER IF NOT EXISTS rfc_event_item_parent BEFORE INSERT ON rfc_event
|
|
55
|
+
WHEN NEW.item_id IS NOT NULL AND NOT EXISTS
|
|
56
|
+
(SELECT 1 FROM rfc_item WHERE id = NEW.item_id AND rfc_id = NEW.rfc_id)
|
|
57
|
+
BEGIN SELECT RAISE(ABORT, 'RFC event item belongs to another RFC'); END;
|
|
58
|
+
CREATE TRIGGER IF NOT EXISTS rfc_event_no_update BEFORE UPDATE ON rfc_event
|
|
59
|
+
BEGIN SELECT RAISE(ABORT, 'RFC event history is append-only'); END;
|
|
60
|
+
CREATE TRIGGER IF NOT EXISTS rfc_event_no_delete BEFORE DELETE ON rfc_event
|
|
61
|
+
BEGIN SELECT RAISE(ABORT, 'RFC event history is append-only'); END;`);
|
|
62
|
+
return db;
|
|
63
|
+
} catch (error) { db.close(); throw error; }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RfcRow { id: string; title: string; status: string; doc_path: string | null; created_at: string }
|
|
67
|
+
export interface RfcItemRow { id: string; rfc_id: string; title: string; status: string; created_at: string }
|
|
68
|
+
export interface RfcItemLinkRow { item_id: string; target_item_id: string }
|
|
69
|
+
export interface RfcDetail extends RfcRow { items: Array<RfcItemRow & { links: RfcItemLinkRow[] }> }
|
|
70
|
+
|
|
71
|
+
export function listRfcs(root?: string): RfcRow[] {
|
|
72
|
+
const db = openRfcRegistry(root);
|
|
73
|
+
try { return db.query('SELECT id, title, status, doc_path, created_at FROM rfc ORDER BY id').all() as RfcRow[]; }
|
|
74
|
+
finally { db.close(); }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function showRfc(id: string, root?: string): RfcDetail | null {
|
|
78
|
+
const db = openRfcRegistry(root);
|
|
79
|
+
try {
|
|
80
|
+
const read = db.transaction(() => {
|
|
81
|
+
const row = db.query('SELECT id, title, status, doc_path, created_at FROM rfc WHERE id = ?').get(id) as RfcRow | null;
|
|
82
|
+
if (!row) return null;
|
|
83
|
+
const items = db.query('SELECT id, rfc_id, title, status, created_at FROM rfc_item WHERE rfc_id = ? ORDER BY id').all(id) as RfcItemRow[];
|
|
84
|
+
return { ...row, items: items.map((item) => ({ ...item, links: db.query('SELECT item_id, target_item_id FROM rfc_item_link WHERE item_id = ? ORDER BY target_item_id').all(item.id) as RfcItemLinkRow[] })) };
|
|
85
|
+
});
|
|
86
|
+
return read();
|
|
87
|
+
} finally { db.close(); }
|
|
88
|
+
}
|
|
@@ -11,6 +11,10 @@ export type SeatRequestRow = {
|
|
|
11
11
|
text: string;
|
|
12
12
|
queuedAt: string;
|
|
13
13
|
reason?: string;
|
|
14
|
+
/** One-line explanation of the automated action recorded on this row. */
|
|
15
|
+
why?: string;
|
|
16
|
+
/** Pointer to the evidence supporting the action (log line, run id, ledger path, …). */
|
|
17
|
+
evidenceRef?: string;
|
|
14
18
|
closedAt?: string;
|
|
15
19
|
[field: string]: unknown;
|
|
16
20
|
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { seatPackLeakPatterns } from './seat-pack.js';
|
|
2
|
+
|
|
3
|
+
/** 필수 칸 — 형식 v2 선언(각 자리 KNOWHOW 머리글)의 공통 아홉. 괄호 수식어(신호(관측) 등)는 정규화해서 비교한다. */
|
|
4
|
+
const REQUIRED_FIELDS = ['상황', '신호', '원인', '처방', '재는 명령', '기대', '유효 조건', '검증', '출처'] as const;
|
|
5
|
+
|
|
6
|
+
/** Only commands whose executable and subcommand are known to be observational are accepted. */
|
|
7
|
+
const READ_COMMANDS: Record<string, ReadonlySet<string> | null> = {
|
|
8
|
+
cat: null, echo: null, rg: null, grep: null, head: null, tail: null,
|
|
9
|
+
wc: null, sort: null, uniq: null, cut: null, jq: null, ls: null, stat: null,
|
|
10
|
+
readlink: null, pwd: null, test: null,
|
|
11
|
+
git: new Set(['rev-parse', 'status', 'log', 'show', 'merge-base']),
|
|
12
|
+
eln: new Set(['logs', 'where', 'tasks', 'config', 'release']),
|
|
13
|
+
'bun bin/elanous.mjs': new Set(['logs', 'where', 'release']),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Git's options are commands too: e.g. show --textconv runs a configured external driver. */
|
|
17
|
+
const READ_GIT_OPTIONS: Record<string, ReadonlySet<string>> = {
|
|
18
|
+
'rev-parse': new Set(['--git-dir', '--show-toplevel', '--is-inside-work-tree', '--verify', '--short', '--abbrev-ref', '--show-prefix']),
|
|
19
|
+
status: new Set(['--short', '--porcelain', '--branch', '--untracked-files=all', '--untracked-files=no']),
|
|
20
|
+
log: new Set(['--oneline', '--no-patch', '--stat', '--name-only', '--name-status', '--all', '--decorate']),
|
|
21
|
+
show: new Set(['--no-patch', '--stat', '--name-only', '--name-status', '--oneline']),
|
|
22
|
+
'merge-base': new Set(['--is-ancestor', '--fork-point', '--all']),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** `logs level <value>` changes daemon state. Accept only the top-level query options. */
|
|
26
|
+
const LOGS_QUERY_FLAGS = new Set([
|
|
27
|
+
'--follow', '-f', '--list-categories', '--list-events', '--explain', '--top-failures',
|
|
28
|
+
'--json', '--json-data', '--test', '--all', '--include-test', '-r', '--help', '-h',
|
|
29
|
+
]);
|
|
30
|
+
const LOGS_QUERY_VALUES = new Set([
|
|
31
|
+
'--level', '--surface', '--space', '--category', '--exact-category', '--axis', '--event',
|
|
32
|
+
'--grep', '--rework-recurrence-disagreement', '--since', '--until', '--before',
|
|
33
|
+
'--session', '--limit', '--threshold', '--instance', '--remote',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
function readOnlyLogs(args: string[]): boolean {
|
|
37
|
+
for (let i = 0; i < args.length; i++) {
|
|
38
|
+
const arg = args[i]!;
|
|
39
|
+
if (LOGS_QUERY_FLAGS.has(arg)) continue;
|
|
40
|
+
if (LOGS_QUERY_VALUES.has(arg) && args[i + 1] && !args[i + 1]!.startsWith('-')) { i++; continue; }
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readOnlyGit(rest: string[]): boolean {
|
|
47
|
+
const [subcommand, ...args] = rest;
|
|
48
|
+
const options = READ_GIT_OPTIONS[subcommand ?? ''];
|
|
49
|
+
if (!options) return false;
|
|
50
|
+
return args.every((arg) => !arg.startsWith('-') || options.has(arg)
|
|
51
|
+
|| ((subcommand === 'log' || subcommand === 'show')
|
|
52
|
+
&& (/^--(?:format|pretty|grep|since|until|max-count)=.+$/u.test(arg) || /^-[1-9][0-9]*$/u.test(arg))));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Do not execute the measurement here: parse only a small, fail-closed shell subset. */
|
|
56
|
+
function readOnlyMeasure(command: string): boolean {
|
|
57
|
+
const source = command.trim().replace(/^`|`$/gu, '');
|
|
58
|
+
const tokens: string[] = [];
|
|
59
|
+
let word = '';
|
|
60
|
+
let quote: '"' | "'" | null = null;
|
|
61
|
+
for (let i = 0; i < source.length; i++) {
|
|
62
|
+
const char = source[i]!;
|
|
63
|
+
if (char === '\\' || char === '`' || char === '$' || char === '\n') return false;
|
|
64
|
+
if (quote) {
|
|
65
|
+
if (char === quote) quote = null;
|
|
66
|
+
else word += char;
|
|
67
|
+
} else if (char === '"' || char === "'") {
|
|
68
|
+
quote = char;
|
|
69
|
+
} else if (/\s/u.test(char)) {
|
|
70
|
+
if (word) { tokens.push(word); word = ''; }
|
|
71
|
+
} else if ('|&;<>()[{}]'.includes(char)) {
|
|
72
|
+
if (word) { tokens.push(word); word = ''; }
|
|
73
|
+
if (char === '|' || char === '&') {
|
|
74
|
+
if (source[i + 1] === char) { tokens.push(char + char); i++; }
|
|
75
|
+
else if (char === '|') tokens.push('|');
|
|
76
|
+
else return false;
|
|
77
|
+
} else if (char === '>') {
|
|
78
|
+
if (source[i + 1] === '>') return false;
|
|
79
|
+
if (source.slice(i + 1, i + 3) === '&1') { tokens.push('>&1'); i += 2; }
|
|
80
|
+
else tokens.push('>');
|
|
81
|
+
} else return false;
|
|
82
|
+
} else word += char;
|
|
83
|
+
}
|
|
84
|
+
if (quote) return false;
|
|
85
|
+
if (word) tokens.push(word);
|
|
86
|
+
if (!tokens.length) return false;
|
|
87
|
+
const segments: string[][] = [[]];
|
|
88
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
89
|
+
const token = tokens[i]!;
|
|
90
|
+
if (token === '>' && tokens[i + 1] === '/dev/null') { i++; continue; }
|
|
91
|
+
if (token === '2' && tokens[i + 1] === '>&1') { i++; continue; }
|
|
92
|
+
if (token === '&&' || token === '||' || token === '|') {
|
|
93
|
+
if (!segments.at(-1)!.length) return false;
|
|
94
|
+
segments.push([]);
|
|
95
|
+
} else if (token === '>' || token === '>&1') return false;
|
|
96
|
+
else segments.at(-1)!.push(token);
|
|
97
|
+
}
|
|
98
|
+
return segments.every((args) => {
|
|
99
|
+
if (!args.length || args.some((arg) => /^(?:--pre|--output|--ext-diff|--exec|--config|--config-env|--write)(?:=|$)/u.test(arg))) return false;
|
|
100
|
+
if (args.some((arg) => /^(?:--output(?:=\S*)?|--replace|--in-place)$/u.test(arg))) return false;
|
|
101
|
+
if (args[0] === 'sort' && args.some((arg) => /^-o\S*|^--compress-program(?:=|$)/u.test(arg))) return false;
|
|
102
|
+
let executable = args[0]!;
|
|
103
|
+
let rest = args.slice(1);
|
|
104
|
+
if (executable === 'bun' && rest[0] === 'bin/elanous.mjs') { executable = 'bun bin/elanous.mjs'; rest = rest.slice(1); }
|
|
105
|
+
if (!Object.hasOwn(READ_COMMANDS, executable)) return false;
|
|
106
|
+
const allowed = READ_COMMANDS[executable]!;
|
|
107
|
+
if (allowed === null) return true;
|
|
108
|
+
if (executable === 'bun bin/elanous.mjs' && rest[0] === '--test') rest = rest.slice(1);
|
|
109
|
+
if (!allowed.has(rest[0] ?? '')) return false;
|
|
110
|
+
if (executable === 'git') return readOnlyGit(rest);
|
|
111
|
+
if ((executable === 'eln' || executable === 'bun bin/elanous.mjs') && rest[0] === 'logs') return readOnlyLogs(rest.slice(1));
|
|
112
|
+
if ((executable === 'eln' || executable === 'bun bin/elanous.mjs') && rest[0] === 'tasks') return rest[1] === 'close' && rest[2] === '--help';
|
|
113
|
+
if ((executable === 'eln' || executable === 'bun bin/elanous.mjs') && rest[0] === 'config') return rest[1] === 'get';
|
|
114
|
+
if ((executable === 'eln' || executable === 'bun bin/elanous.mjs') && rest[0] === 'release') return rest[1] === 'checklist' && rest[2] === 'status';
|
|
115
|
+
return true;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 비밀 표식 — 팩 스캔 표식(seatPackLeakPatterns)에 실제 자격 모양을 더한 집합. */
|
|
120
|
+
const SECRET_SHAPE_PATTERNS: Array<[string, RegExp]> = [
|
|
121
|
+
['api key', /\b(?:sk|rk)-(?:proj-)?[A-Za-z0-9_-]{16,}\b/u],
|
|
122
|
+
['github token', /\b(?:gh[pousr]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{16,})\b/u],
|
|
123
|
+
['slack token', /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/u],
|
|
124
|
+
['aws key id', /\bAKIA[0-9A-Z]{12,}\b/u],
|
|
125
|
+
['private key block', /-----BEGIN [A-Z ]*PRIVATE KEY-----/u],
|
|
126
|
+
['credential assignment', /\b(?:api[_-]?key|secret|token|password)\s*[:=]\s*["']?[A-Za-z0-9][A-Za-z0-9._/+~-]{11,}/iu],
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
export class KnowhowLintError extends Error {
|
|
130
|
+
constructor(file: string, card: string, reason: string) {
|
|
131
|
+
super(`knowhow lint: ${file} ${card} — ${reason}`);
|
|
132
|
+
this.name = 'KnowhowLintError';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface KnowhowCard {
|
|
137
|
+
id: string;
|
|
138
|
+
lines: Array<[number, string]>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** 카드 제목 — `## K-<자리>-<번호>` 뒤에는 줄 끝·공백·콜론·em 대시만 온다. */
|
|
142
|
+
const CARD_HEADING = /^[ \t]{0,3}##\s+(K-[A-Za-z]+-\d+)(?=$|\s|:|—)/u;
|
|
143
|
+
/** 카드처럼 보이는 `##` 제목(예: `## K-OP-7a`) — CARD_HEADING 을 벗어나면 인식 실패로 명시 거부한다. */
|
|
144
|
+
const CARD_HEADING_LIKE = /^[ \t]{0,3}##\s*(K-[^\s:]*)/iu;
|
|
145
|
+
|
|
146
|
+
function parseCards(body: string, file: string): KnowhowCard[] {
|
|
147
|
+
const cards: KnowhowCard[] = [];
|
|
148
|
+
let current: KnowhowCard | null = null;
|
|
149
|
+
let fence: { marker: string; length: number } | null = null;
|
|
150
|
+
for (const [index, line] of body.split(/\r?\n/u).entries()) {
|
|
151
|
+
const fenceLine = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/u);
|
|
152
|
+
if (fence) {
|
|
153
|
+
if (fenceLine && fenceLine[1]![0] === fence.marker && fenceLine[1]!.length >= fence.length
|
|
154
|
+
&& /^[ \t]*$/u.test(fenceLine[2]!)) fence = null;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (fenceLine) {
|
|
158
|
+
fence = { marker: fenceLine[1]![0]!, length: fenceLine[1]!.length };
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const heading = line.match(CARD_HEADING);
|
|
162
|
+
if (heading) {
|
|
163
|
+
current = { id: heading[1]!, lines: [] };
|
|
164
|
+
cards.push(current);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (/^[ \t]{0,3}#/u.test(line)) {
|
|
168
|
+
const like = line.match(CARD_HEADING_LIKE);
|
|
169
|
+
if (like) throw new KnowhowLintError(file, like[1]!, '카드 제목 형식이 K-<자리>-<번호>를 벗어남');
|
|
170
|
+
current = null;
|
|
171
|
+
} else if (current) current.lines.push([index + 1, line]);
|
|
172
|
+
}
|
|
173
|
+
return cards;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseField(line: string): { label: string; value: string } | null {
|
|
177
|
+
const match = line.match(/^-\s*([^:—]+?)\s*[—:]\s*(.*)$/u);
|
|
178
|
+
if (!match) return null;
|
|
179
|
+
return { label: match[1]!.replace(/\s*\([^)]*\)/gu, '').trim(), value: match[2]!.trim() };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** 자리 KNOWHOW.md 카드를 내보내기 전에 검증한다 — 카드 제목 형식 · 필수 칸 · 읽기 전용 재는 명령(이어 적은 여러 줄 포함) · 비밀 표식. */
|
|
183
|
+
export function lintKnowhow(body: string, file: string): void {
|
|
184
|
+
const secrets = [...seatPackLeakPatterns(), ...SECRET_SHAPE_PATTERNS];
|
|
185
|
+
for (const [index, line] of body.split(/\r?\n/u).entries()) {
|
|
186
|
+
for (const [label, pattern] of secrets) {
|
|
187
|
+
if (pattern.test(line)) throw new KnowhowLintError(file, 'file', `비밀 표식 (${label}) — ${file}:${index + 1}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const cards = parseCards(body, file);
|
|
191
|
+
if (!cards.length) throw new KnowhowLintError(file, 'file', '카드가 0개 — 최소 한 개의 K-<자리>-<번호> 카드가 필요함');
|
|
192
|
+
for (const card of cards) {
|
|
193
|
+
const fields = new Map<string, string>();
|
|
194
|
+
const fieldEntries: Array<{ label: string; value: string }> = [];
|
|
195
|
+
let current: { label: string; value: string } | null = null;
|
|
196
|
+
for (const [, line] of card.lines) {
|
|
197
|
+
const field = parseField(line);
|
|
198
|
+
if (field) {
|
|
199
|
+
if (current) fieldEntries.push(current);
|
|
200
|
+
current = { label: field.label, value: field.value };
|
|
201
|
+
} else if (current && /^\s+\S/u.test(line)) {
|
|
202
|
+
current.value += `\n${line.trim()}`;
|
|
203
|
+
} else if (current) {
|
|
204
|
+
fieldEntries.push(current);
|
|
205
|
+
current = null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (current) fieldEntries.push(current);
|
|
209
|
+
for (const field of fieldEntries) {
|
|
210
|
+
if (fields.has(field.label)) throw new KnowhowLintError(file, card.id, `중복 칸: ${field.label}`);
|
|
211
|
+
if ((REQUIRED_FIELDS as readonly string[]).includes(field.label) || field.label === '상태') {
|
|
212
|
+
if (!field.value) throw new KnowhowLintError(file, card.id, `필수 칸 누락: ${field.label}`);
|
|
213
|
+
}
|
|
214
|
+
if (field.label === '재는 명령'
|
|
215
|
+
&& !field.value.split('\n').every((command) => readOnlyMeasure(command))) {
|
|
216
|
+
throw new KnowhowLintError(file, card.id, '재는 명령이 읽기 전용이 아님');
|
|
217
|
+
}
|
|
218
|
+
fields.set(field.label, field.value);
|
|
219
|
+
}
|
|
220
|
+
for (const required of REQUIRED_FIELDS) {
|
|
221
|
+
if (!fields.get(required)) throw new KnowhowLintError(file, card.id, `필수 칸 누락: ${required}`);
|
|
222
|
+
}
|
|
223
|
+
const source = fields.get('출처') ?? '';
|
|
224
|
+
const statusInSource = source.match(/(?:^|[· \t])상태[ \t]*[:—][ \t]*([^·\n]*)/u);
|
|
225
|
+
if (!source.slice(0, statusInSource?.index ?? source.length).replace(/[·\s]+$/u, '').trim()) {
|
|
226
|
+
throw new KnowhowLintError(file, card.id, '필수 칸 누락: 출처');
|
|
227
|
+
}
|
|
228
|
+
const combinedStatus = statusInSource?.[1]?.trim();
|
|
229
|
+
if (!fields.get('상태') && !combinedStatus) {
|
|
230
|
+
throw new KnowhowLintError(file, card.id, '필수 칸 누락: 상태');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -85,7 +85,7 @@ export type SeatDeps = {
|
|
|
85
85
|
/** Release schedules (version · cutAt) — a version is open while its cut is still ahead. Default reads the release ledger; unreadable ⇒ no checklist candidates (fail closed). */
|
|
86
86
|
schedules?: () => Array<{ version: string; cutAt: string; landBy?: string | null }>;
|
|
87
87
|
/** Checklist items of one version — default reads the release ledger DB (checklist.json is only a legacy import source since REL5b). */
|
|
88
|
-
checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null; disposition?: string; evidence?: string }>;
|
|
88
|
+
checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null; disposition?: string; evidence?: string; dispatchKind?: string }>;
|
|
89
89
|
/** Optional injected traffic/handoff snapshots; defaults read the OP files. */
|
|
90
90
|
trafficSignal?: TrafficSignalRead;
|
|
91
91
|
handedCells?: ReadonlySet<string>;
|
|
@@ -588,6 +588,10 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}, ledger
|
|
|
588
588
|
}
|
|
589
589
|
for (const item of items) {
|
|
590
590
|
if (item.owner === seat && (item.status === 'yellow' || item.status === 'red')) {
|
|
591
|
+
if (item.dispatchKind && item.dispatchKind !== 'code') {
|
|
592
|
+
debug.log('seat.loop', 'skip-non-code-cell', { id: item.id, version, dispatchKind: item.dispatchKind });
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
591
595
|
if (signal?.hold?.includes(item.id)) {
|
|
592
596
|
debug.log('seat.loop', 'skip-signal-cell', { id: item.id, why: 'hold' });
|
|
593
597
|
continue;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { userInfo } from 'node:os';
|
|
5
5
|
import { DEFAULT_CONCURRENT_PODS, DEFAULT_DAILY_GOALS } from '../org/seat-budget.js';
|
|
6
|
+
import { lintKnowhow } from './knowhow-lint.js';
|
|
6
7
|
|
|
7
8
|
export interface SeatPackManifest {
|
|
8
9
|
name: string;
|
|
@@ -31,13 +32,20 @@ function walk(directory: string, root: string): string[] {
|
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/** Static leak markers shared by the pack scan and the KNOWHOW lint. Account-name patterns stay in `scan` — they depend on the exporting host's user. */
|
|
36
|
+
export function seatPackLeakPatterns(): Array<[string, RegExp]> {
|
|
37
|
+
return [
|
|
38
|
+
['royal marker', /\u{1F451}/u],
|
|
39
|
+
['track marker', /(?:🅞|🅣|🅢|🅕|\*\*\[(?:OP|TC|MK|UX|S|T|O|F)\]\*\*|\[(?:OP|TC|MK|UX|S|T|O|F)\])/u],
|
|
40
|
+
['absolute user path', /\/Users\/[A-Za-z0-9._-]+/u],
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
function scan(file: string, body: string): void {
|
|
35
45
|
const usernames = new Set([userInfo().username, process.env.ELANOUS_LEAK_HOME_USER].filter((name): name is string => Boolean(name)));
|
|
36
46
|
const accountPatterns = [...usernames].map((name) => new RegExp(`(?<![A-Za-z0-9._-])${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9._-])`, 'iu'));
|
|
37
47
|
const patterns: Array<[string, RegExp]> = [
|
|
38
|
-
|
|
39
|
-
['track marker', /(?:🅞|🅣|🅢|🅕|\*\*\[(?:OP|TC|MK|UX|S|T|O|F)\]\*\*|\[(?:OP|TC|MK|UX|S|T|O|F)\])/u],
|
|
40
|
-
['absolute user path', /\/Users\/[A-Za-z0-9._-]+/u],
|
|
48
|
+
...seatPackLeakPatterns(),
|
|
41
49
|
...accountPatterns.map((pattern): [string, RegExp] => ['account name', pattern]),
|
|
42
50
|
];
|
|
43
51
|
for (const [index, line] of body.split(/\r?\n/u).entries()) {
|
|
@@ -61,6 +69,10 @@ export function exportSeatPack(seat: string, out: string, root = resolve(import.
|
|
|
61
69
|
for (const file of roleFiles) {
|
|
62
70
|
const body = readFileSync(join(repo, file));
|
|
63
71
|
contents.set(file, body);
|
|
72
|
+
// Public leak markers take precedence over formatting errors: callers need
|
|
73
|
+
// the file:line and exit 2 before an export directory can be created.
|
|
74
|
+
scan(file, body.toString('utf8'));
|
|
75
|
+
if (basename(file) === 'KNOWHOW.md') lintKnowhow(body.toString('utf8'), file);
|
|
64
76
|
for (const match of body.toString('utf8').matchAll(/graphs\/([A-Za-z0-9_./-]+(?:\.ya?ml|\/\*\*))/gu)) {
|
|
65
77
|
const ref = `graphs/${match[1]!}`;
|
|
66
78
|
if (ref.split('/').includes('..')) throw new Error(`invalid seat graph: ${ref}`);
|
|
@@ -10,6 +10,13 @@ export type TrafficSignal = {
|
|
|
10
10
|
};
|
|
11
11
|
export type TrafficSignalRead = TrafficSignal | { unreadable: true } | null;
|
|
12
12
|
|
|
13
|
+
/** Pure proposal classification; applying a proposal belongs to the downstream seat-message caller. */
|
|
14
|
+
export function classifySeatTrafficProposal(message: string): 'hold' | 'UNHOLD' | null {
|
|
15
|
+
if (message.includes('첫 줄 확정')) return 'UNHOLD';
|
|
16
|
+
if (message.includes('손 구현 착수')) return 'hold';
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
const defaultPath = (name: string): string => join(homedir(), 'elanous-hq', 'seat-state', 'OP', name);
|
|
14
21
|
const missing = (error: unknown): boolean => (error as NodeJS.ErrnoException).code === 'ENOENT';
|
|
15
22
|
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
// ⛔ 종료(`process.exit`)는 여기가 «안» 한다 — 호출자가 자기 표면에 맞게 끝낸다.
|
|
12
12
|
// 그래서 결과를 «세 값»으로 낸다: 발사 / 저작 전 중단 / 전제 검사 중단.
|
|
13
13
|
import { linesOutsideFencedCode } from '../self-implement/goal-author.js';
|
|
14
|
+
import { existsSync } from 'node:fs';
|
|
15
|
+
import { debug } from '../debug/log.js';
|
|
14
16
|
import { recordAuthoredGoal } from './authored-goal-trace.js';
|
|
15
17
|
import { findAskPathCandidates, type AskPathCandidateOptions } from './ask-path-candidates.js';
|
|
16
18
|
import { retiredEntranceNotice, type EntranceDeclaration } from './entrance-registry.js';
|
|
@@ -18,6 +20,7 @@ import {
|
|
|
18
20
|
classifyConcurrentAuthoring,
|
|
19
21
|
countRepeatedBlocks,
|
|
20
22
|
decideAskPreflight,
|
|
23
|
+
discardNonFileTracedPathItems,
|
|
21
24
|
parseAskTargetPathHintsResult,
|
|
22
25
|
parseBlockedChoice,
|
|
23
26
|
planBlockedPrompt,
|
|
@@ -797,10 +800,10 @@ async function intakeClarifications(
|
|
|
797
800
|
selfResolveClarifications: selfResolveClarifications.value,
|
|
798
801
|
});
|
|
799
802
|
const parentDocument = document;
|
|
800
|
-
effectiveGoalFile = reauthored.path;
|
|
803
|
+
effectiveGoalFile = isAbsolutePath(reauthored.path) ? reauthored.path : resolvePath(deps.cwd(), reauthored.path);
|
|
801
804
|
deps.print(`[ask] ⑴b 재저작됨: ${reauthored.path}`);
|
|
802
805
|
// ⛔ 「재저작이 «났다»」와 「재저작이 «옳았다»」는 다른 물음이다(JDG-T35 3단 ⑶).
|
|
803
|
-
const childDocument = reauthored.authored?.document ?? deps.readFile(
|
|
806
|
+
const childDocument = reauthored.authored?.document ?? deps.readFile(effectiveGoalFile);
|
|
804
807
|
const askFidelity = classifyReauthoredAsk(parentDocument, childDocument);
|
|
805
808
|
if (askFidelity !== 'preserved') {
|
|
806
809
|
deps.print(`[ask] ⑴b ⚠️ 재저작 ask 판정: ${askFidelity} — ⛔ 「preserved」가 아니면 사람의 ask 가 살아남았는지 «확인하라»`);
|
|
@@ -939,7 +942,31 @@ export async function runAskLaunchFlow(
|
|
|
939
942
|
}, markerCounts !== null && markerCounts.missingMarkers > 0 ? 'warn' : 'info');
|
|
940
943
|
|
|
941
944
|
// ⛔ 되묻기에 답하면 «새 골»이 나온다 — 아래 전제 검사·발사는 그 «최종» 골을 써야 한다.
|
|
942
|
-
const goalFile = await intakeClarifications(
|
|
945
|
+
const goalFile = await intakeClarifications({
|
|
946
|
+
...authored,
|
|
947
|
+
path: isAbsolutePath(authored.path) ? authored.path : resolvePath(deps.cwd(), authored.path),
|
|
948
|
+
}, input, deps, selfResolveClarifications);
|
|
949
|
+
// The child reads this file again. Filtering only preflight.paths would leave the same bogus
|
|
950
|
+
// TRACED PATHS prose in the child and repeat its missing-file failure.
|
|
951
|
+
let sanitizeFailed = false;
|
|
952
|
+
let sanitizeFailure: unknown;
|
|
953
|
+
if (existsSync(goalFile)) {
|
|
954
|
+
try {
|
|
955
|
+
const original = deps.readFile(goalFile);
|
|
956
|
+
const sanitized = discardNonFileTracedPathItems(original, deps.cwd());
|
|
957
|
+
if (sanitized.dropped.length > 0) {
|
|
958
|
+
// The child reads this same file, so a failed rewrite must stop launch.
|
|
959
|
+
(deps.writeGoalDocument ?? deps.writeFile)(goalFile, sanitized.document);
|
|
960
|
+
for (const dropped of sanitized.dropped) {
|
|
961
|
+
try { debug.log('harness.preflight', 'traced-path-discarded', { goalFile, dropped }); }
|
|
962
|
+
catch { /* observation must not change launch */ }
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
} catch (error) {
|
|
966
|
+
sanitizeFailed = true;
|
|
967
|
+
sanitizeFailure = error;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
943
970
|
const preflightDeps = await deps.buildPreflightDeps();
|
|
944
971
|
observeLaunchingTree(deps, preflightDeps);
|
|
945
972
|
const askOutsidePathHints = parseAskTargetPathHintsResult(input.askText).paths;
|
|
@@ -954,11 +981,11 @@ export async function runAskLaunchFlow(
|
|
|
954
981
|
...(machineSuggestedTargetPaths.length > 0 ? { machineSuggestedTargetPaths } : {}),
|
|
955
982
|
declaredPathsRoot: deps.cwd(),
|
|
956
983
|
},
|
|
957
|
-
preflightDeps,
|
|
984
|
+
sanitizeFailed ? { ...preflightDeps, readGoalDocument: () => { throw sanitizeFailure; } } : preflightDeps,
|
|
958
985
|
input.forceRequested,
|
|
959
986
|
);
|
|
960
987
|
const preflight = decision.result;
|
|
961
|
-
deps.print(renderLaunchPreflight(preflight, input.forceRequested && preflight.blockers.length > 0, 'before-launch'));
|
|
988
|
+
deps.print(renderLaunchPreflight(preflight, input.forceRequested && decision.shouldLaunch && preflight.blockers.length > 0, 'before-launch'));
|
|
962
989
|
observeLaunchPreflight(deps, 'before-launch', decision);
|
|
963
990
|
// ⛔ 자식 격리 worktree를 만들기 전, 이 흐름을 호출한 작업 트리에서만 센다.
|
|
964
991
|
// 이 관측은 발사 판정의 입력이 아니다 — 의도적으로 옛 판에서 쏘는 경우도 있다.
|
|
@@ -1001,7 +1028,7 @@ export async function runAskLaunchFlow(
|
|
|
1001
1028
|
liveRunWindowMs: preflight.liveRunWindowMs,
|
|
1002
1029
|
// ⛔ 「플래그를 줬다」와 「실제로 뚫었다」는 다른 값이다(리뷰 should-fix).
|
|
1003
1030
|
forceRequested: input.forceRequested,
|
|
1004
|
-
bypassed: input.forceRequested && preflight.blockers.length > 0,
|
|
1031
|
+
bypassed: input.forceRequested && decision.shouldLaunch && preflight.blockers.length > 0,
|
|
1005
1032
|
}, preflight.blockers.length > 0 ? 'warn' : 'info');
|
|
1006
1033
|
return { goalFile, decision };
|
|
1007
1034
|
};
|
|
@@ -1023,14 +1050,27 @@ export async function runAskLaunchFlow(
|
|
|
1023
1050
|
} catch (error) {
|
|
1024
1051
|
candidates = { tokens: [], paths: [], error: error instanceof Error ? error.message : String(error) };
|
|
1025
1052
|
}
|
|
1053
|
+
const selectedPath = candidates.selection?.path
|
|
1054
|
+
?? (!candidates.selection && candidates.paths.length === 1 ? candidates.paths[0] : undefined);
|
|
1055
|
+
const selectionReason = candidates.selection?.reason
|
|
1056
|
+
?? (candidates.error ? 'search-error' : selectedPath ? 'only-candidate'
|
|
1057
|
+
: candidates.paths.length > 0 ? 'ambiguous-without-evidence' : 'no-candidates');
|
|
1026
1058
|
deps.log('ask-auto-target-candidates', {
|
|
1027
1059
|
tokens: candidates.tokens, paths: candidates.paths,
|
|
1060
|
+
evidence: candidates.evidence ?? [],
|
|
1061
|
+
selection: { path: selectedPath ?? null, reason: selectionReason },
|
|
1028
1062
|
...(candidates.error === undefined ? {} : { error: candidates.error }),
|
|
1029
|
-
}, candidates.error === undefined ? 'info' : 'warn');
|
|
1063
|
+
}, candidates.error === undefined && selectedPath ? 'info' : 'warn');
|
|
1030
1064
|
}
|
|
1031
|
-
const selectedPaths = candidates.
|
|
1065
|
+
const selectedPaths = candidates.selection?.path
|
|
1066
|
+
? [candidates.selection.path]
|
|
1067
|
+
: !candidates.selection && candidates.paths.length === 1 ? [candidates.paths[0]!] : [];
|
|
1032
1068
|
if (selectedPaths.length > 0) {
|
|
1033
1069
|
deps.print(`[ask] ⑴ 코드에서 후보 경로 ${selectedPaths.length}개를 찾아 넣고 다시 저작한다: ${selectedPaths.join(' · ')}`);
|
|
1070
|
+
} else if (candidates.paths.length > 0) {
|
|
1071
|
+
deps.print(`[ask] ⑴ ⚠️ 후보 경로를 하나로 판정할 근거가 없다. 대상 경로를 확인해야 한다: ${candidates.paths.join(' · ')}`);
|
|
1072
|
+
} else {
|
|
1073
|
+
deps.print('[ask] ⑴ ⚠️ 자동 재저작에 넣을 대상 경로 후보가 없다. 대상 경로를 확인해야 한다');
|
|
1034
1074
|
}
|
|
1035
1075
|
authored = await authorAndPreflight(selectedPaths.length > 0
|
|
1036
1076
|
? `대상 경로: ${selectedPaths.join(' · ')}\n${input.askText}`
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { statSync } from 'node:fs';
|
|
3
|
-
import { isAbsolute, relative, resolve } from 'node:path';
|
|
3
|
+
import { basename, extname, isAbsolute, relative, resolve } from 'node:path';
|
|
4
4
|
|
|
5
5
|
export interface AskPathCandidateOptions {
|
|
6
6
|
readonly cwd: string;
|
|
7
7
|
readonly run?: (command: string, args: readonly string[], options: { cwd: string; encoding: 'utf8' }) => string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
export interface AskPathCandidates {
|
|
11
|
+
readonly tokens: string[];
|
|
12
|
+
readonly paths: string[];
|
|
13
|
+
readonly evidence?: AskPathEvidence[];
|
|
14
|
+
readonly selection?: AskPathSelection;
|
|
15
|
+
readonly error?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AskPathEvidence {
|
|
19
|
+
readonly path: string;
|
|
20
|
+
readonly pathTokens: string[];
|
|
21
|
+
readonly strongestPathMatch: 'explicit-file' | 'filename' | 'directory' | null;
|
|
22
|
+
readonly contentTokens: string[];
|
|
23
|
+
readonly score: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AskPathSelection =
|
|
27
|
+
| { readonly path: string; readonly reason: 'unique-strongest-evidence' }
|
|
28
|
+
| { readonly path: null; readonly reason: 'tied-strongest-evidence' };
|
|
13
29
|
|
|
14
30
|
const STOP_WORDS = new Set(['the', 'and', 'test', 'src', 'scripts', 'file', 'path', 'paths', 'code', 'with', 'from', 'this', 'that', 'into', 'for', 'then', 'when', 'have', 'does', 'should', 'will', 'please', 'target', 'change']);
|
|
15
31
|
const WORD = /[-/]?[A-Za-z][A-Za-z0-9_.-]*(?:\/[A-Za-z0-9_.-]+)*/g;
|
|
@@ -63,7 +79,15 @@ function existingFile(fragment: string, cwd: string): string | null {
|
|
|
63
79
|
|
|
64
80
|
/** Find existing code paths by lexical evidence from an unmodified ask. */
|
|
65
81
|
export function findAskPathCandidates(askText: string, { cwd, run = execFileSync }: AskPathCandidateOptions): AskPathCandidates {
|
|
66
|
-
const scores = new Map<string, Set<string
|
|
82
|
+
const scores = new Map<string, { explicit: Set<string>; filename: Set<string>; directory: Set<string>; content: Set<string> }>();
|
|
83
|
+
const hitsFor = (path: string) => {
|
|
84
|
+
let hits = scores.get(path);
|
|
85
|
+
if (!hits) {
|
|
86
|
+
hits = { explicit: new Set(), filename: new Set(), directory: new Set(), content: new Set() };
|
|
87
|
+
scores.set(path, hits);
|
|
88
|
+
}
|
|
89
|
+
return hits;
|
|
90
|
+
};
|
|
67
91
|
const tokens: string[] = [];
|
|
68
92
|
const seen = new Set<string>();
|
|
69
93
|
const body = stripAskWrapper(askText);
|
|
@@ -73,11 +97,7 @@ export function findAskPathCandidates(askText: string, { cwd, run = execFileSync
|
|
|
73
97
|
const raw = match[0];
|
|
74
98
|
const token = raw.replace(/^\//, '');
|
|
75
99
|
const file = existingFile(token, cwd);
|
|
76
|
-
if (file)
|
|
77
|
-
const hits = scores.get(file) ?? new Set<string>();
|
|
78
|
-
hits.add(token);
|
|
79
|
-
scores.set(file, hits);
|
|
80
|
-
}
|
|
100
|
+
if (file) hitsFor(file).explicit.add(token);
|
|
81
101
|
if (STOP_WORDS.has(token.toLowerCase()) || isGenericToken(token) || seen.has(token)) continue;
|
|
82
102
|
if (!file && !token.includes('/') && !token.includes('.') && !token.includes('-') && !token.includes('_')
|
|
83
103
|
&& !/[a-z][A-Z]/.test(token) && token.length < 4) continue;
|
|
@@ -85,6 +105,24 @@ export function findAskPathCandidates(askText: string, { cwd, run = execFileSync
|
|
|
85
105
|
tokens.push(token);
|
|
86
106
|
}
|
|
87
107
|
}
|
|
108
|
+
if (tokens.length > 0) {
|
|
109
|
+
let files: string;
|
|
110
|
+
try {
|
|
111
|
+
files = run('rg', ['--files', '--glob', '!*.test.ts', 'src', 'scripts'], { cwd, encoding: 'utf8' });
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if ((error as { status?: number }).status === 1) files = '';
|
|
114
|
+
else return { tokens, paths: [], error: error instanceof Error ? error.message : String(error) };
|
|
115
|
+
}
|
|
116
|
+
for (const path of files.split(/\r?\n/).filter(Boolean)) {
|
|
117
|
+
const fileStem = basename(path, extname(path)).toLowerCase();
|
|
118
|
+
const directories = path.toLowerCase().split('/').slice(0, -1);
|
|
119
|
+
for (const token of tokens) {
|
|
120
|
+
const lower = token.toLowerCase();
|
|
121
|
+
if (fileStem === lower || basename(path).toLowerCase() === lower) hitsFor(path).filename.add(token);
|
|
122
|
+
else if (directories.includes(lower)) hitsFor(path).directory.add(token);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
88
126
|
for (const token of tokens) {
|
|
89
127
|
let matches: string;
|
|
90
128
|
try {
|
|
@@ -94,13 +132,30 @@ export function findAskPathCandidates(askText: string, { cwd, run = execFileSync
|
|
|
94
132
|
return { tokens, paths: [], error: error instanceof Error ? error.message : String(error) };
|
|
95
133
|
}
|
|
96
134
|
for (const path of matches.split(/\r?\n/).filter(Boolean)) {
|
|
97
|
-
|
|
98
|
-
hits.add(token);
|
|
99
|
-
scores.set(path, hits);
|
|
135
|
+
hitsFor(path).content.add(token);
|
|
100
136
|
}
|
|
101
137
|
}
|
|
138
|
+
const evidence = [...scores].map<AskPathEvidence>(([path, hits]) => {
|
|
139
|
+
const tier = hits.explicit.size > 0 ? 3 : hits.filename.size > 0 ? 2 : hits.directory.size > 0 ? 1 : 0;
|
|
140
|
+
return {
|
|
141
|
+
path,
|
|
142
|
+
pathTokens: [...new Set([...hits.explicit, ...hits.filename, ...hits.directory])],
|
|
143
|
+
strongestPathMatch: tier === 3 ? 'explicit-file' : tier === 2 ? 'filename' : tier === 1 ? 'directory' : null,
|
|
144
|
+
contentTokens: [...hits.content],
|
|
145
|
+
// Lexicographic tiers: even many directory hits cannot outrank one filename hit.
|
|
146
|
+
score: tier * 1000 + Math.min(hits.explicit.size + hits.filename.size + hits.directory.size, 9) * 100
|
|
147
|
+
+ Math.min(hits.content.size, 99),
|
|
148
|
+
};
|
|
149
|
+
}).sort((a, b) => b.score - a.score || a.path.localeCompare(b.path, 'en'));
|
|
150
|
+
const top = evidence[0];
|
|
102
151
|
return {
|
|
103
152
|
tokens,
|
|
104
|
-
paths:
|
|
153
|
+
paths: evidence.slice(0, 5).map(({ path }) => path),
|
|
154
|
+
...(top ? {
|
|
155
|
+
evidence: evidence.slice(0, 5),
|
|
156
|
+
selection: evidence[1]?.score === top.score
|
|
157
|
+
? { path: null, reason: 'tied-strongest-evidence' as const }
|
|
158
|
+
: { path: top.path, reason: 'unique-strongest-evidence' as const },
|
|
159
|
+
} : {}),
|
|
105
160
|
};
|
|
106
161
|
}
|