steroids-cli 0.9.30 → 0.9.32
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/commands/loop-phases.d.ts +1 -1
- package/dist/commands/loop-phases.d.ts.map +1 -1
- package/dist/commands/loop-phases.js +404 -67
- package/dist/commands/loop-phases.js.map +1 -1
- package/dist/database/queries.d.ts +20 -0
- package/dist/database/queries.d.ts.map +1 -1
- package/dist/database/queries.js +63 -0
- package/dist/database/queries.js.map +1 -1
- package/dist/git/status.d.ts +10 -0
- package/dist/git/status.d.ts.map +1 -1
- package/dist/git/status.js +66 -0
- package/dist/git/status.js.map +1 -1
- package/dist/git/submission-resolution.d.ts +26 -0
- package/dist/git/submission-resolution.d.ts.map +1 -0
- package/dist/git/submission-resolution.js +213 -0
- package/dist/git/submission-resolution.js.map +1 -0
- package/dist/orchestrator/coordinator.d.ts +2 -0
- package/dist/orchestrator/coordinator.d.ts.map +1 -1
- package/dist/orchestrator/coordinator.js +17 -1
- package/dist/orchestrator/coordinator.js.map +1 -1
- package/dist/orchestrator/post-coder.d.ts.map +1 -1
- package/dist/orchestrator/post-coder.js +82 -5
- package/dist/orchestrator/post-coder.js.map +1 -1
- package/dist/orchestrator/reviewer.d.ts.map +1 -1
- package/dist/orchestrator/reviewer.js +69 -41
- package/dist/orchestrator/reviewer.js.map +1 -1
- package/dist/orchestrator/schemas.d.ts.map +1 -1
- package/dist/orchestrator/schemas.js +12 -0
- package/dist/orchestrator/schemas.js.map +1 -1
- package/dist/orchestrator/types.d.ts +3 -0
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/prompts/coder.d.ts.map +1 -1
- package/dist/prompts/coder.js +65 -2
- package/dist/prompts/coder.js.map +1 -1
- package/dist/prompts/prompt-helpers.d.ts.map +1 -1
- package/dist/prompts/prompt-helpers.js +15 -4
- package/dist/prompts/prompt-helpers.js.map +1 -1
- package/dist/prompts/reviewer.d.ts +4 -3
- package/dist/prompts/reviewer.d.ts.map +1 -1
- package/dist/prompts/reviewer.js +75 -14
- package/dist/prompts/reviewer.js.map +1 -1
- package/dist/runners/global-db.d.ts.map +1 -1
- package/dist/runners/global-db.js +7 -3
- package/dist/runners/global-db.js.map +1 -1
- package/dist/runners/orchestrator-loop.d.ts.map +1 -1
- package/dist/runners/orchestrator-loop.js +2 -2
- package/dist/runners/orchestrator-loop.js.map +1 -1
- package/dist/runners/wakeup.d.ts.map +1 -1
- package/dist/runners/wakeup.js +142 -2
- package/dist/runners/wakeup.js.map +1 -1
- package/package.json +1 -1
|
@@ -21,6 +21,6 @@ export interface CreditExhaustionResult {
|
|
|
21
21
|
message: string;
|
|
22
22
|
retryAfterMs?: number;
|
|
23
23
|
}
|
|
24
|
-
export declare function runCoderPhase(db: ReturnType<typeof openDatabase>['db'], task: ReturnType<typeof getTask>, projectPath: string, action: 'start' | 'resume', jsonMode?: boolean, coordinatorCache?: Map<string, CoordinatorResult>, coordinatorThresholds?: number[], leaseFence?: LeaseFenceContext): Promise<CreditExhaustionResult | void>;
|
|
24
|
+
export declare function runCoderPhase(db: ReturnType<typeof openDatabase>['db'], task: ReturnType<typeof getTask>, projectPath: string, action: 'start' | 'resume', jsonMode?: boolean, coordinatorCache?: Map<string, CoordinatorResult>, coordinatorThresholds?: number[], leaseFence?: LeaseFenceContext, branchName?: string): Promise<CreditExhaustionResult | void>;
|
|
25
25
|
export declare function runReviewerPhase(db: ReturnType<typeof openDatabase>['db'], task: ReturnType<typeof getTask>, projectPath: string, jsonMode?: boolean, coordinatorResult?: CoordinatorResult, branchName?: string, leaseFence?: LeaseFenceContext): Promise<CreditExhaustionResult | void>;
|
|
26
26
|
//# sourceMappingURL=loop-phases.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop-phases.d.ts","sourceRoot":"","sources":["../../src/commands/loop-phases.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,OAAO,
|
|
1
|
+
{"version":3,"file":"loop-phases.d.ts","sourceRoot":"","sources":["../../src/commands/loop-phases.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,OAAO,EAaR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAU9D,OAAO,EAA8C,KAAK,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AA2BpH,OAAO,EAAE,KAAK,iBAAiB,EAAE,CAAC;AAElC,UAAU,iBAAiB;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA8CD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,yBAAyB,GAAG,YAAY,CAAC;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAoTD,wBAAsB,aAAa,CACjC,EAAE,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,IAAI,CAAC,EACzC,IAAI,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,EAChC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,OAAO,GAAG,QAAQ,EAC1B,QAAQ,UAAQ,EAChB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACjD,qBAAqB,CAAC,EAAE,MAAM,EAAE,EAChC,UAAU,CAAC,EAAE,iBAAiB,EAC9B,UAAU,SAAS,GAClB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAikBxC;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,IAAI,CAAC,EACzC,IAAI,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,EAChC,WAAW,EAAE,MAAM,EACnB,QAAQ,UAAQ,EAChB,iBAAiB,CAAC,EAAE,iBAAiB,EACrC,UAAU,GAAE,MAAe,EAC3B,UAAU,CAAC,EAAE,iBAAiB,GAC7B,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAwcxC"}
|
|
@@ -13,6 +13,7 @@ const reviewer_js_1 = require("../orchestrator/reviewer.js");
|
|
|
13
13
|
const coordinator_js_1 = require("../orchestrator/coordinator.js");
|
|
14
14
|
const push_js_1 = require("../git/push.js");
|
|
15
15
|
const status_js_1 = require("../git/status.js");
|
|
16
|
+
const submission_resolution_js_1 = require("../git/submission-resolution.js");
|
|
16
17
|
const invoke_js_1 = require("../orchestrator/invoke.js");
|
|
17
18
|
const fallback_handler_js_1 = require("../orchestrator/fallback-handler.js");
|
|
18
19
|
const loader_js_1 = require("../config/loader.js");
|
|
@@ -136,9 +137,58 @@ async function checkNonRetryableProviderFailure(result, role, projectPath, revie
|
|
|
136
137
|
}
|
|
137
138
|
return null;
|
|
138
139
|
}
|
|
140
|
+
function summarizeErrorMessage(error) {
|
|
141
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
142
|
+
return raw.replace(/\s+/g, ' ').trim().slice(0, 220);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Classify orchestrator invocation failures so we can distinguish transient parse
|
|
146
|
+
* noise from hard provider/config failures (auth/model/availability).
|
|
147
|
+
*/
|
|
148
|
+
async function classifyOrchestratorFailure(error, projectPath) {
|
|
149
|
+
const message = summarizeErrorMessage(error);
|
|
150
|
+
const lower = message.toLowerCase();
|
|
151
|
+
if (lower.includes('orchestrator ai provider not configured')) {
|
|
152
|
+
return { type: 'orchestrator_unconfigured', message, retryable: false };
|
|
153
|
+
}
|
|
154
|
+
if (lower.includes("provider '") && lower.includes('is not available')) {
|
|
155
|
+
return { type: 'provider_unavailable', message, retryable: false };
|
|
156
|
+
}
|
|
157
|
+
const config = (0, loader_js_1.loadConfig)(projectPath);
|
|
158
|
+
const providerName = config.ai?.orchestrator?.provider;
|
|
159
|
+
if (!providerName) {
|
|
160
|
+
return { type: 'orchestrator_unconfigured', message, retryable: false };
|
|
161
|
+
}
|
|
162
|
+
const registry = await (0, registry_js_1.getProviderRegistry)();
|
|
163
|
+
const provider = registry.tryGet(providerName);
|
|
164
|
+
if (!provider) {
|
|
165
|
+
return { type: 'provider_unavailable', message, retryable: false };
|
|
166
|
+
}
|
|
167
|
+
const syntheticFailure = {
|
|
168
|
+
success: false,
|
|
169
|
+
exitCode: 1,
|
|
170
|
+
stdout: '',
|
|
171
|
+
stderr: message,
|
|
172
|
+
duration: 0,
|
|
173
|
+
timedOut: false,
|
|
174
|
+
};
|
|
175
|
+
const classified = provider.classifyResult(syntheticFailure);
|
|
176
|
+
if (!classified) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
type: classified.type,
|
|
181
|
+
message: classified.message || message,
|
|
182
|
+
retryable: classified.retryable,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
139
185
|
const MAX_ORCHESTRATOR_PARSE_RETRIES = 3;
|
|
186
|
+
const MAX_CONTRACT_VIOLATION_RETRIES = 3;
|
|
140
187
|
const CODER_PARSE_FALLBACK_MARKER = '[retry] FALLBACK: Orchestrator failed, defaulting to retry';
|
|
141
188
|
const REVIEWER_PARSE_FALLBACK_MARKER = '[unclear] FALLBACK: Orchestrator failed, retrying review';
|
|
189
|
+
const CONTRACT_CHECKLIST_MARKER = '[contract:checklist]';
|
|
190
|
+
const CONTRACT_REJECTION_RESPONSE_MARKER = '[contract:rejection_response]';
|
|
191
|
+
const MUST_IMPLEMENT_MARKER = '[must_implement]';
|
|
142
192
|
function countConsecutiveOrchestratorFallbackEntries(db, taskId, marker) {
|
|
143
193
|
const audit = (0, queries_js_1.getTaskAudit)(db, taskId);
|
|
144
194
|
let count = 0;
|
|
@@ -173,6 +223,34 @@ function countConsecutiveUnclearEntries(db, taskId) {
|
|
|
173
223
|
}
|
|
174
224
|
return count;
|
|
175
225
|
}
|
|
226
|
+
function countConsecutiveTaggedOrchestratorEntries(db, taskId, requiredTag, tagFamilyPrefix) {
|
|
227
|
+
const audit = (0, queries_js_1.getTaskAudit)(db, taskId);
|
|
228
|
+
let count = 0;
|
|
229
|
+
for (let i = audit.length - 1; i >= 0; i--) {
|
|
230
|
+
const entry = audit[i];
|
|
231
|
+
if (entry.actor !== 'orchestrator') {
|
|
232
|
+
continue; // tolerate non-orchestrator audit noise
|
|
233
|
+
}
|
|
234
|
+
const notes = entry.notes ?? '';
|
|
235
|
+
if (notes.includes(requiredTag)) {
|
|
236
|
+
count += 1;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (tagFamilyPrefix && notes.includes(tagFamilyPrefix)) {
|
|
240
|
+
break; // same family, different category = sequence ended
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
return count;
|
|
245
|
+
}
|
|
246
|
+
function countLatestOpenRejectionItems(notes) {
|
|
247
|
+
if (!notes)
|
|
248
|
+
return 0;
|
|
249
|
+
return notes
|
|
250
|
+
.split('\n')
|
|
251
|
+
.filter(line => /^\s*[-*]\s*\[\s\]\s+/.test(line))
|
|
252
|
+
.length;
|
|
253
|
+
}
|
|
176
254
|
function hasCoderCompletionSignal(output) {
|
|
177
255
|
const lower = output.toLowerCase();
|
|
178
256
|
if (/\b(?:not|no)\s+(?:task\s+)?(?:is\s+)?(?:complete|complete[d]?|finished|done|ready)\b/.test(lower)) {
|
|
@@ -180,57 +258,117 @@ function hasCoderCompletionSignal(output) {
|
|
|
180
258
|
}
|
|
181
259
|
return /\b(?:task\s+)?(?:is\s+)?(?:complete|complete[d]?|implemented|finished|done|ready for review|ready)\b/.test(lower);
|
|
182
260
|
}
|
|
183
|
-
|
|
261
|
+
function extractSubmissionCommitToken(output) {
|
|
262
|
+
const match = output.match(/^\s*SUBMISSION_COMMIT\s*:\s*([0-9a-fA-F]{7,40})\b/im);
|
|
263
|
+
return match?.[1] ?? null;
|
|
264
|
+
}
|
|
265
|
+
function resolveCoderSubmittedCommitSha(projectPath, coderOutput, options = {}) {
|
|
266
|
+
const tokenSha = extractSubmissionCommitToken(coderOutput);
|
|
267
|
+
if (tokenSha && (0, status_js_1.isCommitReachableWithFetch)(projectPath, tokenSha, { forceFetch: true })) {
|
|
268
|
+
try {
|
|
269
|
+
return (0, node_child_process_1.execSync)(`git rev-parse ${tokenSha}^{commit}`, {
|
|
270
|
+
cwd: projectPath,
|
|
271
|
+
encoding: 'utf-8',
|
|
272
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
273
|
+
}).trim();
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return tokenSha;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (options.requireExplicitToken) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
return (0, status_js_1.getCurrentCommitSha)(projectPath) || undefined;
|
|
283
|
+
}
|
|
284
|
+
async function runCoderPhase(db, task, projectPath, action, jsonMode = false, coordinatorCache, coordinatorThresholds, leaseFence, branchName = 'main') {
|
|
184
285
|
if (!task)
|
|
185
286
|
return;
|
|
186
287
|
refreshParallelWorkstreamLease(projectPath, leaseFence);
|
|
187
288
|
let coordinatorGuidance;
|
|
188
289
|
const thresholds = coordinatorThresholds || [2, 5, 9];
|
|
290
|
+
const persistedMustImplement = (0, queries_js_1.getLatestMustImplementGuidance)(db, task.id);
|
|
291
|
+
const activeMustImplement = persistedMustImplement &&
|
|
292
|
+
task.status === 'in_progress' &&
|
|
293
|
+
task.rejection_count >= persistedMustImplement.rejection_count_watermark
|
|
294
|
+
? persistedMustImplement
|
|
295
|
+
: null;
|
|
189
296
|
// Run coordinator at rejection thresholds (same as before)
|
|
190
297
|
const shouldInvokeCoordinator = thresholds.includes(task.rejection_count);
|
|
191
298
|
const cachedResult = coordinatorCache?.get(task.id);
|
|
192
|
-
if (
|
|
299
|
+
if (activeMustImplement) {
|
|
300
|
+
coordinatorGuidance = activeMustImplement.guidance;
|
|
301
|
+
coordinatorCache?.set(task.id, {
|
|
302
|
+
success: true,
|
|
303
|
+
decision: 'guide_coder',
|
|
304
|
+
guidance: activeMustImplement.guidance,
|
|
305
|
+
});
|
|
193
306
|
if (!jsonMode) {
|
|
194
|
-
console.log(`\
|
|
307
|
+
console.log(`\nActive MUST_IMPLEMENT override detected (rc=${activeMustImplement.rejection_count_watermark})`);
|
|
195
308
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
id: t.id, title: t.title, status: t.status,
|
|
203
|
-
}));
|
|
204
|
-
}
|
|
205
|
-
coordExtra.submissionNotes = (0, queries_js_1.getLatestSubmissionNotes)(db, task.id);
|
|
206
|
-
const modified = (0, status_js_1.getModifiedFiles)(projectPath);
|
|
207
|
-
if (modified.length > 0) {
|
|
208
|
-
coordExtra.gitDiffSummary = modified.join('\n');
|
|
209
|
-
}
|
|
210
|
-
if (cachedResult) {
|
|
211
|
-
coordExtra.previousGuidance = cachedResult.guidance;
|
|
212
|
-
}
|
|
213
|
-
const coordResult = await (0, coordinator_js_1.invokeCoordinator)(task, rejectionHistory, projectPath, coordExtra);
|
|
214
|
-
if (coordResult) {
|
|
215
|
-
coordinatorGuidance = coordResult.guidance;
|
|
216
|
-
coordinatorCache?.set(task.id, coordResult);
|
|
217
|
-
(0, queries_js_1.addAuditEntry)(db, task.id, task.status, task.status, 'coordinator', {
|
|
218
|
-
actorType: 'orchestrator',
|
|
219
|
-
notes: `[${coordResult.decision}] ${coordResult.guidance}`,
|
|
220
|
-
});
|
|
221
|
-
if (!jsonMode) {
|
|
222
|
-
console.log(`\nCoordinator decision: ${coordResult.decision}`);
|
|
223
|
-
console.log('Coordinator guidance stored for both coder and reviewer.');
|
|
224
|
-
}
|
|
309
|
+
}
|
|
310
|
+
if (shouldInvokeCoordinator) {
|
|
311
|
+
if (activeMustImplement &&
|
|
312
|
+
task.rejection_count === activeMustImplement.rejection_count_watermark) {
|
|
313
|
+
if (!jsonMode) {
|
|
314
|
+
console.log('\nSkipping coordinator reinvocation in same rejection cycle due to active MUST_IMPLEMENT override');
|
|
225
315
|
}
|
|
226
316
|
}
|
|
227
|
-
|
|
317
|
+
else {
|
|
228
318
|
if (!jsonMode) {
|
|
229
|
-
console.
|
|
319
|
+
console.log(`\n>>> Task has ${task.rejection_count} rejections (threshold hit) - invoking COORDINATOR...\n`);
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const rejectionHistory = (0, queries_js_1.getTaskRejections)(db, task.id);
|
|
323
|
+
const coordExtra = {};
|
|
324
|
+
if (task.section_id) {
|
|
325
|
+
const allSectionTasks = (0, queries_js_1.listTasks)(db, { sectionId: task.section_id });
|
|
326
|
+
coordExtra.sectionTasks = allSectionTasks.map(t => ({
|
|
327
|
+
id: t.id, title: t.title, status: t.status,
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
coordExtra.submissionNotes = (0, queries_js_1.getLatestSubmissionNotes)(db, task.id);
|
|
331
|
+
const modified = (0, status_js_1.getModifiedFiles)(projectPath);
|
|
332
|
+
if (modified.length > 0) {
|
|
333
|
+
coordExtra.gitDiffSummary = modified.join('\n');
|
|
334
|
+
}
|
|
335
|
+
if (cachedResult) {
|
|
336
|
+
coordExtra.previousGuidance = cachedResult.guidance;
|
|
337
|
+
}
|
|
338
|
+
if (activeMustImplement) {
|
|
339
|
+
coordExtra.lockedMustImplementGuidance = activeMustImplement.guidance;
|
|
340
|
+
coordExtra.lockedMustImplementWatermark = activeMustImplement.rejection_count_watermark;
|
|
341
|
+
}
|
|
342
|
+
const coordResult = await (0, coordinator_js_1.invokeCoordinator)(task, rejectionHistory, projectPath, coordExtra);
|
|
343
|
+
if (coordResult) {
|
|
344
|
+
const mustKeepOverride = activeMustImplement &&
|
|
345
|
+
task.rejection_count > activeMustImplement.rejection_count_watermark;
|
|
346
|
+
const normalizedGuidance = mustKeepOverride && !coordResult.guidance.includes('MUST_IMPLEMENT:')
|
|
347
|
+
? `${activeMustImplement.guidance}\n\nAdditional coordinator guidance:\n${coordResult.guidance}`
|
|
348
|
+
: coordResult.guidance;
|
|
349
|
+
coordinatorGuidance = normalizedGuidance;
|
|
350
|
+
coordinatorCache?.set(task.id, {
|
|
351
|
+
...coordResult,
|
|
352
|
+
guidance: normalizedGuidance,
|
|
353
|
+
});
|
|
354
|
+
(0, queries_js_1.addAuditEntry)(db, task.id, task.status, task.status, 'coordinator', {
|
|
355
|
+
actorType: 'orchestrator',
|
|
356
|
+
notes: `[${coordResult.decision}] ${normalizedGuidance}`,
|
|
357
|
+
});
|
|
358
|
+
if (!jsonMode) {
|
|
359
|
+
console.log(`\nCoordinator decision: ${coordResult.decision}`);
|
|
360
|
+
console.log('Coordinator guidance stored for both coder and reviewer.');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
if (!jsonMode) {
|
|
366
|
+
console.warn('Coordinator invocation failed, continuing without guidance:', error);
|
|
367
|
+
}
|
|
230
368
|
}
|
|
231
369
|
}
|
|
232
370
|
}
|
|
233
|
-
else if (cachedResult) {
|
|
371
|
+
else if (cachedResult && !activeMustImplement) {
|
|
234
372
|
coordinatorGuidance = cachedResult.guidance;
|
|
235
373
|
if (!jsonMode && task.rejection_count >= 2) {
|
|
236
374
|
console.log(`\nReusing cached coordinator guidance (decision: ${cachedResult.decision})`);
|
|
@@ -282,6 +420,8 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
282
420
|
const lastRejectionNotes = task.rejection_count > 0
|
|
283
421
|
? (0, queries_js_1.getTaskRejections)(db, task.id).slice(-1)[0]?.notes ?? undefined
|
|
284
422
|
: undefined;
|
|
423
|
+
const requiresExplicitSubmissionCommit = (lastRejectionNotes ?? '').includes('[commit_recovery]');
|
|
424
|
+
const rejectionItemCount = countLatestOpenRejectionItems(lastRejectionNotes);
|
|
285
425
|
const context = {
|
|
286
426
|
task: {
|
|
287
427
|
id: task.id,
|
|
@@ -289,6 +429,7 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
289
429
|
description: task.title, // Use title as description for now
|
|
290
430
|
rejection_notes: lastRejectionNotes,
|
|
291
431
|
rejection_count: task.rejection_count,
|
|
432
|
+
rejection_item_count: rejectionItemCount,
|
|
292
433
|
},
|
|
293
434
|
coder_output: {
|
|
294
435
|
stdout: coderResult.stdout,
|
|
@@ -306,40 +447,57 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
306
447
|
}
|
|
307
448
|
catch (error) {
|
|
308
449
|
console.error('Orchestrator invocation failed:', error);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// Only count as having work if there are actual relevant uncommitted changes,
|
|
312
|
-
// changed files, or recent commits
|
|
313
|
-
const hasWork = hasRelevantChanges;
|
|
314
|
-
if (isTaskComplete && hasWork) {
|
|
450
|
+
const orchestratorFailure = await classifyOrchestratorFailure(error, projectPath);
|
|
451
|
+
if (orchestratorFailure && !orchestratorFailure.retryable) {
|
|
315
452
|
orchestratorOutput = JSON.stringify({
|
|
316
|
-
action:
|
|
317
|
-
reasoning:
|
|
318
|
-
commits: commits.map(c => c.sha),
|
|
319
|
-
next_status: 'review',
|
|
320
|
-
metadata: {
|
|
321
|
-
files_changed: files_changed.length,
|
|
322
|
-
confidence: 'low',
|
|
323
|
-
exit_clean: true,
|
|
324
|
-
has_commits: commits.length > 0,
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
else {
|
|
329
|
-
// Fallback to safe default: retry
|
|
330
|
-
orchestratorOutput = JSON.stringify({
|
|
331
|
-
action: 'retry',
|
|
332
|
-
reasoning: 'FALLBACK: Orchestrator failed, defaulting to retry',
|
|
453
|
+
action: 'error',
|
|
454
|
+
reasoning: `FALLBACK: Non-retryable orchestrator failure (${orchestratorFailure.type})`,
|
|
333
455
|
commits: [],
|
|
334
|
-
next_status: '
|
|
456
|
+
next_status: 'failed',
|
|
335
457
|
metadata: {
|
|
336
458
|
files_changed: 0,
|
|
337
459
|
confidence: 'low',
|
|
338
|
-
exit_clean:
|
|
460
|
+
exit_clean: false,
|
|
339
461
|
has_commits: false,
|
|
340
462
|
}
|
|
341
463
|
});
|
|
342
464
|
}
|
|
465
|
+
else {
|
|
466
|
+
// Check if coder seems finished even if orchestrator failed
|
|
467
|
+
const isTaskComplete = hasCoderCompletionSignal(coderResult.stdout);
|
|
468
|
+
// Only count as having work if there are actual relevant uncommitted changes,
|
|
469
|
+
// changed files, or recent commits
|
|
470
|
+
const hasWork = hasRelevantChanges;
|
|
471
|
+
if (isTaskComplete && hasWork) {
|
|
472
|
+
orchestratorOutput = JSON.stringify({
|
|
473
|
+
action: has_uncommitted ? 'stage_commit_submit' : 'submit',
|
|
474
|
+
reasoning: 'FALLBACK: Orchestrator failed but coder signaled completion',
|
|
475
|
+
commits: commits.map(c => c.sha),
|
|
476
|
+
next_status: 'review',
|
|
477
|
+
metadata: {
|
|
478
|
+
files_changed: files_changed.length,
|
|
479
|
+
confidence: 'low',
|
|
480
|
+
exit_clean: true,
|
|
481
|
+
has_commits: commits.length > 0,
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
// Fallback to safe default: retry
|
|
487
|
+
orchestratorOutput = JSON.stringify({
|
|
488
|
+
action: 'retry',
|
|
489
|
+
reasoning: 'FALLBACK: Orchestrator failed, defaulting to retry',
|
|
490
|
+
commits: [],
|
|
491
|
+
next_status: 'in_progress',
|
|
492
|
+
metadata: {
|
|
493
|
+
files_changed: 0,
|
|
494
|
+
confidence: 'low',
|
|
495
|
+
exit_clean: true,
|
|
496
|
+
has_commits: false,
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
343
501
|
}
|
|
344
502
|
// STEP 5: Parse orchestrator output with fallback
|
|
345
503
|
const handler = new fallback_handler_js_1.OrchestrationFallbackHandler();
|
|
@@ -391,6 +549,90 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
391
549
|
}
|
|
392
550
|
}
|
|
393
551
|
}
|
|
552
|
+
// Contract-violation handling (structured-first, legacy-prefix fallback).
|
|
553
|
+
const legacyChecklistViolation = /^CHECKLIST_REQUIRED:/i.test(decision.reasoning || '');
|
|
554
|
+
const legacyRejectionResponseViolation = /^REJECTION_RESPONSE_REQUIRED:/i.test(decision.reasoning || '');
|
|
555
|
+
const contractViolation = decision.contract_violation
|
|
556
|
+
?? (legacyChecklistViolation ? 'checklist_required' : null)
|
|
557
|
+
?? (legacyRejectionResponseViolation ? 'rejection_response_required' : null);
|
|
558
|
+
if (contractViolation) {
|
|
559
|
+
const marker = contractViolation === 'checklist_required'
|
|
560
|
+
? CONTRACT_CHECKLIST_MARKER
|
|
561
|
+
: CONTRACT_REJECTION_RESPONSE_MARKER;
|
|
562
|
+
const reasonText = (decision.reasoning || '')
|
|
563
|
+
.replace(/^CHECKLIST_REQUIRED:\s*/i, '')
|
|
564
|
+
.replace(/^REJECTION_RESPONSE_REQUIRED:\s*/i, '')
|
|
565
|
+
.trim();
|
|
566
|
+
const cleanReason = reasonText || 'Required output contract not satisfied';
|
|
567
|
+
const consecutiveContractViolations = countConsecutiveTaggedOrchestratorEntries(db, task.id, marker, '[contract:') + 1;
|
|
568
|
+
if (consecutiveContractViolations >= MAX_CONTRACT_VIOLATION_RETRIES) {
|
|
569
|
+
decision = {
|
|
570
|
+
...decision,
|
|
571
|
+
action: 'error',
|
|
572
|
+
next_status: 'failed',
|
|
573
|
+
contract_violation: contractViolation,
|
|
574
|
+
reasoning: `${marker} ${cleanReason} (retry_limit ${consecutiveContractViolations}/${MAX_CONTRACT_VIOLATION_RETRIES})`,
|
|
575
|
+
metadata: {
|
|
576
|
+
...decision.metadata,
|
|
577
|
+
confidence: 'low',
|
|
578
|
+
exit_clean: false,
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
decision = {
|
|
584
|
+
...decision,
|
|
585
|
+
action: 'retry',
|
|
586
|
+
next_status: 'in_progress',
|
|
587
|
+
contract_violation: contractViolation,
|
|
588
|
+
reasoning: `${marker} ${cleanReason} (retry ${consecutiveContractViolations}/${MAX_CONTRACT_VIOLATION_RETRIES})`,
|
|
589
|
+
metadata: {
|
|
590
|
+
...decision.metadata,
|
|
591
|
+
confidence: 'medium',
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// Enforce orchestrator authority over weak/unsupported WONT_FIX claims.
|
|
597
|
+
const structuredOverrideItems = Array.isArray(decision.wont_fix_override_items)
|
|
598
|
+
? decision.wont_fix_override_items.filter(item => typeof item === 'string' && item.trim().length > 0)
|
|
599
|
+
: [];
|
|
600
|
+
const legacyWontFixOverrideMatch = (decision.reasoning || '').match(/WONT_FIX_OVERRIDE:\s*([\s\S]+)/i);
|
|
601
|
+
const fallbackLegacyItems = legacyWontFixOverrideMatch?.[1]
|
|
602
|
+
? legacyWontFixOverrideMatch[1]
|
|
603
|
+
.split('\n')
|
|
604
|
+
.map(line => line.trim())
|
|
605
|
+
.filter(Boolean)
|
|
606
|
+
: [];
|
|
607
|
+
const overrideItems = structuredOverrideItems.length > 0 ? structuredOverrideItems : fallbackLegacyItems;
|
|
608
|
+
if (overrideItems.length > 0) {
|
|
609
|
+
const mandatoryLines = overrideItems.map((item, idx) => `${idx + 1}. ${item}`);
|
|
610
|
+
const mandatoryGuidance = `MUST_IMPLEMENT:
|
|
611
|
+
${mandatoryLines.join('\n')}
|
|
612
|
+
|
|
613
|
+
This is a mandatory orchestrator override. Implement these changes before resubmitting.
|
|
614
|
+
Only use WONT_FIX if you provide exceptional technical evidence and the orchestrator explicitly accepts it.`;
|
|
615
|
+
const persistedNote = `${MUST_IMPLEMENT_MARKER}[rc=${task.rejection_count}] ${mandatoryGuidance}`;
|
|
616
|
+
(0, queries_js_1.addAuditEntry)(db, task.id, task.status, task.status, 'coordinator', {
|
|
617
|
+
actorType: 'orchestrator',
|
|
618
|
+
notes: persistedNote,
|
|
619
|
+
});
|
|
620
|
+
coordinatorCache?.set(task.id, {
|
|
621
|
+
success: true,
|
|
622
|
+
decision: 'guide_coder',
|
|
623
|
+
guidance: mandatoryGuidance,
|
|
624
|
+
});
|
|
625
|
+
decision = {
|
|
626
|
+
...decision,
|
|
627
|
+
action: 'retry',
|
|
628
|
+
next_status: 'in_progress',
|
|
629
|
+
reasoning: `${MUST_IMPLEMENT_MARKER} WONT_FIX override applied`,
|
|
630
|
+
metadata: {
|
|
631
|
+
...decision.metadata,
|
|
632
|
+
confidence: 'medium',
|
|
633
|
+
},
|
|
634
|
+
};
|
|
635
|
+
}
|
|
394
636
|
// STEP 6: Log orchestrator decision for audit trail
|
|
395
637
|
(0, queries_js_1.addAuditEntry)(db, task.id, task.status, decision.next_status, 'orchestrator', {
|
|
396
638
|
actorType: 'orchestrator',
|
|
@@ -399,14 +641,76 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
399
641
|
// STEP 7: Execute the decision
|
|
400
642
|
switch (decision.action) {
|
|
401
643
|
case 'submit':
|
|
402
|
-
|
|
644
|
+
{
|
|
645
|
+
const submissionCommitSha = resolveCoderSubmittedCommitSha(projectPath, coderResult.stdout, {
|
|
646
|
+
requireExplicitToken: requiresExplicitSubmissionCommit,
|
|
647
|
+
});
|
|
648
|
+
if (!submissionCommitSha && requiresExplicitSubmissionCommit) {
|
|
649
|
+
(0, queries_js_1.addAuditEntry)(db, task.id, task.status, task.status, 'orchestrator', {
|
|
650
|
+
actorType: 'orchestrator',
|
|
651
|
+
notes: '[retry] Awaiting explicit SUBMISSION_COMMIT token for commit recovery',
|
|
652
|
+
});
|
|
653
|
+
if (!jsonMode) {
|
|
654
|
+
console.log('\n⟳ Waiting for explicit SUBMISSION_COMMIT token from coder');
|
|
655
|
+
}
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
if (!submissionCommitSha || !(0, status_js_1.isCommitReachable)(projectPath, submissionCommitSha)) {
|
|
659
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', 'Task failed: cannot submit to review without a valid commit hash');
|
|
660
|
+
if (!jsonMode) {
|
|
661
|
+
console.log('\n✗ Task failed (submission commit missing or not in workspace)');
|
|
662
|
+
}
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
if (leaseFence?.parallelSessionId) {
|
|
666
|
+
const pushResult = (0, push_js_1.pushToRemote)(projectPath, 'origin', branchName);
|
|
667
|
+
if (!pushResult.success) {
|
|
668
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', `Task failed: cannot publish submission commit ${submissionCommitSha} to ${branchName} before review`);
|
|
669
|
+
if (!jsonMode) {
|
|
670
|
+
console.log('\n✗ Task failed (unable to push submission commit to branch for review)');
|
|
671
|
+
}
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'review', 'orchestrator', decision.reasoning, submissionCommitSha);
|
|
676
|
+
}
|
|
403
677
|
if (!jsonMode) {
|
|
404
678
|
console.log(`\n✓ Coder complete, submitted to review (confidence: ${decision.metadata.confidence})`);
|
|
405
679
|
}
|
|
406
680
|
break;
|
|
407
681
|
case 'stage_commit_submit':
|
|
408
682
|
if (!has_uncommitted) {
|
|
409
|
-
|
|
683
|
+
const submissionCommitSha = resolveCoderSubmittedCommitSha(projectPath, coderResult.stdout, {
|
|
684
|
+
requireExplicitToken: requiresExplicitSubmissionCommit,
|
|
685
|
+
});
|
|
686
|
+
if (!submissionCommitSha && requiresExplicitSubmissionCommit) {
|
|
687
|
+
(0, queries_js_1.addAuditEntry)(db, task.id, task.status, task.status, 'orchestrator', {
|
|
688
|
+
actorType: 'orchestrator',
|
|
689
|
+
notes: '[retry] Awaiting explicit SUBMISSION_COMMIT token for commit recovery',
|
|
690
|
+
});
|
|
691
|
+
if (!jsonMode) {
|
|
692
|
+
console.log('\n⟳ Waiting for explicit SUBMISSION_COMMIT token from coder');
|
|
693
|
+
}
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
if (!submissionCommitSha || !(0, status_js_1.isCommitReachable)(projectPath, submissionCommitSha)) {
|
|
697
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', 'Task failed: cannot submit to review without a valid commit hash');
|
|
698
|
+
if (!jsonMode) {
|
|
699
|
+
console.log('\n✗ Task failed (submission commit missing or not in workspace)');
|
|
700
|
+
}
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
if (leaseFence?.parallelSessionId) {
|
|
704
|
+
const pushResult = (0, push_js_1.pushToRemote)(projectPath, 'origin', branchName);
|
|
705
|
+
if (!pushResult.success) {
|
|
706
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', `Task failed: cannot publish submission commit ${submissionCommitSha} to ${branchName} before review`);
|
|
707
|
+
if (!jsonMode) {
|
|
708
|
+
console.log('\n✗ Task failed (unable to push submission commit to branch for review)');
|
|
709
|
+
}
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'review', 'orchestrator', 'Auto-commit skipped: no uncommitted changes', submissionCommitSha);
|
|
410
714
|
if (!jsonMode) {
|
|
411
715
|
console.log('\n✓ Auto-commit skipped (no uncommitted files) and submitted to review');
|
|
412
716
|
}
|
|
@@ -421,7 +725,25 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
421
725
|
cwd: projectPath,
|
|
422
726
|
stdio: 'pipe'
|
|
423
727
|
});
|
|
424
|
-
(0,
|
|
728
|
+
const submissionCommitSha = (0, status_js_1.getCurrentCommitSha)(projectPath) || undefined;
|
|
729
|
+
if (!submissionCommitSha || !(0, status_js_1.isCommitReachable)(projectPath, submissionCommitSha)) {
|
|
730
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', 'Task failed: auto-committed but commit hash is not in current workspace');
|
|
731
|
+
if (!jsonMode) {
|
|
732
|
+
console.log('\n✗ Task failed (auto-commit hash not in workspace)');
|
|
733
|
+
}
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
if (leaseFence?.parallelSessionId) {
|
|
737
|
+
const pushResult = (0, push_js_1.pushToRemote)(projectPath, 'origin', branchName);
|
|
738
|
+
if (!pushResult.success) {
|
|
739
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', `Task failed: cannot publish submission commit ${submissionCommitSha} to ${branchName} before review`);
|
|
740
|
+
if (!jsonMode) {
|
|
741
|
+
console.log('\n✗ Task failed (unable to push submission commit to branch for review)');
|
|
742
|
+
}
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'review', 'orchestrator', `Auto-committed and submitted (${decision.reasoning})`, submissionCommitSha);
|
|
425
747
|
if (!jsonMode) {
|
|
426
748
|
console.log(`\n✓ Auto-committed and submitted to review (confidence: ${decision.metadata.confidence})`);
|
|
427
749
|
}
|
|
@@ -455,6 +777,17 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
455
777
|
const config = (0, loader_js_1.loadConfig)(projectPath);
|
|
456
778
|
const multiReviewEnabled = (0, reviewer_js_1.isMultiReviewEnabled)(config);
|
|
457
779
|
const strict = config.ai?.review?.strict ?? true;
|
|
780
|
+
const submissionResolution = (0, submission_resolution_js_1.resolveSubmissionCommitWithRecovery)(projectPath, (0, queries_js_1.getSubmissionCommitShas)(db, task.id));
|
|
781
|
+
if (submissionResolution.status !== 'resolved') {
|
|
782
|
+
const attemptsText = submissionResolution.attempts.join(' | ') || 'none';
|
|
783
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'in_progress', 'orchestrator', `[commit_recovery] Missing reachable submission hash (${submissionResolution.reason}; attempts: ${attemptsText}). ` +
|
|
784
|
+
`Treating task as resubmission. Coder must output exact line: SUBMISSION_COMMIT: <sha> for the commit that implements the task.`);
|
|
785
|
+
if (!jsonMode) {
|
|
786
|
+
console.log('\n⟳ Reviewer hash missing; returning task to coder for hash resubmission');
|
|
787
|
+
}
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
const submissionCommitSha = submissionResolution.sha;
|
|
458
791
|
let reviewerResult;
|
|
459
792
|
let reviewerResults = [];
|
|
460
793
|
// STEP 1: Invoke reviewer(s)
|
|
@@ -692,13 +1025,17 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
692
1025
|
}
|
|
693
1026
|
catch (error) {
|
|
694
1027
|
console.error('Orchestrator invocation failed:', error);
|
|
1028
|
+
const orchestratorFailure = await classifyOrchestratorFailure(error, projectPath);
|
|
695
1029
|
const handler = new fallback_handler_js_1.OrchestrationFallbackHandler();
|
|
696
1030
|
const reviewerStdout = reviewerResult?.stdout ?? '';
|
|
697
1031
|
const explicitDecision = handler.extractExplicitReviewerDecision(reviewerStdout);
|
|
1032
|
+
const failureReason = orchestratorFailure
|
|
1033
|
+
? `${orchestratorFailure.type}: ${orchestratorFailure.message}`
|
|
1034
|
+
: summarizeErrorMessage(error);
|
|
698
1035
|
if (explicitDecision) {
|
|
699
1036
|
decision = {
|
|
700
1037
|
decision: explicitDecision,
|
|
701
|
-
reasoning: `FALLBACK: Orchestrator failed but reviewer explicitly signaled ${explicitDecision.toUpperCase()}`,
|
|
1038
|
+
reasoning: `FALLBACK: Orchestrator failed (${failureReason}) but reviewer explicitly signaled ${explicitDecision.toUpperCase()}`,
|
|
702
1039
|
notes: reviewerStdout,
|
|
703
1040
|
next_status: explicitDecision === 'approve' ? 'completed' :
|
|
704
1041
|
explicitDecision === 'reject' ? 'in_progress' :
|
|
@@ -715,7 +1052,7 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
715
1052
|
else {
|
|
716
1053
|
decision = {
|
|
717
1054
|
decision: 'unclear',
|
|
718
|
-
reasoning:
|
|
1055
|
+
reasoning: `FALLBACK: Orchestrator failed (${failureReason}), retrying review`,
|
|
719
1056
|
notes: 'Review unclear, retrying',
|
|
720
1057
|
next_status: 'review',
|
|
721
1058
|
metadata: {
|
|
@@ -776,7 +1113,7 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
776
1113
|
description: followUp.description,
|
|
777
1114
|
sectionId: task.section_id,
|
|
778
1115
|
referenceTaskId: task.id,
|
|
779
|
-
referenceCommit:
|
|
1116
|
+
referenceCommit: submissionCommitSha,
|
|
780
1117
|
requiresPromotion,
|
|
781
1118
|
depth: nextDepth,
|
|
782
1119
|
});
|
|
@@ -795,7 +1132,7 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
795
1132
|
}
|
|
796
1133
|
}
|
|
797
1134
|
// STEP 6: Execute the decision
|
|
798
|
-
const commitSha =
|
|
1135
|
+
const commitSha = submissionCommitSha;
|
|
799
1136
|
switch (decision.decision) {
|
|
800
1137
|
case 'approve':
|
|
801
1138
|
(0, queries_js_1.approveTask)(db, task.id, 'orchestrator', decision.notes, commitSha);
|