elanous 0.2.25 → 0.2.27
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-37fd16f479f5554a.js → 1454-a9c338f5bb556f42.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-9360b1285b2a346e.js → page-a1ce306364f08da9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-07110da72c45e68f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2d76d4a7eae91bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-30007380f2e2a3c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e5a9302c105725a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-b93c04b5311c97ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-718a49e86bf25f9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-44eaad486bc98a45.js → page-20662900d6b4b455.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-97fa6cf3d34c7eb9.js → page-f707a509515ac50c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-6a16a48d1aa6e91f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-fcb06e305f515527.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-6acf5ec4e6c6d1af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-39f82acf619cca23.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-7c2c8f81b185f9c0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ae7aff07e32676bb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-482c60c1bc540d0a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-7cb996c1aa08b6f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-623e08fe4477d606.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-db460cd8aecb8490.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-ff27902d0b11ef4f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-74366ae2693a7d95.js → page-8397a5c6ca569bf4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-1917ea87ea71ab6b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-25a302273b054fd7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-670510eda8b9443f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d73c3f1e00d93986.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-65caacf54389081e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4bb7556e2e97a750.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-77e04b4f8762c666.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-b9cc8a10bc6078b4.js → page-15c0a4095baddfba.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-4eb96fc7241c2456.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-63e272a4cd3b6634.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-0d64ff212dc55d03.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-7f096248993fc2d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-e47fc72049149a38.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-f7d808e31e005895.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-5c2b24b5496dad73.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-76cdb531668b5191.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e2b63345bba1a11f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-b008aff96be56654.js → page-d376e0c83edc041a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d6db55fe2c04d128.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-87b88f551f421d8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-dab163b338e3b966.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-852170303c6cd755.js → page-08019efef111ed2e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-0260ffc7eda50db1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-97c7361cc2a19319.js → page-980ce6fe36ad95eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-99c4e6c7345a9156.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-8c6901833f897496.js → page-0ab42e0f05823013.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ad9220422d20ba33.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/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +81 -2
- package/scripts/coord-msg-bridge.ts +209 -0
- package/scripts/coord-post.sh +40 -7
- package/scripts/coord-requests-bridge.ts +10 -5
- package/scripts/expose-rubric-suggest.ts +1 -1
- package/scripts/measure-tsc-affected-roots.ts +10 -1
- package/scripts/release-loop/export-check-node.ts +8 -5
- package/scripts/release-loop/node-verdict.ts +7 -2
- package/src/agent/task-notification.ts +30 -1
- package/src/agent-substrate/review-observation.ts +9 -2
- package/src/agent-substrate/self-review-cli.ts +28 -6
- package/src/boot/daemon-tools/self-implement.ts +72 -4
- package/src/budget/llm-cost.ts +5 -1
- package/src/cli/control-cli.ts +29 -0
- package/src/cli/doctor-cli.ts +90 -8
- package/src/cli/msg-cli.ts +15 -5
- package/src/cli/pr-cli.ts +53 -0
- package/src/cli/pr-land-overlap-evidence.ts +18 -10
- package/src/cli/release-cli.ts +32 -5
- package/src/cli/seat-cli.ts +121 -0
- package/src/cli/self-utterances-cli.ts +140 -0
- package/src/cli/tasks-cli.ts +139 -9
- package/src/context-card/cell.ts +40 -0
- package/src/dashboard/boot-timing.ts +35 -0
- package/src/dashboard/index.ts +86 -24
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
- package/src/debug/log.ts +1 -1
- package/src/domains/release-tool.ts +29 -12
- package/src/domains/surface-events.ts +16 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/fleet/host-doctor.ts +73 -2
- package/src/graph-runner/runner.ts +3 -1
- package/src/harness/harness-cli-command.ts +46 -4
- package/src/harness/harness-stop.ts +11 -7
- package/src/index.ts +35 -4
- package/src/instance/current.ts +2 -6
- package/src/instance/resolve.ts +5 -6
- package/src/llm/openai-sampling-fields.ts +89 -0
- package/src/llm/runtime-model-family.ts +61 -0
- package/src/llm/zai-coding-plan.ts +27 -1
- package/src/llm.ts +13 -89
- package/src/machines/machine-ledger.ts +31 -3
- package/src/maturity/cli-maturity.ts +13 -3
- package/src/msg/msg-store.ts +69 -7
- package/src/ops/overview-collect.ts +3 -2
- package/src/ops/overview-owner.ts +29 -0
- package/src/plugin-hooks/shell-hook.ts +17 -2
- package/src/plugin-missions/evaluator-runner.ts +18 -2
- package/src/plugins/install/plugin-install.ts +11 -1
- package/src/prompt-enhance/enhance.ts +11 -0
- package/src/release-loop/checklist.ts +41 -4
- package/src/release-loop/feature-store.ts +10 -9
- package/src/seat-loop/seat-loop.ts +5 -1
- package/src/self-dev/draft-sweep.ts +23 -14
- package/src/self-dev/orchestrate.ts +6 -2
- package/src/self-dev/run-supervisor.ts +35 -1
- package/src/self-implement/goal-author.ts +3 -27
- package/src/self-implement/goal-command-execution.ts +26 -0
- package/src/self-implement/orchestrator.ts +2 -1
- package/src/self-implement/pwa-import-graph.ts +194 -7
- package/src/self-implement/self-implement-runtime.ts +126 -21
- package/src/session/index.ts +14 -4
- package/src/session-runtime/index.ts +55 -11
- package/src/task-agent/cover.ts +28 -9
- package/src/task-agent/live-moves.ts +14 -6
- package/src/task-agent/rejudge-on-head.ts +220 -23
- package/src/task-agent/review-result-capture.ts +64 -1
- package/src/task-agent/shadow.ts +137 -22
- package/src/task-agent/task-hand.ts +18 -2
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +1 -0
- package/src/tool-runtime/tool-search-runtime.ts +25 -0
- package/src/tui.ts +20 -1
- package/src/user-config.ts +3 -59
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12149c51f7d04b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-dea4b795741126fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6a14333d1b1b3967.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8c91a9a66c9ec90a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-89cb33bced824e4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-05a4b2c9b91034aa.js +0 -1
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → Ev2jivDMTSoEc-5rIXdcy}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → Ev2jivDMTSoEc-5rIXdcy}/_ssgManifest.js +0 -0
|
@@ -36,7 +36,7 @@ const walWait = new Int32Array(new SharedArrayBuffer(4));
|
|
|
36
36
|
const SCHEMA_COLUMNS: Record<string, readonly string[]> = {
|
|
37
37
|
imported_versions: ['json_hash', 'imported_at'],
|
|
38
38
|
events: ['reason', 'generation'],
|
|
39
|
-
assignments: ['priority', 'predecessors', 'deadline_version', 'ceo_minutes', 'ceo_date', 'accelerator', 'refs'],
|
|
39
|
+
assignments: ['priority', 'predecessors', 'deadline_version', 'ceo_minutes', 'ceo_date', 'accelerator', 'refs', 'dispatch_kind'],
|
|
40
40
|
release_schedules: ['freeze_from', 'freeze_until', 'publish_at'],
|
|
41
41
|
};
|
|
42
42
|
function schemaCurrent(db: Database): boolean {
|
|
@@ -81,7 +81,7 @@ function open(root = releaseLedgerRoot()): Database {
|
|
|
81
81
|
if (!eventColumns.some((column) => column.name === 'generation')) db.exec('ALTER TABLE events ADD COLUMN generation INTEGER');
|
|
82
82
|
db.exec('CREATE TABLE IF NOT EXISTS release_schedules (version TEXT PRIMARY KEY, cut_at TEXT NOT NULL, land_by TEXT, updated_at TEXT NOT NULL, updated_by TEXT NOT NULL)');
|
|
83
83
|
const assignmentColumns = db.query('PRAGMA table_info(assignments)').all() as Array<{ name: string }>;
|
|
84
|
-
for (const [name, sql] of [['priority', 'TEXT'], ['predecessors', 'TEXT'], ['deadline_version', 'TEXT'], ['ceo_minutes', 'INTEGER'], ['ceo_date', 'TEXT'], ['accelerator', 'INTEGER'], ['refs', 'TEXT']] as const) {
|
|
84
|
+
for (const [name, sql] of [['priority', 'TEXT'], ['predecessors', 'TEXT'], ['deadline_version', 'TEXT'], ['ceo_minutes', 'INTEGER'], ['ceo_date', 'TEXT'], ['accelerator', 'INTEGER'], ['refs', 'TEXT'], ['dispatch_kind', 'TEXT']] as const) {
|
|
85
85
|
if (!assignmentColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE assignments ADD COLUMN ${name} ${sql}`);
|
|
86
86
|
}
|
|
87
87
|
const scheduleColumns = db.query('PRAGMA table_info(release_schedules)').all() as Array<{ name: string }>;
|
|
@@ -178,12 +178,13 @@ function evidenceRefs(db: Database, version: string, id: string): string[] {
|
|
|
178
178
|
return (db.query('SELECT ref FROM evidence WHERE feature_id = ? AND version = ? ORDER BY rowid').all(id, version) as Array<{ ref: string }>).map((row) => row.ref);
|
|
179
179
|
}
|
|
180
180
|
function items(db: Database, version: string): ChecklistItem[] {
|
|
181
|
-
const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.owner, a.kind, a.priority, a.predecessors, a.deadline_version, a.ceo_minutes, a.ceo_date, a.accelerator, a.refs, a.status, a.disposition, a.evidence, a.updated_at, a.updated_by
|
|
181
|
+
const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.owner, a.kind, a.dispatch_kind, a.priority, a.predecessors, a.deadline_version, a.ceo_minutes, a.ceo_date, a.accelerator, a.refs, a.status, a.disposition, a.evidence, a.updated_at, a.updated_by
|
|
182
182
|
FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.version = ? ORDER BY a.rowid`).all(version) as Array<Record<string, string | number | null>>;
|
|
183
183
|
return rows.map((r) => {
|
|
184
184
|
const evidence = [r.evidence, ...evidenceRefs(db, version, r.id as string)].filter((value) => value !== null).join('\n');
|
|
185
185
|
return { id: r.id as string, title: r.title as string, status: r.status as ChecklistItem['status'],
|
|
186
186
|
...(r.owner !== null ? { owner: r.owner as string } : {}), ...(r.kind !== null ? { kind: r.kind as ChecklistItem['kind'] } : {}),
|
|
187
|
+
...(r.dispatch_kind !== null ? { dispatchKind: r.dispatch_kind as ChecklistItem['dispatchKind'] } : {}),
|
|
187
188
|
...(r.priority !== null ? { priority: r.priority as ChecklistItem['priority'] } : {}), ...(r.predecessors !== null ? { predecessors: JSON.parse(r.predecessors as string) as string[] } : {}),
|
|
188
189
|
...(r.deadline_version !== null ? { deadlineVersion: r.deadline_version as string } : {}),
|
|
189
190
|
...(r.ceo_minutes !== null ? { ceoMinutes: r.ceo_minutes as number } : {}), ...(r.ceo_date !== null ? { ceoDate: r.ceo_date as string } : {}),
|
|
@@ -202,9 +203,9 @@ function putItem(db: Database, version: string, item: ChecklistItem): void {
|
|
|
202
203
|
if (!feature) db.query('INSERT INTO features (id, title, owner, kind, created_at) VALUES (?, ?, ?, ?, ?)').run(item.id, item.title, item.owner ?? null, item.kind ?? null, item.updatedAt);
|
|
203
204
|
// features.owner/kind follow the latest assignment so details() and the checklist never disagree after a set.
|
|
204
205
|
else if (item.owner !== undefined || item.kind !== undefined) db.query('UPDATE features SET owner = COALESCE(?, owner), kind = COALESCE(?, kind) WHERE id = ?').run(item.owner ?? null, item.kind ?? null, item.id);
|
|
205
|
-
db.query(`INSERT INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, refs, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
206
|
-
ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, priority=excluded.priority, predecessors=excluded.predecessors, deadline_version=excluded.deadline_version, ceo_minutes=excluded.ceo_minutes, ceo_date=excluded.ceo_date, accelerator=excluded.accelerator, refs=excluded.refs, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
207
|
-
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.refs?.length ? JSON.stringify(item.refs) : null, item.updatedAt, item.updatedBy);
|
|
206
|
+
db.query(`INSERT INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, dispatch_kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, refs, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
207
|
+
ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, dispatch_kind=excluded.dispatch_kind, priority=excluded.priority, predecessors=excluded.predecessors, deadline_version=excluded.deadline_version, ceo_minutes=excluded.ceo_minutes, ceo_date=excluded.ceo_date, accelerator=excluded.accelerator, refs=excluded.refs, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
208
|
+
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.dispatchKind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.refs?.length ? JSON.stringify(item.refs) : null, item.updatedAt, item.updatedBy);
|
|
208
209
|
}
|
|
209
210
|
function jsonHash(contents: string | Buffer): string { return createHash('sha256').update(contents).digest('hex'); }
|
|
210
211
|
|
|
@@ -245,8 +246,8 @@ function importOnDb(db: Database, version: string): boolean {
|
|
|
245
246
|
// Keep a legacy per-version title until an explicit retitle unifies the feature identity.
|
|
246
247
|
if (!existing) putItem(db, version, item);
|
|
247
248
|
else {
|
|
248
|
-
db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, refs, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
249
|
-
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.refs?.length ? JSON.stringify(item.refs) : null, item.updatedAt, item.updatedBy);
|
|
249
|
+
db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, dispatch_kind, priority, predecessors, deadline_version, ceo_minutes, ceo_date, accelerator, refs, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
250
|
+
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.dispatchKind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.ceoMinutes ?? null, item.ceoDate ?? null, item.accelerator === true ? 1 : null, item.refs?.length ? JSON.stringify(item.refs) : null, item.updatedAt, item.updatedBy);
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
253
|
for (const entry of data.history) record(db, version, entry);
|
|
@@ -273,7 +274,7 @@ function importOnDb(db: Database, version: string): boolean {
|
|
|
273
274
|
record(db, version, { at: new Date().toISOString(), by: item.updatedBy, id: item.id, field: 'reimport', from: null, to: item, released: data.released, dev: data.dev });
|
|
274
275
|
continue;
|
|
275
276
|
}
|
|
276
|
-
const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator', 'refs'] as const;
|
|
277
|
+
const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind', 'dispatchKind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator', 'refs'] as const;
|
|
277
278
|
const baseEvidence = (db.query('SELECT evidence FROM assignments WHERE feature_id = ? AND version = ?').get(item.id, version) as { evidence: string | null }).evidence ?? undefined;
|
|
278
279
|
const preserveRefs = item.evidence === baseEvidence || item.evidence === current.evidence;
|
|
279
280
|
const differs = fields.some((field) => field === 'evidence'
|
|
@@ -85,7 +85,7 @@ export type SeatDeps = {
|
|
|
85
85
|
/** Release schedules (version · cutAt) — a version is open while its cut is still ahead. Default reads the release ledger; unreadable ⇒ no checklist candidates (fail closed). */
|
|
86
86
|
schedules?: () => Array<{ version: string; cutAt: string; landBy?: string | null }>;
|
|
87
87
|
/** Checklist items of one version — default reads the release ledger DB (checklist.json is only a legacy import source since REL5b). */
|
|
88
|
-
checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null; disposition?: string; evidence?: string }>;
|
|
88
|
+
checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null; disposition?: string; evidence?: string; dispatchKind?: string }>;
|
|
89
89
|
/** Optional injected traffic/handoff snapshots; defaults read the OP files. */
|
|
90
90
|
trafficSignal?: TrafficSignalRead;
|
|
91
91
|
handedCells?: ReadonlySet<string>;
|
|
@@ -588,6 +588,10 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}, ledger
|
|
|
588
588
|
}
|
|
589
589
|
for (const item of items) {
|
|
590
590
|
if (item.owner === seat && (item.status === 'yellow' || item.status === 'red')) {
|
|
591
|
+
if (item.dispatchKind && item.dispatchKind !== 'code') {
|
|
592
|
+
debug.log('seat.loop', 'skip-non-code-cell', { id: item.id, version, dispatchKind: item.dispatchKind });
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
591
595
|
if (signal?.hold?.includes(item.id)) {
|
|
592
596
|
debug.log('seat.loop', 'skip-signal-cell', { id: item.id, why: 'hold' });
|
|
593
597
|
continue;
|
|
@@ -33,7 +33,7 @@ export interface DraftSweepAdapters {
|
|
|
33
33
|
/** Undefined means liveness could not be established. */
|
|
34
34
|
listLiveBranches(repository: string): Promise<ReadonlySet<string> | undefined>;
|
|
35
35
|
setLabels(repository: string, number: number, change: { add: string; remove: readonly string[] }): Promise<void>;
|
|
36
|
-
closeDraft(repository: string, number: number
|
|
36
|
+
closeDraft(repository: string, number: number): Promise<void>;
|
|
37
37
|
/** Resolve the latest claim comment owner; a failed lookup must not authorize a close. */
|
|
38
38
|
getClaimOwner?(repository: string, number: number): Promise<string | undefined>;
|
|
39
39
|
/** Current owner, accounting for a later claim release; only draft metrics need this distinction. */
|
|
@@ -478,6 +478,23 @@ const label = (action: 'mark-stalled' | 'close', name?: string): string => {
|
|
|
478
478
|
const runningLabel = label('mark-stalled');
|
|
479
479
|
const stalledLabel = label('close', 'stalled');
|
|
480
480
|
const supersededLabel = label('close', 'superseded');
|
|
481
|
+
|
|
482
|
+
/** Keep repository labels bounded; the complete reason and PR number live in the closed event. */
|
|
483
|
+
export function sweptReasonLabel(reason: string): string {
|
|
484
|
+
const kinds = ['superseded-by', 'duplicate-of-open', 'claim-expired', 'stale-unobserved',
|
|
485
|
+
'harvest-expired', 'stale-ended-run'] as const;
|
|
486
|
+
const kind = kinds.find((name) => reason === name || reason.startsWith(`${name} `)) ?? 'other';
|
|
487
|
+
return `elanous:swept:${kind}`;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function closeSweptDraft(adapters: Pick<DraftSweepAdapters, 'setLabels' | 'closeDraft'>,
|
|
491
|
+
repository: string, number: number, reason: string, onLabelApplied?: () => void): Promise<void> {
|
|
492
|
+
await adapters.setLabels(repository, number, { add: sweptReasonLabel(reason), remove: [] });
|
|
493
|
+
onLabelApplied?.();
|
|
494
|
+
await adapters.closeDraft(repository, number);
|
|
495
|
+
try { debug.log('self-dev.draft-sweep', 'closed', { pr: number, reason }); } catch { /* fail-soft */ }
|
|
496
|
+
}
|
|
497
|
+
|
|
481
498
|
const approvalLabel = PR_LABELS.find((entry) => entry.axis === 'state' && entry.sweep.action === 'none')?.name;
|
|
482
499
|
const keepLabel = PR_LABELS.find((entry) => entry.sweep.action === 'exclude')?.name;
|
|
483
500
|
const benchLabel = PR_LABELS.find((entry) => entry.name === 'elanous:bench')!.name;
|
|
@@ -572,8 +589,7 @@ export async function supersedeDraftsOnMerge(input: {
|
|
|
572
589
|
try {
|
|
573
590
|
// Label first: a failed label write must never strand a closed, unlabelled PR outside open-draft sweeps.
|
|
574
591
|
if (!draft.labels.includes(supersededLabel)) await adapters.setLabels(repository, draft.number, { add: supersededLabel, remove });
|
|
575
|
-
await adapters
|
|
576
|
-
`Draft sweep: superseded-by #${merged.number} (https://github.com/${repository}/pull/${merged.number}). Branch preserved.`);
|
|
592
|
+
await closeSweptDraft(adapters, repository, draft.number, decision.reason);
|
|
577
593
|
closed.push(draft.number);
|
|
578
594
|
} catch (failure) {
|
|
579
595
|
kept.push(draft.number);
|
|
@@ -975,8 +991,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
975
991
|
});
|
|
976
992
|
labelsChanged = true;
|
|
977
993
|
}
|
|
978
|
-
await adapters
|
|
979
|
-
`Draft sweep: ${reason} (https://github.com/${repository}/pull/${lineageNewer.number}). Branch preserved.`);
|
|
994
|
+
await closeSweptDraft(adapters, repository, draft.number, reason, () => { labelsChanged = true; });
|
|
980
995
|
entry.applied = true;
|
|
981
996
|
} catch (error) {
|
|
982
997
|
if (labelsChanged) entry.partialApplied = true;
|
|
@@ -1035,11 +1050,10 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
1035
1050
|
if (!apply || action !== 'close') continue;
|
|
1036
1051
|
let labelsChanged = false;
|
|
1037
1052
|
try {
|
|
1038
|
-
|
|
1053
|
+
await adapters.getClaimOwner?.(repository, draft.number);
|
|
1039
1054
|
await adapters.setLabels(repository, draft.number, { add: stalledLabel, remove: [runningLabel] });
|
|
1040
1055
|
labelsChanged = true;
|
|
1041
|
-
await adapters
|
|
1042
|
-
`Draft sweep: 처리 중 표식이 ${CLAIM_IDLE_HOURS}시간 갱신 없음 — ${runStatus === 'self-implement.result final' ? 'self-implement.result final · 남은 워크트리는 live 아님' : '런·워크트리 없음'} · 주인 ${owner}. Closed as stalled. Branch preserved; reopen to restore.`);
|
|
1056
|
+
await closeSweptDraft(adapters, repository, draft.number, entry.reason, () => { labelsChanged = true; });
|
|
1043
1057
|
entry.applied = true;
|
|
1044
1058
|
} catch (error) {
|
|
1045
1059
|
if (labelsChanged) entry.partialApplied = true;
|
|
@@ -1085,12 +1099,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
1085
1099
|
labelsChanged = true;
|
|
1086
1100
|
}
|
|
1087
1101
|
if (action === 'close') {
|
|
1088
|
-
await adapters
|
|
1089
|
-
? `Draft sweep: run unobserved — no run record reachable here, no host worktree for this branch, idle ≥${STALLED_DRAFT_HOURS}h. Closed as stalled. Branch preserved; reopen to restore.`
|
|
1090
|
-
: decision.reason === 'harvest-expired'
|
|
1091
|
-
? `Draft sweep: harvest-expired — 종단이 수확 대상으로 표시한 뒤 ${STALLED_DRAFT_HOURS}h 동안 수확 없음. Branch preserved; reopen to restore.`
|
|
1092
|
-
: `Draft sweep: ${decision.reason}${decision.reason.startsWith('superseded-by #')
|
|
1093
|
-
? ` (https://github.com/${repository}/pull/${decision.reason.slice('superseded-by #'.length).match(/^\d+/)![0]})` : ''}. Branch preserved.`);
|
|
1102
|
+
await closeSweptDraft(adapters, repository, draft.number, reason, () => { labelsChanged = true; });
|
|
1094
1103
|
}
|
|
1095
1104
|
entry.applied = labelsChanged || action === 'close';
|
|
1096
1105
|
} catch (error) {
|
|
@@ -23,6 +23,7 @@ import { parentUnlandedProgressLine } from './run-supervisor.js';
|
|
|
23
23
|
import { mkdirSync, openSync, writeFileSync, closeSync, renameSync, unlinkSync } from 'node:fs';
|
|
24
24
|
import { join, resolve } from 'node:path';
|
|
25
25
|
import { goalPathFromArgv, launchRegistryDir, removeLaunchBinding, writeLaunchBinding } from '../harness/launch-registry.js';
|
|
26
|
+
import { psProcessStartMs } from '../harness/harness-stop.js';
|
|
26
27
|
import { adoptRunCard } from '../task-agent/run-adopt.js';
|
|
27
28
|
import { availableParallelism, cpus } from 'node:os';
|
|
28
29
|
import { selfDevRunsDir } from './run-store.js';
|
|
@@ -912,12 +913,15 @@ async function deleteLabelledJobs(runId: string, selectedTargets: readonly { con
|
|
|
912
913
|
* take minutes before that, and a stop in that window found «no process» (10-01 · three runs). */
|
|
913
914
|
export function writeRunPidRecord(runId: string, runsDir: string = selfDevRunsDir(), registryDir: string | null = launchRegistryDir()): string {
|
|
914
915
|
const pidPath = join(runsDir, runId, 'pid.json');
|
|
916
|
+
// The observer reads `ps -o lstart=`, whose precision is one second. Record that same
|
|
917
|
+
// process birth value when available so a launch near a second boundary still matches.
|
|
918
|
+
const startedAt = psProcessStartMs(process.pid) ?? Date.now() - Math.floor(process.uptime() * 1000);
|
|
915
919
|
mkdirSync(join(runsDir, runId), { recursive: true });
|
|
916
920
|
const temporary = `${pidPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
917
921
|
const fd = openSync(temporary, 'wx', 0o600);
|
|
918
922
|
try {
|
|
919
923
|
try {
|
|
920
|
-
writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt
|
|
924
|
+
writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt, argv0: process.argv0 }));
|
|
921
925
|
} finally { closeSync(fd); }
|
|
922
926
|
renameSync(temporary, pidPath);
|
|
923
927
|
} catch (error) { unlinkSync(temporary); throw error; }
|
|
@@ -925,7 +929,7 @@ export function writeRunPidRecord(runId: string, runsDir: string = selfDevRunsDi
|
|
|
925
929
|
if (registryDir !== null) {
|
|
926
930
|
const cwd = process.cwd();
|
|
927
931
|
const goalPath = goalPathFromArgv(process.argv, cwd);
|
|
928
|
-
writeLaunchBinding(runId, { pid: process.pid, startedAt
|
|
932
|
+
writeLaunchBinding(runId, { pid: process.pid, startedAt, argv0: process.argv0,
|
|
929
933
|
cwd, ...(goalPath ? { goalPath } : {}), runsDir: resolve(runsDir),
|
|
930
934
|
...(process.env.ELANOUS_POD_NAME ? { podName: process.env.ELANOUS_POD_NAME } : {}),
|
|
931
935
|
...(process.env.ELANOUS_TASK_CARD_ID ? { taskCardId: process.env.ELANOUS_TASK_CARD_ID } : {}) }, registryDir);
|
|
@@ -9,7 +9,8 @@ import { recordSelfDevRunSupervisorStop, selfDevRunsDir } from './run-store.js';
|
|
|
9
9
|
import { debug } from '../debug/log.js';
|
|
10
10
|
import { sweepFrozenMerges } from '../self-implement/frozen-merges.js';
|
|
11
11
|
import { routeSupervisorVerdict } from './supervisor-verdict-edges.js';
|
|
12
|
-
import { recordTaskAgentShadowMove, type TaskAgentShadowInput } from '../task-agent/shadow.js';
|
|
12
|
+
import { recordTaskAgentShadowMove, runCardFor, type TaskAgentShadowInput } from '../task-agent/shadow.js';
|
|
13
|
+
import { cardPrNumber, taskAgentStatePath } from '../task-agent/task-hand.js';
|
|
13
14
|
|
|
14
15
|
export interface SupervisorRound {
|
|
15
16
|
round: number;
|
|
@@ -641,6 +642,12 @@ export interface SuperviseRunOptions {
|
|
|
641
642
|
* `false` 면 끈다. 던져도 런의 결말은 바뀌지 않는다(fail-soft).
|
|
642
643
|
*/
|
|
643
644
|
taskAgentShadow?: ((input: TaskAgentShadowInput) => unknown) | false;
|
|
645
|
+
/** TA-LAND-AFTER-REVIEW — false disables the hook; function replaces only the final rejudge for isolated tests. */
|
|
646
|
+
taskAgentRejudge?: ((cardId: string, statePath: string) => Promise<unknown>) | false;
|
|
647
|
+
/** Test-only external I/O for the real completion rejudge. The decision and shadow gate remain production code. */
|
|
648
|
+
taskAgentRejudgeExternal?: Parameters<typeof import('../task-agent/rejudge-on-head.js').rejudgeAsyncResultCard>[2];
|
|
649
|
+
/** Isolated state ledger for the terminal rejudge test; production uses the active instance. */
|
|
650
|
+
taskAgentStatePath?: string;
|
|
644
651
|
observeDeliverables?: () => Promise<{
|
|
645
652
|
readonly deployFindings: ReadonlyMap<string, { target: string; findings?: readonly DeployVerifyFinding[] | readonly unknown[] }>;
|
|
646
653
|
readonly unmeasured: readonly unknown[];
|
|
@@ -658,6 +665,23 @@ function resolveSupervisorStopStorage(runStore: SuperviseRunOptions['runStore'])
|
|
|
658
665
|
return { path: selfDevRunsDir(), source: 'process-default' };
|
|
659
666
|
}
|
|
660
667
|
|
|
668
|
+
/** The supervisor's terminal hook, limited to a uniquely run-bound TA land card. */
|
|
669
|
+
export async function rejudgeTerminalTaskCard(runId: string | null, deps: {
|
|
670
|
+
statePath?: string;
|
|
671
|
+
env?: NodeJS.ProcessEnv;
|
|
672
|
+
rejudgeCard?: (cardId: string, statePath: string) => Promise<unknown>;
|
|
673
|
+
external?: Parameters<typeof import('../task-agent/rejudge-on-head.js').rejudgeAsyncResultCard>[2];
|
|
674
|
+
} = {}): Promise<void> {
|
|
675
|
+
const statePath = deps.statePath ?? taskAgentStatePath();
|
|
676
|
+
const card = runCardFor(runId, { statePath, ...(deps.env ? { env: deps.env } : {}) });
|
|
677
|
+
if (!card || card.status !== 'launched' || card.taLand !== true || cardPrNumber(card) === undefined) return;
|
|
678
|
+
const { rejudgeAsyncResultCard } = await import('../task-agent/rejudge-on-head.js');
|
|
679
|
+
const result = await (deps.rejudgeCard ?? ((id, path) => rejudgeAsyncResultCard(id, path, deps.external)))(card.id, statePath);
|
|
680
|
+
const { requireSuccessfulRejudge } = await import('../task-agent/rejudge-on-head.js');
|
|
681
|
+
requireSuccessfulRejudge(result);
|
|
682
|
+
try { debug.log('task-agent', 'terminal-rejudge', { runId, card: card.id }); } catch { /* fail-soft */ }
|
|
683
|
+
}
|
|
684
|
+
|
|
661
685
|
export async function superviseRun(opts: SuperviseRunOptions): Promise<SupervisorJobResult[]> {
|
|
662
686
|
let results = [...opts.initial];
|
|
663
687
|
let history: SupervisorRound[] = [];
|
|
@@ -808,6 +832,16 @@ export async function superviseRun(opts: SuperviseRunOptions): Promise<Superviso
|
|
|
808
832
|
} catch (error) {
|
|
809
833
|
try { debug.log('task-agent', 'shadow-move-failed', { runId, stopReason: decision.stopReason, error: String(error) }); } catch { /* fail-open */ }
|
|
810
834
|
}
|
|
835
|
+
if (opts.taskAgentRejudge !== false) {
|
|
836
|
+
const rejudgeOptions = { ...(opts.taskAgentStatePath ? { statePath: opts.taskAgentStatePath } : {}), ...(opts.taskAgentRejudge ? { rejudgeCard: opts.taskAgentRejudge } : {}), ...(opts.taskAgentRejudgeExternal ? { external: opts.taskAgentRejudgeExternal } : {}) };
|
|
837
|
+
try { await rejudgeTerminalTaskCard(runId, rejudgeOptions); }
|
|
838
|
+
catch (error) {
|
|
839
|
+
try { await rejudgeTerminalTaskCard(runId, rejudgeOptions); }
|
|
840
|
+
catch (retryError) {
|
|
841
|
+
try { debug.log('task-agent', 'terminal-rejudge-failed', { runId, stopReason: decision.stopReason, error: String(retryError), firstError: String(error), attempts: 2 }); } catch { /* fail-open */ }
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
811
845
|
}
|
|
812
846
|
return results;
|
|
813
847
|
}
|
|
@@ -51,6 +51,9 @@ import { hasGoalStepCodeName } from './goal-step-code-name.js';
|
|
|
51
51
|
import { targetScopedGoalText } from './goal-text-path-scope.js';
|
|
52
52
|
import { parseReleaseNoteSection } from '../release-loop/release-note.js';
|
|
53
53
|
import { declaredNewFilePaths } from './goal-new-paths.js';
|
|
54
|
+
import { classifyGoalCommandExecution, type GoalCommandExecutionResult } from './goal-command-execution.js';
|
|
55
|
+
export { classifyGoalCommandExecution } from './goal-command-execution.js';
|
|
56
|
+
export type { GoalCommandExecutionKind, GoalCommandExecutionResult, GoalCommandExecutionClassification } from './goal-command-execution.js';
|
|
54
57
|
|
|
55
58
|
// ⭐ `situation`/`complication` 은 **선택**이다 — 안 오면 `scqaNarrative` 가 종전 문면을 쓴다.
|
|
56
59
|
// ⛔ 필수로 만들지 않는다: 그러면 인핸싱이 폴백으로 떨어진 저작(LLM 실패·`disabled`)이 전부 죽는다.
|
|
@@ -439,33 +442,6 @@ export function canonicalStructureFinding(
|
|
|
439
442
|
return finding;
|
|
440
443
|
}
|
|
441
444
|
|
|
442
|
-
export type GoalCommandExecutionKind = 'success' | 'missing-command' | 'argument-problem' | 'execution-failure';
|
|
443
|
-
|
|
444
|
-
export interface GoalCommandExecutionResult {
|
|
445
|
-
readonly status: number | null;
|
|
446
|
-
readonly stderr: string;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
export interface GoalCommandExecutionClassification {
|
|
450
|
-
readonly kind: GoalCommandExecutionKind;
|
|
451
|
-
readonly log: string;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/** Classifies an already-observed Commander result without executing a command. */
|
|
455
|
-
export function classifyGoalCommandExecution(result: GoalCommandExecutionResult): GoalCommandExecutionClassification {
|
|
456
|
-
// A clean exit is authoritative: stderr text from a successful command is not a failure signal.
|
|
457
|
-
if (result.status === 0) return { kind: 'success', log: 'command completed successfully' };
|
|
458
|
-
|
|
459
|
-
const stderr = result.stderr;
|
|
460
|
-
if (stderr.includes('unknown command') || stderr.includes('unknown option')) {
|
|
461
|
-
return { kind: 'missing-command', log: 'command is not recognized; this may be not yet implemented rather than a defect' };
|
|
462
|
-
}
|
|
463
|
-
if (stderr.includes('missing required argument') || stderr.includes('required option')) {
|
|
464
|
-
return { kind: 'argument-problem', log: 'command requires a missing argument or option' };
|
|
465
|
-
}
|
|
466
|
-
return { kind: 'execution-failure', log: `command execution failed with code ${String(result.status)}` };
|
|
467
|
-
}
|
|
468
|
-
|
|
469
445
|
/** Optional repository-bounded source reader. Legacy null means the referenced path is missing. */
|
|
470
446
|
export interface GoalFileLintDeps {
|
|
471
447
|
readReferencedFile?: (repositoryRelativePath: string) => string | null | ReferencedFileReadResult;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type GoalCommandExecutionKind = 'success' | 'missing-command' | 'argument-problem' | 'execution-failure';
|
|
2
|
+
|
|
3
|
+
export interface GoalCommandExecutionResult {
|
|
4
|
+
readonly status: number | null;
|
|
5
|
+
readonly stderr: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface GoalCommandExecutionClassification {
|
|
9
|
+
readonly kind: GoalCommandExecutionKind;
|
|
10
|
+
readonly log: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Classifies an already-observed Commander result without executing a command. */
|
|
14
|
+
export function classifyGoalCommandExecution(result: GoalCommandExecutionResult): GoalCommandExecutionClassification {
|
|
15
|
+
// A clean exit is authoritative: stderr text from a successful command is not a failure signal.
|
|
16
|
+
if (result.status === 0) return { kind: 'success', log: 'command completed successfully' };
|
|
17
|
+
|
|
18
|
+
const stderr = result.stderr;
|
|
19
|
+
if (stderr.includes('unknown command') || stderr.includes('unknown option')) {
|
|
20
|
+
return { kind: 'missing-command', log: 'command is not recognized; this may be not yet implemented rather than a defect' };
|
|
21
|
+
}
|
|
22
|
+
if (stderr.includes('missing required argument') || stderr.includes('required option')) {
|
|
23
|
+
return { kind: 'argument-problem', log: 'command requires a missing argument or option' };
|
|
24
|
+
}
|
|
25
|
+
return { kind: 'execution-failure', log: `command execution failed with code ${String(result.status)}` };
|
|
26
|
+
}
|
|
@@ -3647,7 +3647,8 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
|
|
|
3647
3647
|
try {
|
|
3648
3648
|
// 10-05 PODPROVIDER: record which child provider this run uses — «default» when the launch named none.
|
|
3649
3649
|
const launch = opts.launch ?? resolveLaunchStamp();
|
|
3650
|
-
const origin = { ...runOriginData(), childProvider: opts.childLlm?.provider ?? 'default', ...(opts.childLlm?.model ? { childModel: opts.childLlm.model } : {}),
|
|
3650
|
+
const origin = { ...runOriginData(), childProvider: opts.childLlm?.provider ?? 'default', ...(opts.childLlm?.model ? { childModel: opts.childLlm.model } : {}),
|
|
3651
|
+
...(process.env.ELANOUS_TASK_CARD_ID ? { taskCardId: process.env.ELANOUS_TASK_CARD_ID } : {}), launch };
|
|
3651
3652
|
(opts.seams.writeRunLedger ?? appendRunLedgerEntry)({
|
|
3652
3653
|
timestamp: new Date().toISOString(), runId, event: 'run-origin', data: origin,
|
|
3653
3654
|
...(opts.goalId ? { goalId: opts.goalId } : {}), ...shardIdentity,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { readdirSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
3
5
|
|
|
4
6
|
type GraphFiles = {
|
|
5
7
|
readFile?: (path: string) => string;
|
|
@@ -24,6 +26,16 @@ function sourceFiles(cwd: string): string[] {
|
|
|
24
26
|
return files;
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
const IMPORT_SPECIFIER = /\b(?:import|export)\s+(?:[^;'"]*?\s+from\s*)?['"](\.[^'"]+)['"]|\bimport\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
|
|
30
|
+
|
|
31
|
+
/** Repo-relative file candidates a relative import specifier (already joined to its importer's directory) can resolve to. */
|
|
32
|
+
function importCandidates(base: string): string[] {
|
|
33
|
+
return /\.js$/.test(base)
|
|
34
|
+
// A real .js file wins over a same-named .ts source (the actual resolution order).
|
|
35
|
+
? [base, base.replace(/\.js$/, '.ts'), base.replace(/\.js$/, '.tsx'), `${base.slice(0, -3)}/index.ts`, `${base.slice(0, -3)}/index.tsx`, `${base.slice(0, -3)}/index.js`]
|
|
36
|
+
: /\.tsx?$/.test(base) ? [base] : [base + '.ts', base + '.tsx', base + '.js', base + '/index.ts', base + '/index.tsx', base + '/index.js'];
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
/** PWA source entrypoints and the transitive relative imports they resolve inside repository src/. */
|
|
28
40
|
export function pwaReachableSrcFiles(cwd: string, { readFile = (path) => readFileSync(path, 'utf8'), listFiles = sourceFiles }: GraphFiles = {}): Set<string> {
|
|
29
41
|
const root = resolve(cwd);
|
|
@@ -33,7 +45,6 @@ export function pwaReachableSrcFiles(cwd: string, { readFile = (path) => readFil
|
|
|
33
45
|
// Start from everything apps/pwa/tsconfig.json `include`s — its `src/**/*.ts(x)` also matches *.test.* files, and the Next
|
|
34
46
|
// build type-checks them. Excluding tests hid the real entry (tool-result-chain.test.ts → src/nexus/api/meta-api → most of src).
|
|
35
47
|
const queue = [...available].filter((file) => /^apps\/pwa\/src\/.*\.tsx?$/.test(file));
|
|
36
|
-
const imports = /\b(?:import|export)\s+(?:[^;'"]*?\s+from\s*)?['"](\.[^'"]+)['"]|\bimport\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
|
|
37
48
|
while (queue.length) {
|
|
38
49
|
const file = queue.pop()!;
|
|
39
50
|
if (visited.has(file)) continue;
|
|
@@ -42,16 +53,13 @@ export function pwaReachableSrcFiles(cwd: string, { readFile = (path) => readFil
|
|
|
42
53
|
let content: string;
|
|
43
54
|
try { content = readFile(join(root, file)); }
|
|
44
55
|
catch { continue; }
|
|
45
|
-
for (const match of content.matchAll(
|
|
56
|
+
for (const match of content.matchAll(IMPORT_SPECIFIER)) {
|
|
46
57
|
const specifier = match[1] ?? match[2];
|
|
47
58
|
if (!specifier) continue;
|
|
48
59
|
const target = resolve(root, dirname(file), specifier);
|
|
49
60
|
const base = relative(root, target).split(sep).join('/');
|
|
50
61
|
if (!base.startsWith('src/')) continue;
|
|
51
|
-
const candidates =
|
|
52
|
-
// A real .js file wins over a same-named .ts source (the actual resolution order).
|
|
53
|
-
? [base, base.replace(/\.js$/, '.ts'), base.replace(/\.js$/, '.tsx'), `${base.slice(0, -3)}/index.ts`, `${base.slice(0, -3)}/index.tsx`, `${base.slice(0, -3)}/index.js`]
|
|
54
|
-
: /\.tsx?$/.test(base) ? [base] : [base + '.ts', base + '.tsx', base + '.js', base + '/index.ts', base + '/index.tsx', base + '/index.js'];
|
|
62
|
+
const candidates = importCandidates(base);
|
|
55
63
|
const resolved = candidates.find((candidate) => available.has(candidate));
|
|
56
64
|
if (resolved) {
|
|
57
65
|
if (!visited.has(resolved)) queue.push(resolved);
|
|
@@ -63,3 +71,182 @@ export function pwaReachableSrcFiles(cwd: string, { readFile = (path) => readFil
|
|
|
63
71
|
}
|
|
64
72
|
return reached;
|
|
65
73
|
}
|
|
74
|
+
|
|
75
|
+
export type AffectedRoots = { roots: Set<string>; failure?: undefined } | { roots?: undefined; failure: string };
|
|
76
|
+
|
|
77
|
+
function astImportSpecifiers(source: ts.SourceFile): { imports: string[]; ignoredDynamic: number; failure?: string } {
|
|
78
|
+
const parseError = (source as ts.SourceFile & { parseDiagnostics?: readonly ts.Diagnostic[] }).parseDiagnostics?.[0];
|
|
79
|
+
if (parseError) return { imports: [], ignoredDynamic: 0, failure: `${source.fileName} AST 파싱 실패: ${ts.flattenDiagnosticMessageText(parseError.messageText, ' ')}` };
|
|
80
|
+
const imports: string[] = [];
|
|
81
|
+
let ignoredDynamic = 0;
|
|
82
|
+
const literal = (node: ts.Node | undefined): string | undefined => node && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) ? node.text : undefined;
|
|
83
|
+
const visit = (node: ts.Node): void => {
|
|
84
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
85
|
+
const specifier = literal(node.moduleSpecifier);
|
|
86
|
+
if (specifier) imports.push(specifier);
|
|
87
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)) {
|
|
88
|
+
const specifier = literal(node.moduleReference.expression);
|
|
89
|
+
if (specifier) imports.push(specifier);
|
|
90
|
+
} else if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) {
|
|
91
|
+
const specifier = literal(node.argument.literal);
|
|
92
|
+
if (specifier) imports.push(specifier);
|
|
93
|
+
} else if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
94
|
+
|| (ts.isIdentifier(node.expression) && node.expression.text === 'require'))) {
|
|
95
|
+
const specifier = literal(node.arguments[0]);
|
|
96
|
+
if (specifier) imports.push(specifier);
|
|
97
|
+
else ignoredDynamic += 1;
|
|
98
|
+
}
|
|
99
|
+
ts.forEachChild(node, visit);
|
|
100
|
+
};
|
|
101
|
+
visit(source);
|
|
102
|
+
return { imports, ignoredDynamic };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function reExportSpecifiers(source: ts.SourceFile): string[] {
|
|
106
|
+
const importedLocals = new Map<string, string>();
|
|
107
|
+
for (const statement of source.statements) {
|
|
108
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
109
|
+
const from = statement.moduleSpecifier.text;
|
|
110
|
+
const clause = statement.importClause;
|
|
111
|
+
if (clause?.name) importedLocals.set(clause.name.text, from);
|
|
112
|
+
const bindings = clause?.namedBindings;
|
|
113
|
+
if (bindings && ts.isNamespaceImport(bindings)) importedLocals.set(bindings.name.text, from);
|
|
114
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
115
|
+
for (const element of bindings.elements) importedLocals.set(element.name.text, from);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const specifiers: string[] = [];
|
|
119
|
+
for (const statement of source.statements) {
|
|
120
|
+
if (ts.isExportDeclaration(statement)) {
|
|
121
|
+
if (statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) specifiers.push(statement.moduleSpecifier.text);
|
|
122
|
+
else if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
|
|
123
|
+
for (const element of statement.exportClause.elements) {
|
|
124
|
+
const from = importedLocals.get((element.propertyName ?? element.name).text);
|
|
125
|
+
if (from) specifiers.push(from);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else if (ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression)) {
|
|
129
|
+
const from = importedLocals.get(statement.expression.text);
|
|
130
|
+
if (from) specifiers.push(from);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return specifiers;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Declarations visible without an import edge must enter every scoped program. */
|
|
137
|
+
function suppliesAmbientTypes(source: ts.SourceFile): boolean {
|
|
138
|
+
if (source.fileName.endsWith('.d.ts') || !ts.isExternalModule(source)) return true;
|
|
139
|
+
const visit = (node: ts.Node): boolean => {
|
|
140
|
+
if (ts.isModuleDeclaration(node) && (node.name.text === 'global' || ts.isStringLiteral(node.name))) return true;
|
|
141
|
+
return ts.forEachChild(node, visit) ?? false;
|
|
142
|
+
};
|
|
143
|
+
return visit(source);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function gateTypeScriptSources(cwd: string): string[] {
|
|
147
|
+
const files: string[] = [];
|
|
148
|
+
const walk = (dir: string): void => {
|
|
149
|
+
// A partial directory walk is not a valid affected-root measurement: an
|
|
150
|
+
// unreadable subtree may contain the only importer of a changed file.
|
|
151
|
+
const entries = readdirSync(join(cwd, dir), { withFileTypes: true });
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
const path = `${dir}/${entry.name}`;
|
|
154
|
+
if (entry.isDirectory() && entry.name !== 'node_modules' && !entry.name.startsWith('.')) walk(path);
|
|
155
|
+
else if (entry.isFile() && /\.ts$/.test(path)) files.push(path);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
for (const root of ['src', 'scripts', 'test']) {
|
|
159
|
+
// An optional include glob may match no directory in a fixture. Only a
|
|
160
|
+
// confirmed ENOENT is empty; permission/I/O errors must fail closed.
|
|
161
|
+
try { statSync(join(cwd, root)); }
|
|
162
|
+
catch (cause) {
|
|
163
|
+
if ((cause as NodeJS.ErrnoException).code === 'ENOENT') continue;
|
|
164
|
+
throw cause;
|
|
165
|
+
}
|
|
166
|
+
walk(root);
|
|
167
|
+
}
|
|
168
|
+
return files;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Changed .ts files plus the listed files that import one of them directly (one reverse-import step).
|
|
173
|
+
* Every changed file and every listed file must be readable — an unreadable file could be a hidden importer, so the
|
|
174
|
+
* result is a failure the caller turns into a full-program run, never a smaller root set.
|
|
175
|
+
*/
|
|
176
|
+
export function reverseImportAffectedRoots(cwd: string, changed: ReadonlySet<string>, { readFile = (path) => readFileSync(path, 'utf8'), listFiles = gateTypeScriptSources }: GraphFiles = {}): AffectedRoots {
|
|
177
|
+
const root = resolve(cwd);
|
|
178
|
+
const normalize = (file: string): string => file.replaceAll('\\', '/').replace(/^\.\//, '');
|
|
179
|
+
const read = (file: string): string | { failure: string } => {
|
|
180
|
+
try { return readFile(join(root, file)); }
|
|
181
|
+
catch (cause) { return { failure: `${file} 을 읽지 못했다: ${cause instanceof Error ? cause.message : String(cause)}` }; }
|
|
182
|
+
};
|
|
183
|
+
let listed: string[];
|
|
184
|
+
try { listed = listFiles(cwd).map(normalize); }
|
|
185
|
+
catch (cause) { return { failure: `소스 목록을 읽지 못했다: ${cause instanceof Error ? cause.message : String(cause)}` }; }
|
|
186
|
+
const available = new Set(listed);
|
|
187
|
+
const targets = new Set([...changed].map(normalize));
|
|
188
|
+
for (const file of targets) {
|
|
189
|
+
const content = read(file);
|
|
190
|
+
if (typeof content !== 'string') return content;
|
|
191
|
+
}
|
|
192
|
+
const importsByFile = new Map<string, Set<string>>();
|
|
193
|
+
const barrelsByTarget = new Map<string, Set<string>>();
|
|
194
|
+
const ambientRoots = new Set<string>();
|
|
195
|
+
let ignoredDynamic = 0;
|
|
196
|
+
const resolveImport = (file: string, specifier: string): { target?: string; ignored?: true; unresolved?: true } => {
|
|
197
|
+
if (!specifier.startsWith('.')) return { ignored: true };
|
|
198
|
+
const base = relative(root, resolve(root, dirname(file), specifier)).split(sep).join('/');
|
|
199
|
+
// The gate's tsconfig includes these roots. A relative import outside them
|
|
200
|
+
// cannot lead back to a changed gate source; asset imports are not TS
|
|
201
|
+
// source edges either.
|
|
202
|
+
if (!/^(?:src|scripts|test)\//.test(base) || /\.(?:css|json|svg|png|jpg|jpeg|webp|wasm)$/.test(base)) return { ignored: true };
|
|
203
|
+
const target = importCandidates(base).find((candidate) => available.has(candidate));
|
|
204
|
+
return target ? { target } : { unresolved: true };
|
|
205
|
+
};
|
|
206
|
+
for (const file of listed) {
|
|
207
|
+
const content = read(file);
|
|
208
|
+
if (typeof content !== 'string') return content;
|
|
209
|
+
let source: ts.SourceFile;
|
|
210
|
+
try { source = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true); }
|
|
211
|
+
catch (cause) { return { failure: `${file} AST 파싱 실패: ${cause instanceof Error ? cause.message : String(cause)}` }; }
|
|
212
|
+
const scanned = astImportSpecifiers(source);
|
|
213
|
+
if (scanned.failure) return { failure: scanned.failure };
|
|
214
|
+
if (suppliesAmbientTypes(source)) ambientRoots.add(file);
|
|
215
|
+
ignoredDynamic += scanned.ignoredDynamic;
|
|
216
|
+
const imports = new Set<string>();
|
|
217
|
+
for (const specifier of scanned.imports) {
|
|
218
|
+
if (!specifier.startsWith('.')) continue;
|
|
219
|
+
const resolved = resolveImport(file, specifier);
|
|
220
|
+
if (resolved.unresolved) return { failure: `${file} 의 리터럴 import ${specifier} 해석 실패` };
|
|
221
|
+
if (resolved.target) imports.add(resolved.target);
|
|
222
|
+
}
|
|
223
|
+
importsByFile.set(file, imports);
|
|
224
|
+
for (const specifier of reExportSpecifiers(source)) {
|
|
225
|
+
const resolved = resolveImport(file, specifier);
|
|
226
|
+
if (resolved.unresolved) return { failure: `${file} 의 리터럴 re-export ${specifier} 해석 실패` };
|
|
227
|
+
if (!resolved.target) continue;
|
|
228
|
+
const barrels = barrelsByTarget.get(resolved.target) ?? new Set<string>();
|
|
229
|
+
barrels.add(file);
|
|
230
|
+
barrelsByTarget.set(resolved.target, barrels);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (ignoredDynamic) debug.log('typecheck.gate', 'nonliteral-import-ignored', { count: ignoredDynamic });
|
|
234
|
+
// A changed export can break an importer through any number of re-export
|
|
235
|
+
// barrels. Walk that chain to a fixed point, then include direct importers
|
|
236
|
+
// of each barrel. The visited set also terminates circular barrels.
|
|
237
|
+
const exported = new Set(targets);
|
|
238
|
+
const queue = [...targets];
|
|
239
|
+
while (queue.length) {
|
|
240
|
+
const target = queue.pop()!;
|
|
241
|
+
for (const barrel of barrelsByTarget.get(target) ?? []) {
|
|
242
|
+
if (exported.has(barrel)) continue;
|
|
243
|
+
exported.add(barrel);
|
|
244
|
+
queue.push(barrel);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const roots = new Set([...exported, ...ambientRoots]);
|
|
248
|
+
for (const [file, imports] of importsByFile) {
|
|
249
|
+
if ([...imports].some((target) => exported.has(target))) roots.add(file);
|
|
250
|
+
}
|
|
251
|
+
return { roots };
|
|
252
|
+
}
|