@magnusekdahl/parallix 1.3.2 → 1.3.4
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/docs/agents.md +1 -1
- package/docs/use-cases.md +1 -1
- package/lib/agents/agents.js +20 -3
- package/lib/agents/agents.ts +14 -3
- package/lib/agents/mistral-telemetry.js +122 -23
- package/lib/agents/mistral-telemetry.ts +141 -26
- package/lib/agents/mistral.js +128 -14
- package/lib/agents/mistral.ts +145 -5
- package/lib/commands/active.js +69 -39
- package/lib/commands/active.ts +97 -60
- package/lib/commands/config.ts +3 -3
- package/lib/commands/coverage-gate.ts +1 -1
- package/lib/commands/draft.js +1 -1
- package/lib/commands/draft.ts +1 -1
- package/lib/commands/handoff.js +13 -8
- package/lib/commands/handoff.ts +24 -18
- package/lib/commands/rebase.js +1 -1
- package/lib/commands/rebase.ts +2 -2
- package/lib/commands/repair-handoff.js +141 -20
- package/lib/commands/repair-handoff.ts +185 -45
- package/lib/commands/resolve-conflict.js +1 -1
- package/lib/commands/resolve-conflict.ts +2 -2
- package/lib/commands/stats-backfill.ts +10 -10
- package/lib/commands/stats.js +38 -11
- package/lib/commands/stats.ts +40 -96
- package/lib/core/fmt.ts +2 -2
- package/lib/core/git.ts +2 -2
- package/lib/core/gitignore.ts +2 -2
- package/lib/core/mission-utils.js +2 -2
- package/lib/core/mission-utils.ts +2 -2
- package/lib/core/persistent-data-migration.ts +2 -2
- package/lib/core/spawn-tee.ts +1 -1
- package/lib/core/state-map.ts +2 -2
- package/lib/core/storage.ts +1 -1
- package/lib/core/verification.ts +1 -1
- package/lib/review/rebase.ts +12 -12
- package/lib/review/review-artifacts.ts +35 -35
- package/lib/review/review-commands.ts +40 -40
- package/lib/review/review-events.ts +12 -12
- package/lib/review/review-loop.js +236 -7
- package/lib/review/review-loop.ts +338 -22
- package/lib/review/review-polling.ts +6 -6
- package/lib/review/review-prompts.js +8 -4
- package/lib/review/review-prompts.ts +12 -8
- package/lib/review/review-state.js +1 -1
- package/lib/review/review-state.ts +2 -2
- package/package.json +3 -2
- package/prompts/review-verbose.md +1 -1
- package/prompts/review.md +1 -1
- package/px.js +8 -4
package/lib/commands/handoff.ts
CHANGED
|
@@ -58,18 +58,19 @@ import * as nels from '../core/nels.js';
|
|
|
58
58
|
* @param {{skipGate?: boolean, worktree?: string|null, force?: boolean, forceWithLease?: boolean, log?: Function, error?: Function, rebaseFn?: Function, isForgejoReviewEnabledFn?: Function}} [options]
|
|
59
59
|
* @returns {Promise<{ ok: boolean, error?: string, gatekeeperPushedBack?: boolean }>}
|
|
60
60
|
*/
|
|
61
|
-
|
|
62
|
-
/** @type{{skipGate?: boolean, worktree?: string|null, force?: boolean, forceWithLease?: boolean, log?: Function, error?: Function, rebaseFn?: Function, isForgejoReviewEnabledFn?: Function}} */
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
61
|
+
async function performHandoff(slug, options = {}) {
|
|
62
|
+
/** @type{{skipGate?: boolean, worktree?: string|null, force?: boolean, forceWithLease?: boolean, log?: Function, error?: Function, rebaseFn?: Function, isForgejoReviewEnabledFn?: Function, runVerificationGateFn?: Function}} */
|
|
63
|
+
const opts = options;
|
|
64
|
+
const {
|
|
65
|
+
skipGate = false,
|
|
66
|
+
worktree = null,
|
|
67
|
+
force = false,
|
|
68
|
+
forceWithLease = true,
|
|
69
|
+
log = fmt.log.info,
|
|
70
|
+
error = fmt.log.fail,
|
|
71
|
+
rebaseFn = rebaseBeforeReviewRound,
|
|
72
|
+
runVerificationGateFn = runVerificationGate
|
|
73
|
+
} = opts;
|
|
73
74
|
|
|
74
75
|
const verification = verifyHandoff(slug, { worktree: worktree || undefined });
|
|
75
76
|
if (!verification.ok) {
|
|
@@ -198,15 +199,17 @@ import * as nels from '../core/nels.js';
|
|
|
198
199
|
fmt.log.warn('Step 1: Skipping final verification gate (--no-gate)');
|
|
199
200
|
} else {
|
|
200
201
|
log(`Step 1: Running final verification gate for area: ${fmt.bold(area || 'docs')}...`);
|
|
201
|
-
const verifyResult =
|
|
202
|
+
const verifyResult = runVerificationGateFn(area || 'docs', {
|
|
202
203
|
rootDir,
|
|
203
|
-
stdio: '
|
|
204
|
+
stdio: 'pipe',
|
|
204
205
|
runFn: git.run
|
|
205
206
|
});
|
|
206
207
|
if (verifyResult.status !== 0) {
|
|
208
|
+
const stdout = (verifyResult.stdout || '').trim();
|
|
209
|
+
const stderr = (verifyResult.stderr || '').trim();
|
|
207
210
|
const msg = 'Final verification gate failed. Fix errors before submitting or use --no-gate if appropriate.';
|
|
208
211
|
error(msg);
|
|
209
|
-
return { ok: false, error: msg };
|
|
212
|
+
return { ok: false, error: msg, gateOutput: { stdout, stderr } };
|
|
210
213
|
}
|
|
211
214
|
}
|
|
212
215
|
|
|
@@ -346,7 +349,7 @@ import * as nels from '../core/nels.js';
|
|
|
346
349
|
if (!gatesResult.ok) {
|
|
347
350
|
const msg = `Declared gate "${gatesResult.gate}" failed for ${fmt.slug(slug)}: ${gatesResult.error || gatesResult.reason}. Blocking handoff — task remains in active.`;
|
|
348
351
|
error(msg);
|
|
349
|
-
return { ok: false, error: msg };
|
|
352
|
+
return { ok: false, error: msg, gateOutput: { stdout: (gatesResult.stdout || ''), stderr: (gatesResult.stderr || '') } };
|
|
350
353
|
}
|
|
351
354
|
if (gatesResult.skipped) {
|
|
352
355
|
log(`No declared gates for ${fmt.slug(slug)} (${gatesResult.reason}).`);
|
|
@@ -463,16 +466,19 @@ function runDeclaredGates(missionDir, rootDir, options = {}) {
|
|
|
463
466
|
const result = spawnSync('bash', ['-c', cmd], {
|
|
464
467
|
cwd: rootDir,
|
|
465
468
|
encoding: 'utf8',
|
|
466
|
-
stdio:
|
|
469
|
+
stdio: 'pipe'
|
|
467
470
|
});
|
|
468
471
|
|
|
469
472
|
if (result.status !== 0) {
|
|
473
|
+
const stdout = (result.stdout || '').trim();
|
|
470
474
|
const stderr = (result.stderr || '').trim();
|
|
471
475
|
return {
|
|
472
476
|
ok: false,
|
|
473
477
|
gate: cmd,
|
|
474
478
|
reason: 'gate-failed',
|
|
475
|
-
error: stderr || `Gate exited with status ${result.status}
|
|
479
|
+
error: stderr || `Gate exited with status ${result.status}`,
|
|
480
|
+
stdout,
|
|
481
|
+
stderr
|
|
476
482
|
};
|
|
477
483
|
}
|
|
478
484
|
}
|
package/lib/commands/rebase.js
CHANGED
|
@@ -58,7 +58,7 @@ const verification_js_1 = require("../core/verification.js");
|
|
|
58
58
|
* Usage: px rebase [<slug>] [--push]
|
|
59
59
|
*/
|
|
60
60
|
/** @param {string[]} args @param {{inferSlugFn?: Function, findMissionDirFn?: Function, findMissionAreaFn?: Function, getCurrentBranchFn?: Function, resolveConflictsFn?: Function, startAgentFn?: Function, createPrFn?: Function, readTokenFn?: Function, resolveForgejoUserFn?: Function, resolveTaskFileFn?: Function, getTaskImplementerFn?: Function, detectRebaseStateFn?: Function, resolveMissionBaseBranchFn?: Function, gitFn?: Function, exitFn?: Function, isForgejoReviewEnabledFn?: Function, fetchReviewBranchFn?: Function}} opts */
|
|
61
|
-
async function rebase(args, { inferSlugFn = mission_utils_js_1.inferSlug, findMissionDirFn = mission_utils_js_1.findMissionDir, findMissionAreaFn = mission_utils_js_1.findMissionArea, getCurrentBranchFn = git_js_1.getCurrentBranch, resolveConflictsFn = integrate_js_1.default.resolveConflictsForMission, startAgentFn = agents_js_1.startAgent, createPrFn = forgejo_js_1.createPr, readTokenFn = forgejo_js_1.readToken, resolveForgejoUserFn = forgejo_js_1.resolveForgejoUser, resolveTaskFileFn = backlog_js_1.resolveTaskFile, getTaskImplementerFn = backlog_js_1.getTaskImplementer, detectRebaseStateFn = git_js_1.detectRebaseState, resolveMissionBaseBranchFn = mission_utils_js_1.resolveMissionBaseBranch, gitFn = git_js_1.git, exitFn = ((
|
|
61
|
+
async function rebase(args, { inferSlugFn = mission_utils_js_1.inferSlug, findMissionDirFn = mission_utils_js_1.findMissionDir, findMissionAreaFn = mission_utils_js_1.findMissionArea, getCurrentBranchFn = git_js_1.getCurrentBranch, resolveConflictsFn = integrate_js_1.default.resolveConflictsForMission, startAgentFn = agents_js_1.startAgent, createPrFn = forgejo_js_1.createPr, readTokenFn = forgejo_js_1.readToken, resolveForgejoUserFn = forgejo_js_1.resolveForgejoUser, resolveTaskFileFn = backlog_js_1.resolveTaskFile, getTaskImplementerFn = backlog_js_1.getTaskImplementer, detectRebaseStateFn = git_js_1.detectRebaseState, resolveMissionBaseBranchFn = mission_utils_js_1.resolveMissionBaseBranch, gitFn = git_js_1.git, exitFn = ((_code) => process.exit(_code)), isForgejoReviewEnabledFn = product_config_js_1.isForgejoReviewEnabled, fetchReviewBranchFn = forgejo_js_1.fetchReviewBranch, } = {}) {
|
|
62
62
|
const flags = args.filter(a => a.startsWith('--'));
|
|
63
63
|
const params = args.filter(a => !a.startsWith('--'));
|
|
64
64
|
const isPush = flags.includes('--push');
|
package/lib/commands/rebase.ts
CHANGED
|
@@ -32,10 +32,10 @@ async function rebase(args: string[], {
|
|
|
32
32
|
detectRebaseStateFn = detectRebaseState,
|
|
33
33
|
resolveMissionBaseBranchFn = resolveMissionBaseBranch,
|
|
34
34
|
gitFn = git,
|
|
35
|
-
exitFn = ((
|
|
35
|
+
exitFn = ((_code: number) => process.exit(_code)) as (_code: number) => void,
|
|
36
36
|
isForgejoReviewEnabledFn = isForgejoReviewEnabled,
|
|
37
37
|
fetchReviewBranchFn = fetchReviewBranch,
|
|
38
|
-
}: {inferSlugFn?: Function, findMissionDirFn?: Function, findMissionAreaFn?: Function, getCurrentBranchFn?: Function, resolveConflictsFn?: Function, startAgentFn?: Function, createPrFn?: Function, readTokenFn?: Function, resolveForgejoUserFn?: Function, resolveTaskFileFn?: Function, getTaskImplementerFn?: Function, detectRebaseStateFn?: Function, resolveMissionBaseBranchFn?: Function, gitFn?: Function, exitFn?: (
|
|
38
|
+
}: {inferSlugFn?: Function, findMissionDirFn?: Function, findMissionAreaFn?: Function, getCurrentBranchFn?: Function, resolveConflictsFn?: Function, startAgentFn?: Function, createPrFn?: Function, readTokenFn?: Function, resolveForgejoUserFn?: Function, resolveTaskFileFn?: Function, getTaskImplementerFn?: Function, detectRebaseStateFn?: Function, resolveMissionBaseBranchFn?: Function, gitFn?: Function, exitFn?: (_code: number) => void, isForgejoReviewEnabledFn?: Function, fetchReviewBranchFn?: Function} = {}) {
|
|
39
39
|
const flags = args.filter(a => a.startsWith('--'));
|
|
40
40
|
const params = args.filter(a => !a.startsWith('--'));
|
|
41
41
|
const isPush = flags.includes('--push');
|
|
@@ -36,6 +36,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.DispatchAction = exports.FailureClass = void 0;
|
|
40
|
+
exports.getDispatchAction = getDispatchAction;
|
|
41
|
+
exports.classifyError = classifyError;
|
|
39
42
|
exports.repairHandoff = repairHandoff;
|
|
40
43
|
exports.isRelaunchableError = isRelaunchableError;
|
|
41
44
|
exports.buildRelaunchPrompt = buildRelaunchPrompt;
|
|
@@ -43,19 +46,129 @@ const git_js_1 = require("../core/git.js");
|
|
|
43
46
|
const missionUtils = __importStar(require("../core/mission-utils.js"));
|
|
44
47
|
const rebase_js_1 = __importDefault(require("./rebase.js"));
|
|
45
48
|
const fmt = __importStar(require("../core/fmt.js"));
|
|
49
|
+
// ── FailureClass: 8 classes from ADR 0048 ────────────────────────────────────
|
|
50
|
+
const FailureClass = {
|
|
51
|
+
UnverifiableClaims: 'UnverifiableClaims',
|
|
52
|
+
MalformedGates: 'MalformedGates',
|
|
53
|
+
MissingArtifacts: 'MissingArtifacts',
|
|
54
|
+
IncompleteEvidence: 'IncompleteEvidence',
|
|
55
|
+
GitBlockers: 'GitBlockers',
|
|
56
|
+
GateFailure: 'GateFailure',
|
|
57
|
+
InfraBlocker: 'InfraBlocker',
|
|
58
|
+
StateMachineViolation: 'StateMachineViolation',
|
|
59
|
+
};
|
|
60
|
+
exports.FailureClass = FailureClass;
|
|
61
|
+
// ── DispatchAction: 3 actions from ADR 0048 ──────────────────────────────────
|
|
62
|
+
const DispatchAction = {
|
|
63
|
+
AutoRepair: 'AutoRepair',
|
|
64
|
+
AutoSendBack: 'AutoSendBack',
|
|
65
|
+
HumanOnly: 'HumanOnly',
|
|
66
|
+
};
|
|
67
|
+
exports.DispatchAction = DispatchAction;
|
|
68
|
+
// ── Dispatch table: maps each failure class to its prescribed action (ADR 0048) ─
|
|
69
|
+
const DISPATCH_TABLE = {
|
|
70
|
+
[FailureClass.UnverifiableClaims]: DispatchAction.AutoSendBack,
|
|
71
|
+
[FailureClass.MalformedGates]: DispatchAction.AutoRepair,
|
|
72
|
+
[FailureClass.MissingArtifacts]: DispatchAction.AutoSendBack,
|
|
73
|
+
[FailureClass.IncompleteEvidence]: DispatchAction.AutoSendBack,
|
|
74
|
+
[FailureClass.GitBlockers]: DispatchAction.AutoRepair,
|
|
75
|
+
[FailureClass.GateFailure]: DispatchAction.AutoSendBack,
|
|
76
|
+
[FailureClass.InfraBlocker]: DispatchAction.HumanOnly,
|
|
77
|
+
[FailureClass.StateMachineViolation]: DispatchAction.HumanOnly,
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Look up the dispatch action for a given failure class.
|
|
81
|
+
*
|
|
82
|
+
* @param failureClass - One of the 8 failure class values from ADR 0048
|
|
83
|
+
* @returns The prescribed dispatch action, or null if unknown
|
|
84
|
+
*/
|
|
85
|
+
function getDispatchAction(failureClass) {
|
|
86
|
+
return DISPATCH_TABLE[failureClass] ?? null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Classify an error message into one of the 8 failure classes from ADR 0048
|
|
90
|
+
* and return the associated dispatch action.
|
|
91
|
+
*
|
|
92
|
+
* Patterns are checked in order of specificity to avoid collisions.
|
|
93
|
+
* Returns a `reason` field for GitBlockers to distinguish dirty-artifact from behind-branch errors,
|
|
94
|
+
* allowing callers to derive repair-strategy flags without duplicating pattern matching.
|
|
95
|
+
*
|
|
96
|
+
* @param errorMsg - The error message to classify
|
|
97
|
+
* @returns Object with failureClass, dispatchAction, and (for GitBlockers) reason
|
|
98
|
+
*/
|
|
99
|
+
function classifyError(errorMsg) {
|
|
100
|
+
if (!errorMsg || typeof errorMsg !== 'string') {
|
|
101
|
+
return { failureClass: FailureClass.InfraBlocker, dispatchAction: DispatchAction.HumanOnly };
|
|
102
|
+
}
|
|
103
|
+
// 1. IncompleteEvidence: goal-check table missing evidence rows (most specific — checked before generic gate patterns)
|
|
104
|
+
if (errorMsg.includes('has a "## Goal Check" section but no evidence rows') &&
|
|
105
|
+
errorMsg.includes('A goal-check table with real evidence is required before handoff')) {
|
|
106
|
+
return { failureClass: FailureClass.IncompleteEvidence, dispatchAction: DispatchAction.AutoSendBack };
|
|
107
|
+
}
|
|
108
|
+
// 2. GitBlockers: dirty/uncommitted mission artifacts (mechanical git blocker — auto-repairable)
|
|
109
|
+
if (errorMsg.includes('is modified but uncommitted') ||
|
|
110
|
+
errorMsg.includes('Commit the mission contract before handoff') ||
|
|
111
|
+
errorMsg.includes('Commit the implementation evidence before handoff')) {
|
|
112
|
+
return { failureClass: FailureClass.GitBlockers, dispatchAction: DispatchAction.AutoRepair, reason: 'dirty' };
|
|
113
|
+
}
|
|
114
|
+
// 3. GitBlockers: branch behind primary / push rejected (mechanical git blocker — auto-repairable via rebase)
|
|
115
|
+
if (errorMsg.includes('Updates were rejected') ||
|
|
116
|
+
errorMsg.includes('fetch first') ||
|
|
117
|
+
errorMsg.includes('non-fast-forward') ||
|
|
118
|
+
errorMsg.includes('behind its remote') ||
|
|
119
|
+
(errorMsg.includes('git push failed') && (errorMsg.includes('rejected') ||
|
|
120
|
+
errorMsg.includes('remote contains work')))) {
|
|
121
|
+
return { failureClass: FailureClass.GitBlockers, dispatchAction: DispatchAction.AutoRepair, reason: 'behind' };
|
|
122
|
+
}
|
|
123
|
+
// 4. GateFailure: verification gate failed
|
|
124
|
+
if (/verification gate failed/i.test(errorMsg)) {
|
|
125
|
+
return { failureClass: FailureClass.GateFailure, dispatchAction: DispatchAction.AutoSendBack };
|
|
126
|
+
}
|
|
127
|
+
// 5. GateFailure: declared gate failed
|
|
128
|
+
if (/\bdeclared gate\b/i.test(errorMsg) && /\bfailed\b/i.test(errorMsg)) {
|
|
129
|
+
return { failureClass: FailureClass.GateFailure, dispatchAction: DispatchAction.AutoSendBack };
|
|
130
|
+
}
|
|
131
|
+
// 6. UnverifiableClaims: test claims that cannot be verified
|
|
132
|
+
if (/test(s?\s+)?passed/i.test(errorMsg) && /cannot\s+verify|unverifiable|proof\s+(not\s+)?found|stale\s+proof/i.test(errorMsg)) {
|
|
133
|
+
return { failureClass: FailureClass.UnverifiableClaims, dispatchAction: DispatchAction.AutoSendBack };
|
|
134
|
+
}
|
|
135
|
+
// 7. MalformedGates: malformed or non-runnable declared gates
|
|
136
|
+
if (/malformed\s+gate|invalid\s+gate\s+config|gate\s+command\s+(not\s+found|syntax\s+error|not\s+runnable)/i.test(errorMsg) ||
|
|
137
|
+
(/gate/i.test(errorMsg) && /syntax\s+error|not\s+found|missing\s+(file|command)/i.test(errorMsg))) {
|
|
138
|
+
return { failureClass: FailureClass.MalformedGates, dispatchAction: DispatchAction.AutoRepair };
|
|
139
|
+
}
|
|
140
|
+
// 8. MissingArtifacts: mandatory mission artifacts missing
|
|
141
|
+
if (/mandatory\s+(artifact|file|document)|missing\s+(mission\s+)?(artifact|file|document)|required\s+(artifact|file|document)\s+(not\s+)?found/i.test(errorMsg) ||
|
|
142
|
+
(/gatekeeper/i.test(errorMsg) && /missing\s+(artifact|file|document)/i.test(errorMsg))) {
|
|
143
|
+
return { failureClass: FailureClass.MissingArtifacts, dispatchAction: DispatchAction.AutoSendBack };
|
|
144
|
+
}
|
|
145
|
+
// 9. StateMachineViolation: task state machine violations
|
|
146
|
+
if (/state\s+violation|invalid\s+state|transition\s+not\s+allowed|cannot\s+(move|transition)\s+(from|to)\s+\w+\s+(to|from)/i.test(errorMsg) ||
|
|
147
|
+
(/task\s+state/i.test(errorMsg) && /invalid|violation|incorrect/i.test(errorMsg))) {
|
|
148
|
+
return { failureClass: FailureClass.StateMachineViolation, dispatchAction: DispatchAction.HumanOnly };
|
|
149
|
+
}
|
|
150
|
+
// 10. InfraBlocker: forgejo/infrastructure blockers
|
|
151
|
+
if (/forgejo|infrastructure|authentication\s+failed|token\s+(expired|invalid|missing)|forbidden|unauthorized\s+(access|request)|rate\s+limit|connection\s+(refused|timed?\s*out)|network\s+error/i.test(errorMsg)) {
|
|
152
|
+
return { failureClass: FailureClass.InfraBlocker, dispatchAction: DispatchAction.HumanOnly };
|
|
153
|
+
}
|
|
154
|
+
// Default: human-only for unrecognized errors
|
|
155
|
+
return { failureClass: FailureClass.InfraBlocker, dispatchAction: DispatchAction.HumanOnly };
|
|
156
|
+
}
|
|
46
157
|
/**
|
|
47
158
|
* Check if an error message indicates a relaunchable content error (missing/empty goal-check table).
|
|
159
|
+
* Delegates to classifyError for backward-compatible classification.
|
|
48
160
|
*
|
|
49
|
-
* @param
|
|
50
|
-
* @returns
|
|
161
|
+
* @param errorMsg - The error message to check
|
|
162
|
+
* @returns True if the error is relaunchable (IncompleteEvidence or GateFailure)
|
|
51
163
|
*/
|
|
52
164
|
function isRelaunchableError(errorMsg) {
|
|
53
165
|
if (!errorMsg || typeof errorMsg !== 'string') {
|
|
54
166
|
return false;
|
|
55
167
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
168
|
+
const { failureClass } = classifyError(errorMsg);
|
|
169
|
+
// IncompleteEvidence and GateFailure are the only classes that were relaunchable under the old logic
|
|
170
|
+
return failureClass === FailureClass.IncompleteEvidence
|
|
171
|
+
|| failureClass === FailureClass.GateFailure;
|
|
59
172
|
}
|
|
60
173
|
/**
|
|
61
174
|
* Build a relaunch prompt for an agent to fix a relaunchable error.
|
|
@@ -65,10 +178,10 @@ function isRelaunchableError(errorMsg) {
|
|
|
65
178
|
* @param {string} worktree - Path to the mission worktree
|
|
66
179
|
* @returns {string} The relaunch prompt
|
|
67
180
|
*/
|
|
68
|
-
function buildRelaunchPrompt(errorMsg, slug, worktree) {
|
|
181
|
+
function buildRelaunchPrompt(errorMsg, slug, worktree, gateOutput) {
|
|
69
182
|
const year = missionUtils.getMissionYear(slug, worktree);
|
|
70
183
|
const missionDir = missionUtils.findMissionDir(slug, worktree) || missionUtils.missionDirForSlug(worktree, slug);
|
|
71
|
-
|
|
184
|
+
let prompt = `Automated handoff failed for mission ${slug} with a repairable error: ${errorMsg}
|
|
72
185
|
|
|
73
186
|
` +
|
|
74
187
|
`Please fix the final checkpoint document in ${missionDir} by adding a Goal Check table ` +
|
|
@@ -103,6 +216,16 @@ function buildRelaunchPrompt(errorMsg, slug, worktree) {
|
|
|
103
216
|
|
|
104
217
|
` +
|
|
105
218
|
`Do NOT add placeholder or generic evidence. Each row must cite real, verifiable artifacts.`;
|
|
219
|
+
// Append captured gate output if available (task-1387)
|
|
220
|
+
if (gateOutput && (gateOutput.stdout || gateOutput.stderr)) {
|
|
221
|
+
const totalOutput = (gateOutput.stdout || '') + (gateOutput.stderr || '');
|
|
222
|
+
// Truncate if total output exceeds 16000 chars; keep last 8000 chars
|
|
223
|
+
const truncated = totalOutput.length > 16000
|
|
224
|
+
? `[truncated — total ${totalOutput.length} chars, showing last 8000]\n` + totalOutput.slice(-8000)
|
|
225
|
+
: totalOutput;
|
|
226
|
+
prompt += `\n\n--- Captured Gate Output ---\n${truncated}`;
|
|
227
|
+
}
|
|
228
|
+
return prompt;
|
|
106
229
|
}
|
|
107
230
|
/**
|
|
108
231
|
* Attempt to repair a failed automated handoff by auto-committing mission
|
|
@@ -129,23 +252,17 @@ async function repairHandoff(slug, worktree, errorMsg, options = {}) {
|
|
|
129
252
|
const cleanPath = (pathPart.startsWith('"') && pathPart.endsWith('"')) ? pathPart.slice(1, -1) : pathPart;
|
|
130
253
|
return { xy, file: cleanPath };
|
|
131
254
|
}
|
|
132
|
-
// 0. Check if error is repairable
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const isBehind =
|
|
137
|
-
|
|
138
|
-
errorMsg.includes('non-fast-forward') ||
|
|
139
|
-
errorMsg.includes('behind its remote') ||
|
|
140
|
-
// Ensure we don't match generic git push failed unless it has non-fast-forward hints
|
|
141
|
-
(errorMsg.includes('git push failed') && (errorMsg.includes('rejected') ||
|
|
142
|
-
errorMsg.includes('remote contains work'))));
|
|
143
|
-
if (!isDirtyError && !isBehind) {
|
|
255
|
+
// 0. Check if error is repairable via classifyError
|
|
256
|
+
const classification = classifyError(errorMsg);
|
|
257
|
+
const isGitBlocker = classification.failureClass === FailureClass.GitBlockers;
|
|
258
|
+
// Derive isBehind from classification result (avoids duplicating classifyError's behind-branch patterns)
|
|
259
|
+
const isBehind = classification.reason === 'behind';
|
|
260
|
+
if (!isGitBlocker) {
|
|
144
261
|
log(`Handoff error is not automatically repairable: ${errorMsg}`);
|
|
145
262
|
return { repaired: false, blocker: null };
|
|
146
263
|
}
|
|
147
264
|
// 1. Auto-commit mission artifacts if uncommitted
|
|
148
|
-
if (
|
|
265
|
+
if (isGitBlocker) {
|
|
149
266
|
const statusResult = gitFn(['-C', rootDir, 'status', '--porcelain']);
|
|
150
267
|
if (statusResult.status === 0 && statusResult.stdout) {
|
|
151
268
|
const dirtyLines = statusResult.stdout.split('\n')
|
|
@@ -236,6 +353,10 @@ async function repairHandoff(slug, worktree, errorMsg, options = {}) {
|
|
|
236
353
|
}
|
|
237
354
|
repairHandoff.isRelaunchableError = isRelaunchableError;
|
|
238
355
|
repairHandoff.buildRelaunchPrompt = buildRelaunchPrompt;
|
|
356
|
+
repairHandoff.classifyError = classifyError;
|
|
357
|
+
repairHandoff.getDispatchAction = getDispatchAction;
|
|
358
|
+
repairHandoff.FailureClass = FailureClass;
|
|
359
|
+
repairHandoff.DispatchAction = DispatchAction;
|
|
239
360
|
exports.default = repairHandoff;
|
|
240
361
|
if (typeof module !== 'undefined') {
|
|
241
362
|
module.exports = repairHandoff;
|
|
@@ -3,19 +3,155 @@ import * as missionUtils from '../core/mission-utils.js';
|
|
|
3
3
|
import rebase from './rebase.js';
|
|
4
4
|
import * as fmt from '../core/fmt.js';
|
|
5
5
|
|
|
6
|
+
// ── FailureClass: 8 classes from ADR 0048 ────────────────────────────────────
|
|
7
|
+
const FailureClass = {
|
|
8
|
+
UnverifiableClaims: 'UnverifiableClaims',
|
|
9
|
+
MalformedGates: 'MalformedGates',
|
|
10
|
+
MissingArtifacts: 'MissingArtifacts',
|
|
11
|
+
IncompleteEvidence: 'IncompleteEvidence',
|
|
12
|
+
GitBlockers: 'GitBlockers',
|
|
13
|
+
GateFailure: 'GateFailure',
|
|
14
|
+
InfraBlocker: 'InfraBlocker',
|
|
15
|
+
StateMachineViolation: 'StateMachineViolation',
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
type FailureClassType = (typeof FailureClass)[keyof typeof FailureClass];
|
|
19
|
+
|
|
20
|
+
// ── DispatchAction: 3 actions from ADR 0048 ──────────────────────────────────
|
|
21
|
+
const DispatchAction = {
|
|
22
|
+
AutoRepair: 'AutoRepair',
|
|
23
|
+
AutoSendBack: 'AutoSendBack',
|
|
24
|
+
HumanOnly: 'HumanOnly',
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
type DispatchActionType = (typeof DispatchAction)[keyof typeof DispatchAction];
|
|
28
|
+
|
|
29
|
+
// ── Dispatch table: maps each failure class to its prescribed action (ADR 0048) ─
|
|
30
|
+
const DISPATCH_TABLE: Record<FailureClassType, DispatchActionType> = {
|
|
31
|
+
[FailureClass.UnverifiableClaims]: DispatchAction.AutoSendBack,
|
|
32
|
+
[FailureClass.MalformedGates]: DispatchAction.AutoRepair,
|
|
33
|
+
[FailureClass.MissingArtifacts]: DispatchAction.AutoSendBack,
|
|
34
|
+
[FailureClass.IncompleteEvidence]: DispatchAction.AutoSendBack,
|
|
35
|
+
[FailureClass.GitBlockers]: DispatchAction.AutoRepair,
|
|
36
|
+
[FailureClass.GateFailure]: DispatchAction.AutoSendBack,
|
|
37
|
+
[FailureClass.InfraBlocker]: DispatchAction.HumanOnly,
|
|
38
|
+
[FailureClass.StateMachineViolation]: DispatchAction.HumanOnly,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Look up the dispatch action for a given failure class.
|
|
43
|
+
*
|
|
44
|
+
* @param failureClass - One of the 8 failure class values from ADR 0048
|
|
45
|
+
* @returns The prescribed dispatch action, or null if unknown
|
|
46
|
+
*/
|
|
47
|
+
export function getDispatchAction(failureClass: FailureClassType): DispatchActionType | null {
|
|
48
|
+
return DISPATCH_TABLE[failureClass] ?? null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sub-reason for GitBlockers classification: distinguishes dirty-artifact from behind-branch errors.
|
|
53
|
+
* Used by repairHandoff() to decide whether to auto-rebase (behind) vs auto-commit only (dirty).
|
|
54
|
+
*/
|
|
55
|
+
type GitBlockerReason = 'dirty' | 'behind' | 'other';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Classify an error message into one of the 8 failure classes from ADR 0048
|
|
59
|
+
* and return the associated dispatch action.
|
|
60
|
+
*
|
|
61
|
+
* Patterns are checked in order of specificity to avoid collisions.
|
|
62
|
+
* Returns a `reason` field for GitBlockers to distinguish dirty-artifact from behind-branch errors,
|
|
63
|
+
* allowing callers to derive repair-strategy flags without duplicating pattern matching.
|
|
64
|
+
*
|
|
65
|
+
* @param errorMsg - The error message to classify
|
|
66
|
+
* @returns Object with failureClass, dispatchAction, and (for GitBlockers) reason
|
|
67
|
+
*/
|
|
68
|
+
export function classifyError(errorMsg: string): { failureClass: FailureClassType; dispatchAction: DispatchActionType; reason?: GitBlockerReason } {
|
|
69
|
+
if (!errorMsg || typeof errorMsg !== 'string') {
|
|
70
|
+
return { failureClass: FailureClass.InfraBlocker, dispatchAction: DispatchAction.HumanOnly };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 1. IncompleteEvidence: goal-check table missing evidence rows (most specific — checked before generic gate patterns)
|
|
74
|
+
if (errorMsg.includes('has a "## Goal Check" section but no evidence rows') &&
|
|
75
|
+
errorMsg.includes('A goal-check table with real evidence is required before handoff')) {
|
|
76
|
+
return { failureClass: FailureClass.IncompleteEvidence, dispatchAction: DispatchAction.AutoSendBack };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 2. GitBlockers: dirty/uncommitted mission artifacts (mechanical git blocker — auto-repairable)
|
|
80
|
+
if (errorMsg.includes('is modified but uncommitted') ||
|
|
81
|
+
errorMsg.includes('Commit the mission contract before handoff') ||
|
|
82
|
+
errorMsg.includes('Commit the implementation evidence before handoff')) {
|
|
83
|
+
return { failureClass: FailureClass.GitBlockers, dispatchAction: DispatchAction.AutoRepair, reason: 'dirty' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 3. GitBlockers: branch behind primary / push rejected (mechanical git blocker — auto-repairable via rebase)
|
|
87
|
+
if (errorMsg.includes('Updates were rejected') ||
|
|
88
|
+
errorMsg.includes('fetch first') ||
|
|
89
|
+
errorMsg.includes('non-fast-forward') ||
|
|
90
|
+
errorMsg.includes('behind its remote') ||
|
|
91
|
+
(errorMsg.includes('git push failed') && (
|
|
92
|
+
errorMsg.includes('rejected') ||
|
|
93
|
+
errorMsg.includes('remote contains work')
|
|
94
|
+
))) {
|
|
95
|
+
return { failureClass: FailureClass.GitBlockers, dispatchAction: DispatchAction.AutoRepair, reason: 'behind' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 4. GateFailure: verification gate failed
|
|
99
|
+
if (/verification gate failed/i.test(errorMsg)) {
|
|
100
|
+
return { failureClass: FailureClass.GateFailure, dispatchAction: DispatchAction.AutoSendBack };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 5. GateFailure: declared gate failed
|
|
104
|
+
if (/\bdeclared gate\b/i.test(errorMsg) && /\bfailed\b/i.test(errorMsg)) {
|
|
105
|
+
return { failureClass: FailureClass.GateFailure, dispatchAction: DispatchAction.AutoSendBack };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 6. UnverifiableClaims: test claims that cannot be verified
|
|
109
|
+
if (/test(s?\s+)?passed/i.test(errorMsg) && /cannot\s+verify|unverifiable|proof\s+(not\s+)?found|stale\s+proof/i.test(errorMsg)) {
|
|
110
|
+
return { failureClass: FailureClass.UnverifiableClaims, dispatchAction: DispatchAction.AutoSendBack };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 7. MalformedGates: malformed or non-runnable declared gates
|
|
114
|
+
if (/malformed\s+gate|invalid\s+gate\s+config|gate\s+command\s+(not\s+found|syntax\s+error|not\s+runnable)/i.test(errorMsg) ||
|
|
115
|
+
(/gate/i.test(errorMsg) && /syntax\s+error|not\s+found|missing\s+(file|command)/i.test(errorMsg))) {
|
|
116
|
+
return { failureClass: FailureClass.MalformedGates, dispatchAction: DispatchAction.AutoRepair };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 8. MissingArtifacts: mandatory mission artifacts missing
|
|
120
|
+
if (/mandatory\s+(artifact|file|document)|missing\s+(mission\s+)?(artifact|file|document)|required\s+(artifact|file|document)\s+(not\s+)?found/i.test(errorMsg) ||
|
|
121
|
+
(/gatekeeper/i.test(errorMsg) && /missing\s+(artifact|file|document)/i.test(errorMsg))) {
|
|
122
|
+
return { failureClass: FailureClass.MissingArtifacts, dispatchAction: DispatchAction.AutoSendBack };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 9. StateMachineViolation: task state machine violations
|
|
126
|
+
if (/state\s+violation|invalid\s+state|transition\s+not\s+allowed|cannot\s+(move|transition)\s+(from|to)\s+\w+\s+(to|from)/i.test(errorMsg) ||
|
|
127
|
+
(/task\s+state/i.test(errorMsg) && /invalid|violation|incorrect/i.test(errorMsg))) {
|
|
128
|
+
return { failureClass: FailureClass.StateMachineViolation, dispatchAction: DispatchAction.HumanOnly };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 10. InfraBlocker: forgejo/infrastructure blockers
|
|
132
|
+
if (/forgejo|infrastructure|authentication\s+failed|token\s+(expired|invalid|missing)|forbidden|unauthorized\s+(access|request)|rate\s+limit|connection\s+(refused|timed?\s*out)|network\s+error/i.test(errorMsg)) {
|
|
133
|
+
return { failureClass: FailureClass.InfraBlocker, dispatchAction: DispatchAction.HumanOnly };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Default: human-only for unrecognized errors
|
|
137
|
+
return { failureClass: FailureClass.InfraBlocker, dispatchAction: DispatchAction.HumanOnly };
|
|
138
|
+
}
|
|
139
|
+
|
|
6
140
|
/**
|
|
7
141
|
* Check if an error message indicates a relaunchable content error (missing/empty goal-check table).
|
|
142
|
+
* Delegates to classifyError for backward-compatible classification.
|
|
8
143
|
*
|
|
9
|
-
* @param
|
|
10
|
-
* @returns
|
|
144
|
+
* @param errorMsg - The error message to check
|
|
145
|
+
* @returns True if the error is relaunchable (IncompleteEvidence or GateFailure)
|
|
11
146
|
*/
|
|
12
|
-
function isRelaunchableError(errorMsg: string) {
|
|
147
|
+
function isRelaunchableError(errorMsg: string): boolean {
|
|
13
148
|
if (!errorMsg || typeof errorMsg !== 'string') {
|
|
14
149
|
return false;
|
|
15
150
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
151
|
+
const { failureClass } = classifyError(errorMsg);
|
|
152
|
+
// IncompleteEvidence and GateFailure are the only classes that were relaunchable under the old logic
|
|
153
|
+
return failureClass === FailureClass.IncompleteEvidence
|
|
154
|
+
|| failureClass === FailureClass.GateFailure;
|
|
19
155
|
}
|
|
20
156
|
|
|
21
157
|
/**
|
|
@@ -26,45 +162,57 @@ function isRelaunchableError(errorMsg: string) {
|
|
|
26
162
|
* @param {string} worktree - Path to the mission worktree
|
|
27
163
|
* @returns {string} The relaunch prompt
|
|
28
164
|
*/
|
|
29
|
-
function buildRelaunchPrompt(errorMsg: string, slug: string, worktree: string) {
|
|
165
|
+
function buildRelaunchPrompt(errorMsg: string, slug: string, worktree: string, gateOutput?: { stdout: string; stderr: string }) {
|
|
30
166
|
const year = missionUtils.getMissionYear(slug, worktree);
|
|
31
167
|
const missionDir = missionUtils.findMissionDir(slug, worktree) || missionUtils.missionDirForSlug(worktree, slug);
|
|
32
168
|
|
|
33
|
-
|
|
169
|
+
let prompt = `Automated handoff failed for mission ${slug} with a repairable error: ${errorMsg}
|
|
34
170
|
|
|
35
171
|
` +
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
172
|
+
`Please fix the final checkpoint document in ${missionDir} by adding a Goal Check table ` +
|
|
173
|
+
`with real evidence rows (file:line references, test names). The Goal Check table must have ` +
|
|
174
|
+
`a header row and at least one evidence row using pipe syntax (|).
|
|
39
175
|
|
|
40
176
|
` +
|
|
41
|
-
|
|
177
|
+
`Steps:
|
|
42
178
|
` +
|
|
43
|
-
|
|
179
|
+
`1. Open the final checkpoint document (CP-N.md) in ${missionDir}
|
|
44
180
|
` +
|
|
45
|
-
|
|
181
|
+
`2. Add or update the "## Goal Check" section
|
|
46
182
|
` +
|
|
47
|
-
|
|
183
|
+
`3. Create a markdown table with columns for: Goal Check description | Evidence | Status
|
|
48
184
|
` +
|
|
49
|
-
|
|
185
|
+
`4. Add at least one evidence row with real file:line or test name references
|
|
50
186
|
` +
|
|
51
|
-
|
|
187
|
+
`5. Commit the updated checkpoint with a descriptive commit message
|
|
52
188
|
` +
|
|
53
|
-
|
|
189
|
+
`6. Re-run: node parallix review ${slug} --submit
|
|
54
190
|
|
|
55
191
|
` +
|
|
56
|
-
|
|
192
|
+
`Example Goal Check table:
|
|
57
193
|
` +
|
|
58
|
-
|
|
194
|
+
`| Goal Check | Evidence | Status |
|
|
59
195
|
` +
|
|
60
|
-
|
|
196
|
+
`|---|---|---|
|
|
61
197
|
` +
|
|
62
|
-
|
|
198
|
+
`| Final checkpoint has Goal Check section | docs/missions/${year}/${slug}/CP-1.md:15 | PASS |
|
|
63
199
|
` +
|
|
64
|
-
|
|
200
|
+
`| Tests pass | npm test -- parallix/test/repair-handoff.test.js | PASS |
|
|
65
201
|
|
|
66
202
|
` +
|
|
67
|
-
|
|
203
|
+
`Do NOT add placeholder or generic evidence. Each row must cite real, verifiable artifacts.`;
|
|
204
|
+
|
|
205
|
+
// Append captured gate output if available (task-1387)
|
|
206
|
+
if (gateOutput && (gateOutput.stdout || gateOutput.stderr)) {
|
|
207
|
+
const totalOutput = (gateOutput.stdout || '') + (gateOutput.stderr || '');
|
|
208
|
+
// Truncate if total output exceeds 16000 chars; keep last 8000 chars
|
|
209
|
+
const truncated = totalOutput.length > 16000
|
|
210
|
+
? `[truncated — total ${totalOutput.length} chars, showing last 8000]\n` + totalOutput.slice(-8000)
|
|
211
|
+
: totalOutput;
|
|
212
|
+
prompt += `\n\n--- Captured Gate Output ---\n${truncated}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return prompt;
|
|
68
216
|
}
|
|
69
217
|
|
|
70
218
|
/**
|
|
@@ -100,32 +248,19 @@ async function repairHandoff(slug: string, worktree: string, errorMsg: string, o
|
|
|
100
248
|
return { xy, file: cleanPath };
|
|
101
249
|
}
|
|
102
250
|
|
|
103
|
-
// 0. Check if error is repairable
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const isBehind = errorMsg && (
|
|
111
|
-
errorMsg.includes('Updates were rejected') ||
|
|
112
|
-
errorMsg.includes('fetch first') ||
|
|
113
|
-
errorMsg.includes('non-fast-forward') ||
|
|
114
|
-
errorMsg.includes('behind its remote') ||
|
|
115
|
-
// Ensure we don't match generic git push failed unless it has non-fast-forward hints
|
|
116
|
-
(errorMsg.includes('git push failed') && (
|
|
117
|
-
errorMsg.includes('rejected') ||
|
|
118
|
-
errorMsg.includes('remote contains work')
|
|
119
|
-
))
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
if (!isDirtyError && !isBehind) {
|
|
251
|
+
// 0. Check if error is repairable via classifyError
|
|
252
|
+
const classification = classifyError(errorMsg);
|
|
253
|
+
const isGitBlocker = classification.failureClass === FailureClass.GitBlockers;
|
|
254
|
+
// Derive isBehind from classification result (avoids duplicating classifyError's behind-branch patterns)
|
|
255
|
+
const isBehind = classification.reason === 'behind';
|
|
256
|
+
|
|
257
|
+
if (!isGitBlocker) {
|
|
123
258
|
log(`Handoff error is not automatically repairable: ${errorMsg}`);
|
|
124
259
|
return { repaired: false, blocker: null };
|
|
125
260
|
}
|
|
126
261
|
|
|
127
262
|
// 1. Auto-commit mission artifacts if uncommitted
|
|
128
|
-
if (
|
|
263
|
+
if (isGitBlocker) {
|
|
129
264
|
const statusResult = gitFn(['-C', rootDir, 'status', '--porcelain']);
|
|
130
265
|
if (statusResult.status === 0 && statusResult.stdout) {
|
|
131
266
|
const dirtyLines = statusResult.stdout.split('\n')
|
|
@@ -224,9 +359,14 @@ async function repairHandoff(slug: string, worktree: string, errorMsg: string, o
|
|
|
224
359
|
|
|
225
360
|
(repairHandoff as any).isRelaunchableError = isRelaunchableError;
|
|
226
361
|
(repairHandoff as any).buildRelaunchPrompt = buildRelaunchPrompt;
|
|
362
|
+
(repairHandoff as any).classifyError = classifyError;
|
|
363
|
+
(repairHandoff as any).getDispatchAction = getDispatchAction;
|
|
364
|
+
(repairHandoff as any).FailureClass = FailureClass;
|
|
365
|
+
(repairHandoff as any).DispatchAction = DispatchAction;
|
|
227
366
|
|
|
228
367
|
export default repairHandoff;
|
|
229
368
|
export { repairHandoff, isRelaunchableError, buildRelaunchPrompt };
|
|
369
|
+
export { FailureClass, DispatchAction };
|
|
230
370
|
|
|
231
371
|
// CJS compat: ensure require() returns the function directly
|
|
232
372
|
declare const module: { exports: any } | undefined;
|
|
@@ -80,7 +80,7 @@ function buildAgentResolutionPrompt({ slug, area, worktreePath, missionSpecificF
|
|
|
80
80
|
].join('\n');
|
|
81
81
|
}
|
|
82
82
|
/** @param {string[]} args @param {{resolveConflictsFn?: Function, startAgentFn?: Function, exitFn?: Function}} opts */
|
|
83
|
-
async function resolveConflict(args, { resolveConflictsFn = integrate_js_1.default.resolveConflictsForMission, startAgentFn = agents_js_1.startAgent, exitFn = ((
|
|
83
|
+
async function resolveConflict(args, { resolveConflictsFn = integrate_js_1.default.resolveConflictsForMission, startAgentFn = agents_js_1.startAgent, exitFn = ((_code) => process.exit(_code)), } = {}) {
|
|
84
84
|
const explicitSlug = args[0];
|
|
85
85
|
const slug = (0, mission_utils_js_1.inferSlug)(explicitSlug);
|
|
86
86
|
if (!slug) {
|
|
@@ -47,8 +47,8 @@ function buildAgentResolutionPrompt({ slug, area, worktreePath, missionSpecificF
|
|
|
47
47
|
async function resolveConflict(args: string[], {
|
|
48
48
|
resolveConflictsFn = (integrate as any).resolveConflictsForMission,
|
|
49
49
|
startAgentFn = startAgent,
|
|
50
|
-
exitFn = ((
|
|
51
|
-
}: {resolveConflictsFn?: Function, startAgentFn?: Function, exitFn?: (
|
|
50
|
+
exitFn = ((_code: number) => process.exit(_code)) as (_code: number) => void,
|
|
51
|
+
}: {resolveConflictsFn?: Function, startAgentFn?: Function, exitFn?: (_code: number) => void} = {}) {
|
|
52
52
|
const explicitSlug = args[0];
|
|
53
53
|
const slug = inferSlug(explicitSlug);
|
|
54
54
|
if (!slug) {
|