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
|
@@ -53,6 +53,9 @@ export type ChangedTsFiles = {
|
|
|
53
53
|
files: Set<string>;
|
|
54
54
|
base: string;
|
|
55
55
|
baseSource: string;
|
|
56
|
+
head?: string;
|
|
57
|
+
diffFiles?: Set<string>;
|
|
58
|
+
warnings?: string[];
|
|
56
59
|
failures?: { command: string; why: string }[];
|
|
57
60
|
};
|
|
58
61
|
|
|
@@ -78,23 +81,41 @@ export function changedTsFiles(run = sh, env: NodeJS.ProcessEnv = process.env):
|
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
const files = new Set<string>();
|
|
82
|
-
const add = (out: string) => {
|
|
83
|
-
for (const line of out.split('\n')) {
|
|
84
|
-
const file = line.trim().replace(/^\.\//, '');
|
|
85
|
-
if (/\.tsx?$/.test(file) && existsSync(file)) files.add(file);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
84
|
const failures: { command: string; why: string }[] = [];
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
// Revision reads are observational: an unreadable HEAD/base must not turn into a collection failure.
|
|
86
|
+
const quietRevision = (command: string): string => { try { return run(command).trim(); } catch { return ''; } };
|
|
87
|
+
const head = quietRevision('git rev-parse HEAD') || undefined;
|
|
88
|
+
const warnings: string[] = [];
|
|
89
|
+
if (head && quietRevision(`git rev-parse ${base}`) === head) {
|
|
90
|
+
const main = quietRevision('git rev-parse origin/main');
|
|
91
|
+
const merged = main && main !== head ? quietRevision('git merge-base HEAD origin/main') : '';
|
|
92
|
+
if (merged && merged !== head) {
|
|
93
|
+
warnings.push(`base=${base} 가 HEAD 와 같은 커밋 — origin/main (${main})과 달라 merge-base 로 재계산`);
|
|
94
|
+
base = merged;
|
|
95
|
+
baseSource = 'recomputed merge-base(origin/main)';
|
|
96
|
+
} else if (main && main !== head) {
|
|
97
|
+
failures.push({ command: `git rev-parse ${base}`, why: `base 가 HEAD(${head}) 와 같아 변경을 셀 수 없다 — TSC_BASE_REF 로 base 를 주라 (못 셌음 ≠ 0)` });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const files = new Set<string>();
|
|
102
|
+
const diffFiles = new Set<string>();
|
|
103
|
+
const commands = [`git diff --name-only ${base}...HEAD`, 'git diff --name-only HEAD', 'git ls-files --others --exclude-standard'];
|
|
104
|
+
for (const command of commands) {
|
|
105
|
+
try {
|
|
106
|
+
for (const line of run(command).split('\n')) {
|
|
107
|
+
const file = line.trim().replace(/^\.\//, '');
|
|
108
|
+
if (!file) continue;
|
|
109
|
+
const ts = /\.tsx?$/.test(file);
|
|
110
|
+
// A deleted .ts is not a compile candidate, so it must not count as «should have been collected».
|
|
111
|
+
if (!ts || existsSync(file)) diffFiles.add(file);
|
|
112
|
+
if (ts && existsSync(file)) files.add(file);
|
|
113
|
+
}
|
|
114
|
+
} catch (cause) {
|
|
91
115
|
failures.push({ command, why: cause instanceof Error ? cause.message : String(cause) });
|
|
92
116
|
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
addCommand('git diff --name-only HEAD');
|
|
96
|
-
addCommand('git ls-files --others --exclude-standard');
|
|
97
|
-
return { files, base, baseSource, failures };
|
|
117
|
+
}
|
|
118
|
+
return { files, base, baseSource, head, diffFiles, warnings, failures };
|
|
98
119
|
}
|
|
99
120
|
|
|
100
121
|
type RequiredExportField = { typeName: string; fieldName: string };
|
|
@@ -850,10 +871,13 @@ export function runGate(io: Partial<GateIo> = {}): void {
|
|
|
850
871
|
? { additions: [] }
|
|
851
872
|
: exportedLiteralUnionMembersInChanges(changed, base));
|
|
852
873
|
const changedResult = getChanged();
|
|
853
|
-
const { files: changed, base, baseSource, failures = [] } = changedResult instanceof Set
|
|
854
|
-
? { files: changedResult, base: 'injected test seam', baseSource: 'injected test seam', failures: [] }
|
|
874
|
+
const { files: changed, base, baseSource, head, diffFiles, warnings = [], failures = [] } = changedResult instanceof Set
|
|
875
|
+
? { files: changedResult, base: 'injected test seam', baseSource: 'injected test seam', head: 'injected test seam', diffFiles: new Set<string>(), warnings: [], failures: [] }
|
|
855
876
|
: changedResult;
|
|
856
|
-
|
|
877
|
+
for (const message of warnings) warn(`[tsc-gate] ⚠ ${message}`);
|
|
878
|
+
const diffCount = diffFiles?.size;
|
|
879
|
+
const diffTsCount = diffFiles && [...diffFiles].filter((file) => /\.tsx?$/.test(file)).length;
|
|
880
|
+
log(`[tsc-gate] base=${base} (${baseSource}); 변경 .ts ${changed.size}개 관측. (head=${head ?? 'unknown'} · 전체 diff 파일 ${diffCount ?? 'unknown'}개 · diff .ts ${diffTsCount ?? 'unknown'}개)`);
|
|
857
881
|
if (failures.length > 0) {
|
|
858
882
|
error('[tsc-gate] ⛔ 변경 파일 수집 실패:');
|
|
859
883
|
for (const { command, why } of failures) error(` ${command}: ${why}`);
|
|
@@ -861,7 +885,15 @@ export function runGate(io: Partial<GateIo> = {}): void {
|
|
|
861
885
|
exit(1);
|
|
862
886
|
return;
|
|
863
887
|
}
|
|
864
|
-
if (changed.size === 0) {
|
|
888
|
+
if (changed.size === 0) {
|
|
889
|
+
if (!(changedResult instanceof Set) && (!head || diffCount === undefined || diffTsCount === undefined || diffTsCount > 0)) {
|
|
890
|
+
error(`[tsc-gate] FAIL — 변경 파일 수집 불일치: 전체 diff 파일 ${diffCount ?? 'unknown'}개 · diff .ts ${diffTsCount ?? 'unknown'}개 · 수집 .ts 0개 (못 셌음 ≠ 0).`);
|
|
891
|
+
exit(1);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
log(`[tsc-gate] PASS — 변경된 .ts 파일 없음 · base=${base} · head=${head} · 전체 diff 파일 ${diffCount}개 · diff .ts ${diffTsCount}개 · 수집 .ts 0개; tsc 실행 없이 통과.`);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
865
897
|
const fieldCheck = requiredExportFields(changed, base);
|
|
866
898
|
const requiredFields = Array.isArray(fieldCheck) ? fieldCheck : fieldCheck.fields;
|
|
867
899
|
if (!Array.isArray(fieldCheck) && fieldCheck.failure) {
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* DOC-GEN① — 그래프 아나토미를 코드에서 생성한다.
|
|
4
|
+
*
|
|
5
|
+
* 원천은 둘뿐이다.
|
|
6
|
+
* 노드 종류 = src/graph-kinds/registry.ts (HARNESS_CORE_KINDS · WORKFLOW_CORE_KINDS · listNodeKinds)
|
|
7
|
+
* 그래프 = graphs/ YAML 을 src/self-implement/graph-yaml.ts 의 loadGraphTemplates · edgeMapOf 가 읽는다
|
|
8
|
+
*
|
|
9
|
+
* renderGraphAnatomy 는 순수하다 — 파일을 쓰지 않고 process.cwd() 를 읽지 않는다.
|
|
10
|
+
* 뿌리·커밋·시각은 인자로만 받는다. main 만 내부 문서 `graph-anatomy` 에 쓴다.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { execFileSync } from 'node:child_process';
|
|
14
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { dirname, join, relative } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
import { listNodeKinds, type GraphKind, type NodeKindEntry } from '../../src/graph-kinds/registry.js';
|
|
19
|
+
import {
|
|
20
|
+
edgeMapOf,
|
|
21
|
+
loadGraphTemplates,
|
|
22
|
+
parseGraphTemplateYaml,
|
|
23
|
+
type GraphLoadResult,
|
|
24
|
+
type GraphParseIssue,
|
|
25
|
+
type GraphTemplateSpec,
|
|
26
|
+
} from '../../src/self-implement/graph-yaml.js';
|
|
27
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
28
|
+
|
|
29
|
+
/** drafts/ · overlays/ 는 스캔에서 뺀다(실측 10-05). 그 이름인 디렉터리와 그 아래는 안 읽는다. */
|
|
30
|
+
const EXCLUDED_DIR_NAMES = new Set(['drafts', 'overlays']);
|
|
31
|
+
|
|
32
|
+
export interface RenderGraphAnatomyInput {
|
|
33
|
+
readonly graphsRoot: string;
|
|
34
|
+
readonly commit: string;
|
|
35
|
+
readonly generatedAt: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ScannedGraphDir {
|
|
39
|
+
/** graphsRoot 기준 상대 경로. 뿌리 자신은 '.' */
|
|
40
|
+
readonly dir: string;
|
|
41
|
+
readonly result: GraphLoadResult;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function cell(value: string): string {
|
|
45
|
+
return value.replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function edgeCountOf(template: GraphTemplateSpec): number {
|
|
49
|
+
return Object.values(edgeMapOf(template)).reduce((sum, targets) => sum + targets.length, 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function edgeLinesOf(template: GraphTemplateSpec): string[] {
|
|
53
|
+
const lines: string[] = [];
|
|
54
|
+
for (const edge of template.edges) {
|
|
55
|
+
if (edge.to !== undefined) lines.push(`${edge.from} → ${edge.to}`);
|
|
56
|
+
for (const [key, target] of Object.entries(edge.map ?? {})) lines.push(`${edge.from} → ${target} (${key})`);
|
|
57
|
+
for (const fallback of edge.fallback ?? []) lines.push(`${edge.from} → ${fallback.node} (fallback)`);
|
|
58
|
+
}
|
|
59
|
+
return lines;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A directory with recipes.yaml holds workflow graphs run by `elanous graph run` (src/graph-runner/runner.ts), not
|
|
64
|
+
* harness templates. The runner gives a recipe-less node (done · failed) `recipe: 'none'` before the shared parser
|
|
65
|
+
* (runner.ts «command-less node … represented internally as `none`»); read those graphs the same way, and do not
|
|
66
|
+
* read recipes.yaml itself as a graph.
|
|
67
|
+
*/
|
|
68
|
+
function isRunnerDir(abs: string): boolean { return existsSync(join(abs, 'recipes.yaml')); }
|
|
69
|
+
|
|
70
|
+
function parseLikeRunner(source: string, label: string): ReturnType<typeof parseGraphTemplateYaml> {
|
|
71
|
+
let raw: unknown;
|
|
72
|
+
try { raw = parseYaml(source); } catch { return parseGraphTemplateYaml(source, label); }
|
|
73
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return parseGraphTemplateYaml(source, label);
|
|
74
|
+
const document = raw as Record<string, unknown>;
|
|
75
|
+
if (Array.isArray(document.nodes)) {
|
|
76
|
+
document.nodes = document.nodes.map((node: unknown) => node && typeof node === 'object' && !Array.isArray(node) && !('recipe' in node)
|
|
77
|
+
? { ...node, recipe: 'none' } : node);
|
|
78
|
+
}
|
|
79
|
+
return parseGraphTemplateYaml(stringifyYaml(document), label);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function graphFileNames(abs: string): string[] {
|
|
83
|
+
const runner = isRunnerDir(abs);
|
|
84
|
+
return readdirSync(abs).filter((name) => (name.endsWith('.yaml') || name.endsWith('.yml')) && !(runner && name === 'recipes.yaml')).sort();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** loadGraphTemplates for harness dirs; the runner's reading for workflow-graph dirs (same result shape). */
|
|
88
|
+
function loadDir(abs: string): GraphLoadResult {
|
|
89
|
+
if (!isRunnerDir(abs)) return loadGraphTemplates(abs);
|
|
90
|
+
const errors: GraphParseIssue[] = [];
|
|
91
|
+
const warnings: GraphParseIssue[] = [];
|
|
92
|
+
const templates: Record<string, GraphTemplateSpec> = {};
|
|
93
|
+
let files: string[];
|
|
94
|
+
try { files = graphFileNames(abs); }
|
|
95
|
+
catch (error) { return { templates, errors: [{ path: abs, message: `그래프 디렉토리를 못 읽었다: ${String(error)}` }], warnings, scannedFiles: 0 }; }
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
let source: string;
|
|
98
|
+
try { source = readFileSync(join(abs, file), 'utf8'); }
|
|
99
|
+
catch (error) { errors.push({ path: file, message: `읽기 실패: ${String(error)}` }); continue; }
|
|
100
|
+
const result = parseLikeRunner(source, file);
|
|
101
|
+
errors.push(...result.errors);
|
|
102
|
+
warnings.push(...result.warnings);
|
|
103
|
+
if (!result.template) continue;
|
|
104
|
+
if (templates[result.template.graphId]) { errors.push({ path: file, message: `graph_id '${result.template.graphId}' 가 중복이다` }); continue; }
|
|
105
|
+
templates[result.template.graphId] = result.template;
|
|
106
|
+
}
|
|
107
|
+
return { templates, errors, warnings, scannedFiles: files.length };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 뿌리와 하위 디렉터리를 돈다. drafts/ · overlays/ 와 그 아래는 제외. 디렉터리마다 loadGraphTemplates 를 그대로 부른다. */
|
|
111
|
+
export function scanGraphDirs(graphsRoot: string): ScannedGraphDir[] {
|
|
112
|
+
const scanned: ScannedGraphDir[] = [];
|
|
113
|
+
const visit = (dir: string): void => {
|
|
114
|
+
scanned.push({ dir: relative(graphsRoot, dir) || '.', result: loadDir(dir) });
|
|
115
|
+
let names: string[];
|
|
116
|
+
try {
|
|
117
|
+
names = readdirSync(dir, { withFileTypes: true })
|
|
118
|
+
.filter((entry) => entry.isDirectory() && !EXCLUDED_DIR_NAMES.has(entry.name))
|
|
119
|
+
.map((entry) => entry.name)
|
|
120
|
+
.sort();
|
|
121
|
+
} catch {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
for (const name of names) visit(join(dir, name));
|
|
125
|
+
};
|
|
126
|
+
visit(graphsRoot);
|
|
127
|
+
return scanned;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* loadGraphTemplates 는 파일명을 템플릿에 안 남긴다.
|
|
132
|
+
* 같은 디렉터리 yaml 을 parseGraphTemplateYaml 로 한 번씩 읽어 graph_id → 상대 경로를 붙인다.
|
|
133
|
+
* 파싱에 실패한 파일은 표에 안 들어가므로 맵에도 안 넣는다.
|
|
134
|
+
*/
|
|
135
|
+
/** 그 디렉터리에서 loadGraphTemplates 가 센 yaml 이름. 못 읽으면 빈 목록. */
|
|
136
|
+
function yamlNames(graphsRoot: string, dir: string): readonly string[] {
|
|
137
|
+
const abs = dir === '.' ? graphsRoot : join(graphsRoot, dir);
|
|
138
|
+
try {
|
|
139
|
+
return graphFileNames(abs);
|
|
140
|
+
} catch {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function graphIdPaths(graphsRoot: string, dir: string): ReadonlyMap<string, string> {
|
|
146
|
+
const abs = dir === '.' ? graphsRoot : join(graphsRoot, dir);
|
|
147
|
+
const prefix = dir === '.' ? '' : `${dir}/`;
|
|
148
|
+
const paths = new Map<string, string>();
|
|
149
|
+
let names: string[];
|
|
150
|
+
try {
|
|
151
|
+
names = graphFileNames(abs);
|
|
152
|
+
} catch {
|
|
153
|
+
return paths;
|
|
154
|
+
}
|
|
155
|
+
for (const name of names) {
|
|
156
|
+
let source: string;
|
|
157
|
+
try { source = readFileSync(join(abs, name), 'utf8'); }
|
|
158
|
+
catch { continue; }
|
|
159
|
+
const parsed = isRunnerDir(abs) ? parseLikeRunner(source, name) : parseGraphTemplateYaml(source, name);
|
|
160
|
+
if (!parsed.template) continue;
|
|
161
|
+
const rel = `${prefix}${name}`;
|
|
162
|
+
const prev = paths.get(parsed.template.graphId);
|
|
163
|
+
paths.set(parsed.template.graphId, prev === undefined ? rel : `${prev}, ${rel}`);
|
|
164
|
+
}
|
|
165
|
+
return paths;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function kindRows(entries: readonly NodeKindEntry[]): string[] {
|
|
169
|
+
return [...entries]
|
|
170
|
+
.sort((a, b) => (a.graph < b.graph ? -1 : a.graph > b.graph ? 1 : a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0))
|
|
171
|
+
.map((entry) => `| ${cell(entry.graph)} | ${cell(entry.kind)} | ${entry.core ? 'core' : 'plugin'} |`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function issueLines(label: string, issues: readonly GraphParseIssue[]): string[] {
|
|
175
|
+
if (issues.length === 0) return [`- ${label}: 0`];
|
|
176
|
+
return [`- ${label}: ${issues.length}`, ...issues.map((issue) => ` - ${cell(issue.path)} — ${issue.message}`)];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 마크다운 한 장. 파일을 쓰지 않는다. graphsRoot · commit · generatedAt 만 읽는다.
|
|
181
|
+
*/
|
|
182
|
+
export function renderGraphAnatomy(input: RenderGraphAnatomyInput): string {
|
|
183
|
+
const scanned = scanGraphDirs(input.graphsRoot);
|
|
184
|
+
const templates: { dir: string; fileLabel: string; template: GraphTemplateSpec }[] = [];
|
|
185
|
+
for (const { dir, result } of scanned) {
|
|
186
|
+
for (const template of Object.values(result.templates)) {
|
|
187
|
+
templates.push({ dir, fileLabel: dir === '.' ? `${template.graphId}` : `${dir}/${template.graphId}`, template });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
templates.sort((a, b) => (a.template.graphId < b.template.graphId ? -1 : a.template.graphId > b.template.graphId ? 1 : a.dir < b.dir ? -1 : 1));
|
|
191
|
+
|
|
192
|
+
const scannedFiles = scanned.reduce((sum, entry) => sum + entry.result.scannedFiles, 0);
|
|
193
|
+
const missingDirs = scanned.filter((entry) =>
|
|
194
|
+
entry.result.scannedFiles === 0
|
|
195
|
+
&& entry.result.errors.some((issue) => issue.message.startsWith('그래프 디렉토리를 못 읽었다')),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const lines: string[] = [
|
|
199
|
+
'# Graph anatomy',
|
|
200
|
+
'',
|
|
201
|
+
'> 이 문서는 생성물이다 — 손으로 고치지 마라',
|
|
202
|
+
'',
|
|
203
|
+
`- generatedAt: ${input.generatedAt}`,
|
|
204
|
+
`- commit: ${input.commit}`,
|
|
205
|
+
`- graphsRoot: (인자)`,
|
|
206
|
+
`- scannedFiles: ${scannedFiles}`,
|
|
207
|
+
`- scannedDirs: ${scanned.length}`,
|
|
208
|
+
`- templates: ${templates.length}`,
|
|
209
|
+
'',
|
|
210
|
+
'## 표 1 노드 종류',
|
|
211
|
+
'',
|
|
212
|
+
'원천: `src/graph-kinds/registry.ts` — `HARNESS_CORE_KINDS` · `WORKFLOW_CORE_KINDS` · `listNodeKinds()`.',
|
|
213
|
+
'',
|
|
214
|
+
'| graph | kind | core/plugin |',
|
|
215
|
+
'| --- | --- | --- |',
|
|
216
|
+
...kindRows(listNodeKinds()),
|
|
217
|
+
'',
|
|
218
|
+
'## 표 2 그래프',
|
|
219
|
+
'',
|
|
220
|
+
'원천: `loadGraphTemplates(dir)` ⊕ `edgeMapOf(template)`. 간선 수는 edgeMapOf 합(to · map · fallback, 중복 목적지는 한 번). `drafts/` · `overlays/` 는 제외.',
|
|
221
|
+
'',
|
|
222
|
+
'| 파일 경로 | graph_id | 노드 수 | 간선 수 | entryNode | terminalNodes |',
|
|
223
|
+
'| --- | --- | --- | --- | --- | --- |',
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
if (templates.length === 0) {
|
|
227
|
+
lines.push('| (없음) | — | 0 | 0 | — | — |');
|
|
228
|
+
}
|
|
229
|
+
const pathCache = new Map<string, ReadonlyMap<string, string>>();
|
|
230
|
+
for (const { dir, template } of templates) {
|
|
231
|
+
let paths = pathCache.get(dir);
|
|
232
|
+
if (paths === undefined) {
|
|
233
|
+
paths = graphIdPaths(input.graphsRoot, dir);
|
|
234
|
+
pathCache.set(dir, paths);
|
|
235
|
+
}
|
|
236
|
+
const where = paths.get(template.graphId) ?? (dir === '.' ? `${template.graphId}.yaml` : `${dir}/`);
|
|
237
|
+
lines.push(`| ${cell(where)} | ${cell(template.graphId)} | ${template.nodes.length} | ${edgeCountOf(template)} | ${cell(template.entryNode)} | ${cell(template.terminalNodes.join(', '))} |`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
lines.push('', '## 그래프별 노드 · 간선', '');
|
|
241
|
+
if (templates.length === 0) lines.push('(읽힌 그래프 없음)', '');
|
|
242
|
+
for (const { dir, template } of templates) {
|
|
243
|
+
const where = dir === '.' ? '(graphs root)' : dir;
|
|
244
|
+
lines.push(`### ${template.graphId}`, '', `- path: ${where}`, '', '#### 노드', '', '| nodeId | kind |', '| --- | --- |');
|
|
245
|
+
for (const node of template.nodes) lines.push(`| ${cell(node.nodeId)} | ${cell(node.kind)} |`);
|
|
246
|
+
lines.push('', '#### 간선', '');
|
|
247
|
+
const edges = edgeLinesOf(template);
|
|
248
|
+
if (edges.length === 0) lines.push('(간선 없음)');
|
|
249
|
+
else for (const edge of edges) lines.push(`- ${edge}`);
|
|
250
|
+
lines.push('');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
lines.push('## 스캔한 파일', '');
|
|
254
|
+
lines.push('loadGraphTemplates 가 센 yaml. 파싱에 실패한 파일은 표 2에 없고 여기와 오류 절에만 있다.', '');
|
|
255
|
+
lines.push('| 디렉터리 | 파일 |', '| --- | --- |');
|
|
256
|
+
for (const { dir } of scanned) {
|
|
257
|
+
const names = yamlNames(input.graphsRoot, dir);
|
|
258
|
+
const prefix = dir === '.' ? '' : `${dir}/`;
|
|
259
|
+
if (names.length === 0) {
|
|
260
|
+
lines.push(`| ${cell(dir === '.' ? '(graphs root)' : dir)} | (yaml 0) |`);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
for (const name of names) lines.push(`| ${cell(dir === '.' ? '(graphs root)' : dir)} | ${cell(`${prefix}${name}`)} |`);
|
|
264
|
+
}
|
|
265
|
+
lines.push('', '## 로드 오류 · 경고 · scannedFiles', '');
|
|
266
|
+
lines.push(`scannedFiles 합: ${scannedFiles}`, '');
|
|
267
|
+
if (missingDirs.length > 0) {
|
|
268
|
+
lines.push('디렉토리 없음 (0개 읽음과 구분):', '');
|
|
269
|
+
for (const entry of missingDirs) {
|
|
270
|
+
for (const issue of entry.result.errors) lines.push(`- ${cell(issue.path)} — ${issue.message}`);
|
|
271
|
+
}
|
|
272
|
+
lines.push('');
|
|
273
|
+
}
|
|
274
|
+
for (const { dir, result } of scanned) {
|
|
275
|
+
const label = dir === '.' ? '(graphs root)' : dir;
|
|
276
|
+
const unreadable = result.scannedFiles === 0 && result.errors.some((issue) => issue.message.startsWith('그래프 디렉토리를 못 읽었다'));
|
|
277
|
+
lines.push(`### ${label}`, '');
|
|
278
|
+
lines.push(`- scannedFiles: ${result.scannedFiles}${unreadable ? ' (디렉토리 없음)' : result.scannedFiles === 0 ? ' (0개 읽음)' : ''}`);
|
|
279
|
+
lines.push(...issueLines('errors', result.errors));
|
|
280
|
+
lines.push(...issueLines('warnings', result.warnings));
|
|
281
|
+
lines.push('');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const axes: GraphKind[] = ['harness', 'workflow'];
|
|
285
|
+
lines.push('## 축별 종류 수', '');
|
|
286
|
+
for (const axis of axes) lines.push(`- ${axis}: ${listNodeKinds(axis).length}`);
|
|
287
|
+
lines.push('');
|
|
288
|
+
return `${lines.join('\n').replace(/\n{3,}/g, '\n\n')}\n`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function repoRootFromHere(): string {
|
|
292
|
+
return join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function readCommit(repoRoot: string): string {
|
|
296
|
+
return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' }).trim();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function defaultOutputPath(repoRoot: string): string {
|
|
300
|
+
return join(repoRoot, 'docs', 'generated', 'graph-anatomy.md');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function main(): void {
|
|
304
|
+
const repoRoot = repoRootFromHere();
|
|
305
|
+
const graphsRoot = join(repoRoot, 'graphs');
|
|
306
|
+
const generatedAt = new Date().toISOString();
|
|
307
|
+
const commit = readCommit(repoRoot);
|
|
308
|
+
const markdown = renderGraphAnatomy({ graphsRoot, commit, generatedAt });
|
|
309
|
+
const out = defaultOutputPath(repoRoot);
|
|
310
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
311
|
+
writeFileSync(out, markdown);
|
|
312
|
+
process.stdout.write(`${out}\n`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (import.meta.main) main();
|
|
@@ -5,7 +5,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
5
5
|
import { join, relative, resolve, sep } from 'node:path';
|
|
6
6
|
import { stringify } from 'yaml';
|
|
7
7
|
import { debug } from '../src/debug/log.js';
|
|
8
|
-
import { checkBrand } from './brand/check.js';
|
|
8
|
+
import { BRAND_RULES_UNMEASURED, checkBrand } from './brand/check.js';
|
|
9
9
|
import { checkCommands, extractElanousCommands, type HelpRunner } from './docs-cli-check.js';
|
|
10
10
|
import { scanLeaks } from './public-export.js';
|
|
11
11
|
|
|
@@ -25,7 +25,7 @@ function measure(run: () => Criterion): Criterion {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** Files are repository-relative paths (or absolute paths within root); the ledger is never read or written. */
|
|
28
|
-
export function suggestExposeRubric(root: string, files?: readonly string[], help?: HelpRunner): Suggestion[] {
|
|
28
|
+
export function suggestExposeRubric(root: string, files?: readonly string[], help?: HelpRunner, rulesPath?: string): Suggestion[] {
|
|
29
29
|
const names = files ?? readdirSync(join(root, DOCS)).filter((name) => name.endsWith('.md')).map((name) => `${DOCS}/${name}`);
|
|
30
30
|
const paths = [...new Set(names.map((name) => relative(root, resolve(root, name)).split(sep).join('/')))];
|
|
31
31
|
if (paths.some((path) => !/^release\/public\/docs\/[^/]+\.md$/.test(path))) throw new Error('문서 경로는 release/public/docs/*.md 이어야 합니다');
|
|
@@ -77,6 +77,15 @@ export function suggestExposeRubric(root: string, files?: readonly string[], hel
|
|
|
77
77
|
const failed = unmeasured(error);
|
|
78
78
|
return { path, criteria: { reproducible: failed, confidential: failed, brand: failed } };
|
|
79
79
|
}
|
|
80
|
+
const brand = measure(() => {
|
|
81
|
+
const result = checkBrand('public-docs', [absolute], rulesPath);
|
|
82
|
+
if (result.missing) return { status: 'n/a', reason: result.message ?? BRAND_RULES_UNMEASURED };
|
|
83
|
+
if (result.files !== 1 || result.rules === 0) return unmeasured('브랜드 규칙 없음 또는 파일 검사 실패');
|
|
84
|
+
const ids = [...new Set(result.findings.map((finding) => finding.id))].slice(0, 3);
|
|
85
|
+
return ids.length
|
|
86
|
+
? { status: 'fail', reason: `브랜드 규칙: ${ids.join(', ')}` }
|
|
87
|
+
: { status: 'pass', reason: '브랜드 규칙 위반 없음' };
|
|
88
|
+
});
|
|
80
89
|
const reproducible = measure(() => {
|
|
81
90
|
const refs = extractElanousCommands(path, text);
|
|
82
91
|
if (!refs.length) return { status: 'n/a', reason: '문서에 elanous 명령 없음' };
|
|
@@ -95,14 +104,7 @@ export function suggestExposeRubric(root: string, files?: readonly string[], hel
|
|
|
95
104
|
? { status: 'fail', reason: `누출 표식: ${markers.join(', ')}` }
|
|
96
105
|
: { status: 'pass', reason: '누출 표식 없음' };
|
|
97
106
|
});
|
|
98
|
-
|
|
99
|
-
const result = checkBrand('public-docs', [absolute]);
|
|
100
|
-
if (result.missing || result.files !== 1 || result.rules === 0) return unmeasured('브랜드 규칙 없음 또는 파일 검사 실패');
|
|
101
|
-
const ids = [...new Set(result.findings.map((finding) => finding.id))].slice(0, 3);
|
|
102
|
-
return ids.length
|
|
103
|
-
? { status: 'fail', reason: `브랜드 규칙: ${ids.join(', ')}` }
|
|
104
|
-
: { status: 'pass', reason: '브랜드 규칙 위반 없음' };
|
|
105
|
-
});
|
|
107
|
+
// 브랜드 규칙이 없으면 브랜드만 «측정 불가» — 따로 잰 재현성·누출 결과는 덮어쓰지 않는다.
|
|
106
108
|
return { path, criteria: { reproducible, confidential, brand } };
|
|
107
109
|
});
|
|
108
110
|
const statuses = results.flatMap((item) => KEYS.map((key) => item.criteria[key].status));
|
|
@@ -119,19 +121,23 @@ export function run(argv: readonly string[], root = process.cwd(), help?: HelpRu
|
|
|
119
121
|
const json = argv.includes('--json');
|
|
120
122
|
let files: string[] | undefined;
|
|
121
123
|
let out: string | undefined;
|
|
124
|
+
let rulesPath: string | undefined;
|
|
122
125
|
for (let i = 0; i < argv.length; i++) {
|
|
123
126
|
const arg = argv[i]!;
|
|
124
127
|
if (arg === '--json') continue;
|
|
125
128
|
if (arg === '--yaml-patch') {
|
|
126
129
|
out = argv[++i];
|
|
127
130
|
if (!out || out.startsWith('--')) throw new Error('--yaml-patch 에 출력 경로가 필요합니다');
|
|
131
|
+
} else if (arg === '--rules') {
|
|
132
|
+
rulesPath = argv[++i];
|
|
133
|
+
if (!rulesPath || rulesPath.startsWith('--')) throw new Error('--rules 에 규칙 경로가 필요합니다');
|
|
128
134
|
} else if (arg === '--files') {
|
|
129
135
|
files = [];
|
|
130
136
|
while (argv[i + 1] && !argv[i + 1]!.startsWith('--')) files.push(argv[++i]!);
|
|
131
137
|
if (!files.length) throw new Error('--files 에 문서 경로가 필요합니다');
|
|
132
138
|
} else throw new Error(`알 수 없는 옵션: ${arg}`);
|
|
133
139
|
}
|
|
134
|
-
const results = suggestExposeRubric(root, files, help);
|
|
140
|
+
const results = suggestExposeRubric(root, files, help, rulesPath);
|
|
135
141
|
if (out) {
|
|
136
142
|
const target = resolve(root, out);
|
|
137
143
|
const ledger = resolve(root, LEDGER);
|
|
@@ -140,7 +146,7 @@ export function run(argv: readonly string[], root = process.cwd(), help?: HelpRu
|
|
|
140
146
|
}
|
|
141
147
|
if (json) console.log(JSON.stringify(results));
|
|
142
148
|
else {
|
|
143
|
-
for (const { path, criteria } of results) console.log(`${path} · ${KEYS.map((key) => `${key} ${criteria[key].status}${criteria[key].status === 'fail' ? `(${criteria[key].reason.split(': ').slice(1).join(': ') || criteria[key].reason})` : ''}`).join(' · ')}`);
|
|
149
|
+
for (const { path, criteria } of results) console.log(`${path} · ${KEYS.map((key) => `${key} ${criteria[key].status}${criteria[key].status === 'fail' ? `(${criteria[key].reason.split(': ').slice(1).join(': ') || criteria[key].reason})` : criteria[key].reason === BRAND_RULES_UNMEASURED ? `(${criteria[key].reason})` : ''}`).join(' · ')}`);
|
|
144
150
|
const statuses = results.flatMap((item) => KEYS.map((key) => item.criteria[key].status));
|
|
145
151
|
console.log(`합계 문서 ${results.length} · pass ${statuses.filter((status) => status === 'pass').length} · fail ${statuses.filter((status) => status === 'fail').length} · n/a ${statuses.filter((status) => status === 'n/a').length} · maturity/value/evidence/support 사람 판정 (MK)`);
|
|
146
152
|
}
|
|
@@ -5,6 +5,7 @@ import { dirname, extname, isAbsolute, join, resolve } from 'node:path';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { homedir } from 'node:os';
|
|
7
7
|
import { debug } from '../../src/debug/log.js';
|
|
8
|
+
import { runOcr } from '../../skills/omni-digest/src/ocr.js';
|
|
8
9
|
import { streamLLM } from '../../src/llm.js';
|
|
9
10
|
import { unified } from 'unified';
|
|
10
11
|
import remarkParse from 'remark-parse';
|
|
@@ -19,6 +20,7 @@ export interface Counts { inputs: number; diagrams: number; rendered: number; ht
|
|
|
19
20
|
export interface StageResult extends Counts { outcome: 'ok' | 'skipped' }
|
|
20
21
|
export interface StageOptions {
|
|
21
22
|
llm?: (prompt: string) => Promise<string>;
|
|
23
|
+
ocr?: (filePath: string) => Promise<string>;
|
|
22
24
|
toolsPath?: string;
|
|
23
25
|
referenceRoot?: string;
|
|
24
26
|
}
|
|
@@ -62,9 +64,9 @@ function binary(name: string, path = process.env.PATH ?? ''): string | undefined
|
|
|
62
64
|
return undefined;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
function run(bin: string, args: string[], dir: string, timeout: number): ReturnType<typeof spawnSync> {
|
|
67
|
+
function run(bin: string, args: string[], dir: string, timeout: number, cacheDir = dir): ReturnType<typeof spawnSync> {
|
|
66
68
|
return spawnSync(bin, args, { cwd: dir, encoding: 'utf8', timeout, maxBuffer: 20 * 1024 * 1024,
|
|
67
|
-
env: { ...process.env, HOME: dir, XDG_CACHE_HOME: dir, XDG_CONFIG_HOME: dir, UV_CACHE_DIR:
|
|
69
|
+
env: { ...process.env, HOME: dir, XDG_CACHE_HOME: dir, XDG_CONFIG_HOME: dir, UV_CACHE_DIR: cacheDir, PYTHONDONTWRITEBYTECODE: '1' } });
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
function readInputs(dir: string): Input[] {
|
|
@@ -83,7 +85,7 @@ function counts(dir: string): Counts {
|
|
|
83
85
|
return { inputs, diagrams, rendered, html: existsSync(join(dir, 'notes.html')) ? 'yes' : 'no', pdf: existsSync(join(dir, 'notes.pdf')) ? 'yes' : 'skipped' };
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
export function collect(folder: string, options: StageOptions = {}): StageResult {
|
|
88
|
+
export async function collect(folder: string, options: StageOptions = {}): Promise<StageResult> {
|
|
87
89
|
const dir = outputDir(folder);
|
|
88
90
|
const files: Input[] = [];
|
|
89
91
|
const pdfText = binary('pdftotext', options.toolsPath);
|
|
@@ -94,11 +96,22 @@ export function collect(folder: string, options: StageOptions = {}): StageResult
|
|
|
94
96
|
const kind = kinds[extname(entry.name).toLowerCase()];
|
|
95
97
|
if (!entry.isFile() || !kind) continue;
|
|
96
98
|
const at = join(base, entry.name);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
let text: string;
|
|
100
|
+
if (kind === 'markdown') text = readFileSync(at, 'utf8');
|
|
101
|
+
else if (kind === 'image') {
|
|
102
|
+
try {
|
|
103
|
+
const extracted = await (options.ocr ?? runOcr)(at);
|
|
104
|
+
if (!extracted.trim()) throw new Error('Upstage/OCR.space 폴백 결과가 비었다');
|
|
105
|
+
text = extracted;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
108
|
+
text = `이미지 텍스트 못 읽음 · ${reason}`;
|
|
109
|
+
debug.log('lecture-note.collect', 'ocr-failed', { name: prefix + entry.name, reason });
|
|
110
|
+
}
|
|
111
|
+
} else if (pdfText) {
|
|
112
|
+
const result = run(pdfText, ['-layout', at, '-'], dir, 60000);
|
|
113
|
+
text = result.status === 0 ? String(result.stdout) : 'PDF 텍스트 없음 — pdftotext 추출 실패';
|
|
114
|
+
} else text = 'PDF 텍스트 없음 — pdftotext 없음';
|
|
102
115
|
files.push({ name: prefix + entry.name, kind, text });
|
|
103
116
|
}
|
|
104
117
|
writeFileSync(artifact(dir, 'inputs.json'), JSON.stringify(files, null, 2) + '\n');
|
|
@@ -131,19 +144,26 @@ export function render(folder: string, options: StageOptions = {}): StageResult
|
|
|
131
144
|
const renderer = join(options.referenceRoot ?? ownRoot, 'diagram-master/references/engines/mermaid/render_mermaid.py');
|
|
132
145
|
const uv = binary('uv', options.toolsPath);
|
|
133
146
|
let unavailable = 0;
|
|
147
|
+
let failed = 0;
|
|
134
148
|
for (const [i, match] of [...notes.matchAll(mermaid)].entries()) {
|
|
135
149
|
const png = artifact(dir, `diagram-${i + 1}.png`);
|
|
136
150
|
if (existsSync(png)) rmSync(png);
|
|
137
151
|
if (!uv || !existsSync(renderer)) { unavailable++; continue; }
|
|
138
152
|
const mmd = artifact(dir, `diagram-${i + 1}.mmd`);
|
|
139
153
|
writeFileSync(mmd, match[1]!);
|
|
140
|
-
const
|
|
154
|
+
const cacheDir = join(process.env.ELANOUS_STATE_DIR ?? join(homedir(), '.elanous'), 'lecture-note', 'uv-cache');
|
|
155
|
+
const result = run(uv, ['run', 'python', renderer, mmd, '--output', png], dir, 120000, cacheDir);
|
|
141
156
|
if (result.status !== 0 || !existsSync(png)) {
|
|
142
157
|
if (existsSync(png)) rmSync(png);
|
|
143
|
-
|
|
158
|
+
failed++;
|
|
159
|
+
const detail = String(result.stderr ?? '').split(/\r?\n/)[0]?.trim() || result.error?.message ||
|
|
160
|
+
String(result.stdout ?? '').split(/\r?\n/)[0]?.trim() || 'PNG 없음';
|
|
161
|
+
const reason = `Mermaid 렌더 실패 (diagram-${i + 1}, exit=${result.status}): ${detail}`;
|
|
162
|
+
debug.log('lecture-note.render', 'diagram-failed', { name: `diagram-${i + 1}`, reason });
|
|
144
163
|
}
|
|
145
164
|
}
|
|
146
165
|
if (unavailable) console.log(`렌더러 없음: ${unavailable}`);
|
|
166
|
+
if (failed) console.log(`못 그림: ${failed}`);
|
|
147
167
|
return { ...counts(dir), outcome: 'ok' };
|
|
148
168
|
}
|
|
149
169
|
|
|
@@ -216,7 +236,7 @@ export function pdf(folder: string, options: StageOptions = {}): StageResult {
|
|
|
216
236
|
}
|
|
217
237
|
|
|
218
238
|
export async function runStage(stage: Stage, folder: string, options: StageOptions = {}): Promise<StageResult> {
|
|
219
|
-
const result = stage === 'collect' ? collect(folder, options) : stage === 'draft' ? await draft(folder, options) :
|
|
239
|
+
const result = stage === 'collect' ? await collect(folder, options) : stage === 'draft' ? await draft(folder, options) :
|
|
220
240
|
stage === 'render' ? render(folder, options) : stage === 'html' ? html(folder, options) : pdf(folder, options);
|
|
221
241
|
debug.log('lecture.note', stage, { inputs: result.inputs, diagrams: result.diagrams, rendered: result.rendered, pdf: result.pdf });
|
|
222
242
|
return result;
|
|
@@ -29,14 +29,41 @@ function inlineText(value: string): string {
|
|
|
29
29
|
www ? 'w\u200bww.' : `${scheme ?? at}\u200b${slashes ?? ''}`);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function displayId(id: string): string {
|
|
33
|
+
return id.replace(/(-(?:\d{4}-\d{2}-\d{2}|undated))-[a-f\d]{64}$/i, '$1');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function summaryText(value: string): string {
|
|
37
|
+
// 원장 적재 때 줄바꿈이 사라져 «| a | b | |---|---| | 1 | 2 |» 처럼 한 줄로 뭉개진 표는 행 경계 «| |»에서 다시 줄로 편다.
|
|
38
|
+
const lines = value.split(/\r?\n/)
|
|
39
|
+
.flatMap(line => /\|\s*:?-{3,}:?\s*\|/.test(line) && /\|\s+\|/.test(line) ? line.replace(/\|\s+\|/g, '|\n|').split('\n') : [line])
|
|
40
|
+
.map(line => line.trim()).filter(Boolean);
|
|
41
|
+
const cells = (line: string) => line.replace(/^\||\|$/g, '').split('|').map(cell => cell.trim());
|
|
42
|
+
const findTable = () => lines.findIndex((line, index) => {
|
|
43
|
+
if (!line.includes('|') || !lines[index + 1]) return false;
|
|
44
|
+
const separator = cells(lines[index + 1]);
|
|
45
|
+
return separator.length > 1 && separator.every(cell => /^:?-{3,}:?$/.test(cell));
|
|
46
|
+
});
|
|
47
|
+
// 표가 여럿이면 모두 «첫 데이터 행 한 줄»로 접는다 — 뒤 표의 구분선·이스케이프가 남지 않게.
|
|
48
|
+
for (let tableAt = findTable(); tableAt >= 0; tableAt = findTable()) {
|
|
49
|
+
const row = lines[tableAt + 2]?.includes('|') ? lines[tableAt + 2] : lines[tableAt];
|
|
50
|
+
const summary = cells(row).join(' · ');
|
|
51
|
+
let after = tableAt + 2;
|
|
52
|
+
while (after < lines.length && lines[after].includes('|')) after++;
|
|
53
|
+
lines.splice(tableAt, after - tableAt, summary);
|
|
54
|
+
}
|
|
55
|
+
const text = lines.join(' ').replace(/\*\*|__/g, '').replace(/\s+/g, ' ').trim();
|
|
56
|
+
return inlineText(text.length > 200 ? `${text.slice(0, 200)}…` : text);
|
|
57
|
+
}
|
|
58
|
+
|
|
32
59
|
function section(title: string, rows: LessonRow[], ledger: LessonLedger, kind: LessonStatus): string[] {
|
|
33
60
|
const lines = [`## ${title}`, ''];
|
|
34
61
|
if (!rows.length) return [...lines, '없음', ''];
|
|
35
62
|
for (const row of rows) {
|
|
36
63
|
const last = ledger.get(row.id).occurrences.at(-1);
|
|
37
|
-
lines.push(`### ${inlineText(row.id)} — ${inlineText(row.incident)}`,
|
|
38
|
-
`- 원인: ${
|
|
39
|
-
`- 처방: ${
|
|
64
|
+
lines.push(`### ${inlineText(displayId(row.id))} — ${inlineText(row.incident)}`,
|
|
65
|
+
`- 원인: ${summaryText(row.cause)}`,
|
|
66
|
+
`- 처방: ${summaryText(row.remedy)}`,
|
|
40
67
|
`- 재발: ${row.occurrence_count}회`,
|
|
41
68
|
`- 마지막 발생: ${inlineText(last?.at ?? '없음')} · 출처: ${inlineText(last?.source ?? '없음')}`);
|
|
42
69
|
if (kind === 'candidate') lines.push(`- 반증: ${row.disproof?.trim() ? inlineText(row.disproof) : '반증 없음 — 승격 불가'}`);
|