elanous 0.2.14 → 0.2.16
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/{1307-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
- package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
- package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +15 -15
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +15 -15
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +15 -15
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +15 -15
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +15 -15
- package/apps/pwa/out/ceo/index.html +1 -0
- package/apps/pwa/out/ceo/index.txt +33 -0
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +15 -15
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +15 -15
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +15 -15
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +15 -15
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +15 -15
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +15 -15
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +15 -15
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +15 -15
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +15 -15
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +15 -15
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +15 -15
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +15 -15
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +15 -15
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +15 -15
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +15 -15
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +15 -15
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +15 -15
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +15 -15
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +16 -16
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +15 -15
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +15 -15
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +15 -15
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +15 -15
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +15 -15
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +15 -15
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +15 -15
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +15 -15
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +15 -15
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +15 -15
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +15 -15
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +15 -15
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +15 -15
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +15 -15
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +15 -15
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +15 -15
- package/graphs/doc-gen/doc-gen.yaml +24 -0
- package/graphs/doc-gen/recipes.yaml +3 -0
- package/graphs/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/orchestrator/orchestrator.yaml +7 -0
- package/graphs/orchestrator/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -2
- package/graphs/steward/recipes.yaml +2 -1
- package/package.json +1 -1
- package/scripts/brand/check.ts +7 -5
- package/scripts/ci-public-export-import-gate.ts +154 -0
- package/scripts/ci-test-interference-gate.ts +5 -1
- package/scripts/ci-typecheck-changed.ts +82 -31
- package/scripts/docs/doc-rot.ts +164 -0
- package/scripts/docs/generate-graph-anatomy.ts +437 -0
- package/scripts/docs-cli-check.ts +4 -4
- package/scripts/expose-rubric-suggest.ts +18 -12
- package/scripts/goals/goal-census.ts +343 -0
- package/scripts/lecture-note/node.ts +31 -11
- package/scripts/lessons/handbook-page.ts +30 -3
- package/scripts/marketing/generate-ax-brief.ts +140 -0
- package/scripts/marketing/generate-feature-map.ts +118 -0
- package/scripts/measure/author-section-contribution.ts +84 -0
- package/scripts/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/cut-branch.ts +43 -13
- package/scripts/release-loop/gate-node.ts +82 -14
- package/scripts/release-loop/vault-note-node.ts +257 -8
- package/scripts/release-story/draft.ts +57 -9
- package/scripts/rhythm/daily.ts +263 -76
- package/scripts/rhythm/monthly.ts +97 -0
- package/scripts/rhythm/weekly.ts +30 -9
- package/scripts/smart-merge/resync-shadow.ts +147 -0
- package/src/acp/client.ts +17 -2
- package/src/agent-env/profile.ts +161 -0
- package/src/agent-mission/driver.ts +51 -1
- package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
- package/src/agent-mission/supervisor-metrics.ts +218 -0
- package/src/agent-substrate/pr-reviewer.ts +2 -0
- package/src/ask-user-question/pending-questions.ts +5 -0
- package/src/auth/github-app-token.ts +112 -13
- package/src/autopilot/build/llm-conflict-merge.ts +32 -6
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/briefing/brief-items.ts +276 -0
- package/src/budget/usage-outcomes.ts +204 -0
- package/src/claims/claims-render.ts +14 -5
- package/src/cli/agent-env-cli.ts +21 -0
- package/src/cli/brief-cli.ts +73 -0
- package/src/cli/decisions-cli.ts +9 -0
- package/src/cli/doctor-cli.ts +23 -1
- package/src/cli/doctor-node-modules.ts +147 -0
- package/src/cli/intake-cli.ts +46 -17
- package/src/cli/know-cli.ts +20 -0
- package/src/cli/logs-cli.ts +43 -1
- package/src/cli/pod-cli.ts +2 -0
- package/src/cli/pr-cli.ts +80 -12
- package/src/cli/release-cli.ts +66 -29
- package/src/cli/seat-requests-cli.ts +52 -0
- package/src/cli/self-update.ts +200 -1
- package/src/cli/usage-cli.ts +26 -0
- package/src/context-bus/context-now-public.ts +16 -6
- package/src/context-bus/context-now.ts +3 -1
- package/src/context-bus/coord-events.ts +6 -4
- package/src/decisions/decision-cards.ts +33 -7
- package/src/decisions/proact-meter.ts +286 -0
- package/src/decisions/proact1-lite.ts +86 -0
- package/src/decisions/web-push-decision-cards.ts +1 -1
- package/src/discord-self-message.ts +9 -0
- package/src/domains/alert-audit.ts +271 -0
- package/src/domains/core-tools.ts +4 -3
- package/src/domains/ops-facts-tool.ts +31 -1
- package/src/flow-loop/split.ts +24 -0
- package/src/harness/harness-cli-command.ts +147 -17
- package/src/harness/harness-incidents.ts +2 -0
- package/src/harness/harness-pod-dispatch.ts +67 -8
- package/src/harness/harness-queue.ts +380 -50
- package/src/harness/harness-seams.ts +13 -3
- package/src/harness/helper-repair.ts +218 -0
- package/src/harness/helper-scan.ts +27 -4
- package/src/harness/launch-stamp.ts +236 -0
- package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
- package/src/harness/ledger-write-guard.ts +35 -0
- package/src/harness/nested-elanous-policy.ts +114 -0
- package/src/harness/pod-memory-policy.ts +15 -0
- package/src/harness/repository-name.ts +55 -0
- package/src/hq/fence-audit.ts +90 -0
- package/src/hq/hq.ts +70 -6
- package/src/hq/lease.ts +9 -7
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +31 -0
- package/src/hq/transfer.ts +185 -0
- package/src/index.ts +189 -13
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +65 -21
- package/src/intake-plane/front-classifier.ts +6 -0
- package/src/intake-plane/items.ts +3 -2
- package/src/intake-plane/lens.ts +177 -0
- package/src/intake-plane/route.ts +145 -1
- package/src/knowledge/know-find.ts +105 -0
- package/src/llm.ts +75 -19
- package/src/loops/budget.ts +36 -5
- package/src/loops/checker.ts +2 -2
- package/src/loops/orchestrator/author-depth.ts +65 -9
- package/src/loops/orchestrator/author-input-check.ts +8 -3
- package/src/loops/orchestrator/author-ledger.ts +3 -1
- package/src/loops/orchestrator/finish-rate.ts +14 -3
- package/src/loops/orchestrator/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +107 -29
- package/src/loops/orchestrator/traffic.ts +117 -11
- package/src/loops/registry.ts +142 -61
- package/src/maturity/bot-slash-catalog.ts +11 -2
- package/src/maturity/feature-maturity.ts +4 -3
- package/src/maturity/tui-slash-availability.ts +40 -0
- package/src/mss/logging/log-store.ts +35 -4
- package/src/nexus/api/http-server.ts +28 -14
- package/src/nexus/api/loop-edges.ts +173 -0
- package/src/nexus/api/schedules-read.ts +13 -0
- package/src/nexus/api/seat-requests.ts +79 -56
- package/src/oauth/overload-failover-launch.ts +75 -0
- package/src/oauth/overload-failover.ts +137 -0
- package/src/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +6 -3
- package/src/release-loop/checklist.ts +19 -5
- package/src/release-loop/feature-store.ts +59 -31
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +98 -29
- package/src/seat-dispatch/seat-ask.ts +33 -6
- package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
- package/src/seat-dispatch/seat-request-ledger.ts +133 -0
- package/src/seat-loop/seat-loop.ts +215 -12
- package/src/self-dev/ask-launch-flow.ts +22 -1
- package/src/self-dev/authored-goal-trace.ts +64 -0
- package/src/self-dev/dev-cli.ts +19 -1
- package/src/self-dev/dev-pipeline.ts +14 -9
- package/src/self-dev/draft-sweep.ts +90 -10
- package/src/self-dev/draft-triage-rules.ts +25 -8
- package/src/self-dev/entrance-registry.ts +10 -6
- package/src/self-dev/old-door.ts +73 -0
- package/src/self-dev/orchestrate.ts +15 -1
- package/src/self-dev/review-provider-fallback.ts +6 -1
- package/src/self-dev/run-supervisor.ts +49 -2
- package/src/self-implement/app-api-budget.ts +166 -0
- package/src/self-implement/budget-gate.ts +7 -5
- package/src/self-implement/follow-up-goals.ts +289 -0
- package/src/self-implement/frozen-merges.ts +3 -2
- package/src/self-implement/goal-author.ts +50 -6
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/heal-intake.ts +120 -2
- package/src/self-implement/heal-triage.ts +44 -0
- package/src/self-implement/host-regate.ts +9 -2
- package/src/self-implement/l8-merge-queue.ts +132 -0
- package/src/self-implement/orchestrator.ts +325 -47
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/revert-guard.ts +42 -7
- package/src/self-implement/run-ledger.ts +6 -0
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +45 -16
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/session-runtime/index.ts +2 -0
- package/src/session-runtime/retry-api.ts +87 -0
- package/src/session-runtime/retry-policy.ts +107 -6
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +20 -2
- package/src/task-cards/card-cli.ts +25 -0
- package/src/task-cards/card-store.ts +5 -3
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
- package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
- package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
- package/src/telegram-commands.ts +5 -0
- package/src/telegram.ts +72 -4
- package/src/user-config.ts +64 -7
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/nodes/bash.ts +9 -1
- package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
- package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
|
@@ -15,10 +15,12 @@ export function selectScheduledCut(schedules: readonly ReleaseSchedule[], now: D
|
|
|
15
15
|
.sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt) || a.version.localeCompare(b.version))[0] ?? null;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export type AutoStartDeferral = { deferred: true; reason: Exclude<ReleaseReadiness, { ready: true }>['reason'] | 'frozen' };
|
|
19
|
+
|
|
18
20
|
export type AutoStartResult =
|
|
19
21
|
| { status: 'no-cut' }
|
|
20
22
|
| { status: 'planned'; version: string; cutAt: string }
|
|
21
|
-
| { status: 'skipped'; version: string; reason:
|
|
23
|
+
| { status: 'skipped'; version: string; reason: AutoStartDeferral['reason'] }
|
|
22
24
|
| { status: 'started'; version: string; cutAt: string };
|
|
23
25
|
|
|
24
26
|
export interface AutoStartOptions {
|
|
@@ -28,7 +30,7 @@ export interface AutoStartOptions {
|
|
|
28
30
|
ledgerRoot?: string;
|
|
29
31
|
schedules?: (root: string) => ReleaseSchedule[];
|
|
30
32
|
readiness?: (version: string, ledgerRoot: string, now: Date) => ReleaseReadiness;
|
|
31
|
-
launch?: (version: string) => void | Promise<void>;
|
|
33
|
+
launch?: (version: string) => void | AutoStartDeferral | Promise<void | AutoStartDeferral>;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/** Select from the release schedule; the existing `release run --if-ready` owns the launch-time lock and guards. */
|
|
@@ -43,6 +45,7 @@ export async function autoStartScheduledRelease(options: AutoStartOptions): Prom
|
|
|
43
45
|
const readiness = (options.readiness ?? ((v, ledgerRoot, at) => releaseReadiness(v, { ledgerRoot, now: () => at })))(version, root, now);
|
|
44
46
|
if (!readiness.ready) return { status: 'skipped', version, reason: readiness.reason };
|
|
45
47
|
if (!options.launch) throw new Error('auto-start apply requires a launch callback');
|
|
46
|
-
await options.launch(version);
|
|
48
|
+
const launched = await options.launch(version);
|
|
49
|
+
if (launched?.deferred) return { status: 'skipped', version, reason: launched.reason };
|
|
47
50
|
return { status: 'started', version, cutAt };
|
|
48
51
|
}
|
|
@@ -16,6 +16,10 @@ export interface ChecklistItem {
|
|
|
16
16
|
priority?: 'P0' | 'P1' | 'P2';
|
|
17
17
|
predecessors?: string[];
|
|
18
18
|
deadlineVersion?: string;
|
|
19
|
+
ceoMinutes?: number;
|
|
20
|
+
ceoDate?: string;
|
|
21
|
+
/** 다른 칸의 처리량을 올리는 칸(자율성·효율성·동시성). 없으면 가속 등급이 아니다. */
|
|
22
|
+
accelerator?: true;
|
|
19
23
|
evidence?: string;
|
|
20
24
|
disposition?: ChecklistDisposition;
|
|
21
25
|
kind?: ChecklistKind;
|
|
@@ -96,7 +100,12 @@ function change(data: Checklist, id: string, field: string, from: unknown, to: u
|
|
|
96
100
|
debug.log('release-loop.checklist', 'change', { version: data.version, id, field, from: from ?? null, to: to ?? null, by });
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
export function
|
|
103
|
+
export function validateCeoLoad(input: { ceoMinutes?: number; ceoDate?: string }): void {
|
|
104
|
+
if (input.ceoMinutes !== undefined && (!Number.isSafeInteger(input.ceoMinutes) || input.ceoMinutes < 0)) throw new CliUserError('대표 손 분량은 0 이상의 정수 분이어야 한다');
|
|
105
|
+
if (input.ceoDate !== undefined && (!/^\d{4}-\d{2}-\d{2}$/.test(input.ceoDate) || !Number.isFinite(Date.parse(`${input.ceoDate}T00:00:00Z`)) || new Date(`${input.ceoDate}T00:00:00Z`).toISOString().slice(0, 10) !== input.ceoDate)) throw new CliUserError('대표 손 날짜는 YYYY-MM-DD 이어야 한다');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function addItem(v: string, input: { id: string; title: string; owner?: string; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean }, options: { allowDuplicateId?: boolean } = {}): Checklist {
|
|
100
109
|
if (!input.id.trim()) throw new CliUserError('칸 id 가 비었다');
|
|
101
110
|
if (!input.title.trim()) throw new CliUserError('칸 제목이 비었다');
|
|
102
111
|
if (input.kind !== undefined && input.kind !== 'screen') throw new CliUserError(`잘못된 종류: ${input.kind}`, 'screen');
|
|
@@ -104,6 +113,8 @@ export function addItem(v: string, input: { id: string; title: string; owner?: s
|
|
|
104
113
|
if (input.priority !== undefined && !['P0', 'P1', 'P2'].includes(input.priority)) throw new CliUserError(`잘못된 우선순위: ${input.priority}`);
|
|
105
114
|
if (input.predecessors !== undefined && (!Array.isArray(input.predecessors) || input.predecessors.some((p) => typeof p !== 'string' || !p.trim() || p === input.id))) throw new CliUserError('잘못된 선행 칸');
|
|
106
115
|
if (input.deadlineVersion !== undefined) store.validateVersion(input.deadlineVersion);
|
|
116
|
+
if (input.accelerator !== undefined && input.accelerator !== true) throw new CliUserError('가속 등급은 true 이거나 생략한다');
|
|
117
|
+
validateCeoLoad(input);
|
|
107
118
|
return mutate(v, (data, otherItems) => {
|
|
108
119
|
if (data.items.some((item) => item.id === input.id)) throw new CliUserError(`이미 있는 칸: ${input.id}`, 'set <id> 로 고친다');
|
|
109
120
|
const collisions = otherItems(input.id);
|
|
@@ -114,14 +125,14 @@ export function addItem(v: string, input: { id: string; title: string; owner?: s
|
|
|
114
125
|
}
|
|
115
126
|
const by = process.env.ELANOUS_TRACK || 'cli';
|
|
116
127
|
const at = new Date().toISOString();
|
|
117
|
-
const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), ...(input.kind !== undefined ? { kind: input.kind } : {}), ...(input.priority !== undefined ? { priority: input.priority } : {}), ...(input.predecessors !== undefined ? { predecessors: input.predecessors } : {}), ...(input.deadlineVersion !== undefined ? { deadlineVersion: input.deadlineVersion } : {}), updatedAt: at, updatedBy: by };
|
|
128
|
+
const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), ...(input.kind !== undefined ? { kind: input.kind } : {}), ...(input.priority !== undefined ? { priority: input.priority } : {}), ...(input.predecessors !== undefined ? { predecessors: input.predecessors } : {}), ...(input.deadlineVersion !== undefined ? { deadlineVersion: input.deadlineVersion } : {}), ...(input.ceoMinutes !== undefined ? { ceoMinutes: input.ceoMinutes } : {}), ...(input.ceoDate !== undefined ? { ceoDate: input.ceoDate } : {}), ...(input.accelerator === true ? { accelerator: true as const } : {}), updatedAt: at, updatedBy: by };
|
|
118
129
|
data.items.push(item);
|
|
119
130
|
change(data, item.id, 'add', null, item, by, at);
|
|
120
131
|
return true;
|
|
121
132
|
});
|
|
122
133
|
}
|
|
123
134
|
|
|
124
|
-
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string; disposition?: ChecklistDisposition; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string }, by: string): Checklist {
|
|
135
|
+
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string; disposition?: ChecklistDisposition; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean | null }, by: string): Checklist {
|
|
125
136
|
return mutate(v, (data) => {
|
|
126
137
|
const item = data.items.find((i) => i.id === id);
|
|
127
138
|
if (!item) throw new CliUserError(`없는 칸: ${id}`, 'list 로 칸 목록을 본다');
|
|
@@ -131,19 +142,22 @@ export function setItem(v: string, id: string, patch: { status?: ChecklistStatus
|
|
|
131
142
|
if (patch.priority !== undefined && !['P0', 'P1', 'P2'].includes(patch.priority)) throw new CliUserError(`잘못된 우선순위: ${patch.priority}`);
|
|
132
143
|
if (patch.predecessors !== undefined && (!Array.isArray(patch.predecessors) || patch.predecessors.some((p) => typeof p !== 'string' || !p.trim() || p === id))) throw new CliUserError('잘못된 선행 칸');
|
|
133
144
|
if (patch.deadlineVersion !== undefined) store.validateVersion(patch.deadlineVersion);
|
|
145
|
+
if (patch.accelerator !== undefined && patch.accelerator !== true && patch.accelerator !== null) throw new CliUserError('가속 등급은 true(부여) 또는 null(해제)이다');
|
|
146
|
+
validateCeoLoad(patch);
|
|
134
147
|
if (patch.owner !== undefined) {
|
|
135
148
|
parseOwner(patch.owner);
|
|
136
149
|
if (item.owner && patch.owner !== item.owner) {
|
|
137
150
|
throw new CliUserError(`지금 주인: ${item.owner} — --force 로만 바꾼다`, 'release checklist claim <id> --by <자리> --force');
|
|
138
151
|
}
|
|
139
152
|
}
|
|
140
|
-
const fields = (['evidence', 'owner', 'status', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion'] as const).filter((field) => patch[field] !== undefined && JSON.stringify(patch[field]) !== JSON.stringify(item[field]));
|
|
153
|
+
const fields = (['evidence', 'owner', 'status', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator'] as const).filter((field) => patch[field] !== undefined && JSON.stringify(patch[field]) !== JSON.stringify(item[field] ?? null));
|
|
141
154
|
if (fields.length === 0) return false;
|
|
142
155
|
const at = new Date().toISOString();
|
|
143
156
|
for (const field of fields) {
|
|
144
157
|
const from = item[field];
|
|
145
158
|
const to = patch[field]!;
|
|
146
|
-
(
|
|
159
|
+
if (field === 'accelerator' && to === null) delete item.accelerator;
|
|
160
|
+
else (item as unknown as Record<string, unknown>)[field] = to;
|
|
147
161
|
item.updatedAt = at;
|
|
148
162
|
item.updatedBy = by;
|
|
149
163
|
change(data, id, field, from, to, by, at);
|
|
@@ -30,6 +30,18 @@ export function releasedVersion(ledgerRoot = releaseLedgerRoot()): string {
|
|
|
30
30
|
})[0] ?? '';
|
|
31
31
|
}
|
|
32
32
|
const walWait = new Int32Array(new SharedArrayBuffer(4));
|
|
33
|
+
const SCHEMA_COLUMNS: Record<string, readonly string[]> = {
|
|
34
|
+
imported_versions: ['json_hash', 'imported_at'],
|
|
35
|
+
events: ['reason'],
|
|
36
|
+
assignments: ['priority', 'predecessors', 'deadline_version', 'ceo_minutes', 'ceo_date', 'accelerator'],
|
|
37
|
+
release_schedules: ['freeze_from', 'freeze_until'],
|
|
38
|
+
};
|
|
39
|
+
function schemaCurrent(db: Database): boolean {
|
|
40
|
+
return Object.entries(SCHEMA_COLUMNS).every(([table, names]) => {
|
|
41
|
+
const columns = new Set((db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>).map((column) => column.name));
|
|
42
|
+
return names.every((name) => columns.has(name));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
33
45
|
function open(root = releaseLedgerRoot()): Database {
|
|
34
46
|
const path = join(root, 'release', 'features.sqlite');
|
|
35
47
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -48,22 +60,27 @@ function open(root = releaseLedgerRoot()): Database {
|
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
db.exec('PRAGMA foreign_keys = ON');
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (!
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
// Two processes opening a fresh ledger both saw a missing column and both ran ALTER TABLE ("duplicate column name").
|
|
64
|
+
// BEGIN IMMEDIATE serializes the schema check and the migration, so the second writer re-reads the migrated schema.
|
|
65
|
+
// An up-to-date ledger skips the write lock so readers don't queue behind each other.
|
|
66
|
+
if (!schemaCurrent(db)) db.transaction(() => {
|
|
67
|
+
if (!db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'imported_versions'").get()) db.exec(`CREATE TABLE IF NOT EXISTS features (id TEXT PRIMARY KEY, title TEXT NOT NULL, owner TEXT, kind TEXT, created_at TEXT NOT NULL);
|
|
68
|
+
CREATE TABLE IF NOT EXISTS assignments (feature_id TEXT NOT NULL REFERENCES features(id), version TEXT NOT NULL, status TEXT NOT NULL, disposition TEXT, evidence TEXT, title_override TEXT, owner TEXT, kind TEXT, updated_at TEXT NOT NULL, updated_by TEXT NOT NULL, PRIMARY KEY(feature_id, version));
|
|
69
|
+
CREATE TABLE IF NOT EXISTS events (seq INTEGER PRIMARY KEY AUTOINCREMENT, at TEXT NOT NULL, by TEXT NOT NULL, feature_id TEXT NOT NULL, version TEXT NOT NULL, field TEXT NOT NULL, "from" TEXT, "to" TEXT, released TEXT NOT NULL, dev TEXT NOT NULL, reason TEXT);
|
|
70
|
+
CREATE TABLE IF NOT EXISTS evidence (feature_id TEXT NOT NULL, version TEXT NOT NULL, ref TEXT NOT NULL, at TEXT NOT NULL, by TEXT NOT NULL);
|
|
71
|
+
CREATE TABLE IF NOT EXISTS imported_versions (version TEXT PRIMARY KEY, json_hash TEXT, imported_at TEXT);`);
|
|
72
|
+
const columns = db.query('PRAGMA table_info(imported_versions)').all() as Array<{ name: string }>;
|
|
73
|
+
if (!columns.some((column) => column.name === 'json_hash')) db.exec('ALTER TABLE imported_versions ADD COLUMN json_hash TEXT');
|
|
74
|
+
if (!columns.some((column) => column.name === 'imported_at')) db.exec('ALTER TABLE imported_versions ADD COLUMN imported_at TEXT');
|
|
75
|
+
if (!(db.query('PRAGMA table_info(events)').all() as Array<{ name: string }>).some((column) => column.name === 'reason')) db.exec('ALTER TABLE events ADD COLUMN reason TEXT');
|
|
76
|
+
db.exec('CREATE TABLE IF NOT EXISTS release_schedules (version TEXT PRIMARY KEY, cut_at TEXT NOT NULL, land_by TEXT, updated_at TEXT NOT NULL, updated_by TEXT NOT NULL)');
|
|
77
|
+
const assignmentColumns = db.query('PRAGMA table_info(assignments)').all() as Array<{ name: string }>;
|
|
78
|
+
for (const [name, sql] of [['priority', 'TEXT'], ['predecessors', 'TEXT'], ['deadline_version', 'TEXT'], ['ceo_minutes', 'INTEGER'], ['ceo_date', 'TEXT'], ['accelerator', 'INTEGER']] as const) {
|
|
79
|
+
if (!assignmentColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE assignments ADD COLUMN ${name} ${sql}`);
|
|
80
|
+
}
|
|
81
|
+
const scheduleColumns = db.query('PRAGMA table_info(release_schedules)').all() as Array<{ name: string }>;
|
|
82
|
+
for (const name of ['freeze_from', 'freeze_until']) if (!scheduleColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE release_schedules ADD COLUMN ${name} TEXT`);
|
|
83
|
+
}).immediate();
|
|
67
84
|
return db;
|
|
68
85
|
} catch (error) { db.close(); throw error; }
|
|
69
86
|
}
|
|
@@ -90,6 +107,14 @@ export function readSchedules(root?: string): ScheduleRow[] {
|
|
|
90
107
|
finally { db.close(); }
|
|
91
108
|
}
|
|
92
109
|
|
|
110
|
+
/** Versions with checklist assignments, including SQLite-only releases without a schedule or legacy JSON. */
|
|
111
|
+
export function assignedVersions(root = releaseLedgerRoot()): string[] {
|
|
112
|
+
if (!existsSync(join(root, 'release', 'features.sqlite'))) return [];
|
|
113
|
+
const db = open(root);
|
|
114
|
+
try { return (db.query('SELECT DISTINCT version FROM assignments ORDER BY version').all() as Array<{ version: string }>).map(({ version }) => version); }
|
|
115
|
+
finally { db.close(); }
|
|
116
|
+
}
|
|
117
|
+
|
|
93
118
|
export function writeSchedule(version: string, patch: { cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string }, by: string, root?: string): ScheduleRow {
|
|
94
119
|
validateVersion(version);
|
|
95
120
|
const db = open(root);
|
|
@@ -144,16 +169,18 @@ function evidenceRefs(db: Database, version: string, id: string): string[] {
|
|
|
144
169
|
return (db.query('SELECT ref FROM evidence WHERE feature_id = ? AND version = ? ORDER BY rowid').all(id, version) as Array<{ ref: string }>).map((row) => row.ref);
|
|
145
170
|
}
|
|
146
171
|
function items(db: Database, version: string): ChecklistItem[] {
|
|
147
|
-
const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.owner, a.kind, a.priority, a.predecessors, a.deadline_version, a.status, a.disposition, a.evidence, a.updated_at, a.updated_by
|
|
148
|
-
FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.version = ? ORDER BY a.rowid`).all(version) as Array<Record<string, string | null>>;
|
|
172
|
+
const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.owner, a.kind, a.priority, a.predecessors, a.deadline_version, a.ceo_minutes, a.ceo_date, a.accelerator, a.status, a.disposition, a.evidence, a.updated_at, a.updated_by
|
|
173
|
+
FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.version = ? ORDER BY a.rowid`).all(version) as Array<Record<string, string | number | null>>;
|
|
149
174
|
return rows.map((r) => {
|
|
150
|
-
const evidence = [r.evidence, ...evidenceRefs(db, version, r.id
|
|
151
|
-
return { id: r.id
|
|
152
|
-
...(r.owner !== null ? { owner: r.owner
|
|
153
|
-
...(r.priority !== null ? { priority: r.priority as ChecklistItem['priority'] } : {}), ...(r.predecessors !== null ? { predecessors: JSON.parse(r.predecessors
|
|
154
|
-
...(r.deadline_version !== null ? { deadlineVersion: r.deadline_version
|
|
175
|
+
const evidence = [r.evidence, ...evidenceRefs(db, version, r.id as string)].filter((value) => value !== null).join('\n');
|
|
176
|
+
return { id: r.id as string, title: r.title as string, status: r.status as ChecklistItem['status'],
|
|
177
|
+
...(r.owner !== null ? { owner: r.owner as string } : {}), ...(r.kind !== null ? { kind: r.kind as ChecklistItem['kind'] } : {}),
|
|
178
|
+
...(r.priority !== null ? { priority: r.priority as ChecklistItem['priority'] } : {}), ...(r.predecessors !== null ? { predecessors: JSON.parse(r.predecessors as string) as string[] } : {}),
|
|
179
|
+
...(r.deadline_version !== null ? { deadlineVersion: r.deadline_version as string } : {}),
|
|
180
|
+
...(r.ceo_minutes !== null ? { ceoMinutes: r.ceo_minutes as number } : {}), ...(r.ceo_date !== null ? { ceoDate: r.ceo_date as string } : {}),
|
|
181
|
+
...(r.accelerator === 1 ? { accelerator: true as const } : {}),
|
|
155
182
|
...(r.evidence !== null || evidence ? { evidence } : {}), ...(r.disposition !== null ? { disposition: r.disposition as ChecklistItem['disposition'] } : {}),
|
|
156
|
-
updatedAt: r.updated_at
|
|
183
|
+
updatedAt: r.updated_at as string, updatedBy: r.updated_by as string };
|
|
157
184
|
});
|
|
158
185
|
}
|
|
159
186
|
function snapshot(db: Database, version: string, released: string, dev: string): Checklist {
|
|
@@ -165,9 +192,9 @@ function putItem(db: Database, version: string, item: ChecklistItem): void {
|
|
|
165
192
|
if (!feature) db.query('INSERT INTO features (id, title, owner, kind, created_at) VALUES (?, ?, ?, ?, ?)').run(item.id, item.title, item.owner ?? null, item.kind ?? null, item.updatedAt);
|
|
166
193
|
// features.owner/kind follow the latest assignment so details() and the checklist never disagree after a set.
|
|
167
194
|
else if (item.owner !== undefined || item.kind !== undefined) db.query('UPDATE features SET owner = COALESCE(?, owner), kind = COALESCE(?, kind) WHERE id = ?').run(item.owner ?? null, item.kind ?? null, item.id);
|
|
168
|
-
db.query(`INSERT INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
169
|
-
ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, priority=excluded.priority, predecessors=excluded.predecessors, deadline_version=excluded.deadline_version, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
170
|
-
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.updatedAt, item.updatedBy);
|
|
195
|
+
db.query(`INSERT INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
196
|
+
ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, priority=excluded.priority, predecessors=excluded.predecessors, deadline_version=excluded.deadline_version, ceo_minutes=excluded.ceo_minutes, ceo_date=excluded.ceo_date, accelerator=excluded.accelerator, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
197
|
+
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.updatedAt, item.updatedBy);
|
|
171
198
|
}
|
|
172
199
|
function jsonHash(contents: string | Buffer): string { return createHash('sha256').update(contents).digest('hex'); }
|
|
173
200
|
|
|
@@ -208,8 +235,8 @@ function importOnDb(db: Database, version: string): boolean {
|
|
|
208
235
|
// Keep a legacy per-version title until an explicit retitle unifies the feature identity.
|
|
209
236
|
if (!existing) putItem(db, version, item);
|
|
210
237
|
else {
|
|
211
|
-
db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
212
|
-
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.updatedAt, item.updatedBy);
|
|
238
|
+
db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
239
|
+
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.updatedAt, item.updatedBy);
|
|
213
240
|
}
|
|
214
241
|
}
|
|
215
242
|
for (const entry of data.history) record(db, version, entry);
|
|
@@ -236,7 +263,7 @@ function importOnDb(db: Database, version: string): boolean {
|
|
|
236
263
|
record(db, version, { at: new Date().toISOString(), by: item.updatedBy, id: item.id, field: 'reimport', from: null, to: item, released: data.released, dev: data.dev });
|
|
237
264
|
continue;
|
|
238
265
|
}
|
|
239
|
-
const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion'] as const;
|
|
266
|
+
const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator'] as const;
|
|
240
267
|
const baseEvidence = (db.query('SELECT evidence FROM assignments WHERE feature_id = ? AND version = ?').get(item.id, version) as { evidence: string | null }).evidence ?? undefined;
|
|
241
268
|
const preserveRefs = item.evidence === baseEvidence || item.evidence === current.evidence;
|
|
242
269
|
const differs = fields.some((field) => field === 'evidence'
|
|
@@ -381,7 +408,7 @@ export function remove(version: string, id: string, by: string, released = relea
|
|
|
381
408
|
return true;
|
|
382
409
|
});
|
|
383
410
|
}
|
|
384
|
-
export function move(id: string, from: string, to: string, by: string, released = releasedVersion(), dev = devVersion(), reason?: string): Checklist {
|
|
411
|
+
export function move(id: string, from: string, to: string, by: string, released = releasedVersion(), dev = devVersion(), reason?: string, options: { clearDisposition?: boolean } = {}): Checklist {
|
|
385
412
|
validateVersion(from); validateVersion(to);
|
|
386
413
|
if (from === to) throw new CliUserError('같은 판으로 옮길 수 없다');
|
|
387
414
|
const db = open();
|
|
@@ -395,6 +422,7 @@ export function move(id: string, from: string, to: string, by: string, released
|
|
|
395
422
|
if (collision) throw new CliUserError(`이미 있는 칸: ${JSON.stringify(id).slice(1, -1)} — ${to} · 담당 ${JSON.stringify(collision.owner ?? '-').slice(1, -1)} · ${collision.title.replace(/\s+/g, ' ').slice(0, 40)}`);
|
|
396
423
|
const at = new Date().toISOString();
|
|
397
424
|
db.query('UPDATE assignments SET version = ?, updated_at = ?, updated_by = ? WHERE feature_id = ? AND version = ?').run(to, at, by, id, from);
|
|
425
|
+
if (options.clearDisposition) db.query('UPDATE assignments SET disposition = NULL WHERE feature_id = ? AND version = ?').run(id, to);
|
|
398
426
|
db.query('UPDATE evidence SET version = ? WHERE feature_id = ? AND version = ?').run(to, id, from);
|
|
399
427
|
record(db, to, { at, by, id, field: 'move', from, to, released, dev, ...(reason !== undefined ? { reason } : {}) });
|
|
400
428
|
return snapshot(db, to, released, dev);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
5
|
+
import { debug } from '../debug/log.js';
|
|
6
|
+
import { listChecklist, setItem } from './checklist.js';
|
|
7
|
+
import { evidencePlan, landedButYellow, type MergedChecklistPr } from './landed-but-yellow.js';
|
|
8
|
+
import { assignedVersions, evidenceAdd } from './feature-store.js';
|
|
9
|
+
|
|
10
|
+
export interface MergedPrChecklistDeps {
|
|
11
|
+
readPr?: (number: number, cwd: string) => { state: string; baseRefName: string; title: string; body: string | null; mergedAt: string };
|
|
12
|
+
versions?: () => string[];
|
|
13
|
+
checklist?: typeof listChecklist;
|
|
14
|
+
addEvidence?: typeof evidenceAdd;
|
|
15
|
+
setStatus?: typeof setItem;
|
|
16
|
+
log?: typeof debug.log;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A confirmed main merge is the only entry. The existing checklist matcher owns PR-to-cell decisions. */
|
|
20
|
+
export function syncMergedPrChecklist(number: number, cwd: string, goalFile?: string, deps: MergedPrChecklistDeps = {}): void {
|
|
21
|
+
const log = deps.log ?? debug.log.bind(debug);
|
|
22
|
+
const pr = (deps.readPr ?? ((n, dir) => JSON.parse(execFileSync('gh', ['pr', 'view', String(n), '--json', 'state,baseRefName,title,body,mergedAt'], { cwd: dir, encoding: 'utf8', timeout: 30_000 })) as ReturnType<NonNullable<MergedPrChecklistDeps['readPr']>>))(number, cwd);
|
|
23
|
+
if (pr.state !== 'MERGED' || pr.baseRefName !== 'main') return;
|
|
24
|
+
const merged: MergedChecklistPr = { number, title: pr.title, body: pr.body, mergedAt: pr.mergedAt };
|
|
25
|
+
const versions = deps.versions?.() ?? (() => {
|
|
26
|
+
let recorded: string[] = [];
|
|
27
|
+
try { recorded = readdirSync(join(releaseLedgerRoot(), 'release')).filter((version) => /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/.test(version)); }
|
|
28
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
29
|
+
return [...new Set([...assignedVersions(), ...recorded])];
|
|
30
|
+
})();
|
|
31
|
+
let goal: string | undefined;
|
|
32
|
+
if (goalFile) {
|
|
33
|
+
try { goal = readFileSync(goalFile, 'utf8'); }
|
|
34
|
+
catch (error) { log('release.checklist', 'merged-pr-goal-unreadable', { pr: number, goalFile, error: String(error) }); }
|
|
35
|
+
}
|
|
36
|
+
// The audit matcher owns explicit-label and evidence decisions on both PR surfaces.
|
|
37
|
+
// The shared matcher only accepts a whole `칸: X` line. A designated label in the body (`칸: X — …` at a line head)
|
|
38
|
+
// or in the title is restated as such a line; prose mentions are not (the audit command's contract is unchanged).
|
|
39
|
+
const designated = [...goalCells(pr.body ?? ''), ...titleCells(pr.title)];
|
|
40
|
+
// Only the restated labels go to the matcher — raw body text (quotes, fences, indented code) never does.
|
|
41
|
+
const prForMatch = { ...merged, title: '', body: designated.map((id) => `칸: ${id}`).join('\n') };
|
|
42
|
+
const matches = [...new Set(versions)].flatMap((version) => {
|
|
43
|
+
const snapshot = (deps.checklist ?? listChecklist)(version);
|
|
44
|
+
// The audit excludes green/done; evidence still applies to already-green cells.
|
|
45
|
+
const candidates = snapshot.items.map((item) => ({ ...item, status: 'yellow' as const }));
|
|
46
|
+
return landedButYellow(candidates, [prForMatch])
|
|
47
|
+
.filter((row) => row.prs.some((p) => p.basis === 'cell-line'))
|
|
48
|
+
.map((row) => ({ version, snapshot, row }));
|
|
49
|
+
});
|
|
50
|
+
const counts = new Map<string, number>();
|
|
51
|
+
for (const { row } of matches) counts.set(row.id, (counts.get(row.id) ?? 0) + 1);
|
|
52
|
+
for (const [id, count] of counts) {
|
|
53
|
+
if (count > 1) log('release.checklist', 'merged-pr-cell-ambiguous', { pr: number, id, versions: matches.filter((match) => match.row.id === id).map((match) => match.version) });
|
|
54
|
+
}
|
|
55
|
+
for (const { version, snapshot, row } of matches) {
|
|
56
|
+
if (counts.get(row.id) !== 1) continue;
|
|
57
|
+
for (const { id, ref } of evidencePlan([row])) {
|
|
58
|
+
(deps.addEvidence ?? evidenceAdd)(id, version, ref, 'harness', snapshot.released, snapshot.dev);
|
|
59
|
+
log('release.checklist', 'merged-pr-evidence-added', { version, id, pr: number, ref });
|
|
60
|
+
}
|
|
61
|
+
if (goal && declaresCompletion(goal)) {
|
|
62
|
+
if (!goalCells(goal).has(row.id)) {
|
|
63
|
+
log('release.checklist', 'merged-pr-status-skipped', { version, id: row.id, pr: number, reason: 'goal-does-not-name-cell' });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const from = snapshot.items.find((item) => item.id === row.id)!.status;
|
|
67
|
+
if (from !== 'green' && from !== 'done') {
|
|
68
|
+
(deps.setStatus ?? setItem)(version, row.id, { status: 'green' }, 'harness');
|
|
69
|
+
log('release.checklist', 'merged-pr-status-green', { version, id: row.id, pr: number, from, to: 'green' });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const found = new Set(matches.map(({ row }) => row.id));
|
|
74
|
+
const missing = [...new Set(designated)].filter((id) => !found.has(id));
|
|
75
|
+
if (!designated.length) log('release.checklist', 'merged-pr-cell-not-found', { pr: number });
|
|
76
|
+
for (const id of missing) log('release.checklist', 'merged-pr-cell-not-found', { pr: number, id });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const LABEL_IDS = '([A-Za-z0-9_-]+(?:[ \\t]*,[ \\t]*[A-Za-z0-9_-]+)*)';
|
|
80
|
+
const LABEL_END = '(?=[ \\t]*$|[ \\t]+[—–·(-])';
|
|
81
|
+
const DESIGNATION = new RegExp(`^ {0,3}(?:#{1,6}[ \\t]+|[-*][ \\t]+)?칸[ \\t]*:[ \\t]*${LABEL_IDS}${LABEL_END}`);
|
|
82
|
+
|
|
83
|
+
/** Lines of a Markdown document outside fenced code (closed only by the same marker, at least as long), 4-space
|
|
84
|
+
* indented code and `>` quotes — the only lines that may designate a cell or declare completion. */
|
|
85
|
+
function proseLines(text: string): string[] {
|
|
86
|
+
const lines: string[] = [];
|
|
87
|
+
let fence: { char: string; length: number } | null = null;
|
|
88
|
+
for (const raw of text.split('\n')) {
|
|
89
|
+
const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw;
|
|
90
|
+
const marker = /^ {0,3}(`{3,}|~{3,})/.exec(line);
|
|
91
|
+
if (fence) {
|
|
92
|
+
if (marker && marker[1]![0] === fence.char && marker[1]!.length >= fence.length && line.trim() === marker[1]) fence = null;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (marker) { fence = { char: marker[1]![0]!, length: marker[1]!.length }; continue; }
|
|
96
|
+
if (/^( {4}|\t)/.test(line) || /^ {0,3}>/.test(line)) continue;
|
|
97
|
+
lines.push(line);
|
|
98
|
+
}
|
|
99
|
+
return lines;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Cell ids a document designates on a line head: `칸: X[, Y]`, optionally after a heading or list marker. */
|
|
103
|
+
export function goalCells(goal: string): Set<string> {
|
|
104
|
+
const ids = new Set<string>();
|
|
105
|
+
for (const line of proseLines(goal)) {
|
|
106
|
+
const label = DESIGNATION.exec(line);
|
|
107
|
+
if (!label) continue;
|
|
108
|
+
for (const id of label[1]!.split(',')) ids.add(id.trim());
|
|
109
|
+
}
|
|
110
|
+
return ids;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** `이 칸 완료` counts only as a standalone prose line, not inside code or a quote. */
|
|
114
|
+
export function declaresCompletion(goal: string): boolean {
|
|
115
|
+
return proseLines(goal).some((line) => /^\s*이 칸 완료\s*$/.test(line));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A PR title is one authored line: `칸: X — …` may follow a short prefix such as `[TC]` or 「…」. */
|
|
119
|
+
export function titleCells(title: string): Set<string> {
|
|
120
|
+
const ids = new Set<string>();
|
|
121
|
+
for (const label of title.matchAll(new RegExp(`(?<![A-Za-z0-9가-힣])칸[ \\t]*:[ \\t]*${LABEL_IDS}${LABEL_END}`, 'g'))) {
|
|
122
|
+
for (const id of label[1]!.split(',')) ids.add(id.trim());
|
|
123
|
+
}
|
|
124
|
+
return ids;
|
|
125
|
+
}
|