@promptwheel/cli 0.6.0 → 0.7.1
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/dist/bin/promptwheel.d.ts +1 -1
- package/dist/bin/promptwheel.js +2 -2
- package/dist/commands/auto-auth.js +2 -2
- package/dist/commands/auto-auth.js.map +1 -1
- package/dist/commands/solo-analytics.d.ts.map +1 -1
- package/dist/commands/solo-analytics.js +185 -2
- package/dist/commands/solo-analytics.js.map +1 -1
- package/dist/commands/solo-auto.d.ts.map +1 -1
- package/dist/commands/solo-auto.js +11 -12
- package/dist/commands/solo-auto.js.map +1 -1
- package/dist/commands/solo-daemon.d.ts.map +1 -1
- package/dist/commands/solo-daemon.js +4 -0
- package/dist/commands/solo-daemon.js.map +1 -1
- package/dist/commands/solo-inspect.d.ts.map +1 -1
- package/dist/commands/solo-inspect.js +93 -5
- package/dist/commands/solo-inspect.js.map +1 -1
- package/dist/commands/solo-nudge.d.ts.map +1 -1
- package/dist/commands/solo-nudge.js +38 -7
- package/dist/commands/solo-nudge.js.map +1 -1
- package/dist/commands/solo-trajectory.d.ts.map +1 -1
- package/dist/commands/solo-trajectory.js +23 -5
- package/dist/commands/solo-trajectory.js.map +1 -1
- package/dist/commands/solo.d.ts.map +1 -1
- package/dist/commands/solo.js +8 -2
- package/dist/commands/solo.js.map +1 -1
- package/dist/lib/cycle-context.d.ts +5 -0
- package/dist/lib/cycle-context.d.ts.map +1 -1
- package/dist/lib/cycle-context.js +12 -4
- package/dist/lib/cycle-context.js.map +1 -1
- package/dist/lib/daemon-fork.d.ts +1 -0
- package/dist/lib/daemon-fork.d.ts.map +1 -1
- package/dist/lib/daemon-fork.js +2 -0
- package/dist/lib/daemon-fork.js.map +1 -1
- package/dist/lib/daemon.d.ts +1 -0
- package/dist/lib/daemon.d.ts.map +1 -1
- package/dist/lib/daemon.js +2 -1
- package/dist/lib/daemon.js.map +1 -1
- package/dist/lib/display-adapter-log.d.ts +6 -0
- package/dist/lib/display-adapter-log.d.ts.map +1 -1
- package/dist/lib/display-adapter-log.js +3 -0
- package/dist/lib/display-adapter-log.js.map +1 -1
- package/dist/lib/display-adapter-spinner.d.ts +6 -0
- package/dist/lib/display-adapter-spinner.d.ts.map +1 -1
- package/dist/lib/display-adapter-spinner.js +4 -1
- package/dist/lib/display-adapter-spinner.js.map +1 -1
- package/dist/lib/display-adapter-tui.d.ts +6 -0
- package/dist/lib/display-adapter-tui.d.ts.map +1 -1
- package/dist/lib/display-adapter-tui.js +3 -0
- package/dist/lib/display-adapter-tui.js.map +1 -1
- package/dist/lib/display-adapter.d.ts +6 -0
- package/dist/lib/display-adapter.d.ts.map +1 -1
- package/dist/lib/error-ledger.d.ts +39 -0
- package/dist/lib/error-ledger.d.ts.map +1 -0
- package/dist/lib/error-ledger.js +73 -0
- package/dist/lib/error-ledger.js.map +1 -0
- package/dist/lib/goals.d.ts +1 -1
- package/dist/lib/goals.d.ts.map +1 -1
- package/dist/lib/goals.js +39 -7
- package/dist/lib/goals.js.map +1 -1
- package/dist/lib/learnings.d.ts.map +1 -1
- package/dist/lib/learnings.js +92 -68
- package/dist/lib/learnings.js.map +1 -1
- package/dist/lib/pr-outcomes.d.ts +42 -0
- package/dist/lib/pr-outcomes.d.ts.map +1 -0
- package/dist/lib/pr-outcomes.js +97 -0
- package/dist/lib/pr-outcomes.js.map +1 -0
- package/dist/lib/qa-stats.d.ts.map +1 -1
- package/dist/lib/qa-stats.js +3 -1
- package/dist/lib/qa-stats.js.map +1 -1
- package/dist/lib/retention.d.ts +3 -3
- package/dist/lib/retention.d.ts.map +1 -1
- package/dist/lib/retention.js +12 -12
- package/dist/lib/retention.js.map +1 -1
- package/dist/lib/run-history.d.ts +27 -0
- package/dist/lib/run-history.d.ts.map +1 -1
- package/dist/lib/run-history.js.map +1 -1
- package/dist/lib/run-state.d.ts +42 -0
- package/dist/lib/run-state.d.ts.map +1 -1
- package/dist/lib/run-state.js +66 -0
- package/dist/lib/run-state.js.map +1 -1
- package/dist/lib/session-report.d.ts.map +1 -1
- package/dist/lib/session-report.js +42 -0
- package/dist/lib/session-report.js.map +1 -1
- package/dist/lib/solo-auto-between-cycles.d.ts.map +1 -1
- package/dist/lib/solo-auto-between-cycles.js +381 -40
- package/dist/lib/solo-auto-between-cycles.js.map +1 -1
- package/dist/lib/solo-auto-drill.d.ts +228 -0
- package/dist/lib/solo-auto-drill.d.ts.map +1 -0
- package/dist/lib/solo-auto-drill.js +1229 -0
- package/dist/lib/solo-auto-drill.js.map +1 -0
- package/dist/lib/solo-auto-execute.d.ts +8 -0
- package/dist/lib/solo-auto-execute.d.ts.map +1 -1
- package/dist/lib/solo-auto-execute.js +99 -7
- package/dist/lib/solo-auto-execute.js.map +1 -1
- package/dist/lib/solo-auto-filter.js +3 -3
- package/dist/lib/solo-auto-filter.js.map +1 -1
- package/dist/lib/solo-auto-finalize.d.ts.map +1 -1
- package/dist/lib/solo-auto-finalize.js +32 -2
- package/dist/lib/solo-auto-finalize.js.map +1 -1
- package/dist/lib/solo-auto-init-qa.d.ts.map +1 -1
- package/dist/lib/solo-auto-init-qa.js +3 -1
- package/dist/lib/solo-auto-init-qa.js.map +1 -1
- package/dist/lib/solo-auto-scout.d.ts +1 -0
- package/dist/lib/solo-auto-scout.d.ts.map +1 -1
- package/dist/lib/solo-auto-scout.js +44 -13
- package/dist/lib/solo-auto-scout.js.map +1 -1
- package/dist/lib/solo-auto-state.d.ts.map +1 -1
- package/dist/lib/solo-auto-state.js +88 -28
- package/dist/lib/solo-auto-state.js.map +1 -1
- package/dist/lib/solo-auto-types.d.ts +64 -3
- package/dist/lib/solo-auto-types.d.ts.map +1 -1
- package/dist/lib/solo-auto.d.ts +3 -3
- package/dist/lib/solo-auto.d.ts.map +1 -1
- package/dist/lib/solo-auto.js +83 -4
- package/dist/lib/solo-auto.js.map +1 -1
- package/dist/lib/solo-config.d.ts +51 -3
- package/dist/lib/solo-config.d.ts.map +1 -1
- package/dist/lib/solo-config.js +43 -2
- package/dist/lib/solo-config.js.map +1 -1
- package/dist/lib/solo-hints.d.ts +10 -0
- package/dist/lib/solo-hints.d.ts.map +1 -1
- package/dist/lib/solo-hints.js +22 -0
- package/dist/lib/solo-hints.js.map +1 -1
- package/dist/lib/solo-session-summary.d.ts +12 -1
- package/dist/lib/solo-session-summary.d.ts.map +1 -1
- package/dist/lib/solo-session-summary.js +50 -6
- package/dist/lib/solo-session-summary.js.map +1 -1
- package/dist/lib/spindle-incidents.d.ts +29 -0
- package/dist/lib/spindle-incidents.d.ts.map +1 -0
- package/dist/lib/spindle-incidents.js +56 -0
- package/dist/lib/spindle-incidents.js.map +1 -0
- package/dist/lib/spinner.d.ts +1 -1
- package/dist/lib/taste-profile.d.ts.map +1 -1
- package/dist/lib/taste-profile.js +14 -8
- package/dist/lib/taste-profile.js.map +1 -1
- package/dist/lib/ticket-steps/step-spindle.d.ts.map +1 -1
- package/dist/lib/ticket-steps/step-spindle.js +14 -0
- package/dist/lib/ticket-steps/step-spindle.js.map +1 -1
- package/dist/lib/trajectory-generate.d.ts +73 -0
- package/dist/lib/trajectory-generate.d.ts.map +1 -1
- package/dist/lib/trajectory-generate.js +368 -12
- package/dist/lib/trajectory-generate.js.map +1 -1
- package/dist/lib/trajectory.d.ts +1 -1
- package/dist/lib/trajectory.d.ts.map +1 -1
- package/dist/lib/trajectory.js +67 -6
- package/dist/lib/trajectory.js.map +1 -1
- package/dist/tui/screens/auto.d.ts +7 -0
- package/dist/tui/screens/auto.d.ts.map +1 -1
- package/dist/tui/screens/auto.js +18 -1
- package/dist/tui/screens/auto.js.map +1 -1
- package/package.json +3 -3
|
@@ -5,7 +5,7 @@ import * as fs from 'node:fs';
|
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
-
import { readRunState, writeRunState, recordCycle, recordDocsAudit, getQualityRate } from './run-state.js';
|
|
8
|
+
import { readRunState, writeRunState, recordCycle, recordDocsAudit, getQualityRate, snapshotLearningROI } from './run-state.js';
|
|
9
9
|
import { getSessionPhase } from './solo-auto-utils.js';
|
|
10
10
|
import { checkPrStatuses, fetchPrReviewComments, deleteTicketBranch, deleteRemoteBranch, } from './solo-git.js';
|
|
11
11
|
import { loadGuidelines } from './guidelines.js';
|
|
@@ -15,6 +15,7 @@ import { normalizeQaConfig } from './solo-utils.js';
|
|
|
15
15
|
import { getPromptwheelDir } from './solo-config.js';
|
|
16
16
|
import { removePrEntries } from './file-cooldown.js';
|
|
17
17
|
import { recordFormulaMergeOutcome } from './run-state.js';
|
|
18
|
+
import { updatePrOutcome } from './pr-outcomes.js';
|
|
18
19
|
import { recordMergeOutcome, saveSectors, refreshSectors, suggestScopeAdjustment, } from './sectors.js';
|
|
19
20
|
import { loadDedupMemory } from './dedup-memory.js';
|
|
20
21
|
import { calibrateConfidence } from './qa-stats.js';
|
|
@@ -25,7 +26,8 @@ import { buildTasteProfile, saveTasteProfile } from './taste-profile.js';
|
|
|
25
26
|
import { runMeasurement, measureGoals, pickGoalByGap, recordGoalMeasurement, } from './goals.js';
|
|
26
27
|
import { sleep } from './dedup.js';
|
|
27
28
|
import { saveTrajectoryState } from './trajectory.js';
|
|
28
|
-
import { getNextStep as getTrajectoryNextStep, trajectoryComplete, trajectoryStuck, } from '@promptwheel/core/trajectory/shared';
|
|
29
|
+
import { getNextStep as getTrajectoryNextStep, trajectoryComplete, trajectoryFullySucceeded, trajectoryStuck, } from '@promptwheel/core/trajectory/shared';
|
|
30
|
+
import { recordDrillTrajectoryOutcome, computeAmbitionLevel } from './solo-auto-drill.js';
|
|
29
31
|
export async function runPreCycleMaintenance(state) {
|
|
30
32
|
state.cycleCount++;
|
|
31
33
|
state.cycleOutcomes = [];
|
|
@@ -72,7 +74,7 @@ export async function runPreCycleMaintenance(state) {
|
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
// Backpressure from open PRs (skip in direct mode)
|
|
75
|
-
if (state.runMode === '
|
|
77
|
+
if (state.runMode === 'spin' && state.pendingPrUrls.length > 0 && state.deliveryMode !== 'direct') {
|
|
76
78
|
const openRatio = state.pendingPrUrls.length / state.maxPrs;
|
|
77
79
|
if (openRatio > 0.7) {
|
|
78
80
|
console.log(chalk.yellow(` Backpressure: ${state.pendingPrUrls.length}/${state.maxPrs} PRs open — waiting for reviews...`));
|
|
@@ -109,7 +111,7 @@ export async function runPreCycleMaintenance(state) {
|
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
// Periodic pull
|
|
112
|
-
if (state.pullInterval > 0 && state.runMode === '
|
|
114
|
+
if (state.pullInterval > 0 && state.runMode === 'spin') {
|
|
113
115
|
state.cyclesSinceLastPull++;
|
|
114
116
|
if (state.cyclesSinceLastPull >= state.pullInterval) {
|
|
115
117
|
state.cyclesSinceLastPull = 0;
|
|
@@ -132,11 +134,13 @@ export async function runPreCycleMaintenance(state) {
|
|
|
132
134
|
console.log();
|
|
133
135
|
console.log(chalk.bold('Resolution:'));
|
|
134
136
|
console.log(` 1. Resolve the divergence (rebase, merge, or reset)`);
|
|
135
|
-
console.log(` 2. Re-run: promptwheel
|
|
137
|
+
console.log(` 2. Re-run: promptwheel`);
|
|
136
138
|
console.log();
|
|
137
139
|
console.log(chalk.gray(` To keep going despite divergence, set pullPolicy: "warn" in config.`));
|
|
138
140
|
// Signal orchestrator to break — finalizeSession handles cleanup
|
|
139
141
|
state.shutdownRequested = true;
|
|
142
|
+
if (state.shutdownReason === null)
|
|
143
|
+
state.shutdownReason = 'branch_diverged';
|
|
140
144
|
return { shouldSkipCycle: true };
|
|
141
145
|
}
|
|
142
146
|
else {
|
|
@@ -156,7 +160,7 @@ export async function runPreCycleMaintenance(state) {
|
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
// Periodic PR status poll (every 5 cycles)
|
|
159
|
-
if (state.runMode === '
|
|
163
|
+
if (state.runMode === 'spin' && state.cycleCount > 1 && state.cycleCount % 5 === 0 && state.pendingPrUrls.length > 0) {
|
|
160
164
|
try {
|
|
161
165
|
const prStatuses = await checkPrStatuses(state.repoRoot, state.pendingPrUrls);
|
|
162
166
|
for (const pr of prStatuses) {
|
|
@@ -168,6 +172,10 @@ export async function runPreCycleMaintenance(state) {
|
|
|
168
172
|
recordMergeOutcome(state.sectorState, prMeta.sectorId, true);
|
|
169
173
|
recordFormulaMergeOutcome(state.repoRoot, prMeta.formula, true);
|
|
170
174
|
}
|
|
175
|
+
try {
|
|
176
|
+
updatePrOutcome(state.repoRoot, pr.url, 'merged', Date.now());
|
|
177
|
+
}
|
|
178
|
+
catch { /* non-fatal */ }
|
|
171
179
|
if (state.autoConf.learningsEnabled) {
|
|
172
180
|
addLearning(state.repoRoot, {
|
|
173
181
|
text: `PR merged: ${pr.url}`.slice(0, 200),
|
|
@@ -190,6 +198,10 @@ export async function runPreCycleMaintenance(state) {
|
|
|
190
198
|
recordMergeOutcome(state.sectorState, prMeta.sectorId, false);
|
|
191
199
|
recordFormulaMergeOutcome(state.repoRoot, prMeta.formula, false);
|
|
192
200
|
}
|
|
201
|
+
try {
|
|
202
|
+
updatePrOutcome(state.repoRoot, pr.url, 'closed', Date.now());
|
|
203
|
+
}
|
|
204
|
+
catch { /* non-fatal */ }
|
|
193
205
|
if (state.autoConf.learningsEnabled) {
|
|
194
206
|
addLearning(state.repoRoot, {
|
|
195
207
|
text: `PR closed/rejected: ${pr.url}`.slice(0, 200),
|
|
@@ -312,17 +324,21 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
312
324
|
if (recheckResult?.output)
|
|
313
325
|
updatedDetails[name].output = recheckResult.output;
|
|
314
326
|
}
|
|
315
|
-
|
|
327
|
+
const blTmp = blPath + '.tmp';
|
|
328
|
+
fs.writeFileSync(blTmp, JSON.stringify({
|
|
316
329
|
failures: stillFailing,
|
|
317
330
|
details: updatedDetails,
|
|
318
331
|
timestamp: Date.now(),
|
|
319
332
|
}));
|
|
333
|
+
fs.renameSync(blTmp, blPath);
|
|
320
334
|
}
|
|
321
335
|
}
|
|
322
336
|
}
|
|
323
337
|
}
|
|
324
338
|
}
|
|
325
|
-
catch {
|
|
339
|
+
catch (err) {
|
|
340
|
+
console.warn(chalk.gray(` Baseline healing skipped: ${err instanceof Error ? err.message : String(err)}`));
|
|
341
|
+
}
|
|
326
342
|
}
|
|
327
343
|
// Meta-learning extraction (aggregate pattern detection)
|
|
328
344
|
let metaInsightsAdded = 0;
|
|
@@ -343,6 +359,24 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
343
359
|
// Non-fatal
|
|
344
360
|
}
|
|
345
361
|
}
|
|
362
|
+
// Low-yield cycle detection — primary Nash equilibrium stop signal
|
|
363
|
+
const completedThisCount = state.cycleOutcomes.filter(o => o.status === 'completed').length;
|
|
364
|
+
if (completedThisCount === 0 && state.cycleCount >= 2) {
|
|
365
|
+
state.consecutiveLowYieldCycles++;
|
|
366
|
+
const MAX_LOW_YIELD_CYCLES = state.drillMode ? 5 : 3;
|
|
367
|
+
if (state.consecutiveLowYieldCycles >= MAX_LOW_YIELD_CYCLES) {
|
|
368
|
+
console.log(chalk.yellow(` ${state.consecutiveLowYieldCycles} consecutive low-yield cycles — diminishing returns, stopping`));
|
|
369
|
+
state.shutdownRequested = true;
|
|
370
|
+
if (state.shutdownReason === null)
|
|
371
|
+
state.shutdownReason = 'low_yield';
|
|
372
|
+
}
|
|
373
|
+
else if (state.options.verbose) {
|
|
374
|
+
console.log(chalk.gray(` Low-yield cycle (${state.consecutiveLowYieldCycles}/${MAX_LOW_YIELD_CYCLES})`));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
else if (completedThisCount > 0) {
|
|
378
|
+
state.consecutiveLowYieldCycles = 0;
|
|
379
|
+
}
|
|
346
380
|
// Wheel diagnostics one-liner (always shown, not verbose-gated)
|
|
347
381
|
if (state.cycleCount >= 2) {
|
|
348
382
|
const qualityRate = getQualityRate(state.repoRoot);
|
|
@@ -355,7 +389,7 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
355
389
|
const confValue = state.effectiveMinConfidence;
|
|
356
390
|
const insightsStr = metaInsightsAdded > 0 ? ` | insights +${metaInsightsAdded}` : '';
|
|
357
391
|
const baselineStr = baselineFailing > 0 ? ` | baseline failing ${baselineFailing}` : '';
|
|
358
|
-
console.log(chalk.gray(`
|
|
392
|
+
console.log(chalk.gray(` Spin: quality ${qualityPct}% | confidence ${confValue}${baselineStr}${insightsStr}`));
|
|
359
393
|
}
|
|
360
394
|
// Convergence metrics
|
|
361
395
|
if (state.cycleCount >= 3 && state.sectorState) {
|
|
@@ -366,20 +400,85 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
366
400
|
prsMerged: state.totalMergedPrs,
|
|
367
401
|
prsClosed: state.totalClosedPrs,
|
|
368
402
|
};
|
|
369
|
-
|
|
403
|
+
// Build drill context for convergence if in drill mode
|
|
404
|
+
let drillCtx;
|
|
405
|
+
if (state.drillMode && state.drillHistory.length >= 2) {
|
|
406
|
+
const { computeDrillMetrics } = await import('./solo-auto-drill.js');
|
|
407
|
+
const dm = computeDrillMetrics(state.drillHistory);
|
|
408
|
+
drillCtx = {
|
|
409
|
+
completionRate: dm.completionRate,
|
|
410
|
+
step1FailureRate: dm.step1FailureRate,
|
|
411
|
+
consecutiveInsufficient: state.drillConsecutiveInsufficient,
|
|
412
|
+
trajectoryCount: dm.totalTrajectories,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
const metrics = computeConvergenceMetrics(state.sectorState, state.allLearnings.length, rs.recentCycles ?? [], sessionCtx, drillCtx);
|
|
370
416
|
console.log(chalk.gray(` ${formatConvergenceOneLiner(metrics)}`));
|
|
371
417
|
if (metrics.suggestedAction === 'stop') {
|
|
372
|
-
|
|
373
|
-
|
|
418
|
+
if (state.activeTrajectory && state.activeTrajectoryState) {
|
|
419
|
+
// Adaptive threshold: use historical completion rate to decide when to abandon
|
|
420
|
+
// If we historically complete 80% of trajectories, a low-progress one is likely still worth finishing
|
|
421
|
+
// If we historically complete 20%, cut losses earlier
|
|
422
|
+
let abandonThreshold = 50; // default: stop if < 50% complete
|
|
423
|
+
if (state.drillMode && state.drillHistory.length >= 3) {
|
|
424
|
+
const { computeDrillMetrics: cdm } = await import('./solo-auto-drill.js');
|
|
425
|
+
const dm = cdm(state.drillHistory);
|
|
426
|
+
// Higher historical completion → higher threshold (more patience)
|
|
427
|
+
// Lower historical completion → lower threshold (cut losses faster)
|
|
428
|
+
abandonThreshold = Math.round(30 + (dm.weightedCompletionRate * 40)); // range: 30-70%
|
|
429
|
+
}
|
|
430
|
+
const totalSteps = state.activeTrajectory.steps.length;
|
|
431
|
+
const completedSteps = state.activeTrajectory.steps.filter(s => state.activeTrajectoryState.stepStates[s.id]?.status === 'completed').length;
|
|
432
|
+
const progressPct = totalSteps > 0 ? Math.round((completedSteps / totalSteps) * 100) : 0;
|
|
433
|
+
if (progressPct < abandonThreshold) {
|
|
434
|
+
console.log(chalk.yellow(` Convergence suggests stopping — trajectory "${state.activeTrajectory.name}" only ${progressPct}% complete, skipping it`));
|
|
435
|
+
if (state.drillMode) {
|
|
436
|
+
try {
|
|
437
|
+
finishDrillTrajectory(state, 'stalled');
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
console.log(chalk.yellow(` Drill: failed to record trajectory outcome — ${err instanceof Error ? err.message : String(err)}`));
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
state.activeTrajectory = null;
|
|
444
|
+
state.activeTrajectoryState = null;
|
|
445
|
+
state.currentTrajectoryStep = null;
|
|
446
|
+
state.shutdownRequested = true;
|
|
447
|
+
if (state.shutdownReason === null)
|
|
448
|
+
state.shutdownReason = 'convergence';
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
console.log(chalk.gray(` Convergence suggests stopping, but trajectory "${state.activeTrajectory.name}" is ${progressPct}% complete — continuing`));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
console.log(chalk.yellow(` Convergence suggests stopping — most sectors polished, low yield.`));
|
|
456
|
+
state.shutdownRequested = true;
|
|
457
|
+
if (state.shutdownReason === null)
|
|
458
|
+
state.shutdownReason = 'convergence';
|
|
459
|
+
}
|
|
374
460
|
}
|
|
375
461
|
}
|
|
376
462
|
// Scope adjustment (confidence only — impact uses static config floor)
|
|
377
463
|
if (state.sectorState && state.cycleCount >= 3) {
|
|
378
464
|
const scopeAdj = suggestScopeAdjustment(state.sectorState);
|
|
379
465
|
if (scopeAdj === 'widen') {
|
|
380
|
-
|
|
466
|
+
// In drill mode with active trajectory, don't widen — stay focused on trajectory scope
|
|
467
|
+
if (state.drillMode && state.currentTrajectoryStep?.scope) {
|
|
468
|
+
if (state.options.verbose)
|
|
469
|
+
console.log(chalk.gray(` Scope adjustment: drill mode — staying focused on trajectory scope`));
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
state.effectiveMinConfidence = state.autoConf.minConfidence ?? 20;
|
|
473
|
+
if (state.options.verbose)
|
|
474
|
+
console.log(chalk.gray(` Scope adjustment: widening (resetting confidence threshold)`));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else if (scopeAdj === 'narrow' && state.drillMode && state.currentTrajectoryStep) {
|
|
478
|
+
// In drill mode, tighten confidence when trajectory-guided to focus on high-quality proposals
|
|
479
|
+
state.effectiveMinConfidence += 5;
|
|
381
480
|
if (state.options.verbose)
|
|
382
|
-
console.log(chalk.gray(` Scope adjustment:
|
|
481
|
+
console.log(chalk.gray(` Scope adjustment: drill-narrowed (confidence +5)`));
|
|
383
482
|
}
|
|
384
483
|
}
|
|
385
484
|
// Cross-sector pattern learning
|
|
@@ -401,6 +500,14 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
401
500
|
}
|
|
402
501
|
}
|
|
403
502
|
}
|
|
503
|
+
// Learning ROI snapshot (every 10 cycles)
|
|
504
|
+
if (state.cycleCount % 10 === 0 && state.autoConf.learningsEnabled) {
|
|
505
|
+
try {
|
|
506
|
+
const { getLearningEffectiveness } = await import('./learnings.js');
|
|
507
|
+
snapshotLearningROI(state.repoRoot, getLearningEffectiveness);
|
|
508
|
+
}
|
|
509
|
+
catch { /* non-fatal */ }
|
|
510
|
+
}
|
|
404
511
|
// Periodic learnings consolidation
|
|
405
512
|
try {
|
|
406
513
|
if (state.cycleCount % 5 === 0 && state.autoConf.learningsEnabled) {
|
|
@@ -415,8 +522,9 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
415
522
|
}
|
|
416
523
|
}
|
|
417
524
|
}
|
|
418
|
-
catch {
|
|
525
|
+
catch (err) {
|
|
419
526
|
// Non-fatal — learnings persist from previous cycle
|
|
527
|
+
console.warn(chalk.gray(` Learnings consolidation skipped: ${err instanceof Error ? err.message : String(err)}`));
|
|
420
528
|
}
|
|
421
529
|
// Refresh codebase index
|
|
422
530
|
if (state.codebaseIndex && hasStructuralChanges(state.codebaseIndex, state.repoRoot)) {
|
|
@@ -437,7 +545,7 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
437
545
|
}
|
|
438
546
|
}
|
|
439
547
|
// Reload dedup memory
|
|
440
|
-
if (state.runMode === '
|
|
548
|
+
if (state.runMode === 'spin') {
|
|
441
549
|
state.dedupMemory = loadDedupMemory(state.repoRoot);
|
|
442
550
|
}
|
|
443
551
|
// Goal re-measurement
|
|
@@ -482,14 +590,28 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
482
590
|
else {
|
|
483
591
|
// Update current value for next cycle's prompt
|
|
484
592
|
state.activeGoalMeasurement.current = value;
|
|
485
|
-
// Recalculate gap
|
|
486
|
-
if (direction === 'up'
|
|
487
|
-
|
|
593
|
+
// Recalculate gap (guarded against division by zero)
|
|
594
|
+
if (direction === 'up') {
|
|
595
|
+
if (value >= target) {
|
|
596
|
+
state.activeGoalMeasurement.gapPercent = 0;
|
|
597
|
+
}
|
|
598
|
+
else if (target !== 0) {
|
|
599
|
+
state.activeGoalMeasurement.gapPercent = Math.round(((target - value) / target) * 1000) / 10;
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
state.activeGoalMeasurement.gapPercent = 100;
|
|
603
|
+
}
|
|
488
604
|
}
|
|
489
605
|
else if (direction === 'down') {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
606
|
+
if (value <= target) {
|
|
607
|
+
state.activeGoalMeasurement.gapPercent = 0;
|
|
608
|
+
}
|
|
609
|
+
else if (value !== 0) {
|
|
610
|
+
state.activeGoalMeasurement.gapPercent = Math.round(((value - target) / value) * 1000) / 10;
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
state.activeGoalMeasurement.gapPercent = 0;
|
|
614
|
+
}
|
|
493
615
|
}
|
|
494
616
|
}
|
|
495
617
|
}
|
|
@@ -497,6 +619,30 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
497
619
|
console.log(chalk.yellow(` ⚠ Goal "${state.activeGoal.name}" re-measurement failed${error ? `: ${error}` : ''}`));
|
|
498
620
|
}
|
|
499
621
|
}
|
|
622
|
+
// Trajectory cycle budget — abandon if consuming too many cycles.
|
|
623
|
+
// Scales with step count: more steps get more budget (2-step → base, 8-step → ~2x base).
|
|
624
|
+
if (state.drillMode && state.activeTrajectory && state.activeTrajectoryState) {
|
|
625
|
+
const baseMaxCycles = state.autoConf.drill?.maxCyclesPerTrajectory ?? 15;
|
|
626
|
+
const stepsTotal = state.activeTrajectory.steps.length;
|
|
627
|
+
const maxCycles = Math.round(baseMaxCycles * Math.min(2.5, Math.max(0.8, 1 + Math.max(0, stepsTotal - 3) / 5)));
|
|
628
|
+
const totalCyclesUsed = Object.values(state.activeTrajectoryState.stepStates)
|
|
629
|
+
.reduce((sum, s) => sum + (s.cyclesAttempted ?? 0), 0);
|
|
630
|
+
if (totalCyclesUsed >= maxCycles) {
|
|
631
|
+
const completedSteps = state.activeTrajectory.steps.filter(s => state.activeTrajectoryState.stepStates[s.id]?.status === 'completed').length;
|
|
632
|
+
const pct = Math.round((completedSteps / state.activeTrajectory.steps.length) * 100);
|
|
633
|
+
console.log(chalk.yellow(` Drill: trajectory "${state.activeTrajectory.name}" hit cycle budget (${totalCyclesUsed}/${maxCycles} cycles, ${pct}% complete) — abandoning`));
|
|
634
|
+
saveTrajectoryState(state.repoRoot, state.activeTrajectoryState);
|
|
635
|
+
try {
|
|
636
|
+
finishDrillTrajectory(state, 'stalled');
|
|
637
|
+
}
|
|
638
|
+
catch (err) {
|
|
639
|
+
console.log(chalk.yellow(` Drill: failed to record trajectory outcome — ${err instanceof Error ? err.message : String(err)}`));
|
|
640
|
+
}
|
|
641
|
+
state.activeTrajectory = null;
|
|
642
|
+
state.activeTrajectoryState = null;
|
|
643
|
+
state.currentTrajectoryStep = null;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
500
646
|
// Trajectory step progression
|
|
501
647
|
if (state.activeTrajectory && state.activeTrajectoryState && state.currentTrajectoryStep) {
|
|
502
648
|
const step = state.currentTrajectoryStep;
|
|
@@ -504,40 +650,108 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
504
650
|
if (stepState) {
|
|
505
651
|
// Run step verification commands
|
|
506
652
|
let allPassed = true;
|
|
653
|
+
const verificationOutputParts = [];
|
|
507
654
|
if (step.verification_commands.length > 0) {
|
|
508
|
-
|
|
509
|
-
const result = spawnSync('sh', ['-c', cmd], {
|
|
510
|
-
|
|
511
|
-
|
|
655
|
+
for (const cmd of step.verification_commands) {
|
|
656
|
+
const result = spawnSync('sh', ['-c', cmd], {
|
|
657
|
+
cwd: state.repoRoot,
|
|
658
|
+
timeout: 30000,
|
|
659
|
+
encoding: 'utf-8',
|
|
660
|
+
});
|
|
661
|
+
if (result.error) {
|
|
662
|
+
// Timeout or spawn error
|
|
663
|
+
allPassed = false;
|
|
664
|
+
const reason = result.error.message?.includes('TIMEOUT') ? 'timeout (30s)' : result.error.message;
|
|
665
|
+
console.log(chalk.yellow(` ✗ ${cmd} (${reason})`));
|
|
666
|
+
verificationOutputParts.push(`$ ${cmd}\n${reason}`);
|
|
667
|
+
}
|
|
668
|
+
else if (result.status !== 0) {
|
|
669
|
+
allPassed = false;
|
|
670
|
+
const stderr = (result.stderr || '').trim().slice(0, 500);
|
|
671
|
+
const stdout = (result.stdout || '').trim().slice(0, 200);
|
|
672
|
+
console.log(chalk.yellow(` ✗ ${cmd} (exit ${result.status})`));
|
|
673
|
+
if (stderr)
|
|
674
|
+
console.log(chalk.gray(` ${stderr.split('\n')[0]}`));
|
|
675
|
+
else if (stdout)
|
|
676
|
+
console.log(chalk.gray(` ${stdout.split('\n')[0]}`));
|
|
677
|
+
verificationOutputParts.push(`$ ${cmd} (exit ${result.status})\n${stderr || stdout}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
512
680
|
}
|
|
513
681
|
// Optional measurement check
|
|
514
682
|
let measureMet = true;
|
|
515
683
|
if (step.measure) {
|
|
516
|
-
const { value } = runMeasurement(step.measure.cmd, state.repoRoot);
|
|
684
|
+
const { value, error } = runMeasurement(step.measure.cmd, state.repoRoot);
|
|
517
685
|
if (value !== null) {
|
|
686
|
+
const arrow = step.measure.direction === 'up' ? '>=' : '<=';
|
|
518
687
|
measureMet = step.measure.direction === 'up'
|
|
519
688
|
? value >= step.measure.target
|
|
520
689
|
: value <= step.measure.target;
|
|
521
690
|
stepState.measurement = { value, timestamp: Date.now() };
|
|
691
|
+
if (!measureMet) {
|
|
692
|
+
console.log(chalk.yellow(` measure: ${value} (target: ${arrow} ${step.measure.target})`));
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
measureMet = false;
|
|
697
|
+
console.log(chalk.yellow(` measure failed${error ? `: ${error}` : ''}`));
|
|
522
698
|
}
|
|
523
699
|
}
|
|
524
|
-
if (allPassed && measureMet
|
|
700
|
+
if (allPassed && measureMet) {
|
|
525
701
|
// Step completed — advance
|
|
526
702
|
stepState.status = 'completed';
|
|
527
703
|
stepState.completedAt = Date.now();
|
|
528
|
-
|
|
704
|
+
stepState.consecutiveFailures = 0;
|
|
705
|
+
stepState.lastVerificationOutput = undefined;
|
|
706
|
+
const completedCount = state.activeTrajectory.steps.filter(s => state.activeTrajectoryState.stepStates[s.id]?.status === 'completed').length;
|
|
707
|
+
const totalCount = state.activeTrajectory.steps.length;
|
|
708
|
+
console.log(chalk.green(` Trajectory step ${completedCount}/${totalCount} "${step.title}" completed`));
|
|
529
709
|
// Pick next step
|
|
530
710
|
const next = getTrajectoryNextStep(state.activeTrajectory, state.activeTrajectoryState.stepStates);
|
|
531
711
|
state.currentTrajectoryStep = next;
|
|
532
712
|
if (next) {
|
|
533
713
|
state.activeTrajectoryState.currentStepId = next.id;
|
|
534
|
-
state.activeTrajectoryState.stepStates[next.id]
|
|
714
|
+
if (state.activeTrajectoryState.stepStates[next.id]) {
|
|
715
|
+
state.activeTrajectoryState.stepStates[next.id].status = 'active';
|
|
716
|
+
}
|
|
535
717
|
console.log(chalk.cyan(` -> Next step: ${next.title}`));
|
|
536
718
|
}
|
|
537
719
|
else if (trajectoryComplete(state.activeTrajectory, state.activeTrajectoryState.stepStates)) {
|
|
538
|
-
|
|
720
|
+
const fullySucceeded = trajectoryFullySucceeded(state.activeTrajectory, state.activeTrajectoryState.stepStates);
|
|
721
|
+
const outcome = fullySucceeded ? 'completed' : 'stalled';
|
|
722
|
+
if (fullySucceeded) {
|
|
723
|
+
console.log(chalk.green(` Trajectory "${state.activeTrajectory.name}" complete!`));
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
console.log(chalk.yellow(` Trajectory "${state.activeTrajectory.name}" finished with some failed steps`));
|
|
727
|
+
}
|
|
539
728
|
// Save final state before clearing (so completed status persists on disk)
|
|
540
729
|
saveTrajectoryState(state.repoRoot, state.activeTrajectoryState);
|
|
730
|
+
if (state.drillMode) {
|
|
731
|
+
try {
|
|
732
|
+
finishDrillTrajectory(state, outcome);
|
|
733
|
+
}
|
|
734
|
+
catch (err) {
|
|
735
|
+
console.log(chalk.yellow(` Drill: failed to record trajectory outcome — ${err instanceof Error ? err.message : String(err)}`));
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
state.activeTrajectory = null;
|
|
739
|
+
state.activeTrajectoryState = null;
|
|
740
|
+
state.currentTrajectoryStep = null;
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
// No next step available but trajectory isn't complete — shouldn't happen now
|
|
744
|
+
// (failed deps unblock dependents), but handle as fallback
|
|
745
|
+
console.log(chalk.yellow(` Trajectory "${state.activeTrajectory.name}" stalled (remaining steps blocked)`));
|
|
746
|
+
saveTrajectoryState(state.repoRoot, state.activeTrajectoryState);
|
|
747
|
+
if (state.drillMode) {
|
|
748
|
+
try {
|
|
749
|
+
finishDrillTrajectory(state, 'stalled');
|
|
750
|
+
}
|
|
751
|
+
catch (err) {
|
|
752
|
+
console.log(chalk.yellow(` Drill: failed to record trajectory outcome — ${err instanceof Error ? err.message : String(err)}`));
|
|
753
|
+
}
|
|
754
|
+
}
|
|
541
755
|
state.activeTrajectory = null;
|
|
542
756
|
state.activeTrajectoryState = null;
|
|
543
757
|
state.currentTrajectoryStep = null;
|
|
@@ -547,20 +761,52 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
547
761
|
// Step not yet complete — increment attempt counter
|
|
548
762
|
stepState.cyclesAttempted++;
|
|
549
763
|
stepState.lastAttemptedCycle = state.cycleCount;
|
|
550
|
-
//
|
|
551
|
-
|
|
764
|
+
// Track consecutive and total failures for transient/flakiness detection
|
|
765
|
+
stepState.consecutiveFailures = (stepState.consecutiveFailures ?? 0) + 1;
|
|
766
|
+
stepState.totalFailures = (stepState.totalFailures ?? 0) + 1;
|
|
767
|
+
// Capture verification output for prompt injection on next attempt
|
|
768
|
+
if (verificationOutputParts.length > 0) {
|
|
769
|
+
stepState.lastVerificationOutput = verificationOutputParts.join('\n').slice(0, 1000);
|
|
770
|
+
}
|
|
771
|
+
// Check for stuck — pass full step list so each step uses its own max_retries
|
|
772
|
+
const stuckId = trajectoryStuck(state.activeTrajectoryState.stepStates, undefined, state.activeTrajectory.steps);
|
|
552
773
|
if (stuckId) {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
774
|
+
// Fail the actual stuck step (may differ from current step if state was corrupted)
|
|
775
|
+
const stuckStepState = state.activeTrajectoryState.stepStates[stuckId];
|
|
776
|
+
const stuckStep = state.activeTrajectory.steps.find(s => s.id === stuckId);
|
|
777
|
+
const stuckTitle = stuckStep?.title ?? stuckId;
|
|
778
|
+
const stuckAttempts = stuckStepState?.cyclesAttempted ?? stepState.cyclesAttempted;
|
|
779
|
+
console.log(chalk.yellow(` Trajectory step "${stuckTitle}" stuck after ${stuckAttempts} cycles`));
|
|
780
|
+
if (stuckStepState) {
|
|
781
|
+
stuckStepState.status = 'failed';
|
|
782
|
+
stuckStepState.failureReason = 'max retries exceeded';
|
|
783
|
+
}
|
|
556
784
|
// Try to advance to next step
|
|
557
785
|
const next = getTrajectoryNextStep(state.activeTrajectory, state.activeTrajectoryState.stepStates);
|
|
558
786
|
state.currentTrajectoryStep = next;
|
|
559
787
|
if (next) {
|
|
560
788
|
state.activeTrajectoryState.currentStepId = next.id;
|
|
561
|
-
state.activeTrajectoryState.stepStates[next.id]
|
|
789
|
+
if (state.activeTrajectoryState.stepStates[next.id]) {
|
|
790
|
+
state.activeTrajectoryState.stepStates[next.id].status = 'active';
|
|
791
|
+
}
|
|
562
792
|
console.log(chalk.cyan(` -> Skipping to next step: ${next.title}`));
|
|
563
793
|
}
|
|
794
|
+
else {
|
|
795
|
+
// No more steps — trajectory is done (all remaining steps failed or completed)
|
|
796
|
+
console.log(chalk.yellow(` Trajectory "${state.activeTrajectory.name}" ended (no remaining steps)`));
|
|
797
|
+
saveTrajectoryState(state.repoRoot, state.activeTrajectoryState);
|
|
798
|
+
if (state.drillMode) {
|
|
799
|
+
try {
|
|
800
|
+
finishDrillTrajectory(state, 'stalled');
|
|
801
|
+
}
|
|
802
|
+
catch (err) {
|
|
803
|
+
console.log(chalk.yellow(` Drill: failed to record trajectory outcome — ${err instanceof Error ? err.message : String(err)}`));
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
state.activeTrajectory = null;
|
|
807
|
+
state.activeTrajectoryState = null;
|
|
808
|
+
state.currentTrajectoryStep = null;
|
|
809
|
+
}
|
|
564
810
|
}
|
|
565
811
|
}
|
|
566
812
|
if (state.activeTrajectoryState) {
|
|
@@ -568,10 +814,105 @@ export async function runPostCycleMaintenance(state, scope, isDocsAuditCycle) {
|
|
|
568
814
|
}
|
|
569
815
|
}
|
|
570
816
|
}
|
|
571
|
-
// Pause between cycles
|
|
572
|
-
if (state.runMode === '
|
|
817
|
+
// Pause between cycles — shorter when trajectory-guided (work is pre-planned)
|
|
818
|
+
if (state.runMode === 'spin' && !state.shutdownRequested) {
|
|
819
|
+
const pauseMs = state.currentTrajectoryStep ? 1000 : 5000;
|
|
573
820
|
console.log(chalk.gray('Pausing before next cycle...'));
|
|
574
|
-
await sleep(
|
|
821
|
+
await sleep(pauseMs);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
// ── Drill trajectory lifecycle ───────────────────────────────────────────────
|
|
825
|
+
/**
|
|
826
|
+
* Record a drill trajectory's completion/stall into history, record learnings,
|
|
827
|
+
* and log the next-survey message.
|
|
828
|
+
*
|
|
829
|
+
* Must be called BEFORE clearing state.activeTrajectory (needs the trajectory data).
|
|
830
|
+
*/
|
|
831
|
+
function finishDrillTrajectory(state, outcome) {
|
|
832
|
+
if (!state.activeTrajectory || !state.activeTrajectoryState)
|
|
833
|
+
return;
|
|
834
|
+
const traj = state.activeTrajectory;
|
|
835
|
+
const trajState = state.activeTrajectoryState;
|
|
836
|
+
const stepsTotal = traj.steps.length;
|
|
837
|
+
const stepsCompleted = traj.steps.filter(s => trajState.stepStates[s.id]?.status === 'completed').length;
|
|
838
|
+
const stepsFailed = traj.steps.filter(s => trajState.stepStates[s.id]?.status === 'failed').length;
|
|
839
|
+
// Collect failed step details for history
|
|
840
|
+
const failedStepDetails = traj.steps
|
|
841
|
+
.filter(s => trajState.stepStates[s.id]?.status === 'failed')
|
|
842
|
+
.map(s => ({
|
|
843
|
+
id: s.id,
|
|
844
|
+
title: s.title,
|
|
845
|
+
reason: trajState.stepStates[s.id]?.lastVerificationOutput?.slice(0, 200)
|
|
846
|
+
?? trajState.stepStates[s.id]?.failureReason,
|
|
847
|
+
}));
|
|
848
|
+
// Collect completed step summaries for causal chaining
|
|
849
|
+
const completedStepSummaries = traj.steps
|
|
850
|
+
.filter(s => trajState.stepStates[s.id]?.status === 'completed')
|
|
851
|
+
.map(s => s.title);
|
|
852
|
+
// Collect modified files from git (since trajectory started)
|
|
853
|
+
let modifiedFiles;
|
|
854
|
+
try {
|
|
855
|
+
const trajStartTime = trajState.startedAt ?? (state.startTime || 0);
|
|
856
|
+
if (trajStartTime > 0) {
|
|
857
|
+
// Use git log --diff-filter with --since instead of HEAD~N (which fails with shallow repos or few commits)
|
|
858
|
+
const sinceDate = new Date(trajStartTime).toISOString();
|
|
859
|
+
const gitResult = spawnSync('git', [
|
|
860
|
+
'log', '--diff-filter=ACMR', '--name-only', '--pretty=format:',
|
|
861
|
+
`--since=${sinceDate}`,
|
|
862
|
+
], { cwd: state.repoRoot, encoding: 'utf-8', timeout: 5000 });
|
|
863
|
+
if (!gitResult.error && gitResult.status === 0 && gitResult.stdout.trim()) {
|
|
864
|
+
// Deduplicate file names (same file may appear in multiple commits)
|
|
865
|
+
modifiedFiles = [...new Set(gitResult.stdout.trim().split('\n').filter(Boolean))].slice(0, 20);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
catch { /* non-fatal */ }
|
|
870
|
+
// Collect per-step outcomes for telemetry (enables step-level learning)
|
|
871
|
+
const stepOutcomes = traj.steps.map(s => ({
|
|
872
|
+
id: s.id,
|
|
873
|
+
status: (trajState.stepStates[s.id]?.status ?? 'pending'),
|
|
874
|
+
}));
|
|
875
|
+
// Record into drill history (for avoidance + diversity + stats)
|
|
876
|
+
recordDrillTrajectoryOutcome(state, traj.name, traj.description, stepsTotal, stepsCompleted, stepsFailed, outcome, traj.steps, failedStepDetails.length > 0 ? failedStepDetails : undefined, completedStepSummaries.length > 0 ? completedStepSummaries : undefined, modifiedFiles, computeAmbitionLevel(state), {
|
|
877
|
+
stepOutcomes,
|
|
878
|
+
...state.drillGenerationTelemetry,
|
|
879
|
+
});
|
|
880
|
+
state.drillGenerationTelemetry = null;
|
|
881
|
+
// Record learnings
|
|
882
|
+
if (state.autoConf.learningsEnabled) {
|
|
883
|
+
const categories = [...new Set(traj.steps.flatMap(s => s.categories ?? []))];
|
|
884
|
+
const catLabel = categories.join(', ') || 'mixed';
|
|
885
|
+
if (outcome === 'completed') {
|
|
886
|
+
addLearning(state.repoRoot, {
|
|
887
|
+
text: `Drill trajectory "${traj.name}" completed (${stepsCompleted}/${stepsTotal} steps). Theme: ${traj.description}. Categories: ${catLabel}`.slice(0, 200),
|
|
888
|
+
category: 'pattern',
|
|
889
|
+
source: { type: 'drill_completed', detail: traj.name },
|
|
890
|
+
tags: categories,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
const failedSteps = traj.steps
|
|
895
|
+
.filter(s => trajState.stepStates[s.id]?.status === 'failed')
|
|
896
|
+
.map(s => s.title);
|
|
897
|
+
addLearning(state.repoRoot, {
|
|
898
|
+
text: `Drill trajectory "${traj.name}" stalled (${stepsCompleted}/${stepsTotal} completed, ${stepsFailed} failed). Failed: ${failedSteps.join(', ')}`.slice(0, 200),
|
|
899
|
+
category: 'warning',
|
|
900
|
+
source: { type: 'drill_stalled', detail: traj.name },
|
|
901
|
+
tags: categories,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
const rate = stepsTotal > 0 ? Math.round((stepsCompleted / stepsTotal) * 100) : 0;
|
|
906
|
+
console.log(chalk.cyan(` Drill: trajectory ${outcome} (${stepsCompleted}/${stepsTotal} steps, ${rate}% completion)`));
|
|
907
|
+
console.log(chalk.cyan(' Drill: will survey for next trajectory on next cycle'));
|
|
908
|
+
// Notify display adapter that trajectory finished (back to idle)
|
|
909
|
+
state.displayAdapter.drillStateChanged({ active: true });
|
|
910
|
+
// Reload learnings immediately so next trajectory generation has fresh context
|
|
911
|
+
if (state.autoConf.learningsEnabled) {
|
|
912
|
+
try {
|
|
913
|
+
state.allLearnings = loadLearnings(state.repoRoot, 0);
|
|
914
|
+
}
|
|
915
|
+
catch { /* non-fatal */ }
|
|
575
916
|
}
|
|
576
917
|
}
|
|
577
918
|
//# sourceMappingURL=solo-auto-between-cycles.js.map
|