elanous 0.2.14 → 0.2.15
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/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1307-6e7e0377c1db0bdc.js → 1307-33ffcc49ab45123b.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/app/approvals/{page-74383f890f075511.js → page-0e2c57fe6add4b2a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-e05a77df7e005b04.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-b8c49f6b4fa2df91.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-976813c91d7394ea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-fb260b9283dbf646.js → page-28b9448091776c06.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-60c7a8e1a4fe62ce.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-2a52b316cb143d9c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-c3ce483c8d5db370.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-96f074f6538e8a2c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-e97be5334d83a9f5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-955ec2da3be4c560.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-c1343f9499d86f3f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-d23b12bb92242558.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-04250f0e2c0d03df.js → page-e2ee2896811e98d0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-5efbf81124c474ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-85f1a1fc0a82738c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-8ac8d71c2adf9c8a.js → page-da6c08aae0f92768.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-153c2b3bcc89e8de.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-94dc383d3d54db1a.js → page-87a11e6a05f15199.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-b3759cc14da80908.js → page-7d4182964407c09c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-0d6d23b40263a7ea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-f5597e894c02cd33.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-35bdeae2e2c17147.js → page-73735b64bfe1ae67.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-8fcec4c9ea2e7f70.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-9817c7f9007466cc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-134c1bc762aee62c.js → page-97f9f43172a3a90e.js} +1 -1
- package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.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 -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 +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/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/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -2
- package/package.json +1 -1
- package/scripts/brand/check.ts +7 -5
- package/scripts/ci-typecheck-changed.ts +50 -18
- package/scripts/docs/generate-graph-anatomy.ts +315 -0
- package/scripts/expose-rubric-suggest.ts +18 -12
- 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/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/gate-node.ts +80 -14
- package/scripts/release-loop/vault-note-node.ts +240 -8
- package/scripts/release-story/draft.ts +57 -9
- package/scripts/rhythm/daily.ts +263 -76
- package/scripts/rhythm/weekly.ts +30 -9
- package/scripts/smart-merge/resync-shadow.ts +147 -0
- package/src/agent-env/profile.ts +161 -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/briefing/brief-items.ts +121 -0
- package/src/claims/claims-render.ts +14 -5
- package/src/cli/agent-env-cli.ts +21 -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/pod-cli.ts +2 -0
- package/src/cli/release-cli.ts +61 -27
- package/src/cli/seat-requests-cli.ts +52 -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/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/harness/harness-cli-command.ts +111 -15
- package/src/harness/harness-incidents.ts +2 -0
- package/src/harness/harness-pod-dispatch.ts +100 -11
- package/src/harness/harness-queue.ts +380 -50
- package/src/harness/helper-repair.ts +87 -0
- package/src/harness/helper-scan.ts +16 -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/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 +62 -4
- package/src/hq/lease.ts +4 -4
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +31 -0
- package/src/index.ts +77 -7
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +37 -15
- 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/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
- package/src/launch-head/seat-goal-queue.ts +198 -0
- 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/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +11 -4
- package/src/loops/orchestrator/traffic.ts +117 -11
- package/src/loops/registry.ts +142 -61
- package/src/maturity/feature-maturity.ts +2 -1
- package/src/maturity/tui-slash-availability.ts +31 -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/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +6 -3
- package/src/release-loop/checklist.ts +13 -4
- package/src/release-loop/feature-store.ts +58 -31
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +86 -26
- 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 +126 -10
- package/src/self-dev/dev-pipeline.ts +8 -4
- package/src/self-dev/draft-sweep.ts +90 -10
- package/src/self-dev/draft-triage-rules.ts +25 -8
- 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/frozen-merges.ts +3 -2
- package/src/self-implement/goal-author.ts +43 -5
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/heal-intake.ts +120 -2
- 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 +187 -40
- 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 +2 -0
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +1 -1
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +4 -1
- 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-pool.ts +60 -8
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +147 -36
- package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
- package/src/telegram.ts +25 -4
- package/src/user-config.ts +29 -5
- 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/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/control/page-e6b84ab07d3567d5.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/loops/page-160089123a40dd91.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/release/page-e7e10cd3386222e5.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/page-341480ed8e3c1ea8.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/showroom/page-58043db78ba519ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.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/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.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 → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
|
@@ -19,8 +19,38 @@ function publishedTime(value: string): { date: string; short: string; full: stri
|
|
|
19
19
|
return { date, short: `${parts.month}-${parts.day} ${parts.hour}:${parts.minute}`, full: `${date} ${parts.hour}:${parts.minute} KST` };
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// 구분자(` — ` · ` · ` · 줄바꿈 · 。)는 짝이 맞는 «…» 밖에서만 자른다.
|
|
22
23
|
function firstClause(title: string): string {
|
|
23
|
-
|
|
24
|
+
let depth = 0;
|
|
25
|
+
let end = title.length;
|
|
26
|
+
// Balanced «…», (…), […], {…}, “…” and "…" spans are kept whole — a separator inside them never ends the clause.
|
|
27
|
+
const closers: Record<string, string> = { '«': '»', '(': ')', '[': ']', '{': '}', '“': '”', '‘': '’' };
|
|
28
|
+
const stack: string[] = [];
|
|
29
|
+
let quoted = false;
|
|
30
|
+
let single = false;
|
|
31
|
+
for (let i = 0; i < title.length; i++) {
|
|
32
|
+
const char = title[i]!;
|
|
33
|
+
if (char === '"' && (quoted || title.indexOf('"', i + 1) > i)) { quoted = !quoted; continue; }
|
|
34
|
+
// ASCII ' quotes like ", except a word-internal apostrophe (doesn't).
|
|
35
|
+
if (char === "'" && !(/\p{L}/u.test(title[i - 1] ?? '') && /\p{L}/u.test(title[i + 1] ?? ''))
|
|
36
|
+
&& (single || title.indexOf("'", i + 1) > i)) { single = !single; continue; }
|
|
37
|
+
if (single) continue;
|
|
38
|
+
if (closers[char] && title.indexOf(closers[char]!, i) > i) { stack.push(closers[char]!); continue; }
|
|
39
|
+
if (stack.length && char === stack.at(-1)) { stack.pop(); continue; }
|
|
40
|
+
if (quoted || stack.length) continue;
|
|
41
|
+
if (char === '«' && title.indexOf('»', i) > i) depth++;
|
|
42
|
+
else if (char === '»' && depth > 0) depth--;
|
|
43
|
+
else if (depth === 0 && (char === '\n' || char === '。' || (/[—–·]/.test(char) && /\s/.test(title[i - 1] ?? '') && /\s/.test(title[i + 1] ?? '')))) { end = i; break; }
|
|
44
|
+
}
|
|
45
|
+
const clause = title.slice(0, end);
|
|
46
|
+
const unmatched = new Set<number>();
|
|
47
|
+
const open: number[] = [];
|
|
48
|
+
for (let i = 0; i < clause.length; i++) {
|
|
49
|
+
if (clause[i] === '«') open.push(i);
|
|
50
|
+
else if (clause[i] === '»') { if (open.length) open.pop(); else unmatched.add(i); }
|
|
51
|
+
}
|
|
52
|
+
for (const i of open) unmatched.add(i);
|
|
53
|
+
return clause.split('').filter((_, i) => !unmatched.has(i)).join('').trim().replace(/\s+/g, ' ');
|
|
24
54
|
}
|
|
25
55
|
|
|
26
56
|
function changeCount(draft: string): number {
|
|
@@ -68,7 +98,190 @@ function releaseHistory(context: GraphContext, root: string): string[] {
|
|
|
68
98
|
return history;
|
|
69
99
|
}
|
|
70
100
|
|
|
101
|
+
type LedgerNode = { nodeId: string; ok: boolean; output?: string };
|
|
102
|
+
type LedgerRun = { graphId: string; runId: string; startedAt?: string; input?: { version?: string }; nodes?: LedgerNode[] };
|
|
103
|
+
|
|
104
|
+
function publishedPreviousRun(dir: string, root: string, previousVersion: string, current: LedgerRun): LedgerRun | undefined {
|
|
105
|
+
const record: unknown = JSON.parse(readFileSync(join(root, 'release', previousVersion, 'release.json'), 'utf8'));
|
|
106
|
+
if (!record || typeof record !== 'object') return undefined;
|
|
107
|
+
const published = record as Record<string, unknown>;
|
|
108
|
+
if (published.version !== previousVersion || published.tag !== `v${previousVersion}`
|
|
109
|
+
|| typeof published.sourceCommit !== 'string' || !/^[0-9a-f]{8,40}$/i.test(published.sourceCommit)
|
|
110
|
+
|| typeof published.publishedAt !== 'string' || !Number.isFinite(Date.parse(published.publishedAt))) return undefined;
|
|
111
|
+
const publishedAt = published.publishedAt as string;
|
|
112
|
+
const candidates = readdirSync(dir).filter((file) => /^[\w.-]+\.json$/.test(file) && !file.endsWith('.decision.json')).flatMap((file) => {
|
|
113
|
+
try {
|
|
114
|
+
const value = JSON.parse(readFileSync(join(dir, file), 'utf8')) as LedgerRun;
|
|
115
|
+
if (value.graphId !== 'release-loop' || value.input?.version !== previousVersion || !Array.isArray(value.nodes)
|
|
116
|
+
|| !value.nodes.some((node) => node.nodeId === 'publish' && node.ok)
|
|
117
|
+
|| ledgerOutput(value, 'publish')?.tag !== published.tag
|
|
118
|
+
|| ledgerOutput(value, 'version-release')?.commit !== published.sourceCommit
|
|
119
|
+
|| (typeof value.startedAt === 'string' && value.startedAt > publishedAt)
|
|
120
|
+
|| (typeof current.startedAt === 'string' && typeof value.startedAt === 'string' && value.startedAt > current.startedAt)) return [];
|
|
121
|
+
return [value];
|
|
122
|
+
} catch { return []; }
|
|
123
|
+
});
|
|
124
|
+
// Repeated publications of the same tag/commit cannot identify which run supplied the published count.
|
|
125
|
+
if (new Set(candidates.map((entry) => ledgerOutput(entry, 'known-issues')?.count)).size > 1) return undefined;
|
|
126
|
+
return candidates[0];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function ledgerOutput(run: LedgerRun, nodeId: string): Record<string, unknown> | undefined {
|
|
130
|
+
const node = run.nodes?.filter((entry) => entry.nodeId === nodeId && entry.ok).at(-1);
|
|
131
|
+
try {
|
|
132
|
+
const raw = node?.output?.trim().split(/\r?\n/).at(-1);
|
|
133
|
+
const value: unknown = JSON.parse(raw ?? '');
|
|
134
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
135
|
+
} catch { return undefined; }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function stabilityLine(context: GraphContext, root: string): string {
|
|
139
|
+
const identity = context as GraphContext & { graphId?: string; runId?: string };
|
|
140
|
+
const fields: Record<string, string | number> = { introduced: '?', preexisting: '?', recut: '?', hosts: '?', knownIssuesDelta: '?' };
|
|
141
|
+
const unreadable: string[] = [];
|
|
142
|
+
let line: string;
|
|
143
|
+
try {
|
|
144
|
+
if (identity.graphId !== 'release-loop' || !identity.runId || !/^[\w.-]+$/.test(identity.runId)) throw new Error('런 식별자 없음');
|
|
145
|
+
const dir = join(root, 'graph-runs', 'release-loop');
|
|
146
|
+
const ledgerPath = join(dir, `${identity.runId}.json`);
|
|
147
|
+
if (!existsSync(ledgerPath)) throw new Error(`런 원장 없음(${identity.runId}.json)`);
|
|
148
|
+
const run = JSON.parse(readFileSync(ledgerPath, 'utf8')) as LedgerRun;
|
|
149
|
+
if (run.graphId !== 'release-loop' || run.runId !== identity.runId || run.input?.version !== context.input.version || !Array.isArray(run.nodes)) throw new Error('런 원장 식별 불일치');
|
|
150
|
+
const gate = ledgerOutput(run, 'gate');
|
|
151
|
+
if (Array.isArray(gate?.introduced)) fields.introduced = gate.introduced.length;
|
|
152
|
+
else unreadable.push('gate.introduced');
|
|
153
|
+
if (typeof gate?.preexisting === 'number' && Number.isSafeInteger(gate.preexisting) && gate.preexisting >= 0) fields.preexisting = gate.preexisting;
|
|
154
|
+
else unreadable.push('gate.preexisting');
|
|
155
|
+
// The cutoff manifest contains baseline/cutoff SHAs and included landings, not the cut branch's pick count.
|
|
156
|
+
// Neither a different SHA nor the number of included landings establishes whether there was a recut.
|
|
157
|
+
unreadable.push('cutoff.pick count not recorded');
|
|
158
|
+
// ops-upgrade is downstream of vault-note in the release graph; its host results do not exist yet.
|
|
159
|
+
unreadable.push('ops-upgrade.hosts not yet available');
|
|
160
|
+
const current = ledgerOutput(run, 'known-issues');
|
|
161
|
+
try {
|
|
162
|
+
// Same baseline as the landing section: the newest earlier version that was actually published (an unpublished, folded version is skipped).
|
|
163
|
+
const previousVersion = previousPublishedCut(root, context.input.version)?.version;
|
|
164
|
+
const previousRun = previousVersion ? publishedPreviousRun(dir, root, previousVersion, run) : undefined;
|
|
165
|
+
const previous = previousRun ? ledgerOutput(previousRun, 'known-issues')?.count : undefined;
|
|
166
|
+
if (typeof current?.count === 'number' && Number.isSafeInteger(current.count) && current.count >= 0
|
|
167
|
+
&& typeof previous === 'number' && Number.isSafeInteger(previous) && previous >= 0) {
|
|
168
|
+
const delta = current.count - previous;
|
|
169
|
+
fields.knownIssuesDelta = `${delta >= 0 ? '+' : ''}${delta}`;
|
|
170
|
+
} else unreadable.push('known-issues.count(previous/current)');
|
|
171
|
+
} catch { unreadable.push('known-issues.count(previous/current)'); }
|
|
172
|
+
line = `안정: 게이트 introduced ${fields.introduced} · preexisting ${fields.preexisting} · 재컷 ${fields.recut} · 호스트 판올림 성공 ${fields.hosts} · known-issues 직전 판 대비 ${fields.knownIssuesDelta}`;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
175
|
+
unreadable.push(reason);
|
|
176
|
+
line = `안정: 측정 불가: ${reason.replace(/[\r\n]+/g, ' ')}`;
|
|
177
|
+
}
|
|
178
|
+
try { debug.log('release-loop.vault-note', 'stability-line', { fields, unreadable }); } catch { /* Observation cannot block publication. */ }
|
|
179
|
+
return line;
|
|
180
|
+
}
|
|
181
|
+
|
|
71
182
|
interface ReleaseRecord { version: string; tag: string; sourceCommit: string; publishedAt: string; publicRepo?: string }
|
|
183
|
+
|
|
184
|
+
const landingTopics = [
|
|
185
|
+
{ name: '조직·루프', match: /조직|좌석|스튜어드|본부|운영|자율|루프|미션|cron|schedule|seat|steward|loop|mission|hq\b|ops\b/i },
|
|
186
|
+
{ name: '하니스', match: /하니스|골|게이트|검증|테스트|리뷰|실행기|워크트리|harness|goal|gate|review|test|worktree|self-implement|self-dev|graph/i },
|
|
187
|
+
{ name: '채널·PWA', match: /채널|화면|텔레그램|디스코드|메신저|발송|알림|모바일|음성|channel|pwa|telegram|discord|outbound|tui|web|ios|android/i },
|
|
188
|
+
{ name: '지식·교훈', match: /지식|기억|교훈|관측|로그|리서치|검색|문서|knowledge|memory|lesson|research|observation|logs|docs|manual/i },
|
|
189
|
+
{ name: '공개·브랜드', match: /공개|브랜드|발행|릴리스|홍보|광고|마케팅|public|brand|release|publish|marketing|npm/i },
|
|
190
|
+
{ name: '그 밖', match: /[\s\S]*/ },
|
|
191
|
+
] as const;
|
|
192
|
+
|
|
193
|
+
type LandingLine = { title: string; line?: string; sha: string; prNumber?: number };
|
|
194
|
+
|
|
195
|
+
function balanced(text: string): boolean {
|
|
196
|
+
const pairs: Record<string, string> = { '»': '«', ')': '(', ']': '[', '}': '{', '”': '“', '’': '‘' };
|
|
197
|
+
const stack: string[] = [];
|
|
198
|
+
let quote = false;
|
|
199
|
+
let single = false;
|
|
200
|
+
const chars = [...text];
|
|
201
|
+
for (const [i, char] of chars.entries()) {
|
|
202
|
+
if (char === '"') { quote = !quote; continue; }
|
|
203
|
+
// ASCII ' pairs like ", except a word-internal apostrophe (doesn't) which never opens a quote.
|
|
204
|
+
if (char === "'") {
|
|
205
|
+
if (!(/\p{L}/u.test(chars[i - 1] ?? '') && /\p{L}/u.test(chars[i + 1] ?? ''))) single = !single;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (char in pairs) { if (stack.pop() !== pairs[char]) return false; }
|
|
209
|
+
else if ('«([{“‘'.includes(char)) stack.push(char);
|
|
210
|
+
}
|
|
211
|
+
return !quote && !single && stack.length === 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function landingClause(text: string): string {
|
|
215
|
+
// A candidate with broken punctuation is not repaired by deleting its unmatched character.
|
|
216
|
+
if (!balanced(text)) return '';
|
|
217
|
+
// release/next.md lines lead with their kind («feat — …»); the headline is the change, not the kind.
|
|
218
|
+
const body = text.replace(/^-\s+/, '').replace(/^(feat|fix|internal|docs|test|perf|refactor|chore|ops|security|breaking)\s+[—–-]\s+/i, '');
|
|
219
|
+
const clause = firstClause(body);
|
|
220
|
+
return balanced(clause) ? clause : '';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function versionKey(value: string): number[] | null {
|
|
224
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value);
|
|
225
|
+
return match ? match.slice(1).map(Number) : null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Newest release below `version` with a readable release.json; null when there is none to compare against. */
|
|
229
|
+
function previousPublishedCut(root: string, version: string): { version: string; sourceCommit: string } | null {
|
|
230
|
+
const current = versionKey(version);
|
|
231
|
+
if (!current) return null;
|
|
232
|
+
const compare = (a: number[], b: number[]) => a[0]! - b[0]! || a[1]! - b[1]! || a[2]! - b[2]!;
|
|
233
|
+
const earlier = readdirSync(join(root, 'release'))
|
|
234
|
+
.map((name) => ({ name, key: versionKey(name) }))
|
|
235
|
+
.filter((row): row is { name: string; key: number[] } => !!row.key && compare(row.key, current) < 0)
|
|
236
|
+
.sort((a, b) => compare(b.key, a.key));
|
|
237
|
+
for (const { name } of earlier) {
|
|
238
|
+
const path = join(root, 'release', name, 'release.json');
|
|
239
|
+
if (!existsSync(path)) continue;
|
|
240
|
+
const record = JSON.parse(readFileSync(path, 'utf8')) as { sourceCommit?: unknown };
|
|
241
|
+
if (typeof record.sourceCommit !== 'string' || !/^[0-9a-f]{40}$/i.test(record.sourceCommit)) throw new Error(`v${name} release.json sourceCommit invalid`);
|
|
242
|
+
return { version: name, sourceCommit: record.sourceCommit };
|
|
243
|
+
}
|
|
244
|
+
// Earlier versions exist but none was published: the «직전 판 컷» claim cannot be checked, so it is not presented as settled.
|
|
245
|
+
if (earlier.length) throw new Error(`직전 판 컷 확인 못 함 — 이전 판 ${earlier.length}개에 release.json 없음`);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function landingsAtCut(root: string, version: string, cut: string): { groups: Array<{ name: string; lines: LandingLine[] }>; error?: string } {
|
|
250
|
+
try {
|
|
251
|
+
const manifest = JSON.parse(readFileSync(join(root, 'release', version, 'manifest.json'), 'utf8')) as {
|
|
252
|
+
version?: unknown; baseline?: { sha?: unknown }; cutoff?: { sha?: unknown }; in?: unknown;
|
|
253
|
+
};
|
|
254
|
+
if (manifest.version !== version || !/^[0-9a-f]{8,40}$/i.test(String(manifest.baseline?.sha ?? ''))
|
|
255
|
+
|| manifest.cutoff?.sha !== cut || !Array.isArray(manifest.in)
|
|
256
|
+
|| !manifest.in.every((entry: unknown) => {
|
|
257
|
+
const item = entry as Partial<LandingLine> | null;
|
|
258
|
+
return item && typeof item.title === 'string' && typeof item.sha === 'string'
|
|
259
|
+
&& (item.line === undefined || typeof item.line === 'string')
|
|
260
|
+
&& (item.prNumber === undefined || Number.isInteger(item.prNumber));
|
|
261
|
+
})) throw new Error('manifest.json 판·직전 컷·이번 컷 또는 착지 줄 불일치');
|
|
262
|
+
// The «직전 판 컷» claim is checked against the newest earlier published release, when one exists.
|
|
263
|
+
const previous = previousPublishedCut(root, version);
|
|
264
|
+
if (previous && previous.sourceCommit.toLowerCase() !== String(manifest.baseline!.sha).toLowerCase())
|
|
265
|
+
throw new Error(`직전 판 컷 불일치 — manifest baseline ${String(manifest.baseline!.sha).slice(0, 8)} ≠ v${previous.version} 컷 ${previous.sourceCommit.slice(0, 8)}`);
|
|
266
|
+
const groups = landingTopics.map(({ name }) => ({ name, lines: [] as LandingLine[] }));
|
|
267
|
+
for (const entry of manifest.in as LandingLine[]) {
|
|
268
|
+
const text = entry.line?.trim() || entry.title.trim();
|
|
269
|
+
const topic = landingTopics.findIndex(({ match }) => match.test(text));
|
|
270
|
+
groups[topic]!.lines.push(entry);
|
|
271
|
+
}
|
|
272
|
+
return { groups: groups.filter((group) => group.lines.length) };
|
|
273
|
+
} catch (error) {
|
|
274
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
275
|
+
return { groups: [], error: `manifest.json: ${reason.replace(/^manifest\.json\s*/, '').replace(/\s+/g, ' ')}` };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function representative(groups: Array<{ name: string; lines: LandingLine[] }>, fallback: string): string {
|
|
280
|
+
const candidates = [...groups].sort((a, b) => b.lines.length - a.lines.length);
|
|
281
|
+
const lines = candidates.map(({ lines }) => lines.map((entry) => landingClause(entry.line?.trim() || entry.title)).find(Boolean))
|
|
282
|
+
.filter((line): line is string => !!line).slice(0, 3);
|
|
283
|
+
return lines.join(' · ') || fallback;
|
|
284
|
+
}
|
|
72
285
|
export interface VaultNoteDeps {
|
|
73
286
|
instanceRoot?: string;
|
|
74
287
|
productionRoot?: string;
|
|
@@ -104,12 +317,22 @@ export function runVaultNote(context: GraphContext = readGraphContext(), deps: V
|
|
|
104
317
|
const published = publishedTime(record.publishedAt);
|
|
105
318
|
const draft = readFileSync(join(root, 'release', version, 'prepared', 'notes-draft.md'), 'utf8');
|
|
106
319
|
const items = (deps.checklist ?? ((v) => listChecklist(v).items))(version).filter((item) => item.status === 'green');
|
|
107
|
-
const
|
|
320
|
+
const landings = landingsAtCut(root, version, record.sourceCommit);
|
|
321
|
+
const oneLine = representative(landings.groups, items.map((item) => landingClause(item.title)).filter(Boolean).slice(0, 3).join(' · ') || '착지 내용 확인 필요');
|
|
108
322
|
const location = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
:
|
|
112
|
-
|
|
323
|
+
let identity: { graphId?: string; runId?: string } = {};
|
|
324
|
+
if (location && !('runId' in context)) {
|
|
325
|
+
try { identity = JSON.parse(location.trimStart().startsWith('{') ? location : readFileSync(location, 'utf8')) as typeof identity; }
|
|
326
|
+
catch { /* Missing or malformed graph identity is reported by the stability line, not by publication. */ }
|
|
327
|
+
}
|
|
328
|
+
const runContext = { ...identity, ...context };
|
|
329
|
+
const stability = stabilityLine(runContext, root);
|
|
330
|
+
let history: string[];
|
|
331
|
+
try { history = releaseHistory(runContext, root); }
|
|
332
|
+
catch (error) {
|
|
333
|
+
history = [];
|
|
334
|
+
try { debug.log('release.vault-note', 'history-unreadable', { reason: error instanceof Error ? error.message : String(error) }); } catch { /* Observation cannot block publication. */ }
|
|
335
|
+
}
|
|
113
336
|
// OP 10-04 19:10 ①: group by feature family (cell id family: HQ-FENCE2 → HQ · LOOP-OBS1 → LOOP · K10i → K), not by seat.
|
|
114
337
|
const groups = new Map<string, typeof items>();
|
|
115
338
|
for (const item of items) {
|
|
@@ -125,6 +348,14 @@ export function runVaultNote(context: GraphContext = readGraphContext(), deps: V
|
|
|
125
348
|
'',
|
|
126
349
|
]),
|
|
127
350
|
]);
|
|
351
|
+
const landingSection = landings.error
|
|
352
|
+
? [`- 못 읽음 · ${landings.error}`]
|
|
353
|
+
: landings.groups.length
|
|
354
|
+
? landings.groups.flatMap(({ name, lines }) => [
|
|
355
|
+
`### ${name} (${lines.length}건)`, '',
|
|
356
|
+
...lines.map((entry) => `- ${entry.line?.trim() || entry.title.trim()}${entry.prNumber ? ` (#${entry.prNumber})` : ''}`), '',
|
|
357
|
+
])
|
|
358
|
+
: ['- 착지 0건'];
|
|
128
359
|
const target = join(vault, directory);
|
|
129
360
|
const name = `엘라누스 v${version} (${published.date})`;
|
|
130
361
|
// 대표 10-04 19:25: notes live under <major>.x/<major>.<minor>/ · the index stays at the top.
|
|
@@ -139,14 +370,15 @@ export function runVaultNote(context: GraphContext = readGraphContext(), deps: V
|
|
|
139
370
|
if (!rowExists && !header.test(current)) throw new Error(`index table header missing: ${indexPath}`);
|
|
140
371
|
mkdirSync(noteDir, { recursive: true });
|
|
141
372
|
const content = [
|
|
142
|
-
'---', `title:
|
|
373
|
+
'---', `title: ${JSON.stringify(`엘라누스 v${version} — ${oneLine}`)}`,
|
|
143
374
|
`version: ${version}`, `published: ${published.full}`, `cut: ${record.sourceCommit.slice(0, 8)}`,
|
|
144
375
|
'tags: [elanous, release]', 'links:', ` github: https://github.com/${publicRepo}/releases/tag/${record.tag}`,
|
|
145
376
|
` npm: elanous@${version}`, '---', '', `# 엘라누스 v${version} — ${oneLine}`, '',
|
|
146
377
|
'## 한 줄', '', oneLine, '',
|
|
147
378
|
'## 발행 경과', '', `- ${published.full} 발행`, ...history, '',
|
|
379
|
+
'## 이번 판 착지 (직전 판 컷 ~ 이번 판 컷)', '', ...landingSection,
|
|
148
380
|
'## 이제 할 수 있는 것', '', ...abilities, '## 숫자', '',
|
|
149
|
-
`- 변경 ${changeCount(draft)}건`, `- green 칸 ${items.length}개`, '',
|
|
381
|
+
`- 변경 ${changeCount(draft)}건`, `- green 칸 ${items.length}개`, stability, '',
|
|
150
382
|
].join('\n');
|
|
151
383
|
// A handwritten note at the old flat location (e.g. OP's v0.2.13 sample) counts as existing — never duplicate it.
|
|
152
384
|
let existed = existsSync(join(target, `${name}.md`));
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
-
import { checkBrand } from '../brand/check.js';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { BRAND_RULES_UNMEASURED, checkBrand } from '../brand/check.js';
|
|
5
5
|
import { ClaimsLedger } from '../../src/claims/claims-ledger.js';
|
|
6
6
|
import { debug } from '../../src/debug/log.js';
|
|
7
7
|
import { effectiveInstanceRoot } from '../../src/instance/resolve.js';
|
|
8
8
|
import { listChecklist, type ChecklistItem } from '../../src/release-loop/checklist.js';
|
|
9
|
+
import { appendSeatRequestRows, listSeatRequests, withSeatRequestLedgerLock } from '../../src/seat-dispatch/seat-request-ledger.js';
|
|
10
|
+
import { finishNode, readGraphContext, type GraphContext } from '../release-loop/node-verdict.js';
|
|
9
11
|
|
|
10
12
|
const repoRoot = resolve(import.meta.dir, '../..');
|
|
11
13
|
|
|
@@ -16,6 +18,7 @@ type DraftOptions = {
|
|
|
16
18
|
checklistRoot?: string;
|
|
17
19
|
stateDir?: string;
|
|
18
20
|
manualRoot?: string;
|
|
21
|
+
rulesPath?: string;
|
|
19
22
|
};
|
|
20
23
|
|
|
21
24
|
type DraftResult = {
|
|
@@ -27,6 +30,7 @@ type DraftResult = {
|
|
|
27
30
|
claims: number;
|
|
28
31
|
manualCandidates: number;
|
|
29
32
|
brandFindings: number;
|
|
33
|
+
brandUnmeasured?: string;
|
|
30
34
|
files: string[];
|
|
31
35
|
};
|
|
32
36
|
|
|
@@ -126,6 +130,7 @@ export function draftReleaseStory(options: DraftOptions): DraftResult {
|
|
|
126
130
|
const outDir = options.outDir ?? join(effectiveInstanceRoot(), 'release', version, 'story');
|
|
127
131
|
mkdirSync(outDir, { recursive: true });
|
|
128
132
|
let brandFindings = 0;
|
|
133
|
+
let brandUnmeasured: string | undefined;
|
|
129
134
|
const files: string[] = [];
|
|
130
135
|
for (const [name, body, scope] of [
|
|
131
136
|
['announcement.md', announcement, 'release-notes'],
|
|
@@ -134,20 +139,63 @@ export function draftReleaseStory(options: DraftOptions): DraftResult {
|
|
|
134
139
|
] as const) {
|
|
135
140
|
const file = join(outDir, name);
|
|
136
141
|
writeFileSync(file, body);
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
142
|
+
const checked = checkBrand(scope, [file], options.rulesPath);
|
|
143
|
+
if (checked.missing) {
|
|
144
|
+
brandUnmeasured = checked.message ?? BRAND_RULES_UNMEASURED;
|
|
145
|
+
writeFileSync(file, `> ⚠ ${brandUnmeasured}\n\n${body}`);
|
|
146
|
+
} else {
|
|
147
|
+
brandFindings += checked.findings.length;
|
|
148
|
+
if (checked.findings.length) writeFileSync(file, checked.findings.map(({ id, match }) => `> ⚠ 브랜드 규칙: ${id} «${match}»`).join('\n') + '\n\n' + body);
|
|
149
|
+
}
|
|
140
150
|
files.push(file);
|
|
141
151
|
}
|
|
142
152
|
const result: DraftResult = { version, status: 'drafted', userLines: userLines.length, internalDropped, greenCells: green.length,
|
|
143
|
-
claims: evidence.sentences.length, manualCandidates: candidates.length, brandFindings, files };
|
|
153
|
+
claims: evidence.sentences.length, manualCandidates: candidates.length, brandFindings, ...(brandUnmeasured ? { brandUnmeasured } : {}), files };
|
|
144
154
|
debug.log('release.story', 'drafted', { version, userLines: result.userLines, internalDropped, greenCells: result.greenCells,
|
|
145
|
-
claims: result.claims, manualCandidates: result.manualCandidates, brandFindings });
|
|
155
|
+
claims: result.claims, manualCandidates: result.manualCandidates, brandFindings, ...(brandUnmeasured ? { brandUnmeasured } : {}) });
|
|
146
156
|
return result;
|
|
147
157
|
}
|
|
148
158
|
|
|
149
|
-
|
|
159
|
+
export function runReleaseStory(context: GraphContext = readGraphContext(), draft: typeof draftReleaseStory = draftReleaseStory) {
|
|
160
|
+
const version = context.input.version;
|
|
150
161
|
try {
|
|
162
|
+
if (context.outputs.publish?.outcome !== 'ok' || context.outputs.verify?.outcome !== 'ok')
|
|
163
|
+
throw new Error('published release and verification required');
|
|
164
|
+
const result = draft({ version });
|
|
165
|
+
if (result.status === 'drafted') {
|
|
166
|
+
const root = effectiveInstanceRoot();
|
|
167
|
+
const requestsPath = join(root, 'seat-requests', 'requests.jsonl');
|
|
168
|
+
const key = `release-story:${version}`;
|
|
169
|
+
const storyDir = dirname(result.files[0]!);
|
|
170
|
+
withSeatRequestLedgerLock(requestsPath, () => {
|
|
171
|
+
if (listSeatRequests(root).some((row) => row.key === key)) return;
|
|
172
|
+
appendSeatRequestRows(requestsPath, [{ key, seat: 'MK', source: 'release-story', version, status: 'pending',
|
|
173
|
+
text: `${version} 공지 초안 준비됨: ${storyDir}`, queuedAt: new Date().toISOString() }]);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return { outcome: 'ok' as const, verdict: 'pass' as const, summary: result.status,
|
|
177
|
+
files: result.files, status: result.status };
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
180
|
+
debug.log('release.story', 'draft-failed', { version, reason });
|
|
181
|
+
return { outcome: 'fail' as const, verdict: 'fail' as const, summary: `release story failed: ${reason}`, reason };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (import.meta.main) {
|
|
186
|
+
if (process.argv.includes('--graph')) {
|
|
187
|
+
let version = '';
|
|
188
|
+
try {
|
|
189
|
+
if (process.argv.length !== 3) throw new Error('usage: bun scripts/release-story/draft.ts --graph');
|
|
190
|
+
const context = readGraphContext();
|
|
191
|
+
version = context.input.version;
|
|
192
|
+
process.exitCode = finishNode('release-story', version, runReleaseStory(context));
|
|
193
|
+
} catch (error) {
|
|
194
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
195
|
+
debug.log('release.story', 'draft-failed', { version, reason });
|
|
196
|
+
process.exitCode = finishNode('release-story', version, { outcome: 'fail', verdict: 'fail', summary: `release story failed: ${reason}`, reason });
|
|
197
|
+
}
|
|
198
|
+
} else try {
|
|
151
199
|
const args = process.argv.slice(2);
|
|
152
200
|
let version: string | undefined;
|
|
153
201
|
let nextPath: string | undefined;
|
|
@@ -164,7 +212,7 @@ if (import.meta.main) {
|
|
|
164
212
|
if (!version || (args.includes('--next') && !nextPath) || (args.includes('--out') && !outDir))
|
|
165
213
|
throw new Error('usage: bun scripts/release-story/draft.ts --version <v> [--next <path>] [--out <dir>] [--json]');
|
|
166
214
|
const result = draftReleaseStory({ version, ...(nextPath ? { nextPath } : {}), ...(outDir ? { outDir } : {}) });
|
|
167
|
-
console.log(json ? JSON.stringify(result) : result.status === 'drafted' ? `drafted: ${result.files.join(', ')}` : result.status);
|
|
215
|
+
console.log(json ? JSON.stringify(result) : result.status === 'drafted' ? `drafted: ${result.files.join(', ')}${result.brandUnmeasured ? ` · ${result.brandUnmeasured}` : ''}` : result.status);
|
|
168
216
|
} catch (error) {
|
|
169
217
|
console.error(error instanceof Error ? error.message : String(error));
|
|
170
218
|
process.exitCode = 1;
|