elanous 0.2.27 → 0.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1454-a9c338f5bb556f42.js → 1454-29d1d773d5996a49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-07110da72c45e68f.js → page-24453f6a302816b4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-d2d76d4a7eae91bd.js → page-6c131d30bb1cf871.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-fa46d4588e8442c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-e5a9302c105725a3.js → page-dc0772b11861af51.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-6a16a48d1aa6e91f.js → page-256ffd365d32e100.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e0de78ae7b99f01c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-39f82acf619cca23.js → page-4c741eb760f2f77c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-7c2c8f81b185f9c0.js → page-e9ba98f5f1440a21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-ae7aff07e32676bb.js → page-dafc901bb1b72b5f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-482c60c1bc540d0a.js → page-2f6f303fa114ec24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-7cb996c1aa08b6f5.js → layout-1634081c5272006c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/{page-623e08fe4477d606.js → page-ced0bcfc6542b3fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-db460cd8aecb8490.js → page-bb1f05cd51c38944.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-ff27902d0b11ef4f.js → page-646b50f63b6fe732.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-1917ea87ea71ab6b.js → page-f736324afa2117c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-25a302273b054fd7.js → page-6f131b51193a36f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-670510eda8b9443f.js → not-found-435f5e2faf02bc01.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-497e0eedf20e7890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a8d28fdf995e16ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4bb7556e2e97a750.js → page-8966452228753c5e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-4eb96fc7241c2456.js → page-66a67eb19ebd976b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-0d64ff212dc55d03.js → page-c71267e7681da982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-7f096248993fc2d4.js → page-a21d1e1797172692.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-e47fc72049149a38.js → page-cb249e81ed339318.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-f7d808e31e005895.js → page-781cfc5114e097da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-5c2b24b5496dad73.js → page-478157214db6a023.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-76cdb531668b5191.js → page-86141f12cf090b7f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-7256c73bb1c36751.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-dab163b338e3b966.js → page-979d318a534e92da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-0260ffc7eda50db1.js → page-21050928a697ed7a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-99c4e6c7345a9156.js → page-601fb77340e63c9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-11bf9e5725a72c22.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/graphs/implement-loop.yaml +38 -1
- package/graphs/steward/recipes.yaml +6 -0
- package/graphs/steward/release-day.yaml +23 -0
- package/graphs/steward/steward.yaml +9 -1
- package/graphs/work/code-pr.yaml +23 -0
- package/package.json +1 -1
- package/scripts/browser-observe-cycle.ts +76 -2
- package/scripts/ci-typecheck-changed.ts +50 -17
- package/scripts/detect-test-interference.ts +31 -1
- package/scripts/release-loop/docs-node.ts +51 -22
- package/scripts/release-loop/gate-node.ts +37 -4
- package/scripts/release-loop/node-verdict.ts +3 -1
- package/scripts/release-loop/publish-node.ts +3 -0
- package/scripts/release-loop/tui-node-checks.ts +5 -2
- package/scripts/skills/public-check.ts +5 -2
- package/scripts/test-deterministic.ts +520 -37
- package/scripts/ux-sim.ts +7 -1
- package/scripts/verify-kpack6-live.ts +230 -0
- package/src/acp/codex-plugins.ts +17 -0
- package/src/agent/task-notification.ts +2 -1
- package/src/agent-mission/in-agent-plugin.ts +8 -3
- package/src/agent-substrate/self-review-cli.ts +34 -10
- package/src/briefing/brief-items.ts +151 -35
- package/src/budget/llm-cost.ts +25 -2
- package/src/budget/usage-outcomes.ts +32 -0
- package/src/claims/claims-ledger.ts +23 -0
- package/src/claims/claims-render.ts +22 -2
- package/src/cli/brief-cli.ts +131 -10
- package/src/cli/claims-cli.ts +24 -4
- package/src/cli/msg-cli.ts +165 -9
- package/src/cli/plugin-cli.ts +11 -3
- package/src/cli/pr-cli.ts +80 -18
- package/src/cli/pr-land-refusal.ts +20 -0
- package/src/cli/release-cli.ts +2 -0
- package/src/cli/rfc-cli.ts +24 -0
- package/src/cli/tailscale-serve.ts +5 -8
- package/src/cli/tasks-cli.ts +393 -26
- package/src/cli/usage-cli.ts +9 -4
- package/src/dashboard/first-screen-band.ts +63 -13
- package/src/dashboard/index.ts +8 -3
- package/src/dashboard/skill-detection-runtime.ts +24 -4
- package/src/domains/morning-report.ts +3 -1
- package/src/github/pr-labels.ts +10 -4
- package/src/graph-runner/runner.ts +101 -14
- package/src/harness/harness-clean.ts +21 -3
- package/src/harness/harness-cli-command.ts +22 -6
- package/src/harness/harness-pod-dispatch.ts +11 -4
- package/src/hq/seats.ts +17 -3
- package/src/index.ts +34 -5
- package/src/intelligence-map/model-bench-scores.ts +102 -0
- package/src/loops/orchestrator/inbox.ts +73 -0
- package/src/loops/orchestrator/resync-shadow.ts +100 -12
- package/src/loops/orchestrator/tick.ts +73 -2
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/msg/msg-sse-server.ts +141 -0
- package/src/msg/msg-store.ts +84 -14
- package/src/patents/ledger.ts +128 -0
- package/src/plugins/install/market-fetch.ts +32 -1
- package/src/pod-lease/admission.ts +116 -41
- package/src/release-loop/checklist.ts +11 -0
- package/src/release-loop/merged-pr-checklist.ts +24 -5
- package/src/release-loop/rubric.ts +70 -0
- package/src/rfc-registry/registry.ts +88 -0
- package/src/seat-dispatch/seat-request-ledger.ts +4 -0
- package/src/seat-loop/knowhow-lint.ts +233 -0
- package/src/seat-loop/seat-pack.ts +16 -4
- package/src/seat-loop/traffic-signal.ts +7 -0
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/ask-path-candidates.ts +69 -14
- package/src/self-dev/decompose-proposal.ts +43 -1
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/launch-preflight-text.ts +5 -1
- package/src/self-dev/launch-preflight.ts +143 -10
- package/src/self-implement/gate-cli.ts +6 -2
- package/src/self-implement/goal-author.ts +2 -1
- package/src/self-implement/goal-new-paths.ts +4 -5
- package/src/self-implement/graph-authority.ts +17 -3
- package/src/self-implement/graph-yaml.ts +15 -7
- package/src/self-implement/headless-elanous-driver.ts +16 -1
- package/src/self-implement/host-regate.ts +44 -9
- package/src/self-implement/orchestrator.ts +3 -10
- package/src/self-implement/rework-policy.ts +80 -2
- package/src/self-implement/speed-claims.ts +145 -6
- package/src/skills/tools/persistent-grounding.ts +59 -4
- package/src/skills/tools/shell.ts +79 -1
- package/src/steward/rejudge-tick.ts +12 -1
- package/src/steward/release-day.ts +158 -0
- package/src/steward/resync-tick.ts +123 -0
- package/src/task-agent/actions.ts +1 -0
- package/src/task-agent/adopt-pr.ts +140 -0
- package/src/task-agent/board.ts +11 -1
- package/src/task-agent/card-evidence.ts +37 -5
- package/src/task-agent/cover.ts +84 -17
- package/src/task-agent/failure-notify.ts +61 -0
- package/src/task-agent/green-sweep.ts +170 -0
- package/src/task-agent/land-worktree.ts +52 -5
- package/src/task-agent/live-move-history.ts +1 -0
- package/src/task-agent/live-moves.ts +43 -12
- package/src/task-agent/parent-host.ts +1 -1
- package/src/task-agent/postland-card.ts +139 -0
- package/src/task-agent/rejudge-on-head.ts +106 -7
- package/src/task-agent/remote-rejudge-bind.ts +62 -0
- package/src/task-agent/repair-branch-cas.ts +182 -0
- package/src/task-agent/repair-cycle.ts +275 -0
- package/src/task-agent/repair-next-move.ts +31 -0
- package/src/task-agent/repair-review.ts +24 -0
- package/src/task-agent/repair-worker.ts +87 -0
- package/src/task-agent/review-result-capture.ts +2 -1
- package/src/task-agent/shadow.ts +13 -7
- package/src/task-agent/task-hand.ts +115 -6
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +70 -12
- package/src/telegram.ts +30 -1
- package/src/user-config.ts +8 -1
- package/src/ux-sim/fault-sqlite-busy.ts +50 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-a1ce306364f08da9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-30007380f2e2a3c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-b93c04b5311c97ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-718a49e86bf25f9c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-20662900d6b4b455.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f707a509515ac50c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-fcb06e305f515527.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-6acf5ec4e6c6d1af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-8397a5c6ca569bf4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d73c3f1e00d93986.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-65caacf54389081e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-77e04b4f8762c666.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-15c0a4095baddfba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-63e272a4cd3b6634.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e2b63345bba1a11f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d376e0c83edc041a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d6db55fe2c04d128.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-87b88f551f421d8b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-08019efef111ed2e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-980ce6fe36ad95eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-0ab42e0f05823013.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ad9220422d20ba33.js +0 -1
- /package/apps/pwa/out/_next/static/{Ev2jivDMTSoEc-5rIXdcy → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{Ev2jivDMTSoEc-5rIXdcy → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
// ⛔ 그래도 관측·산출에는 «그대로» 남긴다 — 「지나갔다」와 「없었다」는 다른 값이다.
|
|
18
18
|
|
|
19
19
|
import { execFileSync } from 'node:child_process';
|
|
20
|
-
import { existsSync } from 'node:fs';
|
|
21
|
-
import { isAbsolute, resolve } from 'node:path';
|
|
20
|
+
import { closeSync, existsSync, lstatSync, openSync, readSync, statSync } from 'node:fs';
|
|
21
|
+
import { dirname, isAbsolute, resolve, sep } from 'node:path';
|
|
22
|
+
import { linesOutsideFencedCode } from '../self-implement/goal-author.js';
|
|
23
|
+
import { declaredNewFilePaths } from '../self-implement/goal-new-paths.js';
|
|
22
24
|
import { resolveLogTargets } from '../cli/logs-cli.js';
|
|
23
25
|
import { debug } from '../debug/log.js';
|
|
24
26
|
import { siblingTestPath, normalizePreexistingFailureObservedAt, normalizePreexistingFailureRecords, emitAskMarkerObservation, emittedAskMarkerObservations, setAskMarkerObserver } from './launch-preflight-text.js';
|
|
@@ -734,8 +736,9 @@ export interface AskPreflightOptions {
|
|
|
734
736
|
readonly openPrsLimit?: number;
|
|
735
737
|
/** 중단 런 원장 조회의 최대 반환 행 수. 상한 도달은 결과의 `truncated` 상태로 남는다. */
|
|
736
738
|
readonly interruptedRunsLimit?: number;
|
|
737
|
-
/** ⛔ 저작 «전» 예비 검사용 —
|
|
738
|
-
*
|
|
739
|
+
/** ⛔ 저작 «전» 예비 검사용 — 충돌 판정에는 이 ask 힌트 경로를 쓴다.
|
|
740
|
+
* 골 파일이 이미 있으면 문서의 TRACED PATHS 는 별도로 읽어 누락을 검사한다.
|
|
741
|
+
* ⚠️ 힌트는 «추정»이다. 정본은 저작된 골의 TRACED PATHS 이고 발사 직전에 다시 판정한다. */
|
|
739
742
|
readonly pathsOverride?: readonly string[];
|
|
740
743
|
/** 원 ask 경로와 최종 대상의 차이를 ask-outside-path 경고에 보인다. */
|
|
741
744
|
readonly askOutsidePathHints?: readonly string[];
|
|
@@ -770,6 +773,78 @@ export interface AskPreflightDecision {
|
|
|
770
773
|
readonly plannedBranchResolution?: PlannedBranchResolution;
|
|
771
774
|
}
|
|
772
775
|
|
|
776
|
+
/** OP #980: fragments kept by cleanup remain inspectable traced targets, including bare names. */
|
|
777
|
+
function isFileShapedTracedPath(path: string): boolean {
|
|
778
|
+
return path.trim().length > 0 && (!/\s/.test(path) || path.includes('/'));
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Keep lexical `..`/`.` segments: resolving them first hides ENOTDIR behind a file parent.
|
|
782
|
+
function tracedFsPath(path: string, root: string): string {
|
|
783
|
+
return isAbsolute(path) ? path : `${root}${root.endsWith(sep) ? '' : sep}${path}`;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function tracedPathExists(path: string, root: string): boolean {
|
|
787
|
+
try {
|
|
788
|
+
const target = tracedFsPath(path, root);
|
|
789
|
+
if (!statSync(target).isFile()) return false;
|
|
790
|
+
const fd = openSync(target, 'r');
|
|
791
|
+
try { readSync(fd, Buffer.alloc(1), 0, 1, 0); }
|
|
792
|
+
finally { closeSync(fd); }
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
catch { return false; }
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function tracedPathIsFile(path: string, root: string): boolean {
|
|
799
|
+
try { return statSync(tracedFsPath(path, root)).isFile(); }
|
|
800
|
+
catch { return false; }
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function tracedPathOccupation(path: string, root: string): 'occupied' | 'missing' | 'inaccessible' {
|
|
804
|
+
const target = tracedFsPath(path, root);
|
|
805
|
+
try { lstatSync(target); return 'occupied'; }
|
|
806
|
+
catch (error) {
|
|
807
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') return 'inaccessible';
|
|
808
|
+
for (let parent = dirname(target); parent !== dirname(parent); parent = dirname(parent)) {
|
|
809
|
+
try {
|
|
810
|
+
if (lstatSync(parent).isSymbolicLink()) {
|
|
811
|
+
try { statSync(parent); } catch { return 'inaccessible'; }
|
|
812
|
+
}
|
|
813
|
+
} catch (parentError) {
|
|
814
|
+
if ((parentError as NodeJS.ErrnoException).code !== 'ENOENT') return 'inaccessible';
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return 'missing';
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/** Remove only malformed TRACED PATHS fragments before the child reads the goal file again. */
|
|
822
|
+
export function discardNonFileTracedPathItems(document: string, _root: string): { document: string; dropped: string[] } {
|
|
823
|
+
const dropped: string[] = [];
|
|
824
|
+
let inTracedPaths = false;
|
|
825
|
+
const outsideStarts = new Set(linesOutsideFencedCode(document).map((line) => line.start));
|
|
826
|
+
let offset = 0;
|
|
827
|
+
const lines = document.split('\n').flatMap((line) => {
|
|
828
|
+
const lineStart = offset;
|
|
829
|
+
offset += line.length + 1;
|
|
830
|
+
if (!outsideStarts.has(lineStart)) return [line];
|
|
831
|
+
const body = line.replace(/\r$/, '');
|
|
832
|
+
if (/^\s*##\s+TRACED PATHS\s*$/.test(body)) { inTracedPaths = true; return [line]; }
|
|
833
|
+
if (inTracedPaths && /^\s*#{1,6}\s/.test(body)) inTracedPaths = false;
|
|
834
|
+
if (!inTracedPaths) return [line];
|
|
835
|
+
const item = /^(\s*(?:[-*+]|\d+[.)])\s+)(.*)$/.exec(body);
|
|
836
|
+
if (!item) return [line];
|
|
837
|
+
// OP #980: a fragment with whitespace and no slash is prose; all other forms stay verbatim.
|
|
838
|
+
const fragments = item[2]!.split(' · ');
|
|
839
|
+
const kept = fragments.filter((fragment) => !(/\s/.test(fragment.trim()) && !fragment.includes('/')));
|
|
840
|
+
if (kept.length === fragments.length) return [line];
|
|
841
|
+
if (kept.length === 0) { dropped.push(line); return []; }
|
|
842
|
+
dropped.push(line);
|
|
843
|
+
return [`${item[1]}${kept.join(' · ')}${line.endsWith('\r') ? '\r' : ''}`];
|
|
844
|
+
});
|
|
845
|
+
return { document: lines.join('\n'), dropped };
|
|
846
|
+
}
|
|
847
|
+
|
|
773
848
|
/** 인자 검증 — ⛔ 잘못된 값을 «조용히» 기본값으로 바꾸지 않는다(리뷰 should-fix). */
|
|
774
849
|
/** ⛔ 기본값은 «내부 상수»다 — 아무도 안 넘기는 인자를 계약 표면으로 두지 않는다(리뷰 must-fix). */
|
|
775
850
|
export const DEFAULT_LIVE_RUN_WINDOW_MINUTES = 30;
|
|
@@ -1001,20 +1076,48 @@ export function decideAskPreflight(
|
|
|
1001
1076
|
): AskPreflightDecision {
|
|
1002
1077
|
const limit = opts.openPrsLimit ?? 200;
|
|
1003
1078
|
const interruptedRunsLimit = resolveInterruptedRunsLimit(opts.interruptedRunsLimit);
|
|
1004
|
-
//
|
|
1005
|
-
// ⚠️ 골 문서를 읽지 «않는다» — 없는 파일을 읽으려 하면 여기서 던져 저작 자체가 막힌다.
|
|
1079
|
+
// pathsOverride is an independent pre-authoring inspection, even if a stale goal file exists.
|
|
1006
1080
|
let goalDocument: string | null = null;
|
|
1007
1081
|
let goalRulersUnknownReason: string | undefined;
|
|
1082
|
+
let goalReadFailed = false;
|
|
1083
|
+
const resolvedGoalFile = opts.declaredPathsRoot?.trim() && !isAbsolute(opts.goalFile)
|
|
1084
|
+
? resolve(opts.declaredPathsRoot, opts.goalFile)
|
|
1085
|
+
: opts.goalFile;
|
|
1008
1086
|
if (!opts.pathsOverride) {
|
|
1009
1087
|
try {
|
|
1010
|
-
goalDocument = deps.readGoalDocument(
|
|
1088
|
+
goalDocument = deps.readGoalDocument(resolvedGoalFile);
|
|
1011
1089
|
} catch (error) {
|
|
1090
|
+
goalReadFailed = true;
|
|
1012
1091
|
goalRulersUnknownReason = `골 문서 판독 불가: ${error instanceof Error ? error.message.split('\n')[0] : String(error)}`;
|
|
1013
1092
|
}
|
|
1014
1093
|
} else {
|
|
1015
1094
|
goalRulersUnknownReason = '저작 전 예비 검사에는 골 문서가 없다';
|
|
1016
1095
|
}
|
|
1017
|
-
|
|
1096
|
+
// Resolve against the caller's root, but keep injected virtual goal documents out of
|
|
1097
|
+
// filesystem checks when no authored file exists there.
|
|
1098
|
+
const hasAuthoredGoal = !opts.pathsOverride && opts.goalFile !== '' && goalDocument !== null
|
|
1099
|
+
&& existsSync(resolvedGoalFile);
|
|
1100
|
+
const authoredTracedPaths = goalDocument === null ? [] : deps.tracedPaths(goalDocument);
|
|
1101
|
+
const tracedPathsRaw = opts.pathsOverride ?? authoredTracedPaths;
|
|
1102
|
+
const inspectionRoot = goalDocument === null || !hasAuthoredGoal
|
|
1103
|
+
? undefined
|
|
1104
|
+
: opts.declaredPathsRoot?.trim() || process.cwd();
|
|
1105
|
+
const tracedPaths = inspectionRoot && !opts.pathsOverride
|
|
1106
|
+
? tracedPathsRaw.filter((path) => tracedPathExists(path, inspectionRoot) || isFileShapedTracedPath(path))
|
|
1107
|
+
: tracedPathsRaw;
|
|
1108
|
+
const declaredNewPaths = goalDocument === null ? new Set<string>() : new Set(declaredNewFilePaths(goalDocument));
|
|
1109
|
+
const invalidTracedPaths: Array<{ path: string; reason: 'occupied-new' | 'not-file' | 'missing' | 'inaccessible' }> = inspectionRoot
|
|
1110
|
+
? authoredTracedPaths.flatMap<{ path: string; reason: 'occupied-new' | 'not-file' | 'missing' | 'inaccessible' }>((path) => {
|
|
1111
|
+
const occupation = tracedPathOccupation(path, inspectionRoot);
|
|
1112
|
+
if (!isFileShapedTracedPath(path) && occupation !== 'occupied') return [];
|
|
1113
|
+
const isDeclaredNew = declaredNewPaths.has(path.replace(/^\.\//, ''));
|
|
1114
|
+
if (isDeclaredNew) return occupation === 'missing' ? [] : [{ path, reason: occupation === 'occupied' ? 'occupied-new' as const : 'inaccessible' as const }];
|
|
1115
|
+
if (tracedPathExists(path, inspectionRoot)) return [];
|
|
1116
|
+
return [{ path, reason: occupation === 'occupied'
|
|
1117
|
+
? (tracedPathIsFile(path, inspectionRoot) ? 'inaccessible' as const : 'not-file' as const)
|
|
1118
|
+
: occupation }];
|
|
1119
|
+
})
|
|
1120
|
+
: [];
|
|
1018
1121
|
let premiseMockedBySignal: number | null = null;
|
|
1019
1122
|
let invariantFunctionExits: number | null = null;
|
|
1020
1123
|
if (goalDocument !== null) {
|
|
@@ -1197,11 +1300,41 @@ export function decideAskPreflight(
|
|
|
1197
1300
|
...(interruptedRunsUnknownReason ? { interruptedRunsUnknownReason } : {}),
|
|
1198
1301
|
...(recentChangesUnknownReason ? { recentChangesUnknownReason } : {}),
|
|
1199
1302
|
});
|
|
1200
|
-
const
|
|
1303
|
+
const missingTracedPathBlockers: LaunchPreflightBlocker[] = [
|
|
1304
|
+
...(hasAuthoredGoal && authoredTracedPaths.length === 0 ? [{
|
|
1305
|
+
kind: 'missing-traced-path' as const,
|
|
1306
|
+
name: '최종 골의 TRACED PATHS 경로 0',
|
|
1307
|
+
detail: '정리 후 보존된 traced 경로가 없다 — 실제 파일 또는 유효한 신설: 파일 경로를 골에 선언해야 발사할 수 있다',
|
|
1308
|
+
}] : []),
|
|
1309
|
+
...invalidTracedPaths.map(({ path, reason }) => ({
|
|
1310
|
+
kind: 'missing-traced-path' as const,
|
|
1311
|
+
name: reason === 'occupied-new' ? `신설 traced path 가 이미 점유됨: ${path}`
|
|
1312
|
+
: reason === 'not-file' ? `traced path 가 파일이 아님: ${path}`
|
|
1313
|
+
: reason === 'inaccessible' ? `traced path 경로 접근 불가: ${path}` : `실재하지 않는 traced path: ${path}`,
|
|
1314
|
+
detail: reason === 'occupied-new'
|
|
1315
|
+
? `골의 신설: 경로 ${path} 가 이미 파일·디렉터리·링크로 점유돼 있다 — 새 파일 경로를 바로잡아야 한다`
|
|
1316
|
+
: reason === 'not-file'
|
|
1317
|
+
? `골의 TRACED PATHS 경로 ${path} 가 존재하지만 읽을 수 있는 파일이 아니다 — 파일 경로를 바로잡아야 한다`
|
|
1318
|
+
: reason === 'inaccessible'
|
|
1319
|
+
? `골의 TRACED PATHS 경로 ${path} 를 검사할 수 없다 — 부모가 파일이거나 접근 권한이 없다. 경로를 바로잡아야 한다`
|
|
1320
|
+
: `골의 TRACED PATHS 에 선언됐지만 ${inspectionRoot} 에 없고 유효한 신설: 목록에도 없다 — 발사 전에 골 경로를 바로잡아야 한다. 신설은 파일 경로로(확장자 포함) 선언한다`,
|
|
1321
|
+
})),
|
|
1322
|
+
];
|
|
1323
|
+
const goalReadBlockers: LaunchPreflightBlocker[] = goalReadFailed ? [{
|
|
1324
|
+
kind: 'goal-unreadable',
|
|
1325
|
+
name: `골 문서 판독 불가: ${opts.goalFile}`,
|
|
1326
|
+
detail: `${goalRulersUnknownReason} — 최종 골의 traced path 를 확인할 수 없어 발사를 멈춘다`,
|
|
1327
|
+
}] : [];
|
|
1328
|
+
const decisionResult = {
|
|
1329
|
+
...result,
|
|
1330
|
+
blockers: [...result.blockers, ...missingTracedPathBlockers, ...goalReadBlockers],
|
|
1331
|
+
warnings: [...result.warnings, ...askMarkerWarnings],
|
|
1332
|
+
runningRunsConfidenceAppendix: runningRunsConfidence.appendix,
|
|
1333
|
+
};
|
|
1201
1334
|
emittedAskMarkerObservations.add(decisionResult);
|
|
1202
1335
|
return {
|
|
1203
1336
|
result: decisionResult,
|
|
1204
|
-
shouldLaunch: result.blockers.length === 0 || force,
|
|
1337
|
+
shouldLaunch: missingTracedPathBlockers.length === 0 && goalReadBlockers.length === 0 && (result.blockers.length === 0 || force),
|
|
1205
1338
|
plannedBranchResolution,
|
|
1206
1339
|
};
|
|
1207
1340
|
}
|
|
@@ -426,6 +426,7 @@ function changedTestCountNote(
|
|
|
426
426
|
|
|
427
427
|
const IMPORTER_GREP_PATHS = ['*.test.ts', '*.test.tsx', '*.test.mts', '*.test.mtsx', '*.test.cts', '*.test.ctsx', '*.test.js', '*.test.jsx', '*.test.mjs', '*.test.mjsx', '*.test.cjs', '*.test.cjsx'];
|
|
428
428
|
const SOURCE_PATH_RE = /^src\/.+\.(?:[cm]?[jt]sx?)$/;
|
|
429
|
+
const SCRIPT_SOURCE_PATH_RE = /^scripts\/.+\.(?:[cm]?[jt]sx?)$/;
|
|
429
430
|
const SOURCE_FILE_RE = /\.(?:[cm]?[jt]sx?)$/;
|
|
430
431
|
|
|
431
432
|
function buildGateImporterTestIndex(
|
|
@@ -433,7 +434,7 @@ function buildGateImporterTestIndex(
|
|
|
433
434
|
cwd: string,
|
|
434
435
|
runCommand: SelfGateCliDeps['runCommand'],
|
|
435
436
|
): ImporterTestIndex | null | { readonly lookupFailed: string } {
|
|
436
|
-
const sources = changed.filter((path) => SOURCE_PATH_RE.test(path) && !isTestPath(path));
|
|
437
|
+
const sources = changed.filter((path) => (SOURCE_PATH_RE.test(path) || SCRIPT_SOURCE_PATH_RE.test(path)) && !isTestPath(path));
|
|
437
438
|
if (sources.length === 0) return null;
|
|
438
439
|
const result = (runCommand ?? defaultRunCommand)('git', ['grep', '-l', '-e', '', '--', ...IMPORTER_GREP_PATHS], cwd);
|
|
439
440
|
if ((result.status !== 0 && result.status !== 1) || result.error) {
|
|
@@ -596,7 +597,10 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
|
|
|
596
597
|
if (options.shards !== undefined && (!Number.isSafeInteger(options.shards) || options.shards < 1)) throw new Error('invalid --shards count');
|
|
597
598
|
const selection = (deps.changedFiles ?? ((dir, input) => defaultChangedFiles(dir, input, deps.runCommand)))(cwd, options);
|
|
598
599
|
const exists = deps.exists ?? ((path) => existsSync(`${cwd}/${path}`));
|
|
599
|
-
|
|
600
|
+
// Caller: src/release-loop/light-rc.ts runLightRc → runSelfGateCli(dir, { ..., includeImporterTests: true }).
|
|
601
|
+
// scripts/ joins the existing importer index only for that opt-in path.
|
|
602
|
+
const importerIndex = buildGateImporterTestIndex(options.includeImporterTests
|
|
603
|
+
? selection.files : selection.files.filter((path) => !SCRIPT_SOURCE_PATH_RE.test(path)), cwd, deps.runCommand);
|
|
600
604
|
const lookupFailed = importerIndex && 'lookupFailed' in importerIndex ? importerIndex.lookupFailed : undefined;
|
|
601
605
|
const availableIndex = importerIndex && !('lookupFailed' in importerIndex) ? importerIndex : undefined;
|
|
602
606
|
const routeIndex = availableIndex?.testPaths
|
|
@@ -1701,7 +1701,8 @@ export function lintGoalFile(document: string, branch: string, deps: GoalFileLin
|
|
|
1701
1701
|
}
|
|
1702
1702
|
if (deps.readReferencedFile) {
|
|
1703
1703
|
// AUTHOR-NEW-FILE-PATHS — 문서가 «새로 만들 파일»로 선언한 경로. 실행 쪽 범위 검사와 같은 줄을 읽는다.
|
|
1704
|
-
|
|
1704
|
+
// 저작과 실행은 같은 Original ask 범위만 선언으로 인정한다. 문서의 다른 절은 근거일 뿐이다.
|
|
1705
|
+
const declaredNew = new Set(declaredNewFilePaths(extractVerbatimOriginalAsk(document)?.ask ?? ''));
|
|
1705
1706
|
for (const reference of tracedPathReferences(document)) {
|
|
1706
1707
|
const result = deps.readReferencedFile(reference.path);
|
|
1707
1708
|
const normalized = typeof result === 'string' ? { kind: 'ok' as const, contents: result }
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ⭐ AUTHOR-NEW-FILE-PATHS — 저작 골 문서가 «새로 만들 파일»로 선언한 경로.
|
|
3
3
|
*
|
|
4
|
-
* 저작(TRACED PATHS 존재 검사)과 실행(orchestrator 범위 검사)이 **같은
|
|
5
|
-
* 저작 통과·실행 보류가 다시 갈리지 않는다(TC 13:50).
|
|
6
|
-
* src/self-implement/orchestrator.ts 의 DECLARED_NEW_FILES_LINE 와 같은 꼴을 쓴다.
|
|
4
|
+
* 저작(TRACED PATHS 존재 검사)과 실행(orchestrator 범위 검사)이 **같은 함수**로
|
|
5
|
+
* 신설 경로를 읽어야 저작 통과·실행 보류가 다시 갈리지 않는다(TC 13:50).
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
// SCOPE-NEW-FILES-LINE — 칸 문면은 새로 만들 파일을 «신설:» 줄로 따로 적는다(OP D칸 규칙).
|
|
10
|
-
//
|
|
9
|
+
// 저작과 실행이 이 함수를 공유한다(한쪽만 읽으면 저작과 실행이 갈린다).
|
|
11
10
|
export const DECLARED_NEW_FILES_LINE = /^\s*(?:신설|new\s*files?)\s*[::]\s*(.*)$/i;
|
|
12
11
|
|
|
13
12
|
/** 목록 기호 «- »·굵게 «**» 는 장식이다 — 그것만 벗기고 같은 정규식으로 문는다. */
|
|
@@ -32,7 +31,7 @@ export function declaredNewFilePaths(document: string): string[] {
|
|
|
32
31
|
const match = DECLARED_NEW_FILES_LINE.exec(bareDeclarationLine(rawLine));
|
|
33
32
|
if (!match) continue;
|
|
34
33
|
for (const fragment of match[1]!.split(/[·,]/)) {
|
|
35
|
-
//
|
|
34
|
+
// 저작·실행 공통 정규화 — 등 코드, ./ 접두.
|
|
36
35
|
const piece = fragment.trim().replace(/^`+|`+$/g, '').replace(/\\/g, '/').replace(/^\.\//, '');
|
|
37
36
|
if (!isRepositoryFileCandidate(piece)) continue;
|
|
38
37
|
paths.push(piece);
|
|
@@ -280,11 +280,24 @@ export function graphAuthorityFields(authority: GraphAuthority, template: GraphT
|
|
|
280
280
|
* (`wrong-stage` · `does-not-apply` · `key-absent` · `unparseable` 이 서로 다른 처방이다).
|
|
281
281
|
* ⛔ **거절은 조용하지 않다** — 패치가 안 물면 `rejections` 로 나오고 «기준 선언»이 그대로 산다. */
|
|
282
282
|
/** 오버레이 조건이 «읽을 수 있는» 상태 키. ⛔ 여기 없는 키는 원장에 값이 안 실린다.
|
|
283
|
-
* 🔑 그래서 «떨어뜨리되 말은 한다» — 아래 `stateExtraKeys` 가 그 이름을 남긴다.
|
|
284
|
-
|
|
283
|
+
* 🔑 그래서 «떨어뜨리되 말은 한다» — 아래 `stateExtraKeys` 가 그 이름을 남긴다.
|
|
284
|
+
* ⭐ `author_grade` — 적응형 저작(GRAPH-AUTHOR-GRADE) 첫 조각: 저작 등급을 오버레이 상태의 «이름»으로 싣는다. */
|
|
285
|
+
export const OVERLAY_STATE_KEYS = ['attempts', 'author_grade', 'goal_id'] as const;
|
|
286
|
+
|
|
287
|
+
/** 저작 등급의 오버레이 어휘 — `goal-author` 의 `GoalAuthorGrade`(`full|lite`) 를 오버레이 등급(`normal`) 로 옮긴다.
|
|
288
|
+
* ⛔ 모르는 값(빈 값·오타)은 `undefined` — 등급을 «지어내지 않는다». */
|
|
289
|
+
export function normalizeAuthorGrade(value: unknown): 'lite' | 'normal' | 'complex' | undefined {
|
|
290
|
+
if (value === 'lite') return 'lite';
|
|
291
|
+
if (value === 'full') return 'normal';
|
|
292
|
+
if (value === 'complex') return 'complex';
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
285
295
|
|
|
286
296
|
/** `graph-overlay-decision` 원장에 «판정에 실제로 쓴» 상태를 싣는다.
|
|
287
297
|
* ⛔ `hasOwn` 으로 복사한다 — 그래야 「없음」과 「false」와 「0」이 원장에서 «다른 값»이다.
|
|
298
|
+
* ⭐ `author_grade` 만은 `normalizeAuthorGrade` 를 «거쳐» 실린다 — 저작 어휘(`full`)가 원장에
|
|
299
|
+
* 그대로 «사실»로 남으면 오버레이 어휘(`normal`)와 섞인다. 모르는 등급은 키는 남되 값이
|
|
300
|
+
* `undefined` 다(등급을 지어내지 않는다 · «있었다»는 사실도 조용히 버리지 않는다).
|
|
288
301
|
* ⛔⭐ 목록 밖 키는 값을 «안» 싣지만 ***조용히 버리지 않는다*** — `stateExtraKeys` 로 이름을 남긴다.
|
|
289
302
|
* 🩸 이 골이 생긴 이유가 「원장이 판정의 입력을 안 남겨서 손으로 재현하다 오판했다」이므로,
|
|
290
303
|
* 여기서 «조용히» 떨어뜨리면 같은 병을 한 층 아래에서 다시 짓는 것이다. */
|
|
@@ -293,7 +306,8 @@ export function overlayDecisionObservation<T extends Readonly<Record<string, unk
|
|
|
293
306
|
}): T & { readonly state: Readonly<Record<string, unknown>>; readonly stateExtraKeys?: readonly string[] } {
|
|
294
307
|
const state: Record<string, unknown> = {};
|
|
295
308
|
for (const key of OVERLAY_STATE_KEYS) {
|
|
296
|
-
if (Object.hasOwn(input.state, key))
|
|
309
|
+
if (!Object.hasOwn(input.state, key)) continue;
|
|
310
|
+
state[key] = key === 'author_grade' ? normalizeAuthorGrade(input.state[key]) : input.state[key];
|
|
297
311
|
}
|
|
298
312
|
const extra = Object.keys(input.state).filter((key) => !(OVERLAY_STATE_KEYS as readonly string[]).includes(key)).sort();
|
|
299
313
|
return { ...input, state, ...(extra.length > 0 ? { stateExtraKeys: extra } : {}) };
|
|
@@ -33,6 +33,8 @@ export interface GraphNodeSpec {
|
|
|
33
33
|
readonly fanOut?: string;
|
|
34
34
|
readonly contract?: GraphNodeContract;
|
|
35
35
|
readonly call?: string;
|
|
36
|
+
/** Entry selected by this call when a unit exposes more than one entry. */
|
|
37
|
+
readonly callEntry?: string;
|
|
36
38
|
readonly budget?: { readonly innerSteps: number };
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -60,7 +62,7 @@ export interface GraphTemplateSpec {
|
|
|
60
62
|
readonly state?: readonly string[];
|
|
61
63
|
/** ⭐ 런 계약(선택) — 이 그래프가 «기본으로» 요구하는 실행 칸 등. 발사 인자가 덮는다(graph-run-contract.ts). */
|
|
62
64
|
readonly runContract?: GraphRunContractSpec;
|
|
63
|
-
readonly unit?: { readonly inputs: readonly string[]; readonly outputs: readonly string[]; readonly exits: readonly string[] };
|
|
65
|
+
readonly unit?: { readonly inputs: readonly string[]; readonly outputs: readonly string[]; readonly exits: readonly string[]; readonly entries?: readonly string[] };
|
|
64
66
|
readonly nodes: readonly GraphNodeSpec[];
|
|
65
67
|
readonly edges: readonly GraphEdgeSpec[];
|
|
66
68
|
}
|
|
@@ -155,7 +157,7 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
|
|
|
155
157
|
: undefined;
|
|
156
158
|
// A subgraph node becomes a unit call only when it names `call:`; without it the node keeps the
|
|
157
159
|
// pre-unit meaning (catalog role / recipe node), so existing declarations stay valid.
|
|
158
|
-
if (kind === 'subgraph' && (n.call !== undefined || n.budget !== undefined)) {
|
|
160
|
+
if (kind === 'subgraph' && (n.call !== undefined || n.budget !== undefined || n.call_entry !== undefined)) {
|
|
159
161
|
if (typeof n.call !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*@(?:\^?[1-9]\d*)$/.test(n.call))
|
|
160
162
|
errors.push({ path: `${at}/call`, message: 'call 은 id@정수판 또는 id@^주판이어야 한다' });
|
|
161
163
|
const budget = n.budget as Record<string, unknown> | undefined;
|
|
@@ -166,8 +168,10 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
|
|
|
166
168
|
!Array.isArray(contractRaw.outputs) || !contractRaw.outputs.every(v => typeof v === 'string'))
|
|
167
169
|
errors.push({ path: `${at}/contract`, message: 'subgraph 에는 inputs·outputs 계약이 필요하다' });
|
|
168
170
|
if (recipe !== 'none') errors.push({ path: `${at}/recipe`, message: 'subgraph 는 recipe 를 실행하지 않는다' });
|
|
169
|
-
|
|
170
|
-
|
|
171
|
+
if (n.call_entry !== undefined && (typeof n.call_entry !== 'string' || !n.call_entry.trim()))
|
|
172
|
+
errors.push({ path: `${at}/call_entry`, message: 'call_entry 는 비어 있지 않은 노드 id 여야 한다' });
|
|
173
|
+
} else if (kind !== 'subgraph' && (n.call !== undefined || n.budget !== undefined || n.call_entry !== undefined)) {
|
|
174
|
+
errors.push({ path: at, message: 'call·call_entry·budget 은 subgraph 전용이다' });
|
|
171
175
|
}
|
|
172
176
|
// ⛔ 계약이 없으면 「이 노드가 무엇을 할 수 있나」를 답할 수 없다 — 경고로 남긴다(거절은 오버레이 쪽).
|
|
173
177
|
if (contract === undefined) warnings.push({ path: `${at}/contract`, message: '계약(inputs·tools·outputs)이 없다 — 권한을 코드에서만 알 수 있다' });
|
|
@@ -182,6 +186,7 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
|
|
|
182
186
|
...(typeof n.fan_out === 'string' ? { fanOut: n.fan_out } : {}),
|
|
183
187
|
...(contract ? { contract } : {}),
|
|
184
188
|
...(kind === 'subgraph' && typeof n.call === 'string' ? { call: n.call } : {}),
|
|
189
|
+
...(kind === 'subgraph' && typeof n.call_entry === 'string' ? { callEntry: n.call_entry } : {}),
|
|
185
190
|
...(kind === 'subgraph' && n.budget && typeof n.budget === 'object' && !Array.isArray(n.budget) &&
|
|
186
191
|
typeof (n.budget as Record<string, unknown>).inner_steps === 'number'
|
|
187
192
|
? { budget: { innerSteps: (n.budget as { inner_steps: number }).inner_steps } } : {}),
|
|
@@ -242,15 +247,18 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
|
|
|
242
247
|
if (doc.unit !== undefined) {
|
|
243
248
|
const rawUnit = doc.unit as Record<string, unknown>;
|
|
244
249
|
if (!rawUnit || typeof rawUnit !== 'object' || Array.isArray(rawUnit) ||
|
|
245
|
-
Object.keys(rawUnit).some(key => !['inputs', 'outputs', 'exits'].includes(key)) ||
|
|
250
|
+
Object.keys(rawUnit).some(key => !['inputs', 'outputs', 'exits', 'entries'].includes(key)) ||
|
|
246
251
|
!['inputs', 'outputs', 'exits'].every(key => Array.isArray(rawUnit[key]) &&
|
|
247
252
|
(rawUnit[key] as unknown[]).every(v => typeof v === 'string' && v.length > 0) &&
|
|
248
253
|
new Set(rawUnit[key] as string[]).size === (rawUnit[key] as string[]).length) ||
|
|
249
254
|
!(rawUnit.exits as string[]).length ||
|
|
250
255
|
(rawUnit.exits as string[]).some(exit => !terminalNodes.includes(exit)) ||
|
|
251
|
-
terminalNodes.some(exit => !(rawUnit.exits as string[]).includes(exit))
|
|
256
|
+
terminalNodes.some(exit => !(rawUnit.exits as string[]).includes(exit)) ||
|
|
257
|
+
(rawUnit.entries !== undefined && (!Array.isArray(rawUnit.entries) || !(rawUnit.entries as unknown[]).every(v => typeof v === 'string' && v.length > 0 && known.has(v)) ||
|
|
258
|
+
new Set(rawUnit.entries as string[]).size !== (rawUnit.entries as string[]).length || !(rawUnit.entries as string[]).includes(entryNode!)))) {
|
|
252
259
|
errors.push({ path: `${label}/unit`, message: 'unit 의 inputs·outputs·exits 및 terminal_nodes 가 일치해야 한다' });
|
|
253
|
-
} else unit = { inputs: rawUnit.inputs as string[], outputs: rawUnit.outputs as string[], exits: rawUnit.exits as string[]
|
|
260
|
+
} else unit = { inputs: rawUnit.inputs as string[], outputs: rawUnit.outputs as string[], exits: rawUnit.exits as string[],
|
|
261
|
+
...(rawUnit.entries === undefined ? {} : { entries: rawUnit.entries as string[] }) };
|
|
254
262
|
}
|
|
255
263
|
const runContract = parseGraphRunContract(doc.run_contract);
|
|
256
264
|
if (runContract.error) errors.push({ path: `${label}/run_contract`, message: runContract.error });
|
|
@@ -1361,7 +1361,22 @@ export async function runHeadlessGoalLoopPty(opts: HeadlessGoalLoopPtyOptions):
|
|
|
1361
1361
|
return {};
|
|
1362
1362
|
}
|
|
1363
1363
|
const target = childLlm
|
|
1364
|
-
? resolveExplicitChildEscalateTarget(tier, process.env,
|
|
1364
|
+
? resolveExplicitChildEscalateTarget(tier, process.env,
|
|
1365
|
+
{ provider: childLlm.provider, model: childLlm.model, ...(childLlm.effort ? { effort: childLlm.effort } : {}) },
|
|
1366
|
+
{
|
|
1367
|
+
escalateChild: tier === 'none' ? undefined : (() => {
|
|
1368
|
+
try { return getUserConfig().tools.selfImplement.escalateChild; }
|
|
1369
|
+
catch (error) {
|
|
1370
|
+
try { debug.log('self-dev.rework', 'escalate-config-unavailable', { reason: errorMessage(error) }); }
|
|
1371
|
+
catch { /* Observability cannot block child launch. */ }
|
|
1372
|
+
return undefined;
|
|
1373
|
+
}
|
|
1374
|
+
})(),
|
|
1375
|
+
observe: observation => {
|
|
1376
|
+
try { debug.log(observation.category, observation.event, observation.data); }
|
|
1377
|
+
catch { /* Observability cannot block child launch. */ }
|
|
1378
|
+
},
|
|
1379
|
+
})
|
|
1365
1380
|
: resolveEscalateTarget(tier);
|
|
1366
1381
|
if (!target) return {};
|
|
1367
1382
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { tmpdir } from 'node:os';
|
|
4
5
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -7,7 +8,7 @@ import { getUserConfig } from '../user-config.js';
|
|
|
7
8
|
import { publicExposureFiles, runExposeGate } from './expose-gate.js';
|
|
8
9
|
import { admitLandingMerge } from './frozen-merges.js';
|
|
9
10
|
import { releaseGitDiffPaths, releasePathHold, releasePathHoldShouldPost, releasePathHoldComment, releasePathHoldCommentsArgs, RELEASE_PATH_LABEL } from '../self-dev/release-path-guard.js';
|
|
10
|
-
import { detectTestInterference, parseFailureCount, runBunTest } from '../../scripts/detect-test-interference.js';
|
|
11
|
+
import { detectTestInterference, failedNamesFromStreams, parseFailureCount, runBunTest, type MeasuredRun } from '../../scripts/detect-test-interference.js';
|
|
11
12
|
import { MAX_INSPECTED_TEST_FILES, runTestInterferenceGate } from '../../scripts/ci-test-interference-gate.js';
|
|
12
13
|
import { pwaGraph, pwaReach } from '../../scripts/ci-pwa-build-gate.js';
|
|
13
14
|
import { classifyMergeFailure, clipRegateDetail, parseConflictFiles, type HostRegateFailure } from './regate-detail.js';
|
|
@@ -32,6 +33,12 @@ export function provideCheckoutDependencies(candidate: string, sourceRoot: strin
|
|
|
32
33
|
|
|
33
34
|
export interface HostRegateInput { prNumber: number; headCommit: string; repoRoot: string; goalFile?: string; verifyOnly?: boolean; /** TA-LIVE-LAND-2: run the full landing gates (release-path hold · neighbours · PR re-read) but stop before the freeze admission and `gh pr merge` — the task agent lands it later via `pr land --expected-head`. */ noMerge?: true; /** Called by a resume sweep that already holds this PR's claim. */ resumed?: true }
|
|
34
35
|
export interface HostRegateResult { passed: boolean; failures: HostRegateFailure[]; os: string; status?: 'passed' | 'failed' | 'unmeasured' | 'frozen'; /** verifyOnly: 실제로 얹어 잰 base 끝 */ baseCommit?: string }
|
|
36
|
+
/** Only a missing host repository is recoverable by selecting the run's recorded root. Gate failures are final. */
|
|
37
|
+
export function hostRepositoryUnavailable(result: HostRegateResult): boolean {
|
|
38
|
+
return !result.passed && result.status === 'unmeasured'
|
|
39
|
+
&& result.failures[0]?.step === 'host-regate'
|
|
40
|
+
&& result.failures[0].detail === 'host repository unavailable';
|
|
41
|
+
}
|
|
35
42
|
export type InterferenceVerdict = { passed: boolean; detail?: string; unmeasured?: boolean; /** neighbour tests left out so the selection fits the inspection cap */ neighborsTrimmed?: number; /** importer tests left out after changed tests and all selected neighbors take priority */ importerTrimmed?: number };
|
|
36
43
|
export type HostRegateDeps = {
|
|
37
44
|
command?: (bin: string, args: readonly string[], cwd: string, env?: NodeJS.ProcessEnv) => { status: number | null; stdout: string; stderr: string };
|
|
@@ -57,6 +64,18 @@ const defaultCommand: NonNullable<HostRegateDeps['command']> = (bin, args, cwd,
|
|
|
57
64
|
return { status: overflow ? null : r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + (r.error ? `${r.error.message} (${overflow ? `output exceeded ${SPAWN_MAX_BUFFER} bytes` : 'command unavailable'})` : '') };
|
|
58
65
|
};
|
|
59
66
|
|
|
67
|
+
/** Keep the failure identity in both the returned detail and the host-regate ledger event. */
|
|
68
|
+
function failureNames(runs: readonly MeasuredRun[], fileCap: number, testCap: number): string {
|
|
69
|
+
const failed = runs.filter((run) => run.fail !== null && run.fail > 0);
|
|
70
|
+
if (!failed.length) return 'none';
|
|
71
|
+
const files = [...new Set(failed.flatMap((run) => run.failedFiles ?? []))];
|
|
72
|
+
const tests = [...new Set(failed.flatMap((run) => run.failedTests ?? []))];
|
|
73
|
+
const bounded = (names: string[], cap: number): string => names.length
|
|
74
|
+
? `${names.slice(0, cap).map((name) => name.slice(0, 120)).join(' | ')}${names.length > cap ? ` | +${names.length - cap} more` : ''}`
|
|
75
|
+
: 'unknown';
|
|
76
|
+
return `files=${bounded(files, fileCap)}; tests=${bounded(tests, testCap)}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
60
79
|
/** The informational pr-land gate always returns zero; enforce its measured report and the combined test result instead. */
|
|
61
80
|
export async function defaultInterference(files: readonly string[], cwd: string, opts: {
|
|
62
81
|
neighbors?: boolean;
|
|
@@ -136,7 +155,9 @@ export async function defaultInterference(files: readonly string[], cwd: string,
|
|
|
136
155
|
const run = await (opts.run ?? ((paths, dir) => runBunTest(paths, undefined, dir)))(selected, cwd);
|
|
137
156
|
const fail = run.signal || run.exitCode === null ? null : parseFailureCount(`${run.stdout}${run.stderr}`);
|
|
138
157
|
if (fail === null) return { passed: false, unmeasured: true, ...trimmed, detail: `single test run unmeasured: ${selected[0]}` };
|
|
139
|
-
|
|
158
|
+
if (fail === 0) return { passed: true, ...trimmed };
|
|
159
|
+
const names = failedNamesFromStreams(run);
|
|
160
|
+
return { passed: false, ...trimmed, detail: `single=${fail} fail; ${failureNames([{ files: selected, fail, status: 'measured', failedFiles: names.files, failedTests: names.tests }], 8, 20)}` };
|
|
140
161
|
}
|
|
141
162
|
// The informational pr-land gate always returns zero; its measured report is the verdict.
|
|
142
163
|
let report: Awaited<ReturnType<typeof detectTestInterference>> | undefined;
|
|
@@ -146,7 +167,10 @@ export async function defaultInterference(files: readonly string[], cwd: string,
|
|
|
146
167
|
});
|
|
147
168
|
if (!report || report.status === 'unmeasurable' || selected.length > report.order.length) return { passed: false, unmeasured: true, ...trimmed, detail: `test interference measurement unavailable or truncated (selected ${selected.length}, changed tests ${tests.length}, cap ${MAX_INSPECTED_TEST_FILES})${trimNote}` };
|
|
148
169
|
if (report.isolatedFailures !== 0 || report.combinedFailures !== 0 || report.status === 'interference') {
|
|
149
|
-
|
|
170
|
+
const bothFailed = report.isolatedFailures! > 0 && report.combinedFailures! > 0;
|
|
171
|
+
const isolatedNames = failureNames(report.isolated, bothFailed ? 4 : 8, bothFailed ? 10 : 20);
|
|
172
|
+
const combinedNames = failureNames([report.combined], bothFailed ? 4 : 8, bothFailed ? 10 : 20);
|
|
173
|
+
return { passed: false, ...trimmed, detail: `isolated=${report.isolatedFailures}, combined=${report.combinedFailures}, interference=${report.status}; isolated ${isolatedNames}; combined ${combinedNames}${trimNote}` };
|
|
150
174
|
}
|
|
151
175
|
return { passed: true, ...trimmed, ...(trimNote ? { detail: trimNote.slice(3) } : {}) };
|
|
152
176
|
}
|
|
@@ -237,6 +261,10 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
237
261
|
let endLanding: ((merged?: boolean) => void) | undefined;
|
|
238
262
|
let landed = false;
|
|
239
263
|
let verifiedBase: string | undefined;
|
|
264
|
+
// Two host-regate slots may fetch in the same repository at once. FETCH_HEAD is shared;
|
|
265
|
+
// each measurement needs its own ref so another fetch cannot replace its evidence.
|
|
266
|
+
const fetchNamespace = `refs/elanous/regate/${process.pid}-${randomUUID()}`;
|
|
267
|
+
const fetchedRefs = new Set<string>();
|
|
240
268
|
let interferenceNote: Record<string, unknown> = {};
|
|
241
269
|
// HOST-REGATE-REASON-LOST: `facts` carries the expected/actual head, conflict files and merge failure kind of the
|
|
242
270
|
// pr-head · merge-conflict · merge steps; the detail keeps a bounded head ⊕ tail instead of the first 300 characters.
|
|
@@ -250,6 +278,12 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
250
278
|
if (r.status !== 0) throw new Error(clipRegateDetail((r.stderr || r.stdout || `${bin} exited ${r.status}`).trim()));
|
|
251
279
|
return r.stdout.trim();
|
|
252
280
|
};
|
|
281
|
+
const fetchRef = (source: string, name: 'base' | 'head'): string => {
|
|
282
|
+
const ref = `${fetchNamespace}/${name}`;
|
|
283
|
+
fetchedRefs.add(ref);
|
|
284
|
+
run('git', ['fetch', '--no-write-fetch-head', 'origin', `+${source}:${ref}`], input.repoRoot);
|
|
285
|
+
return run('git', ['rev-parse', ref], input.repoRoot);
|
|
286
|
+
};
|
|
253
287
|
try {
|
|
254
288
|
if (!Number.isSafeInteger(input.prNumber) || input.prNumber <= 0 || !/^[0-9a-f]{40}$/i.test(input.headCommit)) return result('unmeasured', 'input', 'invalid PR number or checked head SHA');
|
|
255
289
|
release = await (deps.acquire ?? acquireSlot)(input.repoRoot);
|
|
@@ -272,8 +306,7 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
272
306
|
const initialPrBase = baseCommit;
|
|
273
307
|
let buildPwa = false;
|
|
274
308
|
try {
|
|
275
|
-
|
|
276
|
-
let fetchedBase = run('git', ['rev-parse', 'FETCH_HEAD'], input.repoRoot);
|
|
309
|
+
let fetchedBase = fetchRef(`refs/heads/${baseRefName}`, 'base');
|
|
277
310
|
if (input.verifyOnly) { baseCommit = fetchedBase; verifiedBase = fetchedBase; }
|
|
278
311
|
else {
|
|
279
312
|
for (let attempt = 1; fetchedBase !== baseCommit; attempt++) {
|
|
@@ -290,12 +323,10 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
290
323
|
return prHeadFailure(view);
|
|
291
324
|
}
|
|
292
325
|
baseCommit = view.baseRefOid!;
|
|
293
|
-
|
|
294
|
-
fetchedBase = run('git', ['rev-parse', 'FETCH_HEAD'], input.repoRoot);
|
|
326
|
+
fetchedBase = fetchRef(`refs/heads/${baseRefName}`, 'base');
|
|
295
327
|
}
|
|
296
328
|
}
|
|
297
|
-
|
|
298
|
-
if (run('git', ['rev-parse', 'FETCH_HEAD'], input.repoRoot) !== input.headCommit) throw new Error('fetched PR head differs from checked SHA');
|
|
329
|
+
if (fetchRef(`refs/pull/${input.prNumber}/head`, 'head') !== input.headCommit) throw new Error('fetched PR head differs from checked SHA');
|
|
299
330
|
const slotWorktree = (release as (() => void) & { worktree?: string }).worktree;
|
|
300
331
|
ephemeral = !!deps.makeTemp || !slotWorktree;
|
|
301
332
|
worktree = deps.makeTemp?.() ?? slotWorktree ?? mkdtempSync(join(tmpdir(), 'host-regate-'));
|
|
@@ -463,6 +494,10 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
463
494
|
return result('unmeasured', 'host-regate', String(e));
|
|
464
495
|
} finally {
|
|
465
496
|
try { endLanding?.(landed); } catch (e) { log('unmeasured', { pr: input.prNumber, files, os: process.platform, failedStep: 'landing-release', detail: String(e) }); }
|
|
497
|
+
for (const ref of fetchedRefs) {
|
|
498
|
+
try { run('git', ['update-ref', '-d', ref], input.repoRoot); }
|
|
499
|
+
catch (e) { log('unmeasured', { pr: input.prNumber, files, os: process.platform, failedStep: 'fetch-ref-cleanup', detail: String(e) }); }
|
|
500
|
+
}
|
|
466
501
|
try {
|
|
467
502
|
if (attached && worktree) {
|
|
468
503
|
if (slotCommonDir) verifySlot();
|
|
@@ -94,6 +94,7 @@ import { boundaryGlobs, boundaryViolations } from './goal-boundary.js';
|
|
|
94
94
|
import { askAtExecution, type ExecutionClarificationResult } from './execution-clarification.js';
|
|
95
95
|
import { classifyOutOfScope } from './scope-adjacency.js';
|
|
96
96
|
import { targetScopedGoalText } from './goal-text-path-scope.js';
|
|
97
|
+
import { declaredNewFilePaths } from './goal-new-paths.js';
|
|
97
98
|
import { dropStaleReverts, type RevertGuardResult } from './revert-guard.js';
|
|
98
99
|
import type { SelfDevGoalType } from '../self-dev/orchestrate.js';
|
|
99
100
|
import { DEFAULT_BRANCH_WORKTREE_BASE, gateGitResidue, observeGitResidue } from '../git-fs/worktree.js';
|
|
@@ -813,10 +814,8 @@ function parseDeclaredTargetPathsFromAsk(askText: string): { readable: boolean;
|
|
|
813
814
|
return { readable: true, paths: [...new Set(paths.filter(Boolean))] };
|
|
814
815
|
}
|
|
815
816
|
|
|
816
|
-
// SCOPE-NEW-FILES-LINE — 칸
|
|
817
|
+
// SCOPE-NEW-FILES-LINE — 칸 문면의 신설 경로도 선언 범위다.
|
|
817
818
|
// 10-11 FLEX-SIGNALS run-95799887: 칸이 적은 신설 파일 둘을 «범위 밖»으로 세어 구현을 보류했다.
|
|
818
|
-
const DECLARED_NEW_FILES_LINE = /^\s*(?:신설|new\s*files?)\s*[::]\s*(.*)$/i;
|
|
819
|
-
|
|
820
819
|
function declaredPathsFromGoalDocumentText(document: string): { readable: boolean; paths: readonly string[] } {
|
|
821
820
|
const originalAsk = extractVerbatimOriginalAsk(document)?.ask ?? null;
|
|
822
821
|
if (originalAsk === null) return { readable: false, paths: [] };
|
|
@@ -826,13 +825,6 @@ function declaredPathsFromGoalDocumentText(document: string): { readable: boolea
|
|
|
826
825
|
return newFiles.length > 0 ? { readable: true, paths: [...new Set([...declared.paths, ...newFiles])] } : declared;
|
|
827
826
|
}
|
|
828
827
|
|
|
829
|
-
function declaredNewFilePaths(askText: string): readonly string[] {
|
|
830
|
-
return askText.split(/\r?\n/).flatMap((line) => {
|
|
831
|
-
const match = DECLARED_NEW_FILES_LINE.exec(line);
|
|
832
|
-
return match ? parseDeclaredTargetPathsFromAsk(`대상 경로: ${match[1]}`).paths : [];
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
|
|
836
828
|
/** P3 전제의 경계 — ask 머리줄 «대상 경로:» ⊕ «신설:» 줄의 합집합. 못 읽으면 빈 목록(그때 P3 는 unknown). */
|
|
837
829
|
function declaredPremiseScopePaths(askText: string): string[] {
|
|
838
830
|
return [...new Set([...parseDeclaredTargetPathsFromAsk(askText).paths, ...declaredNewFilePaths(askText)])];
|
|
@@ -8263,6 +8255,7 @@ async function runSelfImplementInner(
|
|
|
8263
8255
|
url: pr.url,
|
|
8264
8256
|
number: pr.number,
|
|
8265
8257
|
branch: wt.branch,
|
|
8258
|
+
headCommit: runGitCommand(wt.path, ['rev-parse', '--verify', 'HEAD'], { encoding: 'utf8', timeout: 10_000 }).stdout.trim(),
|
|
8266
8259
|
autoMerge: canAuto,
|
|
8267
8260
|
autoReview: !!labels,
|
|
8268
8261
|
...(preparedPrBody.truncated ? { prBodyTruncated: true, prBodyOriginalChars: preparedPrBody.originalChars } : {}),
|