elanous 0.2.16 → 0.2.18
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/P6YTVgzuZz5oTZhY4Din_/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1307-b46d45b705e8c9ae.js → 1307-30dff22c9e1f3073.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6158.9c0449a9a8d0064b.js → 6158.af2e93b7adff709b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3cb52e5ea8fc2fa5.js → page-4733c967b56a4277.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c1e20d7d338713f1.js → page-bb742f995c6769e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-482c5dbde699947c.js → page-02b073a2ac793b83.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a0e2a6d1bfb52fc5.js → page-308111abadf35670.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-c53598cfce6156f2.js → page-5d97b2901e0d1ea6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-13a63953f53c6a23.js → page-c39554c321576982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-5d3c8429d04f3831.js → page-8c3d8e63ae2e1c98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-3193bdc0bbab047a.js → page-9b72a07d424701c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-6107ca8ddd5e3bdd.js → page-a572f58cd3ca5864.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-4167348049166852.js → page-83a700d4c754d802.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-de52e8fdf9095f7a.js → not-found-7b3170f374f77b9d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-ca3952cd78a83ca2.js → page-a3c58f5c72a5f3f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1ebfe049c640e171.js → page-1467047104141297.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-abe8e3b74bea1ffc.js → page-44d5f767491692bf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-d28573d6792b4275.js → page-3cc47dca0922ab9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-d751de5f8062ff5e.js → page-b17cd46cf0e9b8cf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-fe1759f94003b32b.js → page-61553851aa606146.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-d64c984e855c8c68.js → page-a1f585e154e61c59.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-9afdee66b820c76e.js → page-5150ef17b5383a57.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-3544f154c87b43ee.js → page-134da468538baf48.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -0
- package/apps/pwa/out/decisions/index.txt +30 -0
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/intake/intake-daily.yaml +9 -2
- package/graphs/intake/news-sources.yaml +4 -0
- package/graphs/intake/recipes.yaml +6 -1
- package/package.json +2 -1
- package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
- package/samples/workflows/templates/chat-driven-research.yaml +35 -0
- package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
- package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
- package/samples/workflows/templates/manual-quickstart.yaml +21 -0
- package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
- package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
- package/scripts/__fixtures__/intake-news.xml +7 -0
- package/scripts/ci-public-export-import-gate.ts +7 -2
- package/scripts/ci-typecheck-changed.ts +21 -6
- package/scripts/discord-seat-probe.ts +3 -2
- package/scripts/docs/generate-cli-anatomy.ts +162 -0
- package/scripts/goals/authoring-metrics.ts +73 -0
- package/scripts/intake-news.ts +169 -0
- package/scripts/landing-heal/must-fix-match.ts +7 -7
- package/scripts/landing-heal/run-mission.ts +3 -2
- package/scripts/public-export-test-run.ts +37 -2
- package/scripts/release-loop/checklist-gate-node.ts +13 -11
- package/scripts/release-loop/cut-branch.ts +2 -1
- package/scripts/release-loop/cutoff.ts +4 -2
- package/scripts/release-loop/docs-land-node.ts +77 -11
- package/scripts/release-loop/docs-node.ts +1 -1
- package/scripts/release-loop/gate-node.ts +99 -14
- package/scripts/release-loop/node-verdict.ts +2 -1
- package/scripts/release-loop/npm-publish-node.ts +11 -7
- package/scripts/release-loop/ops-upgrade-node.ts +8 -1
- package/scripts/release-loop/preflight.ts +246 -0
- package/scripts/release-loop/publish-node.ts +10 -3
- package/scripts/release-loop/release-docs.ts +134 -0
- package/scripts/release-loop/release-readiness.ts +11 -6
- package/scripts/release-loop/release-version.ts +48 -0
- package/scripts/release-loop/resume-release.ts +115 -0
- package/scripts/release-loop/unattended-release.ts +43 -7
- package/scripts/release-loop/version-node.ts +149 -41
- package/scripts/release-story/draft.ts +42 -14
- package/scripts/rhythm/daily.ts +25 -4
- package/scripts/skills/public-check.ts +62 -12
- package/scripts/test-deterministic.ts +51 -33
- package/scripts/test-diet/effectiveness.ts +62 -1
- package/scripts/test-diet/lib.ts +1 -1
- package/scripts/test-diet/node.ts +8 -1
- package/src/agent-mission/driver.ts +53 -10
- package/src/agent-mission/review-loop.ts +4 -3
- package/src/agent-substrate/pr-comment-meta.ts +20 -0
- package/src/autopilot/build/llm-conflict-merge.ts +28 -8
- package/src/autopilot/track-agent.ts +5 -3
- package/src/briefing/brief-items.ts +86 -17
- package/src/cli/autopilot-cli.ts +86 -1
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/decisions-cli.ts +4 -1
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/intake-cli.ts +74 -2
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/pr-cli.ts +12 -1
- package/src/cli/provider-cli.ts +24 -0
- package/src/cli/release-cli.ts +101 -17
- package/src/cli/self-update-canary.ts +46 -0
- package/src/cli/self-update.ts +200 -1
- package/src/cli/tasks-cli.ts +309 -3
- package/src/context-bus/condense.ts +19 -3
- package/src/context-bus/context-day.ts +77 -24
- package/src/context-bus/context-first.ts +33 -0
- package/src/context-card/index.ts +45 -0
- package/src/context-card/task.ts +76 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
- package/src/decisions/decision-ledger.ts +10 -1
- package/src/discord-self-message.ts +20 -2
- package/src/discord-slash-wire.ts +1 -0
- package/src/domains/outbound-alert.ts +200 -27
- package/src/domains/outbound-log-probe.ts +16 -1
- package/src/domains/release-tool.ts +2 -2
- package/src/execution-loop/gates.ts +11 -2
- package/src/flow-loop/split.ts +8 -4
- package/src/git-fs/worktree.ts +23 -3
- package/src/harness/harness-cli-command.ts +151 -11
- package/src/harness/harness-pod-dispatch.ts +11 -0
- package/src/harness/harness-queue.ts +466 -32
- package/src/harness/helper-scan.ts +10 -3
- package/src/harness/nested-elanous-policy.ts +23 -4
- package/src/hq/fence-wrapper.ts +123 -0
- package/src/hq/hq.ts +8 -0
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +190 -23
- package/src/intake-plane/adopt-gate.ts +49 -0
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/collect-github.ts +57 -25
- package/src/intake-plane/digest.ts +39 -4
- package/src/intake-plane/discord-seat-work.ts +8 -2
- package/src/intake-plane/requirement-funnel.ts +316 -0
- package/src/knowledge/know-find.ts +10 -1
- package/src/loops/budget.ts +36 -0
- package/src/loops/orchestrator/finish-rate.ts +73 -1
- package/src/loops/orchestrator/grid-place.ts +11 -0
- package/src/loops/orchestrator/tick.ts +295 -20
- package/src/maturity/feature-maturity.ts +3 -2
- package/src/nexus/api/decisions-route.ts +11 -1
- package/src/nexus/api/grid.ts +61 -0
- package/src/nexus/api/http-server.ts +19 -1
- package/src/nexus/api/loop-edges.ts +69 -13
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/oauth/codex.ts +120 -2
- package/src/onboarding/first-chat-gate.ts +29 -0
- package/src/release-loop/checklist.ts +104 -6
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/landing-freeze.ts +24 -14
- package/src/release-loop/placement.ts +13 -3
- package/src/release-loop/rubric.ts +80 -0
- package/src/seat-dispatch/seat-ask.ts +79 -16
- package/src/seat-loop/seat-loop.ts +107 -37
- package/src/self-dev/ask-launch-flow.ts +50 -10
- package/src/self-dev/ask-path-candidates.ts +68 -0
- package/src/self-dev/dev-pipeline.ts +4 -0
- package/src/self-dev/draft-triage-rules.ts +1 -1
- package/src/self-dev/orchestrate-cli.ts +22 -0
- package/src/self-dev/round-pr-supersede.ts +244 -0
- package/src/self-dev/run-store.ts +5 -1
- package/src/self-dev/run-supervisor.ts +21 -1
- package/src/self-dev/supervisor-verdict-edges.ts +46 -0
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/frozen-merges.ts +25 -20
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author-clarification.ts +81 -0
- package/src/self-implement/goal-author-cli.ts +33 -5
- package/src/self-implement/goal-author.ts +93 -12
- package/src/self-implement/goal-clarification-escalation.ts +37 -1
- package/src/self-implement/goal-document-write.ts +28 -0
- package/src/self-implement/heal-intake.ts +4 -0
- package/src/self-implement/host-regate.ts +3 -1
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +78 -13
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +151 -0
- package/src/task-agent/judge.ts +122 -0
- package/src/task-agent/mission.ts +423 -0
- package/src/task-agent/shadow.ts +90 -0
- package/src/task-agent/task-hand.ts +235 -0
- package/src/task-cards/card-store.ts +4 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +43 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +81 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +225 -35
- package/src/telegram-commands.ts +1 -1
- package/src/telegram-loops-command.ts +22 -15
- package/src/telegram.ts +5 -20
- package/src/user-config.ts +86 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8c2d9526164f5d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-dce686e582c23937.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-51f811fa119308b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-5b042736aa113756.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-57924393e3e4e539.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0f5cd57ca928f42b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-59f03fda76c03d22.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1ea64acadd704ace.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
|
@@ -18,6 +18,22 @@ function observeGoalAuthor(
|
|
|
18
18
|
debug.log(category === 'goal-author' ? 'harness.author' : 'harness.author.clarify', event, data);
|
|
19
19
|
}
|
|
20
20
|
import { getUserConfig } from '../user-config.js';
|
|
21
|
+
|
|
22
|
+
export type GoalAuthorGrade = 'full' | 'lite';
|
|
23
|
+
export type GoalAuthorGradeSource = 'flag' | 'env' | 'config' | 'default';
|
|
24
|
+
export interface GoalAuthorGradeSelection { grade: GoalAuthorGrade; source: GoalAuthorGradeSource }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Precedence flag > env (`ELANOUS_AUTHOR_GRADE`) > config > default(full). Malformed env or
|
|
28
|
+
* configuration values are ignored and keep the full pipeline. The env seam lets the Pod carry the
|
|
29
|
+
* grade without a CLI flag an older in-Pod elanous would reject (AUTHOR-LITE2-POD).
|
|
30
|
+
*/
|
|
31
|
+
export function resolveGoalAuthorGrade(flag?: GoalAuthorGrade, configured?: unknown, env: unknown = process.env.ELANOUS_AUTHOR_GRADE): GoalAuthorGradeSelection {
|
|
32
|
+
if (flag !== undefined) return { grade: flag, source: 'flag' };
|
|
33
|
+
if (env === 'lite' || env === 'full') return { grade: env, source: 'env' };
|
|
34
|
+
if (configured === 'lite' || configured === 'full') return { grade: configured, source: 'config' };
|
|
35
|
+
return { grade: 'full', source: 'default' };
|
|
36
|
+
}
|
|
21
37
|
import type { ObserveOnlySource } from './observe-only.js';
|
|
22
38
|
import type { Question } from '../ask-user-question/types.js';
|
|
23
39
|
import { requestTestScenario as defaultRequestTestScenario, type TestScenarioRequestInput, type TestScenarioRequestResult } from './test-scenario-request.js';
|
|
@@ -45,6 +61,7 @@ type GoalEnhancement = Omit<Pick<EnhanceResult, 'original' | 'checklist' | 'verb
|
|
|
45
61
|
export interface GoalAuthorDeps {
|
|
46
62
|
ground: (ask: string, groundingDeps?: GoalAuthorGroundingDeps) => Promise<CodebaseGrounding>;
|
|
47
63
|
persistentGrounding?: GoalAuthorGroundingDeps;
|
|
64
|
+
authorGrade?: GoalAuthorGradeSelection;
|
|
48
65
|
enhance: (ask: string, opts?: EnhanceOpts) => Promise<GoalEnhancement>;
|
|
49
66
|
/** Additive enhancer options supplied by the file-writing entrypoint. */
|
|
50
67
|
enhanceOpts?: EnhanceOpts;
|
|
@@ -4925,6 +4942,17 @@ async function authorGoalWithSupersededRootIntent(
|
|
|
4925
4942
|
}
|
|
4926
4943
|
// ⭐ 이 저작 «한 번»의 id. goalId 보다 «먼저» 있어야 ground·enhance 의 소요를 그 골에 붙일 수 있다.
|
|
4927
4944
|
const authorRunId = generateGoalId();
|
|
4945
|
+
const authorGrade = deps.authorGrade ?? resolveGoalAuthorGrade(undefined, getUserConfig().harness?.authorGrade);
|
|
4946
|
+
const lite = authorGrade.grade === 'lite';
|
|
4947
|
+
// lite 는 판정선을 남기는 대신 단계를 덜 한다 — 요청에 반증 줄이 하나도 없으면 판정선 없는 골이 나가므로 시작 전에 멈춘다.
|
|
4948
|
+
if (lite && !ask.split(/\r?\n/u).some((line) => /판정선|판정 신호|반증|decision signal/iu.test(line))) {
|
|
4949
|
+
try { observeGoalAuthor('goal-author', 'author-grade-refused', { authorRunId, reason: 'lite-without-falsifiable-signal' }); }
|
|
4950
|
+
catch { /* observation is fail-soft */ }
|
|
4951
|
+
throw new Error('lite authoring needs a falsifiable signal line (판정선/반증) in the ask — add one or use --author-grade full');
|
|
4952
|
+
}
|
|
4953
|
+
const skippedPhases = lite ? ['adversarial-review', 'goal-steps-decomposed', 'persistent-grounding'] : [];
|
|
4954
|
+
try { observeGoalAuthor('goal-author', 'author-grade', { authorRunId, grade: authorGrade.grade, source: authorGrade.source, skippedPhases }); }
|
|
4955
|
+
catch { /* observation is fail-soft */ }
|
|
4928
4956
|
const authorStartedAt = Date.now();
|
|
4929
4957
|
const enhanceTokens: GoalAuthorTokens = { inputTokens: null, outputTokens: null };
|
|
4930
4958
|
const onEnhanceUsage = (usage: LLMUsage): void => {
|
|
@@ -4934,18 +4962,20 @@ async function authorGoalWithSupersededRootIntent(
|
|
|
4934
4962
|
};
|
|
4935
4963
|
let facts: CodebaseGrounding | null = null;
|
|
4936
4964
|
let groundingError = false;
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4965
|
+
if (!lite) {
|
|
4966
|
+
const persistentGrounding = resolveGoalAuthorPersistentGrounding('persistentGrounding' in deps ? deps.persistentGrounding : undefined);
|
|
4967
|
+
const groundStartedAt = startGoalAuthorPhase('ground', authorRunId, deps.onProgress);
|
|
4968
|
+
try {
|
|
4969
|
+
facts = await deps.ground(ask, persistentGrounding.deps);
|
|
4970
|
+
} catch {
|
|
4971
|
+
groundingError = true;
|
|
4972
|
+
} finally {
|
|
4973
|
+
endGoalAuthorPhase('ground', groundStartedAt, authorRunId, deps.onProgress);
|
|
4974
|
+
observeGoalAuthorPersistentGroundingResult(
|
|
4975
|
+
persistentGrounding.decision,
|
|
4976
|
+
goalAuthorPersistentGroundingMetrics(facts, groundingError, authorRunId),
|
|
4977
|
+
);
|
|
4978
|
+
}
|
|
4949
4979
|
}
|
|
4950
4980
|
|
|
4951
4981
|
const enhanceStartedAt = startGoalAuthorPhase('enhance', authorRunId, deps.onProgress);
|
|
@@ -4989,6 +5019,57 @@ async function authorGoalWithSupersededRootIntent(
|
|
|
4989
5019
|
}
|
|
4990
5020
|
enhancement = { ...enhancement, original: ask };
|
|
4991
5021
|
|
|
5022
|
+
if (lite) {
|
|
5023
|
+
const goalType = deps.goalType ?? 'implement';
|
|
5024
|
+
const goalId = deps.goalId ?? generateGoalId();
|
|
5025
|
+
const signal = decisionSignalSection(null, ask, enhancement.decisionSignal);
|
|
5026
|
+
const falsifiableSignal = signal.filled > 0 ? signal.lines : [
|
|
5027
|
+
...signal.lines,
|
|
5028
|
+
...ask.split(/\r?\n/u).filter((line) => /판정선|판정 신호|반증|decision signal/iu.test(line) && !signal.lines.some((rendered) => rendered.includes(line.trim()))),
|
|
5029
|
+
];
|
|
5030
|
+
const targetPaths = [...new Set(declaredTargetPaths(ask))];
|
|
5031
|
+
const checklistIds = [...new Set(Array.from(ask.matchAll(/\b[A-Z][A-Z0-9]+-[A-Z0-9]+\b/g), (match) => match[0]))];
|
|
5032
|
+
const askFile = deps.askFile === undefined ? undefined : validateAskFile(deps.askFile);
|
|
5033
|
+
const inheritedRootIntent = deps.parentDocument === undefined ? null : parseRootIntent(deps.parentDocument);
|
|
5034
|
+
const rootIntent = validateRootIntent(deps.supersessionRootIntent ?? inheritedRootIntent ?? deps.rootIntent ?? goalSummary(ask));
|
|
5035
|
+
const document = [
|
|
5036
|
+
deps.goalTitle ?? goalSummary(ask),
|
|
5037
|
+
`- GoalId: ${goalId}`,
|
|
5038
|
+
`- RootIntent: ${rootIntent}`,
|
|
5039
|
+
`- GoalType: ${goalType}`,
|
|
5040
|
+
...(askFile === undefined ? [] : [`- AskFile: ${askFile}`]),
|
|
5041
|
+
'',
|
|
5042
|
+
'## PROBLEM',
|
|
5043
|
+
`- 왜: ${enhancement.complication ?? goalSummary(ask)}`,
|
|
5044
|
+
'',
|
|
5045
|
+
'## WHAT TO BUILD',
|
|
5046
|
+
`- 무엇: ${enhancement.situation ?? goalSummary(ask)}`,
|
|
5047
|
+
ORIGINAL_ASK_MARKER,
|
|
5048
|
+
fenceFor(ask), ask, fenceFor(ask),
|
|
5049
|
+
'',
|
|
5050
|
+
'## ACCEPTANCE CRITERIA',
|
|
5051
|
+
...enhancement.checklist.map(requestedCriterionLine),
|
|
5052
|
+
'',
|
|
5053
|
+
'## REQUIRED EVIDENCE',
|
|
5054
|
+
'- [requested] Verify the decision signal and the verbatim ask.',
|
|
5055
|
+
'',
|
|
5056
|
+
...(goalType === 'implement' ? ['## TRACED PATHS', ...(targetPaths.length ? targetPaths.map((path) => `- ${path}`) : ['- Target paths: not declared in the ask.']), ''] : []),
|
|
5057
|
+
'## SCOPE BOUNDARY',
|
|
5058
|
+
`- 대상 경로: ${targetPaths.length ? targetPaths.join(' · ') : 'not declared'}`,
|
|
5059
|
+
`- 칸 id: ${checklistIds.length ? checklistIds.join(' · ') : 'not declared'}`,
|
|
5060
|
+
'',
|
|
5061
|
+
'## 답하지 못하는 것',
|
|
5062
|
+
'- No persistent grounding was performed in lite authoring.',
|
|
5063
|
+
'',
|
|
5064
|
+
'## 불변식',
|
|
5065
|
+
'- No persistent grounding was performed in lite authoring.',
|
|
5066
|
+
'',
|
|
5067
|
+
'## 판정 신호',
|
|
5068
|
+
...falsifiableSignal,
|
|
5069
|
+
].join('\n');
|
|
5070
|
+
return { document, facts: null, grounded: false, authorRunId };
|
|
5071
|
+
}
|
|
5072
|
+
|
|
4992
5073
|
observeGoalAuthor('goal-author', 'constant-instruction-placement', {
|
|
4993
5074
|
authorRunId,
|
|
4994
5075
|
lines: CONSTANT_DOCUMENT_INSTRUCTION_POLICY.length,
|
|
@@ -11,9 +11,13 @@ import {
|
|
|
11
11
|
writePendingQuestion,
|
|
12
12
|
} from '../ask-user-question/pending-questions.js';
|
|
13
13
|
import { debug } from '../debug/log.js';
|
|
14
|
+
import { writeGoalDocumentAtomic } from './goal-document-write.js';
|
|
14
15
|
import {
|
|
15
16
|
applyClarificationReply,
|
|
16
17
|
parseGoalDocumentClarifications,
|
|
18
|
+
selfAnswerGoalDocumentClarifications,
|
|
19
|
+
type GoalClarificationSelfResolver,
|
|
20
|
+
type GoalClarificationSelfAnswerPath,
|
|
17
21
|
} from './goal-author-clarification.js';
|
|
18
22
|
import type { HitlDelivery } from '../hitl/types.js';
|
|
19
23
|
|
|
@@ -49,6 +53,8 @@ export interface GoalClarificationEscalationInput {
|
|
|
49
53
|
resolveOriginDelivery?: (sessionId: string) => HitlDelivery | undefined;
|
|
50
54
|
/** Non-blocking terminal fallback for a non-TUI delivery with no installed resolver. */
|
|
51
55
|
fallback?: (message: string) => void;
|
|
56
|
+
selfResolveClarification?: GoalClarificationSelfResolver;
|
|
57
|
+
selfAnswerPath?: GoalClarificationSelfAnswerPath;
|
|
52
58
|
/** Fail-soft pending persistence for terminal fallbacks; injectable for focused tests. */
|
|
53
59
|
pendingQuestionPersistence?: {
|
|
54
60
|
create: typeof createPendingQuestion;
|
|
@@ -404,6 +410,7 @@ export async function escalateGoalDocumentClarifications(
|
|
|
404
410
|
const dispatchDelivery = delivery ?? 'modal';
|
|
405
411
|
const recordedDelivery = input.resolvedDelivery ?? dispatchDelivery;
|
|
406
412
|
let answersWritten = 0;
|
|
413
|
+
let selfAnswered = 0;
|
|
407
414
|
let questionIds: string[] = [];
|
|
408
415
|
const observeEscalation = (event: GoalClarificationObservationEvent, data: Record<string, unknown>): void => {
|
|
409
416
|
writeEscalationObservation(event, { ...data, deliverySource, answersWritten, questionIds });
|
|
@@ -430,6 +437,35 @@ export async function escalateGoalDocumentClarifications(
|
|
|
430
437
|
return result;
|
|
431
438
|
}
|
|
432
439
|
|
|
440
|
+
if (input.selfAnswerPath) {
|
|
441
|
+
try {
|
|
442
|
+
const resolution = await selfAnswerGoalDocumentClarifications(document, {
|
|
443
|
+
path: input.selfAnswerPath,
|
|
444
|
+
ask: document,
|
|
445
|
+
resolver: input.selfResolveClarification,
|
|
446
|
+
});
|
|
447
|
+
if (resolution.newlyAnswered > 0) {
|
|
448
|
+
writeGoalDocumentAtomic(input.goalFile, resolution.document);
|
|
449
|
+
answersWritten += resolution.newlyAnswered;
|
|
450
|
+
selfAnswered += resolution.newlyAnswered;
|
|
451
|
+
document = resolution.document;
|
|
452
|
+
clarifications = parseGoalDocumentClarifications(document);
|
|
453
|
+
unanswered = clarifications.filter((clarification) => !clarification.answered);
|
|
454
|
+
deliveryIds = mapClarificationDeliveryIds(clarifications);
|
|
455
|
+
questionIds = [...new Set(unanswered.map((clarification) => clarification.questionId))];
|
|
456
|
+
}
|
|
457
|
+
if (unanswered.length === 0) {
|
|
458
|
+
const result = { unanswered: 0, escalated: 0, delivery: recordedDelivery, outcome: 'skipped' as const, answeredBy: 'agent' as const };
|
|
459
|
+
observeEscalation(result.outcome, { goalFile: input.goalFile, ...result, reason: 'self-answered' });
|
|
460
|
+
return result;
|
|
461
|
+
}
|
|
462
|
+
} catch (error) {
|
|
463
|
+
debug.log(GOAL_CLARIFICATION_ESCALATION_CATEGORY, 'self-answer-failed', {
|
|
464
|
+
path: input.selfAnswerPath, reason: error instanceof Error ? error.message : String(error),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
433
469
|
const dispatch = input.dispatch ?? dispatchAskUserQuestion;
|
|
434
470
|
const pendingQuestionPersistence = input.pendingQuestionPersistence ?? {
|
|
435
471
|
create: createPendingQuestion,
|
|
@@ -444,7 +480,7 @@ export async function escalateGoalDocumentClarifications(
|
|
|
444
480
|
// ⛔⭐⭐⭐ 여기서부터의 조기 반환·폴백은 이 «누적값»을 쓴다 — 'none' 을 하드코딩하면
|
|
445
481
|
// 앞 배치가 답했는데 뒤 배치가 취소·실패했을 때 「아무도 안 답했다」는 «거짓»이 된다
|
|
446
482
|
// (무인 리뷰 R2 must-fix · 실측 형태: 관측이 거짓말한다).
|
|
447
|
-
const seenProvenance = { explicit: new Set<'human' | 'agent'>(), unmarkedAnswer: false };
|
|
483
|
+
const seenProvenance = { explicit: new Set<'human' | 'agent'>(selfAnswered > 0 ? ['agent' as const] : []), unmarkedAnswer: false };
|
|
448
484
|
const aggregate = (): GoalClarificationAnsweredBy => {
|
|
449
485
|
const { explicit, unmarkedAnswer } = seenProvenance;
|
|
450
486
|
// ⭐ 표시 없는 답도 «한 출처»로 센다 — legacy 경로는 사람이다(레포 안 리졸버는 이제 전부 명시한다).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Atomic goal-document replacement for self-answered clarifications (POD-SELF-ANSWER). */
|
|
2
|
+
import { chmodSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
export interface GoalDocumentWriteIo {
|
|
5
|
+
writeFileSync: (path: string, data: string, options?: { mode?: number }) => void;
|
|
6
|
+
renameSync: (from: string, to: string) => void;
|
|
7
|
+
rmSync: (path: string, options: { force: boolean }) => void;
|
|
8
|
+
statSync: (path: string) => { mode: number };
|
|
9
|
+
chmodSync: (path: string, mode: number) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const realIo: GoalDocumentWriteIo = { writeFileSync, renameSync, rmSync, statSync, chmodSync };
|
|
13
|
+
|
|
14
|
+
/** Replace a goal document atomically (temp file next to it + rename) so a failed write never leaves a torn goal.
|
|
15
|
+
* The existing file's permission bits are kept, so a 0600 goal stays 0600 after replacement. */
|
|
16
|
+
export function writeGoalDocumentAtomic(path: string, document: string, io: GoalDocumentWriteIo = realIo): void {
|
|
17
|
+
let mode: number | undefined;
|
|
18
|
+
try { mode = io.statSync(path).mode & 0o777; } catch { mode = undefined; }
|
|
19
|
+
const temporary = `${path}.self-answer-${process.pid}-${Date.now()}.tmp`;
|
|
20
|
+
try {
|
|
21
|
+
io.writeFileSync(temporary, document, mode === undefined ? undefined : { mode });
|
|
22
|
+
if (mode !== undefined) io.chmodSync(temporary, mode);
|
|
23
|
+
io.renameSync(temporary, path);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
try { io.rmSync(temporary, { force: true }); } catch { /* best effort */ }
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -68,6 +68,10 @@ function liveHealRuns(root: string): Set<string> {
|
|
|
68
68
|
/** A test process must never start the real heal loop — it spawns external grounding (10-05 load 150 incident). */
|
|
69
69
|
export async function startHealLoop(file: string, options: HealLoopOptions, env: NodeJS.ProcessEnv = process.env,
|
|
70
70
|
run: StartHealLoop = async (graph, args) => (await import('../graph-runner/runner.js')).runGraph(graph, args)): Promise<unknown> {
|
|
71
|
+
if (env.ELANOUS_POD_NAME) {
|
|
72
|
+
debug.log('heal.intake', 'skipped-in-pod', { ref: options.input.failureEvent.ref });
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
71
75
|
if (env.NODE_ENV === 'test' || env.ELANOUS_TEST_HOME) {
|
|
72
76
|
debug.log('heal.intake', 'loop-start-skipped-test', { runId: options.runId });
|
|
73
77
|
return undefined;
|
|
@@ -30,6 +30,8 @@ export type HostRegateDeps = {
|
|
|
30
30
|
acquire?: (repoRoot: string) => Promise<() => void>;
|
|
31
31
|
log?: (event: 'passed' | 'failed' | 'unmeasured' | 'base-raced', data: Record<string, unknown>) => void;
|
|
32
32
|
freezeRoot?: string;
|
|
33
|
+
/** Test seam for the operational freeze authority; production always uses the host root. */
|
|
34
|
+
prodFreezeRoot?: string;
|
|
33
35
|
runExposeGate?: typeof runExposeGate;
|
|
34
36
|
syncMergedChecklist?: typeof import('../release-loop/merged-pr-checklist.js').syncMergedPrChecklist;
|
|
35
37
|
};
|
|
@@ -241,7 +243,7 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
241
243
|
// --match-head-commit pins the head. gh has no base pin, so the base was re-read just
|
|
242
244
|
// above; the seconds between that read and the merge are checked after the fact below.
|
|
243
245
|
if (input.verifyOnly) return result('passed');
|
|
244
|
-
const landing = admitLandingMerge({ prNumber: input.prNumber, headCommit: input.headCommit, repoRoot: input.repoRoot, ...(input.goalFile ? { goalFile: input.goalFile } : {}) }, deps.freezeRoot, {}, input.resumed ? undefined : { prNumber: input.prNumber, repoRoot: input.repoRoot, headCommit: input.headCommit });
|
|
246
|
+
const landing = admitLandingMerge({ prNumber: input.prNumber, headCommit: input.headCommit, repoRoot: input.repoRoot, ...(input.goalFile ? { goalFile: input.goalFile } : {}) }, deps.freezeRoot, {}, input.resumed ? undefined : { prNumber: input.prNumber, repoRoot: input.repoRoot, headCommit: input.headCommit }, { prodFreezeRoot: deps.prodFreezeRoot });
|
|
245
247
|
if (landing.kind !== 'merge') {
|
|
246
248
|
debug.log('harness.merge', 'frozen', { pr: input.prNumber, ...(landing.kind === 'held' ? { reason: landing.freeze.reason, until: landing.freeze.until } : { resumedElsewhere: true }) });
|
|
247
249
|
return { passed: true, failures: [], os: process.platform, status: 'frozen' };
|
|
@@ -129,12 +129,33 @@ export async function runL8ShadowQueue(cwd: string, deps: L8ShadowQueueDeps = {}
|
|
|
129
129
|
if (changed.some((file) => file.startsWith('-') || file.startsWith('/') || file.split('/').includes('..') || file.includes('\\'))) throw new Error('unsafe changed-file path');
|
|
130
130
|
verdict.tests = files.filter((file) => /(?:^|\/)[^/]+\.(?:test|spec)\.(?:[cm]?[jt]sx?)$/.test(file));
|
|
131
131
|
const merge = execute('git', ['-c', 'user.name=elanous shadow', '-c', 'user.email=shadow@localhost', 'merge', '--no-ff', '--no-edit', item.head], temp);
|
|
132
|
+
let autoResolvedNextMd = false;
|
|
132
133
|
if (merge.status !== 0) {
|
|
133
|
-
const unresolved = run('git', ['diff', '--name-only', '--diff-filter=U'], temp);
|
|
134
|
-
if (!unresolved) throw new Error(`git merge failed: ${merge.stderr.trim() || `exit ${merge.status}`}`);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
const unresolved = run('git', ['diff', '--name-only', '--diff-filter=U'], temp).split('\n').filter(Boolean);
|
|
135
|
+
if (!unresolved.length) throw new Error(`git merge failed: ${merge.stderr.trim() || `exit ${merge.status}`}`);
|
|
136
|
+
if (unresolved.length === 1 && unresolved[0] === NEXT_MD_PATH) {
|
|
137
|
+
const stage = (n: number): string => {
|
|
138
|
+
const result = execute('git', ['show', `:${n}:${NEXT_MD_PATH}`], temp!);
|
|
139
|
+
if (result.status !== 0) throw new Error(`release/next.md stage ${n} unavailable: ${result.stderr}`);
|
|
140
|
+
return result.stdout;
|
|
141
|
+
};
|
|
142
|
+
// Stage 2 is main in this checkout; preserve the incoming PR (stage 3) and union main's additions.
|
|
143
|
+
const resolved = resolveNextMdConflict(stage(1), stage(2), stage(3));
|
|
144
|
+
if (resolved === null) {
|
|
145
|
+
verdict.verdict = 'conflict';
|
|
146
|
+
verdict.detail = 'release/next.md conflict is not append-only';
|
|
147
|
+
} else {
|
|
148
|
+
writeFileSync(join(temp, NEXT_MD_PATH), resolved);
|
|
149
|
+
run('git', ['add', '--', NEXT_MD_PATH], temp);
|
|
150
|
+
run('git', ['-c', 'user.name=elanous shadow', '-c', 'user.email=shadow@localhost', 'commit', '-m', 'Integrate PR for L8 shadow evaluation'], temp);
|
|
151
|
+
autoResolvedNextMd = true;
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
verdict.verdict = 'conflict';
|
|
155
|
+
verdict.detail = unresolved.join('\n');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (merge.status === 0 || autoResolvedNextMd) {
|
|
138
159
|
if (verdict.tests.length) {
|
|
139
160
|
if (verdict.tests.some((file) => !existsSync(join(temp!, file)))) throw new Error('changed test path unavailable');
|
|
140
161
|
run('bun', ['install', '--frozen-lockfile'], temp);
|
|
@@ -153,6 +174,9 @@ export async function runL8ShadowQueue(cwd: string, deps: L8ShadowQueueDeps = {}
|
|
|
153
174
|
} else {
|
|
154
175
|
verdict.verdict = 'pass';
|
|
155
176
|
}
|
|
177
|
+
if (autoResolvedNextMd) {
|
|
178
|
+
verdict.detail = verdict.detail ? `release/next.md auto-resolved; ${verdict.detail}` : 'release/next.md auto-resolved';
|
|
179
|
+
}
|
|
156
180
|
}
|
|
157
181
|
} finally {
|
|
158
182
|
try {
|
|
@@ -34,6 +34,7 @@ import { judgePredictionAccuracy } from './judge-prediction-accuracy.js';
|
|
|
34
34
|
import { measureReviewFindingRecurrence, type ReviewFindingRecurrence } from './review-finding-recurrence.js';
|
|
35
35
|
import { createHash } from 'node:crypto';
|
|
36
36
|
import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
37
|
+
import { writeGoalDocumentAtomic } from './goal-document-write.js';
|
|
37
38
|
import { hostname, loadavg } from 'node:os';
|
|
38
39
|
import { resolveHostId } from '../platform/host-id.js';
|
|
39
40
|
import { resolveInstanceName } from '../instance-identity.js';
|
|
@@ -242,7 +243,7 @@ export type { QuotaAccountAvailabilityEvidence };
|
|
|
242
243
|
import { decideReworkSalvage, type ReworkSalvageEvidence } from './rework-salvage.js';
|
|
243
244
|
import { type SupervisorGoalFillEvidence, fillUnverifiableGoalSlots, type SupervisorGoalFillResult } from './goal-supervisor-fill.js';
|
|
244
245
|
import { escalateGoalDocumentClarifications, recordSupervisorPlanRevision, type GoalClarificationEscalationResult, type SupervisorPlanRevisionRelaxation, type SupervisorPlanRevisionResult } from './goal-clarification-escalation.js';
|
|
245
|
-
import { parseGoalDocumentClarifications } from './goal-author-clarification.js';
|
|
246
|
+
import { parseGoalDocumentClarifications, selfAnswerGoalDocumentClarifications, type GoalClarificationSelfResolver } from './goal-author-clarification.js';
|
|
246
247
|
import {
|
|
247
248
|
createFileAskUserQuestionResolver,
|
|
248
249
|
type AskUserQuestionDispatchContext,
|
|
@@ -1077,6 +1078,9 @@ export interface SelfImplementSeams {
|
|
|
1077
1078
|
openPr: (opts: { title: string; body: string; head: string; base?: string; draft?: boolean; labels?: string[]; cwd: string }) => Promise<{ url: string; number: number }>;
|
|
1078
1079
|
/** PR 생성 뒤 런의 라운드 대화를 게시한다. 실패는 PR 개설을 되돌리지 않는다. */
|
|
1079
1080
|
postPrComment?: (opts: { number: number; body: string; cwd: string }) => Promise<void>;
|
|
1081
|
+
/** Read and edit the single marked status comment; missing seams fall back to individual comments. */
|
|
1082
|
+
findPrComment?: (opts: { number: number; marker: string; cwd: string; comments?: unknown }) => Promise<{ id: number; body: string } | undefined>;
|
|
1083
|
+
editPrComment?: (opts: { id: number; body: string; cwd: string }) => Promise<void>;
|
|
1080
1084
|
addPrLabel?: (opts: { number: number; label: string; cwd: string }) => Promise<void>;
|
|
1081
1085
|
readPrFiles?: (opts: { number: number; cwd: string; base?: string }) => Promise<string[]>;
|
|
1082
1086
|
/** PR의 base/head SHA와 관측된 PR base를 한 응답에서 고정한다. 미주입·실패·빈 SHA면 검사 대상을 증명할 수 없어 fail-closed로 PR을 열어 둔다. */
|
|
@@ -1154,6 +1158,8 @@ export interface SelfImplementSeams {
|
|
|
1154
1158
|
/** ⭐ 「이 표면에 답할 사람이 있나」. 기본은 실제 stdin 의 TTY 여부다 —
|
|
1155
1159
|
* ⛔ 실물에 매이면 테스트가 «환경»을 검사하게 되므로 주입 가능하게 둔다. */
|
|
1156
1160
|
stdinIsInteractive?: () => boolean;
|
|
1161
|
+
/** Optional author self-answer seam for non-TTY Pods; never installs a terminal HITL resolver. */
|
|
1162
|
+
selfResolveClarification?: GoalClarificationSelfResolver;
|
|
1157
1163
|
}
|
|
1158
1164
|
|
|
1159
1165
|
/** onProgress seam 이 통지하는 단계(관측·UX 라벨). */
|
|
@@ -3182,6 +3188,20 @@ function autoMergeEnabled(opts: Pick<SelfImplementOptions, 'completion' | 'autoM
|
|
|
3182
3188
|
return completionIntentOf(opts) === 'auto-merge';
|
|
3183
3189
|
}
|
|
3184
3190
|
|
|
3191
|
+
/**
|
|
3192
|
+
* FREEZE-POD — a Pod child never merges its own PR. The host's landing freeze is invisible inside the Pod, so an
|
|
3193
|
+
* auto-merge run there always stops at merge-ready and the launching host re-gates and merges (or holds it).
|
|
3194
|
+
* Local (non-Pod) runs are returned unchanged.
|
|
3195
|
+
*/
|
|
3196
|
+
export function routePodMergeToHost<T extends Pick<SelfImplementOptions, 'completion' | 'autoMerge' | 'mergeByHost'>>(
|
|
3197
|
+
opts: T, env: NodeJS.ProcessEnv = process.env,
|
|
3198
|
+
): { opts: T; reason: 'requested' | 'pod-child-auto-merge' | null } {
|
|
3199
|
+
const podChild = env.ELANOUS_SUBSTRATE === 'pod' && Boolean(env.ELANOUS_POD_NAME?.trim());
|
|
3200
|
+
if (!podChild || !autoMergeEnabled(opts)) return { opts, reason: null };
|
|
3201
|
+
if (opts.mergeByHost === true) return { opts, reason: 'requested' };
|
|
3202
|
+
return { opts: { ...opts, mergeByHost: true }, reason: 'pod-child-auto-merge' };
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3185
3205
|
const SELF_IMPLEMENT_LOG_CATEGORY = 'self-implement';
|
|
3186
3206
|
|
|
3187
3207
|
/** Emits a run-aware auxiliary log without admitting it to the run ledger. */
|
|
@@ -3465,6 +3485,12 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
|
|
|
3465
3485
|
}
|
|
3466
3486
|
const identity = resolveRunIdentity({ explicit: opts.runId });
|
|
3467
3487
|
const { runId } = identity;
|
|
3488
|
+
const podMerge = routePodMergeToHost(opts);
|
|
3489
|
+
if (podMerge.reason) {
|
|
3490
|
+
opts = podMerge.opts;
|
|
3491
|
+
try { debug.log('self-implement.pod', 'merge-routed-to-host', { job: process.env.ELANOUS_POD_NAME ?? null, runId, reason: podMerge.reason }); }
|
|
3492
|
+
catch { /* observation must not block the run */ }
|
|
3493
|
+
}
|
|
3468
3494
|
const branch = opts.branchName ?? plannedSelfImplBranch(opts.feature, opts.goalId, runId);
|
|
3469
3495
|
try {
|
|
3470
3496
|
debug.log('self-implement.branch', 'planned', {
|
|
@@ -5364,6 +5390,7 @@ async function runSelfImplementInner(
|
|
|
5364
5390
|
? { resolvedDelivery: installDecision.delivery }
|
|
5365
5391
|
: {}),
|
|
5366
5392
|
...(s.escalateGoalClarifications ? { dispatch: s.escalateGoalClarifications } : {}),
|
|
5393
|
+
...(!stdinIsInteractive && !clarificationResolverLease.installed ? { selfAnswerPath: 'ask' as const, selfResolveClarification: s.selfResolveClarification } : {}),
|
|
5367
5394
|
fallback: (message) => process.stdout.write(`${message}\n`),
|
|
5368
5395
|
});
|
|
5369
5396
|
} finally {
|
|
@@ -5440,9 +5467,11 @@ async function runSelfImplementInner(
|
|
|
5440
5467
|
const omission = `\n\n[${omittedChars} characters omitted; original body length ${body.length}]`;
|
|
5441
5468
|
prCommentBuffer.push(`${body.slice(0, MAX_PR_COMMENT_BODY_CHARS - omission.length)}${omission}`);
|
|
5442
5469
|
};
|
|
5443
|
-
|
|
5470
|
+
let statusCommentCreateAttempted = false;
|
|
5471
|
+
const flushPrComments = async (pr: { number: number }, salvageAction?: 'launched'): Promise<void> => {
|
|
5444
5472
|
if (!prCommentBuffer.length) return;
|
|
5445
5473
|
if (!s.postPrComment) {
|
|
5474
|
+
observe('pr.comment.mode', { number: pr.number, mode: 'seam-missing', buffered: prCommentBuffer.length });
|
|
5446
5475
|
observe('pr.comment.seam-missing', { number: pr.number, buffered: prCommentBuffer.length }, { level: 'warn' });
|
|
5447
5476
|
return;
|
|
5448
5477
|
}
|
|
@@ -5450,24 +5479,39 @@ async function runSelfImplementInner(
|
|
|
5450
5479
|
if (s.findPrComment && s.editPrComment) {
|
|
5451
5480
|
try {
|
|
5452
5481
|
const existing = await s.findPrComment({ number: pr.number, marker, cwd: wt.path });
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5482
|
+
if (!existing && statusCommentCreateAttempted) {
|
|
5483
|
+
observe('pr.comment.mode', { number: pr.number, mode: 'individual-fallback', reason: 'status-creation-uncertain', buffered: prCommentBuffer.length });
|
|
5484
|
+
} else {
|
|
5485
|
+
const history = prCommentBuffer.join('\n\n');
|
|
5486
|
+
const body = existing
|
|
5487
|
+
? existing.body.includes('</details>')
|
|
5488
|
+
? existing.body.replace(/<\/details>(?![\s\S]*<\/details>)/, `\n\n${history}\n</details>`)
|
|
5489
|
+
: `${existing.body}\n\n<details>\n<summary>Round history</summary>\n\n${history}\n</details>`
|
|
5490
|
+
: `${marker}\n<details>\n<summary>Round history</summary>\n\n${history}\n</details>`;
|
|
5491
|
+
if (existing) await s.editPrComment({ id: existing.id, body, cwd: wt.path });
|
|
5492
|
+
else {
|
|
5493
|
+
statusCommentCreateAttempted = true;
|
|
5494
|
+
await s.postPrComment({ number: pr.number, body, cwd: wt.path });
|
|
5495
|
+
}
|
|
5496
|
+
observe('pr.comment.mode', { number: pr.number, mode: existing ? 'status-updated' : 'status-created', buffered: prCommentBuffer.length });
|
|
5497
|
+
prCommentBuffer.length = 0;
|
|
5498
|
+
}
|
|
5461
5499
|
} catch (error) {
|
|
5462
5500
|
// Unknown lookup/edit outcome is not proof of absence: never create a second status comment.
|
|
5463
5501
|
observe('pr.comment.status-failed', { number: pr.number, error: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
|
|
5502
|
+
observe('pr.comment.mode', { number: pr.number, mode: 'individual-fallback', reason: 'status-failed', buffered: prCommentBuffer.length });
|
|
5464
5503
|
}
|
|
5504
|
+
} else {
|
|
5505
|
+
observe('pr.comment.mode', { number: pr.number, mode: 'individual-fallback', reason: 'status-seam-missing', buffered: prCommentBuffer.length });
|
|
5465
5506
|
}
|
|
5466
|
-
|
|
5507
|
+
if (!prCommentBuffer.length) return;
|
|
5508
|
+
const pending = prCommentBuffer.splice(0);
|
|
5509
|
+
for (const body of pending) {
|
|
5467
5510
|
try {
|
|
5468
5511
|
await s.postPrComment({ number: pr.number, body, cwd: wt.path });
|
|
5469
5512
|
} catch (error) {
|
|
5470
|
-
|
|
5513
|
+
// Posting may have succeeded remotely even if the response failed; do not retry an uncertain comment.
|
|
5514
|
+
observe('pr.comment.post-failed', { number: pr.number, ...(salvageAction && body.includes('Rework salvage status: launched.') ? { salvageAction } : {}), error: error instanceof Error ? error.message : String(error) }, { level: 'warn' });
|
|
5471
5515
|
}
|
|
5472
5516
|
}
|
|
5473
5517
|
};
|
|
@@ -5818,7 +5862,13 @@ async function runSelfImplementInner(
|
|
|
5818
5862
|
};
|
|
5819
5863
|
const postSalvageStatus = async (pr: { number: number } | undefined, action: 'launched' | 'parked', reason?: string): Promise<void> => {
|
|
5820
5864
|
if (!pr) return;
|
|
5865
|
+
if (action === 'launched') {
|
|
5866
|
+
bufferPrComment(round, 'author', 'Rework salvage status: launched.', reason ? [`reason: ${reason}`] : []);
|
|
5867
|
+
await flushPrComments(pr, 'launched');
|
|
5868
|
+
return;
|
|
5869
|
+
}
|
|
5821
5870
|
if (!s.postPrComment) {
|
|
5871
|
+
observe('pr.comment.mode', { number: pr.number, mode: 'seam-missing', salvageAction: action });
|
|
5822
5872
|
observe('pr.comment.seam-missing', { number: pr.number, salvageAction: action }, { level: 'warn' });
|
|
5823
5873
|
return;
|
|
5824
5874
|
}
|
|
@@ -5827,6 +5877,7 @@ async function runSelfImplementInner(
|
|
|
5827
5877
|
`Rework salvage status: ${action}.`,
|
|
5828
5878
|
...(reason ? ['', `- reason: ${reason}`] : []),
|
|
5829
5879
|
].join('\n');
|
|
5880
|
+
observe('pr.comment.mode', { number: pr.number, mode: 'individual', salvageAction: action });
|
|
5830
5881
|
try {
|
|
5831
5882
|
await s.postPrComment({ number: pr.number, body, cwd: wt.path });
|
|
5832
5883
|
} catch (error) {
|
|
@@ -5996,7 +6047,7 @@ async function runSelfImplementInner(
|
|
|
5996
6047
|
if (!s.launchReworkSalvage) throw new Error('launch seam unavailable');
|
|
5997
6048
|
await s.launchReworkSalvage({ goalFile: opts.goalFile!, base: wt.branch, salvageAttempt: (opts.salvageAttempt ?? 0) + 1 });
|
|
5998
6049
|
observe('rework-salvage', { action: 'launched', ...launchObservation });
|
|
5999
|
-
await postSalvageStatus(pr, 'launched');
|
|
6050
|
+
await postSalvageStatus(pr, 'launched', environmentReason ?? (hardCapBlockedExtend ? 'hard-cap-extend' : undefined));
|
|
6000
6051
|
return 'launched';
|
|
6001
6052
|
} catch (error) {
|
|
6002
6053
|
const reason = 'launch-failed';
|
|
@@ -6175,6 +6226,20 @@ async function runSelfImplementInner(
|
|
|
6175
6226
|
if (relaxation) pendingPlanRevision = { ...pendingPlanRevision!, relaxation };
|
|
6176
6227
|
if (opts.goalFile) {
|
|
6177
6228
|
try {
|
|
6229
|
+
if (!(s.stdinIsInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)))()) {
|
|
6230
|
+
try {
|
|
6231
|
+
const source = readFileSync(opts.goalFile, 'utf8');
|
|
6232
|
+
// Always run the shared self-answer so every rework pass leaves one observation, even with nothing pending.
|
|
6233
|
+
const resolved = await selfAnswerGoalDocumentClarifications(source, {
|
|
6234
|
+
path: 'rework', ask: source, resolver: s.selfResolveClarification,
|
|
6235
|
+
});
|
|
6236
|
+
if (resolved.newlyAnswered > 0) writeGoalDocumentAtomic(opts.goalFile, resolved.document);
|
|
6237
|
+
} catch (error) {
|
|
6238
|
+
debug.log('self-implement.clarification-escalation', 'self-answer-failed', {
|
|
6239
|
+
path: 'rework', reason: error instanceof Error ? error.message : String(error),
|
|
6240
|
+
});
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
6178
6243
|
const recorded: SupervisorPlanRevisionResult = recordSupervisorPlanRevision(opts.goalFile, { reason: parsedDecision.reason, round, ...(relaxation ? { relaxation } : {}) });
|
|
6179
6244
|
pendingPlanRevision = { ...pendingPlanRevision!, ...(relaxation ? { relaxation } : {}), application: {
|
|
6180
6245
|
status: recorded.status === 'applied' || recorded.status === 'already-applied' ? recorded.status : 'failed',
|
|
@@ -2,7 +2,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { debug } from '../debug/log.js';
|
|
5
|
-
import { parse as parsePrCommentMeta } from '../agent-substrate/pr-comment-meta.js';
|
|
5
|
+
import { expandRecords, parse as parsePrCommentMeta } from '../agent-substrate/pr-comment-meta.js';
|
|
6
6
|
|
|
7
7
|
export interface PriorDraftPr {
|
|
8
8
|
number: number;
|
|
@@ -85,9 +85,10 @@ export async function collectPriorDraftFindings({
|
|
|
85
85
|
continue;
|
|
86
86
|
}
|
|
87
87
|
const prActivity = Date.parse(pr.state.toUpperCase() === 'CLOSED' ? pr.closedAt ?? pr.createdAt : pr.createdAt);
|
|
88
|
+
const records = comments.flatMap((comment) => expandRecords(comment.body).map((body) => ({ ...comment, body })));
|
|
88
89
|
// A PR bearing the current run's comment is this run's PR, not a predecessor.
|
|
89
|
-
if (
|
|
90
|
-
const reviewers =
|
|
90
|
+
if (records.some((comment) => parsePrCommentMeta(comment.body)?.run === currentRunId)) continue;
|
|
91
|
+
const reviewers = records.flatMap((comment) => {
|
|
91
92
|
const meta = parsePrCommentMeta(comment.body);
|
|
92
93
|
return meta?.role === 'reviewer' && meta.run && meta.round !== undefined
|
|
93
94
|
? [{ comment, meta }] : [];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { appendFileSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { debug } from '../debug/log.js';
|
|
3
|
+
|
|
4
|
+
export interface SkillRun {
|
|
5
|
+
skill: string;
|
|
6
|
+
runId: string;
|
|
7
|
+
outcome: 'success' | 'failure';
|
|
8
|
+
failureKind?: string;
|
|
9
|
+
userCorrected: boolean;
|
|
10
|
+
retries: number;
|
|
11
|
+
durationMs: number;
|
|
12
|
+
at: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SkillFixProposal {
|
|
16
|
+
skill: string;
|
|
17
|
+
failureKind: string;
|
|
18
|
+
evidenceRunIds: string[];
|
|
19
|
+
suggestion: string;
|
|
20
|
+
expectedEffect: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Record one run at the caller-supplied ledger path; never touch a skill file. */
|
|
24
|
+
export function appendSkillRun(ledgerPath: string, run: SkillRun): void {
|
|
25
|
+
appendFileSync(ledgerPath, `${JSON.stringify(run)}\n`, 'utf8');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Read the JSONL ledger in append order; an absent ledger has no runs. */
|
|
29
|
+
export function readSkillRuns(ledgerPath: string): SkillRun[] {
|
|
30
|
+
let text: string;
|
|
31
|
+
try {
|
|
32
|
+
text = readFileSync(ledgerPath, 'utf8');
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
return text.split('\n').filter((line) => line.trim() !== '').map((line) => JSON.parse(line) as SkillRun);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Shadow-only suggestions for recurring failures within the same skill and failure kind. */
|
|
41
|
+
export function proposeSkillFixes(
|
|
42
|
+
runs: readonly SkillRun[],
|
|
43
|
+
{ threshold = 3 }: { threshold?: number } = {},
|
|
44
|
+
): SkillFixProposal[] {
|
|
45
|
+
const grouped = new Map<string, Map<string, Map<string, SkillRun>>>();
|
|
46
|
+
for (const run of runs) {
|
|
47
|
+
if (run.outcome !== 'failure' || !run.failureKind) continue;
|
|
48
|
+
let kinds = grouped.get(run.skill);
|
|
49
|
+
if (!kinds) { kinds = new Map(); grouped.set(run.skill, kinds); }
|
|
50
|
+
let failures = kinds.get(run.failureKind);
|
|
51
|
+
if (!failures) { failures = new Map(); kinds.set(run.failureKind, failures); }
|
|
52
|
+
const previous = failures.get(run.runId);
|
|
53
|
+
if (!previous || run.at >= previous.at) failures.set(run.runId, run);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const proposals: SkillFixProposal[] = [];
|
|
57
|
+
for (const [skill, kinds] of grouped) {
|
|
58
|
+
for (const [failureKind, failures] of kinds) {
|
|
59
|
+
if (failures.size < threshold) continue;
|
|
60
|
+
const evidenceRunIds = [...failures.values()]
|
|
61
|
+
.map((run, index) => ({ run, index }))
|
|
62
|
+
.sort((a, b) => a.run.at.localeCompare(b.run.at) || a.index - b.index)
|
|
63
|
+
.slice(-3)
|
|
64
|
+
.map(({ run }) => run.runId);
|
|
65
|
+
proposals.push({
|
|
66
|
+
skill,
|
|
67
|
+
failureKind,
|
|
68
|
+
evidenceRunIds,
|
|
69
|
+
suggestion: `스킬 ${skill}의 ${failureKind} 실패 처리를 점검·개선하세요 — 같은 실패가 ${failures.size}번 반복됐습니다.`,
|
|
70
|
+
expectedEffect: `${skill}의 ${failureKind} 재발 감소`,
|
|
71
|
+
});
|
|
72
|
+
debug.log('skill.feedback', 'proposal', { skill, failureKind, runs: failures.size });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return proposals;
|
|
76
|
+
}
|