elanous 0.2.11 → 0.2.12
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/1147-466ec15afacc1a65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1394-556de6d71bbdb0fb.js → 1394-ee3795b112907122.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-0259994816604c49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-cb169620945d1952.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-cefe8169e36cf62a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-cb95a7432c9ee744.js → page-d2e35228bc497119.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12b6d2f8d2d1aea.js → page-5c05b0da4bc1392c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-b9efe868bfd36255.js → page-e0ccdb576812b523.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-813c4a52c9d05ca8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-a022b257950f39e6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-a2ffc717e04b8b0a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-b59ee4e70fc18fcf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a67e11ba962337b5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-1efd0cb9e0ed9d13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-d5dc7ff69a37b4cf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-2a9932ddb62d76bd.js → page-1d6a533ce5a6efcb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-dfca9b7d2de0b135.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-793fa536b3a6ef37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6dd3bfbbde2a5c2c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-e7a529e0507ac86c.js → not-found-490653e8d4cc8852.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-77447356baad75d2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-6d604227e1664b71.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-0be8d4b2ced75ba0.js → page-4048543b4b7ad5e0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-4c3a3126abed551a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-64b0aecb1ca75e75.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-53f80edc6b8dc07c.js → page-db14d0be7f1c460c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-9547b5cec7cf76bf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-2cddeb9e2aeedc55.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-e297866051595d81.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-baba2e047872fb0f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-e895da4ec9cac345.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.js +1 -0
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/context/nightly-condense.yaml +21 -0
- package/graphs/context/recipes.yaml +12 -0
- package/graphs/demo/inside-seed.yaml +20 -0
- package/graphs/demo/recipes.yaml +12 -0
- package/package.json +1 -1
- package/plugins/card-followup/examples/sample-card.png +0 -0
- package/scripts/lib/telegram-inject-args.ts +31 -0
- package/scripts/measure-pod-memory-by-goal.ts +130 -0
- package/scripts/release-loop/auto-approve-node.ts +1 -1
- package/scripts/release-loop/checklist-gate-node.ts +2 -1
- package/scripts/release-loop/gate-node.ts +8 -2
- package/scripts/release-loop/ops-upgrade-node.ts +47 -6
- package/scripts/release-loop/publish-node.ts +4 -1
- package/scripts/release-loop/version-node.ts +13 -3
- package/scripts/telegram-inject.ts +14 -6
- package/scripts/test-diet/remeasure-list.ts +112 -0
- package/scripts/ux/demo-rehearsal.ts +17 -13
- package/scripts/ux/demo-seed.ts +108 -0
- package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
- package/src/agent-mission/driver.ts +138 -16
- package/src/agent-mission/pty-decision.ts +1 -1
- package/src/autopilot/pty-control-loop.ts +4 -1
- package/src/chat/index.ts +1 -0
- package/src/cli/persona-cli.ts +17 -14
- package/src/cli/pod-cli.ts +17 -0
- package/src/cli/pod-memory-advice.ts +35 -0
- package/src/cli/release-cli.ts +46 -6
- package/src/context-bus/context-now-surfaces.ts +17 -0
- package/src/context-bus/context-now.ts +2 -2
- package/src/context-bus/coord-events.ts +2 -2
- package/src/context-bus/nightly-condense.ts +153 -0
- package/src/context-bus/session-events.ts +35 -0
- package/src/dashboard/dashboard-status-lines.ts +2 -0
- package/src/dashboard/index.ts +8 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
- package/src/domains/outbound-alert.ts +99 -31
- package/src/domains/outbound-log-probe.ts +8 -0
- package/src/execution-loop/launch-gate.ts +17 -8
- package/src/field-note/telegram.ts +142 -0
- package/src/graph-runner/graph-grow-llm.ts +23 -13
- package/src/graph-runner/graph-grow.ts +5 -2
- package/src/graph-runner/runner.ts +12 -8
- package/src/harness/harness-cli-command.ts +138 -16
- package/src/hooks/consult-intake.ts +153 -0
- package/src/hooks/receiver.ts +78 -1
- package/src/index.ts +33 -9
- package/src/intake-plane/discord-seat-work.ts +28 -2
- package/src/intake-plane/wish-card.ts +33 -0
- package/src/llm/difficulty.ts +1 -1
- package/src/maturity/feature-maturity.ts +2 -1
- package/src/nexus/api/http-server.ts +31 -8
- package/src/nexus/api/outbound-report.ts +40 -7
- package/src/nexus/api/personas.ts +95 -53
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-history.ts +88 -0
- package/src/nexus/api/workflows.ts +52 -0
- package/src/nexus/outbound/delivery-ledger.ts +17 -8
- package/src/nexus/outbound/router.ts +8 -3
- package/src/persona/presets.ts +8 -1
- package/src/plugins/maker/market-bundle.ts +2 -1
- package/src/release-loop/checklist.ts +15 -3
- package/src/release-loop/feature-store.ts +24 -10
- package/src/release-loop/landed-but-yellow.ts +48 -0
- package/src/seat-loop/seat-loop.ts +168 -7
- package/src/self-dev/draft-sweep.ts +44 -10
- package/src/self-dev/draft-triage-rules.ts +54 -2
- package/src/self-implement/budget-gate.ts +29 -12
- package/src/self-implement/gate-candidate-ranking.ts +60 -0
- package/src/self-implement/gate-scope.ts +9 -8
- package/src/self-implement/headless-elanous-driver.ts +7 -2
- package/src/self-implement/orchestrator.ts +15 -0
- package/src/self-implement/test-seams.ts +1 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +8 -0
- package/src/telegram-commands.ts +21 -0
- package/src/telegram-project-command.ts +78 -4
- package/src/telegram.ts +32 -5
- package/src/user-config.ts +8 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-d6b1d156fb95e74b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-371423d1a573d64b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4b764b125339ac17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cdf8e4b01067bcf1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4dc614f3ce043e63.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d02c782fc792e2af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-dc6a79fb003f746c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-0fffb84cafdc9da6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-fca096e3e860306f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ebfa78f0d9885c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-3dc5f21f7e39622d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
- package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → -6XrAoD3eU7NyMHAE4xPS}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → -6XrAoD3eU7NyMHAE4xPS}/_ssgManifest.js +0 -0
|
@@ -27,6 +27,10 @@ export interface DraftSweepAdapters {
|
|
|
27
27
|
closeDraft(repository: string, number: number, comment: string): Promise<void>;
|
|
28
28
|
/** Resolve the latest claim comment owner; a failed lookup must not authorize a close. */
|
|
29
29
|
getClaimOwner?(repository: string, number: number): Promise<string | undefined>;
|
|
30
|
+
/** Whether the current owned run has a self-implement.result final observation, even if its worktree remains. */
|
|
31
|
+
hasFinalRunResult?(draft: SweepDraft, repository: string): Promise<boolean | undefined>;
|
|
32
|
+
/** Per-file last change on the draft branch; undefined means file coverage is unverified. */
|
|
33
|
+
getLatestFileChanges?(draft: SweepDraft, repository: string): Promise<Readonly<Record<string, string>> | undefined>;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export interface DraftSweepOptions {
|
|
@@ -120,9 +124,23 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
120
124
|
return finish();
|
|
121
125
|
}
|
|
122
126
|
let statuses: Map<number, string | undefined>;
|
|
127
|
+
let finality: Map<number, boolean | undefined>;
|
|
128
|
+
let latestFileChanges: Map<number, Readonly<Record<string, string>> | undefined>;
|
|
123
129
|
try {
|
|
124
130
|
statuses = new Map<number, string | undefined>();
|
|
125
|
-
|
|
131
|
+
finality = new Map<number, boolean | undefined>();
|
|
132
|
+
latestFileChanges = new Map<number, Readonly<Record<string, string>> | undefined>();
|
|
133
|
+
for (const draft of drafts) {
|
|
134
|
+
const status = await adapters.getRunStatus(draft, repository);
|
|
135
|
+
// A prior final cannot terminate a new active run on the same draft.
|
|
136
|
+
const active = status === 'running' || status === 'probable-running';
|
|
137
|
+
const final = active ? false : await adapters.hasFinalRunResult?.(draft, repository);
|
|
138
|
+
finality.set(draft.number, final);
|
|
139
|
+
statuses.set(draft.number, final ? 'self-implement.result final' : status);
|
|
140
|
+
if (draft.changedFiles?.length && adapters.getLatestFileChanges) {
|
|
141
|
+
latestFileChanges.set(draft.number, await adapters.getLatestFileChanges(draft, repository));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
126
144
|
} catch (error) {
|
|
127
145
|
result.error = String(error);
|
|
128
146
|
return finish();
|
|
@@ -132,8 +150,10 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
132
150
|
let unobserved = 0;
|
|
133
151
|
let claimed = 0;
|
|
134
152
|
let claimExpired = 0;
|
|
135
|
-
for (const
|
|
153
|
+
for (const listedDraft of drafts) {
|
|
154
|
+
const draft = { ...listedDraft, latestFileChanges: latestFileChanges.get(listedDraft.number) };
|
|
136
155
|
const runStatus = statuses.get(draft.number);
|
|
156
|
+
const finalRunResult = finality.get(draft.number);
|
|
137
157
|
const states = stateLabels.filter((state) => draft.labels.includes(state.name));
|
|
138
158
|
const contradiction = states.length > 1 || draft.labels.includes(approvalLabel ?? '');
|
|
139
159
|
if (draft.labels.includes(releaseHoldLabel ?? '') || (!contradiction && draft.labels.includes(keepLabel ?? ''))) {
|
|
@@ -147,8 +167,14 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
147
167
|
? (now.getTime() - Date.parse(draft.updatedAt)) / 3_600_000 : NaN;
|
|
148
168
|
// An unobserved claim may still have a merged twin: liveness/holds retain their priority,
|
|
149
169
|
// but a superseded draft must not be hidden by the six-hour claim window.
|
|
150
|
-
const mergedDecision = isClaimed ? decideDraft({ draft, runStatus, mergedTwins: merged,
|
|
151
|
-
|
|
170
|
+
const mergedDecision = isClaimed ? decideDraft({ draft, runStatus, mergedTwins: merged, openDrafts: drafts, liveBranches,
|
|
171
|
+
finalRunResult, ageHours: idleHours }) : undefined;
|
|
172
|
+
const supersededClaim = mergedDecision?.reason.startsWith('superseded-by #') === true
|
|
173
|
+
|| mergedDecision?.reason.startsWith('duplicate-of-open #') === true;
|
|
174
|
+
if (!contradiction && mergedDecision?.reason === 'branch-finality-unobserved') {
|
|
175
|
+
record({ number: draft.number, action: 'keep', reason: mergedDecision.reason, applied: false });
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
152
178
|
// Without an observed update time, neither expiration nor a fallback to creation time can authorize mutation —
|
|
153
179
|
// but a merged twin does not need the update time, so superseded keeps its priority over the unobserved claim.
|
|
154
180
|
if (isClaimed && !supersededClaim && (typeof draft.updatedAt !== 'string' || !Number.isFinite(Date.parse(draft.updatedAt)))) {
|
|
@@ -162,13 +188,21 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
162
188
|
}
|
|
163
189
|
if (isClaimed && !supersededClaim && Number.isFinite(idleHours) && idleHours >= CLAIM_IDLE_HOURS) {
|
|
164
190
|
claimExpired += 1;
|
|
165
|
-
if (
|
|
191
|
+
if (liveBranches.has(draft.branch) && finalRunResult === undefined
|
|
192
|
+
&& runStatus !== 'running' && runStatus !== 'probable-running') {
|
|
193
|
+
record({ number: draft.number, action: 'keep', reason: 'branch-finality-unobserved', applied: false });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (runStatus === 'running' || runStatus === 'probable-running' ||
|
|
197
|
+
(runStatus !== 'self-implement.result final' && liveBranches.has(draft.branch))) {
|
|
166
198
|
record({ number: draft.number, action: 'keep', reason: 'claim-expired-but-live', applied: false });
|
|
167
199
|
continue;
|
|
168
200
|
}
|
|
169
201
|
const action = closes >= DRAFT_SWEEP_CLOSE_CAP ? 'keep' : 'close';
|
|
170
202
|
if (action === 'close') closes += 1;
|
|
171
|
-
const entry: DraftSweepEntry = { number: draft.number, action,
|
|
203
|
+
const entry: DraftSweepEntry = { number: draft.number, action,
|
|
204
|
+
reason: action === 'close' ? runStatus === 'self-implement.result final'
|
|
205
|
+
? 'claim-expired (self-implement.result final; worktree is not live)' : 'claim-expired' : 'close-cap',
|
|
172
206
|
...(action === 'close' ? { statusLabel: stalledLabel } : {}), applied: false };
|
|
173
207
|
record(entry);
|
|
174
208
|
if (!apply || action !== 'close') continue;
|
|
@@ -178,7 +212,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
178
212
|
await adapters.setLabels(repository, draft.number, { add: stalledLabel, remove: [runningLabel] });
|
|
179
213
|
labelsChanged = true;
|
|
180
214
|
await adapters.closeDraft(repository, draft.number,
|
|
181
|
-
`Draft sweep: 처리 중 표식이 ${CLAIM_IDLE_HOURS}시간 갱신 없음 — 런·워크트리 없음 · 주인 ${owner}. Closed as stalled. Branch preserved; reopen to restore.`);
|
|
215
|
+
`Draft sweep: 처리 중 표식이 ${CLAIM_IDLE_HOURS}시간 갱신 없음 — ${runStatus === 'self-implement.result final' ? 'self-implement.result final · 남은 워크트리는 live 아님' : '런·워크트리 없음'} · 주인 ${owner}. Closed as stalled. Branch preserved; reopen to restore.`);
|
|
182
216
|
entry.applied = true;
|
|
183
217
|
} catch (error) {
|
|
184
218
|
if (labelsChanged) entry.partialApplied = true;
|
|
@@ -191,8 +225,8 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
191
225
|
record({ number: draft.number, action: 'keep', reason: 'outside-harness', applied: false });
|
|
192
226
|
continue;
|
|
193
227
|
}
|
|
194
|
-
let decision = supersededClaim ? mergedDecision! : decideDraft({ draft, runStatus, mergedTwins: merged, liveBranches,
|
|
195
|
-
ageHours: runStatus ? ageHours : idleHours });
|
|
228
|
+
let decision = supersededClaim ? mergedDecision! : decideDraft({ draft, runStatus, mergedTwins: merged, openDrafts: drafts, liveBranches,
|
|
229
|
+
finalRunResult, ageHours: runStatus ? ageHours : idleHours });
|
|
196
230
|
if (!runStatus && (decision.reason === 'unobserved' || decision.reason === 'stale-unobserved')) unobserved += 1;
|
|
197
231
|
if (!contradiction && decision.action === 'close') {
|
|
198
232
|
if (closes >= DRAFT_SWEEP_CLOSE_CAP) decision = { action: 'keep', reason: 'close-cap' };
|
|
@@ -204,7 +238,7 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
|
|
|
204
238
|
: decision.reason;
|
|
205
239
|
const isLive = decision.reason === 'live';
|
|
206
240
|
const target = action === 'report' ? undefined : action === 'close'
|
|
207
|
-
? decision.reason.startsWith('superseded-by #') ? supersededLabel : stalledLabel
|
|
241
|
+
? decision.reason.startsWith('superseded-by #') || decision.reason.startsWith('duplicate-of-open #') ? supersededLabel : stalledLabel
|
|
208
242
|
: isLive ? (states.length ? runningLabel : undefined) : runStatus && (states[0]?.name === runningLabel || states.length === 0) ? stalledLabel : undefined;
|
|
209
243
|
const entry: DraftSweepEntry = { number: draft.number, action, reason, ...(target ? { statusLabel: target } : {}), applied: false };
|
|
210
244
|
record(entry);
|
|
@@ -6,14 +6,25 @@ export interface DraftTriagePr {
|
|
|
6
6
|
title?: string;
|
|
7
7
|
branch: string;
|
|
8
8
|
labels?: readonly string[];
|
|
9
|
+
body?: string;
|
|
10
|
+
mergeCommitMessage?: string;
|
|
11
|
+
landingVerifiedComments?: readonly string[];
|
|
12
|
+
createdAt?: string;
|
|
13
|
+
mergedAt?: string;
|
|
14
|
+
changedFiles?: readonly string[];
|
|
15
|
+
/** Latest change to each draft file, from the draft branch's commit history. Missing is not creation time. */
|
|
16
|
+
latestFileChanges?: Readonly<Record<string, string>>;
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
export interface DraftTriageInput {
|
|
12
20
|
draft: DraftTriagePr;
|
|
13
21
|
runStatus: string | undefined;
|
|
14
22
|
mergedTwins: readonly DraftTriagePr[];
|
|
23
|
+
openDrafts?: readonly DraftTriagePr[];
|
|
15
24
|
ageHours: number;
|
|
16
25
|
liveBranches: ReadonlySet<string>;
|
|
26
|
+
/** false only when the current run's final result has actually been checked and ruled out. */
|
|
27
|
+
finalRunResult?: boolean;
|
|
17
28
|
}
|
|
18
29
|
|
|
19
30
|
export type DraftDecision = { action: 'close' | 'keep'; reason: string };
|
|
@@ -25,11 +36,22 @@ const PROTECTED_LABELS: ReadonlySet<string> = new Set<string>([...PR_LABELS
|
|
|
25
36
|
.map((label) => label.name), RELEASE_PATH_LABEL]);
|
|
26
37
|
|
|
27
38
|
const goalId = (branch: string): string | undefined => /(?:^|[-/])goalid-([a-f0-9]+)(?=-|\/|$)/i.exec(branch)?.[1]?.toLowerCase();
|
|
39
|
+
export const branchStem = (branch: string): string | undefined => /^self-impl\/(.+)-[a-f0-9]{8}-r[a-f0-9]+$/i.exec(branch)?.[1];
|
|
40
|
+
const slot = (body?: string): string | undefined => /^칸:\s*(.+?)\s*$/m.exec(body ?? '')?.[1];
|
|
41
|
+
const referencesDraft = (text: string, number: number): boolean =>
|
|
42
|
+
new RegExp(`(?:\\(수확\\s*#${number}\\s*\\)|\\bsuperseded\\s*#${number}(?!\\d))`, 'i').test(text);
|
|
43
|
+
const landingCommentReferencesDraft = (text: string, number: number): boolean =>
|
|
44
|
+
new RegExp(`\\blanding-verified\\s*:\\s*(?:draft|superseded|수확)\\s*#${number}(?!\\d)\\b`, 'i').test(text);
|
|
28
45
|
|
|
29
|
-
export function decideDraft({ draft, runStatus, mergedTwins, ageHours, liveBranches }: DraftTriageInput): DraftDecision {
|
|
46
|
+
export function decideDraft({ draft, runStatus, mergedTwins, openDrafts, ageHours, liveBranches, finalRunResult }: DraftTriageInput): DraftDecision {
|
|
30
47
|
const held = draft.labels?.find((label) => PROTECTED_LABELS.has(label));
|
|
31
48
|
if (held) return { action: 'keep', reason: `label:${held}` };
|
|
32
|
-
if (runStatus
|
|
49
|
+
if (runStatus !== 'self-implement.result final' && draft.branch && liveBranches.has(draft.branch)
|
|
50
|
+
&& finalRunResult === undefined && runStatus !== 'running' && runStatus !== 'probable-running') {
|
|
51
|
+
return { action: 'keep', reason: 'branch-finality-unobserved' };
|
|
52
|
+
}
|
|
53
|
+
if (runStatus === 'running' || runStatus === 'probable-running' ||
|
|
54
|
+
(runStatus !== 'self-implement.result final' && draft.branch && liveBranches.has(draft.branch))) {
|
|
33
55
|
return { action: 'keep', reason: 'live' };
|
|
34
56
|
}
|
|
35
57
|
const lineage = goalId(draft.branch);
|
|
@@ -37,9 +59,39 @@ export function decideDraft({ draft, runStatus, mergedTwins, ageHours, liveBranc
|
|
|
37
59
|
(draft.title && pr.title === draft.title) || (lineage && goalId(pr.branch) === lineage)
|
|
38
60
|
));
|
|
39
61
|
if (twin) return { action: 'close', reason: `superseded-by #${twin.number}` };
|
|
62
|
+
const stem = branchStem(draft.branch);
|
|
63
|
+
const createdAt = Date.parse(draft.createdAt ?? '');
|
|
64
|
+
const newer = stem && draft.title && Number.isFinite(createdAt) ? openDrafts
|
|
65
|
+
?.filter((pr) => pr.number !== draft.number && pr.title === draft.title && branchStem(pr.branch) === stem
|
|
66
|
+
&& Number.isFinite(Date.parse(pr.createdAt ?? '')) && Date.parse(pr.createdAt!) > createdAt)
|
|
67
|
+
.sort((a, b) => Date.parse(b.createdAt!) - Date.parse(a.createdAt!) || b.number - a.number)[0] : undefined;
|
|
68
|
+
if (newer) return { action: 'close', reason: `duplicate-of-open #${newer.number}` };
|
|
69
|
+
const harvest = mergedTwins.find((pr) => pr.number !== draft.number && (
|
|
70
|
+
[pr.mergeCommitMessage, pr.body].some((text) => text && referencesDraft(text, draft.number))
|
|
71
|
+
|| pr.landingVerifiedComments?.some((text) => landingCommentReferencesDraft(text, draft.number))
|
|
72
|
+
));
|
|
73
|
+
if (harvest) return { action: 'close', reason: `superseded-by #${harvest.number} (harvest #${draft.number})` };
|
|
74
|
+
const created = Date.parse(draft.createdAt ?? '');
|
|
75
|
+
const files = draft.changedFiles;
|
|
76
|
+
const draftSlot = slot(draft.body);
|
|
77
|
+
const later = mergedTwins.filter((pr) => pr.number !== draft.number && Number.isFinite(created)
|
|
78
|
+
&& Number.isFinite(Date.parse(pr.mergedAt ?? '')) && Date.parse(pr.mergedAt!) > created
|
|
79
|
+
&& ((lineage && goalId(pr.branch) === lineage) || (draftSlot && slot(pr.body) === draftSlot)));
|
|
80
|
+
if (files?.length && files.every((file) => {
|
|
81
|
+
const changed = Date.parse(draft.latestFileChanges?.[file] ?? '');
|
|
82
|
+
return Number.isFinite(changed) && changed >= created && later.some((pr) =>
|
|
83
|
+
pr.changedFiles?.includes(file) && Date.parse(pr.mergedAt!) > changed);
|
|
84
|
+
})) {
|
|
85
|
+
const covering = later.filter((pr) => pr.changedFiles?.some((file) => files.includes(file)
|
|
86
|
+
&& Date.parse(pr.mergedAt!) > Date.parse(draft.latestFileChanges![file]!)));
|
|
87
|
+
return { action: 'close', reason: `superseded-by #${covering.map((pr) => pr.number).join(', #')} (all-files-landed)` };
|
|
88
|
+
}
|
|
40
89
|
if (!runStatus) return Number.isFinite(ageHours) && ageHours >= STALLED_DRAFT_HOURS
|
|
41
90
|
? { action: 'close', reason: 'stale-unobserved' }
|
|
42
91
|
: { action: 'keep', reason: 'unobserved' };
|
|
92
|
+
if (runStatus === 'self-implement.result final') return Number.isFinite(ageHours) && ageHours >= STALLED_DRAFT_HOURS
|
|
93
|
+
? { action: 'close', reason: 'stale-ended-run (self-implement.result final; worktree is not live)' }
|
|
94
|
+
: { action: 'keep', reason: 'recent (self-implement.result final; worktree is not live)' };
|
|
43
95
|
if (Number.isFinite(ageHours) && ageHours >= STALLED_DRAFT_HOURS) return { action: 'close', reason: 'stale-ended-run' };
|
|
44
96
|
return { action: 'keep', reason: 'recent' };
|
|
45
97
|
}
|
|
@@ -101,21 +101,27 @@ function codexCandidateUsable(candidate: BudgetCodexCandidate, cap: number | und
|
|
|
101
101
|
function slotUsable(
|
|
102
102
|
provider: string,
|
|
103
103
|
input: DecideBudgetInput,
|
|
104
|
-
|
|
104
|
+
allowUnmeasured = false,
|
|
105
|
+
): { usable: boolean; reason: string; unmeasured?: boolean } {
|
|
105
106
|
if (provider === CODEX_PROVIDER) {
|
|
106
107
|
const cap = capFor(input.maxUsedPercent, CODEX_PROVIDER);
|
|
107
|
-
const
|
|
108
|
-
|
|
108
|
+
const measuredUsable = input.codexCandidates.some((candidate) => codexCandidateUsable(candidate, cap));
|
|
109
|
+
const unmeasured = allowUnmeasured && !measuredUsable && (input.codexCandidates.length === 0
|
|
110
|
+
|| input.codexCandidates.some((candidate) => candidate.reached !== true
|
|
111
|
+
&& (typeof candidate.usedPercent !== 'number' || !Number.isFinite(candidate.usedPercent))));
|
|
112
|
+
return { usable: measuredUsable || unmeasured, reason: formatCodexReason(input.codexCandidates, cap), ...(unmeasured ? { unmeasured: true } : {}) };
|
|
109
113
|
}
|
|
110
114
|
if (provider === GROK_PROVIDER) {
|
|
111
115
|
const cap = capFor(input.maxUsedPercent, GROK_PROVIDER);
|
|
112
116
|
const known = typeof input.grokUsedPercent === 'number' && Number.isFinite(input.grokUsedPercent);
|
|
113
|
-
const
|
|
117
|
+
const unmeasured = allowUnmeasured && !known;
|
|
118
|
+
const usable = unmeasured || (cap === undefined
|
|
114
119
|
? known
|
|
115
|
-
: known && (input.grokUsedPercent as number) < cap;
|
|
120
|
+
: known && (input.grokUsedPercent as number) < cap);
|
|
116
121
|
return {
|
|
117
122
|
usable,
|
|
118
123
|
reason: formatGrokReason(input.grokUsedPercent, cap),
|
|
124
|
+
...(unmeasured ? { unmeasured: true } : {}),
|
|
119
125
|
};
|
|
120
126
|
}
|
|
121
127
|
return { usable: false, reason: `${provider}: 예산 판정 없음` };
|
|
@@ -127,6 +133,15 @@ function slotUsable(
|
|
|
127
133
|
* 아무것도 못 쓰면 onShortfall 이 wait-reset 일 때만 wait-reset, 아니면 stop.
|
|
128
134
|
*/
|
|
129
135
|
export function decideBudget(input: DecideBudgetInput): BudgetDecision {
|
|
136
|
+
return decideBudgetWithPolicy(input, false).decision;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Only the harness launch gate treats an unmeasured provider as launchable. */
|
|
140
|
+
export function decideLaunchBudget(input: DecideBudgetInput): { decision: BudgetDecision; unmeasuredProvider?: string } {
|
|
141
|
+
return decideBudgetWithPolicy(input, true);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function decideBudgetWithPolicy(input: DecideBudgetInput, allowUnmeasured: boolean): { decision: BudgetDecision; unmeasuredProvider?: string } {
|
|
130
145
|
const reasons: string[] = [];
|
|
131
146
|
if (input.preference.chain.length === 0) {
|
|
132
147
|
reasons.push('chain: 비었음 — tools.selfImplement.childLlm.chain 도 llm.fallbackChain 도 없다');
|
|
@@ -134,7 +149,7 @@ export function decideBudget(input: DecideBudgetInput): BudgetDecision {
|
|
|
134
149
|
let firstUnusable = false;
|
|
135
150
|
for (let index = 0; index < input.preference.chain.length; index++) {
|
|
136
151
|
const slot = input.preference.chain[index]!;
|
|
137
|
-
const judged = slotUsable(slot.provider, input);
|
|
152
|
+
const judged = slotUsable(slot.provider, input, allowUnmeasured);
|
|
138
153
|
reasons.push(judged.reason);
|
|
139
154
|
if (!judged.usable) {
|
|
140
155
|
if (index === 0) firstUnusable = true;
|
|
@@ -142,16 +157,18 @@ export function decideBudget(input: DecideBudgetInput): BudgetDecision {
|
|
|
142
157
|
}
|
|
143
158
|
const action: BudgetAction = index === 0 || !firstUnusable ? 'proceed' : 'next-provider';
|
|
144
159
|
return {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
160
|
+
decision: {
|
|
161
|
+
action,
|
|
162
|
+
provider: slot.provider,
|
|
163
|
+
...(slot.model !== undefined ? { model: slot.model } : {}),
|
|
164
|
+
reasons,
|
|
165
|
+
},
|
|
166
|
+
...(judged.unmeasured ? { unmeasuredProvider: slot.provider } : {}),
|
|
149
167
|
};
|
|
150
168
|
}
|
|
151
169
|
const onShortfall = input.preference.budgetGate.onShortfall;
|
|
152
170
|
return {
|
|
153
|
-
action: onShortfall === 'wait-reset' ? 'wait-reset' : 'stop',
|
|
154
|
-
reasons,
|
|
171
|
+
decision: { action: onShortfall === 'wait-reset' ? 'wait-reset' : 'stop', reasons },
|
|
155
172
|
};
|
|
156
173
|
}
|
|
157
174
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { GateCallerReason } from './gate-scope.js';
|
|
2
|
+
|
|
3
|
+
export interface GateCallerCandidate {
|
|
4
|
+
readonly file: string;
|
|
5
|
+
readonly reasons: readonly GateCallerReason[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Path segments between `file` and the nearest changed path's directory (0 = same directory). */
|
|
9
|
+
function directoryDistance(file: string, changedPaths: readonly string[]): number {
|
|
10
|
+
const dir = file.split('/').slice(0, -1);
|
|
11
|
+
let best = Infinity;
|
|
12
|
+
for (const changed of changedPaths) {
|
|
13
|
+
const other = changed.split('/').slice(0, -1);
|
|
14
|
+
let shared = 0;
|
|
15
|
+
while (shared < dir.length && shared < other.length && dir[shared] === other[shared]) shared += 1;
|
|
16
|
+
best = Math.min(best, dir.length - shared + other.length - shared);
|
|
17
|
+
}
|
|
18
|
+
return best;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Rank gate caller tests by closeness to the change, nearest first (GATE-CALLERS2 — the cap keeps the head).
|
|
23
|
+
* A `route` candidate names a route on a changed diff line, so it ranks with touched tests (distance 0);
|
|
24
|
+
* otherwise the distance is the shortest reverse-import path from a changed file (direct importer = 1,
|
|
25
|
+
* unreachable = after every reachable candidate). Equal distances prefer the test nearest a changed file
|
|
26
|
+
* in the directory tree, then numeric-aware path order. Inputs are not modified.
|
|
27
|
+
* `importersByFile` maps each imported file to its immediate importers (tests or sources).
|
|
28
|
+
*/
|
|
29
|
+
export function rankGateCallerCandidates<T extends GateCallerCandidate>(
|
|
30
|
+
changedPaths: readonly string[],
|
|
31
|
+
candidates: readonly T[],
|
|
32
|
+
importersByFile: ReadonlyMap<string, readonly string[]>,
|
|
33
|
+
): T[] {
|
|
34
|
+
const distances = new Map<string, number>();
|
|
35
|
+
const queue: string[] = [];
|
|
36
|
+
for (const path of changedPaths) {
|
|
37
|
+
if (distances.has(path)) continue;
|
|
38
|
+
distances.set(path, 0);
|
|
39
|
+
queue.push(path);
|
|
40
|
+
}
|
|
41
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
42
|
+
const source = queue[index]!;
|
|
43
|
+
const nextDistance = distances.get(source)! + 1;
|
|
44
|
+
for (const importer of importersByFile.get(source) ?? []) {
|
|
45
|
+
if (distances.has(importer)) continue;
|
|
46
|
+
distances.set(importer, nextDistance);
|
|
47
|
+
queue.push(importer);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const keys = new Map(candidates.map((candidate) => [candidate, {
|
|
51
|
+
distance: candidate.reasons.includes('route') ? 0 : distances.get(candidate.file) ?? Infinity,
|
|
52
|
+
directory: directoryDistance(candidate.file, changedPaths),
|
|
53
|
+
}]));
|
|
54
|
+
return [...candidates].sort((left, right) => {
|
|
55
|
+
const a = keys.get(left)!;
|
|
56
|
+
const b = keys.get(right)!;
|
|
57
|
+
return (a.distance - b.distance || 0) || (a.directory - b.directory || 0)
|
|
58
|
+
|| left.file.localeCompare(right.file, undefined, { numeric: true });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
import { importerTestsNotInRunSet, type ImporterTestIndex } from './importer-test-index.js';
|
|
32
32
|
import type { RouteConsumerTestIndex } from './route-consumer-test-index.js';
|
|
33
|
+
import { rankGateCallerCandidates } from './gate-candidate-ranking.js';
|
|
33
34
|
|
|
34
35
|
export const GATE_CALLER_TEST_LIMIT = 30;
|
|
35
36
|
export type GateCallerReason = 'import' | 'route';
|
|
@@ -249,11 +250,7 @@ interface GateScopeDecision {
|
|
|
249
250
|
readonly unverified: readonly string[];
|
|
250
251
|
/** elanous 자체가 남긴 런타임 산출물. 미검증 사용자 변경에서 제외하되 경로를 보존한다. */
|
|
251
252
|
readonly elanousRuntimeArtifacts: readonly string[];
|
|
252
|
-
/** ⭐
|
|
253
|
-
* ⚠️ 직전 판본은 이 자리에 `unrunRelatedTests`(=실행집합에 없는 연관 테스트)를 뒀는데,
|
|
254
|
-
* 실행집합이 연관 테스트를 **전부 포함**하도록 바뀌었으므로 그 값은 **정의상 항상 빈 배열**이었다.
|
|
255
|
-
* 빈 배열을 보고 *"안 돈 게 없다"* 고 읽게 되지만 그건 사실 확인이 아니라 계산의 결과였다
|
|
256
|
-
* (관측이 사실 아닌 값을 나르는 형태). ⇒ **실제로 일어난 일**을 센다: 합집합이 구제한 파일들. */
|
|
253
|
+
/** ⭐ 편집되지 않았는데 함께 끌어와 돌린 연관 테스트(`changed-tests` 분기) — 합집합이 실제로 구제한 파일들. */
|
|
257
254
|
readonly pulledInRelatedTests: readonly string[];
|
|
258
255
|
/** 실행 집합에 없는, 변경 소스를 상대 import 하는 테스트. 색인이 주입되지 않으면 null. */
|
|
259
256
|
readonly importerTestsNotRun: {
|
|
@@ -352,8 +349,9 @@ export function resolveGateScope(
|
|
|
352
349
|
}
|
|
353
350
|
}
|
|
354
351
|
}
|
|
355
|
-
|
|
356
|
-
|
|
352
|
+
// GATE-CALLERS2 — the cap keeps the candidates closest to the change, not the alphabetically first.
|
|
353
|
+
const candidates = rankGateCallerCandidates(userChanged, [...callers].map(([file, reasons]) => ({ file, reasons: [...reasons] })),
|
|
354
|
+
importerTestIndex?.testsBySource ?? new Map());
|
|
357
355
|
const callerTests = candidates.slice(0, GATE_CALLER_TEST_LIMIT);
|
|
358
356
|
const callerTestsOverflow = candidates.slice(GATE_CALLER_TEST_LIMIT);
|
|
359
357
|
const runWithCallers = (tests: readonly string[]) => [...new Set([...tests, ...callerTests.map(({ file }) => file)])];
|
|
@@ -364,11 +362,14 @@ export function resolveGateScope(
|
|
|
364
362
|
});
|
|
365
363
|
|
|
366
364
|
if (testFiles.length > 0) {
|
|
365
|
+
// Edited tests ⊕ every related test of the changed sources ⊕ capped callers (restored 10-04: #23467 narrowed this
|
|
366
|
+
// to edited tests only, so a co-located sibling of a changed source stopped running — 0.2.12 gate introduced).
|
|
367
367
|
const allRelatedTests = conventional;
|
|
368
368
|
const runSet = runWithCallers([...testFiles, ...allRelatedTests]);
|
|
369
369
|
// 편집된 테스트만 돌렸다면 빠졌을 것들 — 이 목록이 비어있지 않다는 것은 합집합이 실제로 구제했다는 뜻이다.
|
|
370
370
|
const pulledInRelatedTests = allRelatedTests.filter((f) => !testFiles.includes(f));
|
|
371
|
-
return { ...base, ...withDocumentNonContribution(runSet), testArgs: runSet, unverified: unverifiedFor(runSet), pulledInRelatedTests,
|
|
371
|
+
return { ...base, ...withDocumentNonContribution(runSet), testArgs: runSet, unverified: unverifiedFor(runSet), pulledInRelatedTests,
|
|
372
|
+
importerTestsNotRun: observationFor(runSet), skipTestStep: false, reason: 'changed-tests' };
|
|
372
373
|
}
|
|
373
374
|
if (userChanged.length === 0) {
|
|
374
375
|
// postsync 빈 범위는 기존 no-changes(깨끗함)와 다른 값이다. 일반 호출자는 계속 no-changes를 읽는다.
|
|
@@ -1170,11 +1170,16 @@ export interface HeadlessGoalLoopPtyResult {
|
|
|
1170
1170
|
|
|
1171
1171
|
export function pickImplementRoleForGoal(document: string): ReturnType<typeof resolveRoleLlm> | undefined {
|
|
1172
1172
|
try {
|
|
1173
|
-
const
|
|
1173
|
+
const config = getUserConfig();
|
|
1174
|
+
const role = resolveRoleLlm('implement', { config });
|
|
1175
|
+
if (config.harness?.difficultyPlacement !== true || role.source !== 'default') return role;
|
|
1174
1176
|
const signals = goalDifficultySignals(document);
|
|
1175
1177
|
const level = classifyGoalDifficulty(signals);
|
|
1176
1178
|
const picked = resolveImplementDifficulty(role, level);
|
|
1177
|
-
try {
|
|
1179
|
+
try {
|
|
1180
|
+
debug.log('llm.difficulty', 'pick', { level, tier: picked.tier ?? null, signals });
|
|
1181
|
+
debug.log('harness.role-llm', 'difficulty-placement', { difficulty: level, tier: picked.tier, source: role.source });
|
|
1182
|
+
} catch { /* observation is fail-soft */ }
|
|
1178
1183
|
return picked;
|
|
1179
1184
|
} catch { return undefined; }
|
|
1180
1185
|
}
|
|
@@ -63,6 +63,7 @@ import { GoalRunStore, insertGoalRunRecord, type GoalPriorRuns } from './goal-ru
|
|
|
63
63
|
import { writeGoalRunRecordFragment } from './goal-execution-records.js';
|
|
64
64
|
import { getUserConfig } from '../user-config.js';
|
|
65
65
|
import { appendRunLedgerEntry, loadRunLedger, parseRunShardIdentity, queryRunChain, type RunChainShardSibling, type RunLedgerEntry, type RunLedgerWriter, type RunOriginData, type RunShardIdentity } from './run-ledger.js';
|
|
66
|
+
import { emitSessionEvent, type SessionEventInput } from '../context-bus/session-events.js';
|
|
66
67
|
import { decideLineageSupersede, lineageSupersedeCloseComment, type LineageSupersedeOpenDraft } from './lineage-supersede.js';
|
|
67
68
|
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
68
69
|
import { enqueueControlMemo, readSoftStopRequestStatus, type SoftStopRequestRead } from '../harness/control-inbox.js';
|
|
@@ -874,6 +875,8 @@ export type ImplementTerminalStatus = {
|
|
|
874
875
|
export interface SelfImplementSeams {
|
|
875
876
|
/** Run-observer ledger sink. Tests inject an in-memory sink to avoid touching user state. */
|
|
876
877
|
writeRunLedger?: RunLedgerWriter;
|
|
878
|
+
/** Optional context-journal sink for isolated runs. */
|
|
879
|
+
emitContextEvent?: (input: SessionEventInput) => void;
|
|
877
880
|
/** Registers this run as an ephemeral loop-agent asset; failures are observed but never affect execution. */
|
|
878
881
|
registerLoopAgent?: (input: LoopAgentInput) => void;
|
|
879
882
|
/** Wraps implementation and gate promises with their effective wall-clock timeout; tests observe the actual stage budgets. */
|
|
@@ -3384,6 +3387,16 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
|
|
|
3384
3387
|
detail: 'observe-only: SelfImplement execution skipped before child boot',
|
|
3385
3388
|
};
|
|
3386
3389
|
}
|
|
3390
|
+
const emitContext = (kind: 'task-claimed' | 'task-done', text: string, pr?: number): void => {
|
|
3391
|
+
try {
|
|
3392
|
+
(opts.seams.emitContextEvent ?? emitSessionEvent)({ kind, seat: 'TC', text,
|
|
3393
|
+
ref: `runId=${runId}${pr === undefined ? '' : ` PR=#${pr}`}` });
|
|
3394
|
+
} catch (error) {
|
|
3395
|
+
try { logRunAwareFailSoft(runId, 'context-event-failed', { kind, error: safeErrorDescription(error) }); }
|
|
3396
|
+
catch { /* context publication never changes the run result */ }
|
|
3397
|
+
}
|
|
3398
|
+
};
|
|
3399
|
+
emitContext('task-claimed', `Self-implement claimed: ${opts.feature.split(/\r?\n/, 1)[0]}`);
|
|
3387
3400
|
const parentHarnessSpaceId = getHarnessSpace()?.id || undefined;
|
|
3388
3401
|
const configRoot = getElanousConfigDir() || undefined;
|
|
3389
3402
|
const stateRoot = elanousStateRoot() || undefined;
|
|
@@ -4091,6 +4104,8 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
|
|
|
4091
4104
|
}, { level: 'warn' });
|
|
4092
4105
|
} catch { /* fail-soft — 종결 기록 실패가 원래 예외를 덮으면 안 된다 */ }
|
|
4093
4106
|
}
|
|
4107
|
+
emitContext('task-done', `Self-implement ${terminalResult?.stage ?? 'crashed'}: ${opts.feature.split(/\r?\n/, 1)[0]}`,
|
|
4108
|
+
terminalResult?.prNumber ?? capturedPrNumber);
|
|
4094
4109
|
finalizeTraversal();
|
|
4095
4110
|
try { unregisterProviderErrorSink(); } catch { /* fail-soft — cleanup must not change the terminal result */ }
|
|
4096
4111
|
}
|
|
@@ -21,6 +21,7 @@ export function seams(over: Partial<SelfImplementSeams> & { gateResults?: boolea
|
|
|
21
21
|
// ⭐ 실제 경로는 그것을 «의도한» 전용 테스트에서만 탄다(그 테스트가 심을 명시로 준다).
|
|
22
22
|
refreshCodexQuotaSignals: async () => ({ accounts: [] }),
|
|
23
23
|
writeRunLedger: () => {},
|
|
24
|
+
emitContextEvent: () => {},
|
|
24
25
|
registerLoopAgent: () => {},
|
|
25
26
|
createWorktree: async ({ branch, base }) => ({ path: `/wt/${branch}`, branch, base, resolvedBase: 'a'.repeat(40), invokedHead: 'a'.repeat(40) }),
|
|
26
27
|
implement: async ({ feature }) => { features.push(feature); return { ok: true, summary: 'impl' }; },
|
|
@@ -304,13 +304,15 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
304
304
|
|
|
305
305
|
let member: PodPoolMember | null = null;
|
|
306
306
|
let pool = options.poolScheduler;
|
|
307
|
-
if (!pool
|
|
308
|
-
const spec = resolvePodPoolSpec(options.pool, env);
|
|
309
|
-
if (!spec) throw new Error('pod command: 풀 스펙이 비었다');
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
307
|
+
if (!pool) {
|
|
308
|
+
const spec = resolvePodPoolSpec(options.pool, env, () => options.kubectl ? undefined : getUserConfig().pod?.pool);
|
|
309
|
+
if (options.pool && !spec) throw new Error('pod command: 풀 스펙이 비었다');
|
|
310
|
+
if (spec) {
|
|
311
|
+
const members = parsePodPool(spec);
|
|
312
|
+
const checked = (options.checkPool ?? checkPodPool)(members, baseKubectl as PoolKubectl);
|
|
313
|
+
if (!checked.ok) throw new Error('pod command: 풀의 노드가 하나도 준비되지 않았다');
|
|
314
|
+
pool = new PodPoolScheduler(checked.ready);
|
|
315
|
+
}
|
|
314
316
|
}
|
|
315
317
|
if (pool) {
|
|
316
318
|
for (;;) {
|
|
@@ -357,6 +359,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
357
359
|
if (synced && !synced.ok) throw new Error(`pod command: 이미지 판을 못 맞췄다: ${synced.detail}`);
|
|
358
360
|
if (synced?.imageRef) member = { ...member, imageRef: synced.imageRef };
|
|
359
361
|
}
|
|
362
|
+
const imageRef = member?.imageRef;
|
|
363
|
+
if (!imageRef) log('pod.command-job', 'image-fallback-local', { reason: !member ? 'no-pool-member' : !imageCommit ? 'image-commit-unavailable' : 'registry-tag-unavailable' });
|
|
360
364
|
const ghToken = options.clone ? (options.ghToken ?? defaultGhToken)() : undefined;
|
|
361
365
|
const secret = commandJobSecret({ name, namespace, launch, skills, skillEnvText, ...(grokAuth !== undefined ? { grokAuth } : {}), ...(ghToken !== undefined ? { ghToken } : {}) });
|
|
362
366
|
if (secret) {
|
|
@@ -364,8 +368,8 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
364
368
|
if (applied.status !== 0) throw new Error(applied.stderr.trim() || 'pod command: Secret 적용 실패');
|
|
365
369
|
}
|
|
366
370
|
const manifest = podCommandJobManifest({
|
|
367
|
-
name, namespace, launch, image:
|
|
368
|
-
...(
|
|
371
|
+
name, namespace, launch, image: imageRef ?? image,
|
|
372
|
+
...(imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
|
|
369
373
|
repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
|
|
370
374
|
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(memoryLimit ? { memoryLimit } : {}), deadlineSeconds,
|
|
371
375
|
...(options.runId ? { runId: options.runId } : {}),
|
|
@@ -398,7 +402,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
398
402
|
llm: llm ?? null,
|
|
399
403
|
command: command.join(' ').slice(0, COMMAND_LOG_CHARS),
|
|
400
404
|
});
|
|
401
|
-
return { exitCode, artifactsDir, job: name, image:
|
|
405
|
+
return { exitCode, artifactsDir, job: name, image: imageRef ?? image };
|
|
402
406
|
} catch (err) {
|
|
403
407
|
cleanupSecret();
|
|
404
408
|
throw err;
|
|
@@ -436,6 +436,12 @@ export function goalTouchesPwa(feature: string): boolean {
|
|
|
436
436
|
return PWA_PATH.test(feature);
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
/** A launch-time warning, not a memory-tier override or a test-command parser. */
|
|
440
|
+
export function broadPodTestWarning(goal: string): string | null {
|
|
441
|
+
const broad = /(?:전체\s*(?:시험|테스트)|(?:full|whole|entire|all)\s+(?:test|suite)|(?:전체|모든)\s*(?:test|suite)|\bbun\s+test\s+(?:[\w./-]+\/|[\w./-]+(?<!\.[cm]?[jt]sx?))(?:\s|$)|\bbun\s+test\s*(?:$|[;&|]))/imu.test(goal);
|
|
442
|
+
return broad ? '[pod] 넓은 시험 골 경고: 10-04 W10b 32Gi OOM — 넓은 bun test 한 프로세스가 16GB+ 사용; 바꾼 시험 파일만 지정해 실행하세요.' : null;
|
|
443
|
+
}
|
|
444
|
+
|
|
439
445
|
export function parsePodMemoryTier(v: string | undefined): PodMemoryTier | null {
|
|
440
446
|
const t = v?.trim().toLowerCase();
|
|
441
447
|
return t && (POD_MEMORY_TIERS as readonly string[]).includes(t) ? (t as PodMemoryTier) : null;
|
|
@@ -660,6 +666,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
660
666
|
debug.log('self-implement.pod', 'memory-directive-ignored', { spaceId: input.spaceId, where, reason }, { level: 'warn' });
|
|
661
667
|
}
|
|
662
668
|
}
|
|
669
|
+
const broadTestWarning = broadPodTestWarning(`${input.feature}\n${parentGoal ?? ''}`);
|
|
670
|
+
if (broadTestWarning) console.warn(broadTestWarning);
|
|
663
671
|
let memoryLimit = selectedMemoryLimit;
|
|
664
672
|
const after = input.after ?? podPredecessorFor(input.feature, env, parentGoal);
|
|
665
673
|
if (after !== undefined) debug.log('self-implement.pod', 'predecessor', { spaceId: input.spaceId, after, source: input.after !== undefined ? 'input' : 'goal-or-option' });
|
package/src/telegram-commands.ts
CHANGED
|
@@ -46,6 +46,9 @@ import { classifyIntake } from './ad-pipeline/intake.js';
|
|
|
46
46
|
import { createAdPipelineDeps, runAdPipeline } from './ad-pipeline/run.js';
|
|
47
47
|
import { projectCommand } from './telegram-project-command.js';
|
|
48
48
|
import { telegramNowSlash } from './context-bus/context-now-surfaces.js';
|
|
49
|
+
import { createWishCard } from './intake-plane/wish-card.js';
|
|
50
|
+
import { telegramDecisionOwner } from './decisions/telegram-decision-cards.js';
|
|
51
|
+
import { CardStore } from './task-cards/card-store.js';
|
|
49
52
|
import type { ContextNowDeps } from './context-bus/context-now.js';
|
|
50
53
|
|
|
51
54
|
/** How many prior user/assistant turns to pass into `executeSkill` as
|
|
@@ -155,6 +158,7 @@ export interface TgCommandContext {
|
|
|
155
158
|
userConfig: UserConfig;
|
|
156
159
|
/** All registered commands — used by `/help` to enumerate. */
|
|
157
160
|
allCommands: TgSlashCommand[];
|
|
161
|
+
sendButtons?: (text: string, buttons: Array<Array<{ text: string; data: string }>>) => Promise<void>;
|
|
158
162
|
/** When the caller posted a placeholder for a streaming command,
|
|
159
163
|
* the streamer edits it in-place as the handler accumulates
|
|
160
164
|
* output. Undefined for instant commands. Handlers must tolerate
|
|
@@ -590,6 +594,23 @@ export function defaultTelegramCommands(nowDeps?: ContextNowDeps): TgSlashComman
|
|
|
590
594
|
},
|
|
591
595
|
},
|
|
592
596
|
projectCommand(),
|
|
597
|
+
{
|
|
598
|
+
name: 'wish',
|
|
599
|
+
description: '소원을 카드로 남기기',
|
|
600
|
+
handler: async (args, ctx, { userConfig }) => {
|
|
601
|
+
if (String(ctx.userId) !== telegramDecisionOwner(userConfig) || ctx.chatId !== ctx.userId || !ctx.isDm) {
|
|
602
|
+
return '대표만 쓸 수 있습니다';
|
|
603
|
+
}
|
|
604
|
+
if (args.length === 0) return '/wish <소원 한 줄>';
|
|
605
|
+
const store = new CardStore();
|
|
606
|
+
try {
|
|
607
|
+
const card = createWishCard({ text: args.join(' '), source: 'telegram', ref: `${ctx.chatId}:${ctx.messageId}` }, store);
|
|
608
|
+
return `소원 카드로 남겼습니다 — ${card.title} (카드 ${card.cardId.slice(0, 8)})`;
|
|
609
|
+
} finally {
|
|
610
|
+
store.close();
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
},
|
|
593
614
|
{
|
|
594
615
|
name: 'fork',
|
|
595
616
|
description: 'Fork the current chat session (or /fork <prefix>) and continue HERE on the fork · time-travel: /fork before:N',
|