steroids-cli 0.9.29 → 0.9.31
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.map +1 -1
- package/dist/commands/loop-phases.js +321 -66
- package/dist/commands/loop-phases.js.map +1 -1
- package/dist/database/queries.d.ts +16 -0
- package/dist/database/queries.d.ts.map +1 -1
- package/dist/database/queries.js +47 -0
- package/dist/database/queries.js.map +1 -1
- 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 +56 -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 +2 -3
- package/dist/prompts/reviewer.d.ts.map +1 -1
- package/dist/prompts/reviewer.js +9 -5
- 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/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
|
@@ -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;AAwBpH,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;AAsRD,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,GAC7B,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAwfxC;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,CAkcxC"}
|
|
@@ -136,9 +136,58 @@ async function checkNonRetryableProviderFailure(result, role, projectPath, revie
|
|
|
136
136
|
}
|
|
137
137
|
return null;
|
|
138
138
|
}
|
|
139
|
+
function summarizeErrorMessage(error) {
|
|
140
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
141
|
+
return raw.replace(/\s+/g, ' ').trim().slice(0, 220);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Classify orchestrator invocation failures so we can distinguish transient parse
|
|
145
|
+
* noise from hard provider/config failures (auth/model/availability).
|
|
146
|
+
*/
|
|
147
|
+
async function classifyOrchestratorFailure(error, projectPath) {
|
|
148
|
+
const message = summarizeErrorMessage(error);
|
|
149
|
+
const lower = message.toLowerCase();
|
|
150
|
+
if (lower.includes('orchestrator ai provider not configured')) {
|
|
151
|
+
return { type: 'orchestrator_unconfigured', message, retryable: false };
|
|
152
|
+
}
|
|
153
|
+
if (lower.includes("provider '") && lower.includes('is not available')) {
|
|
154
|
+
return { type: 'provider_unavailable', message, retryable: false };
|
|
155
|
+
}
|
|
156
|
+
const config = (0, loader_js_1.loadConfig)(projectPath);
|
|
157
|
+
const providerName = config.ai?.orchestrator?.provider;
|
|
158
|
+
if (!providerName) {
|
|
159
|
+
return { type: 'orchestrator_unconfigured', message, retryable: false };
|
|
160
|
+
}
|
|
161
|
+
const registry = await (0, registry_js_1.getProviderRegistry)();
|
|
162
|
+
const provider = registry.tryGet(providerName);
|
|
163
|
+
if (!provider) {
|
|
164
|
+
return { type: 'provider_unavailable', message, retryable: false };
|
|
165
|
+
}
|
|
166
|
+
const syntheticFailure = {
|
|
167
|
+
success: false,
|
|
168
|
+
exitCode: 1,
|
|
169
|
+
stdout: '',
|
|
170
|
+
stderr: message,
|
|
171
|
+
duration: 0,
|
|
172
|
+
timedOut: false,
|
|
173
|
+
};
|
|
174
|
+
const classified = provider.classifyResult(syntheticFailure);
|
|
175
|
+
if (!classified) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
type: classified.type,
|
|
180
|
+
message: classified.message || message,
|
|
181
|
+
retryable: classified.retryable,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
139
184
|
const MAX_ORCHESTRATOR_PARSE_RETRIES = 3;
|
|
185
|
+
const MAX_CONTRACT_VIOLATION_RETRIES = 3;
|
|
140
186
|
const CODER_PARSE_FALLBACK_MARKER = '[retry] FALLBACK: Orchestrator failed, defaulting to retry';
|
|
141
187
|
const REVIEWER_PARSE_FALLBACK_MARKER = '[unclear] FALLBACK: Orchestrator failed, retrying review';
|
|
188
|
+
const CONTRACT_CHECKLIST_MARKER = '[contract:checklist]';
|
|
189
|
+
const CONTRACT_REJECTION_RESPONSE_MARKER = '[contract:rejection_response]';
|
|
190
|
+
const MUST_IMPLEMENT_MARKER = '[must_implement]';
|
|
142
191
|
function countConsecutiveOrchestratorFallbackEntries(db, taskId, marker) {
|
|
143
192
|
const audit = (0, queries_js_1.getTaskAudit)(db, taskId);
|
|
144
193
|
let count = 0;
|
|
@@ -173,6 +222,34 @@ function countConsecutiveUnclearEntries(db, taskId) {
|
|
|
173
222
|
}
|
|
174
223
|
return count;
|
|
175
224
|
}
|
|
225
|
+
function countConsecutiveTaggedOrchestratorEntries(db, taskId, requiredTag, tagFamilyPrefix) {
|
|
226
|
+
const audit = (0, queries_js_1.getTaskAudit)(db, taskId);
|
|
227
|
+
let count = 0;
|
|
228
|
+
for (let i = audit.length - 1; i >= 0; i--) {
|
|
229
|
+
const entry = audit[i];
|
|
230
|
+
if (entry.actor !== 'orchestrator') {
|
|
231
|
+
continue; // tolerate non-orchestrator audit noise
|
|
232
|
+
}
|
|
233
|
+
const notes = entry.notes ?? '';
|
|
234
|
+
if (notes.includes(requiredTag)) {
|
|
235
|
+
count += 1;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (tagFamilyPrefix && notes.includes(tagFamilyPrefix)) {
|
|
239
|
+
break; // same family, different category = sequence ended
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
return count;
|
|
244
|
+
}
|
|
245
|
+
function countLatestOpenRejectionItems(notes) {
|
|
246
|
+
if (!notes)
|
|
247
|
+
return 0;
|
|
248
|
+
return notes
|
|
249
|
+
.split('\n')
|
|
250
|
+
.filter(line => /^\s*[-*]\s*\[\s\]\s+/.test(line))
|
|
251
|
+
.length;
|
|
252
|
+
}
|
|
176
253
|
function hasCoderCompletionSignal(output) {
|
|
177
254
|
const lower = output.toLowerCase();
|
|
178
255
|
if (/\b(?:not|no)\s+(?:task\s+)?(?:is\s+)?(?:complete|complete[d]?|finished|done|ready)\b/.test(lower)) {
|
|
@@ -186,51 +263,88 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
186
263
|
refreshParallelWorkstreamLease(projectPath, leaseFence);
|
|
187
264
|
let coordinatorGuidance;
|
|
188
265
|
const thresholds = coordinatorThresholds || [2, 5, 9];
|
|
266
|
+
const persistedMustImplement = (0, queries_js_1.getLatestMustImplementGuidance)(db, task.id);
|
|
267
|
+
const activeMustImplement = persistedMustImplement &&
|
|
268
|
+
task.status === 'in_progress' &&
|
|
269
|
+
task.rejection_count >= persistedMustImplement.rejection_count_watermark
|
|
270
|
+
? persistedMustImplement
|
|
271
|
+
: null;
|
|
189
272
|
// Run coordinator at rejection thresholds (same as before)
|
|
190
273
|
const shouldInvokeCoordinator = thresholds.includes(task.rejection_count);
|
|
191
274
|
const cachedResult = coordinatorCache?.get(task.id);
|
|
192
|
-
if (
|
|
275
|
+
if (activeMustImplement) {
|
|
276
|
+
coordinatorGuidance = activeMustImplement.guidance;
|
|
277
|
+
coordinatorCache?.set(task.id, {
|
|
278
|
+
success: true,
|
|
279
|
+
decision: 'guide_coder',
|
|
280
|
+
guidance: activeMustImplement.guidance,
|
|
281
|
+
});
|
|
193
282
|
if (!jsonMode) {
|
|
194
|
-
console.log(`\
|
|
283
|
+
console.log(`\nActive MUST_IMPLEMENT override detected (rc=${activeMustImplement.rejection_count_watermark})`);
|
|
195
284
|
}
|
|
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
|
-
}
|
|
285
|
+
}
|
|
286
|
+
if (shouldInvokeCoordinator) {
|
|
287
|
+
if (activeMustImplement &&
|
|
288
|
+
task.rejection_count === activeMustImplement.rejection_count_watermark) {
|
|
289
|
+
if (!jsonMode) {
|
|
290
|
+
console.log('\nSkipping coordinator reinvocation in same rejection cycle due to active MUST_IMPLEMENT override');
|
|
225
291
|
}
|
|
226
292
|
}
|
|
227
|
-
|
|
293
|
+
else {
|
|
228
294
|
if (!jsonMode) {
|
|
229
|
-
console.
|
|
295
|
+
console.log(`\n>>> Task has ${task.rejection_count} rejections (threshold hit) - invoking COORDINATOR...\n`);
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const rejectionHistory = (0, queries_js_1.getTaskRejections)(db, task.id);
|
|
299
|
+
const coordExtra = {};
|
|
300
|
+
if (task.section_id) {
|
|
301
|
+
const allSectionTasks = (0, queries_js_1.listTasks)(db, { sectionId: task.section_id });
|
|
302
|
+
coordExtra.sectionTasks = allSectionTasks.map(t => ({
|
|
303
|
+
id: t.id, title: t.title, status: t.status,
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
coordExtra.submissionNotes = (0, queries_js_1.getLatestSubmissionNotes)(db, task.id);
|
|
307
|
+
const modified = (0, status_js_1.getModifiedFiles)(projectPath);
|
|
308
|
+
if (modified.length > 0) {
|
|
309
|
+
coordExtra.gitDiffSummary = modified.join('\n');
|
|
310
|
+
}
|
|
311
|
+
if (cachedResult) {
|
|
312
|
+
coordExtra.previousGuidance = cachedResult.guidance;
|
|
313
|
+
}
|
|
314
|
+
if (activeMustImplement) {
|
|
315
|
+
coordExtra.lockedMustImplementGuidance = activeMustImplement.guidance;
|
|
316
|
+
coordExtra.lockedMustImplementWatermark = activeMustImplement.rejection_count_watermark;
|
|
317
|
+
}
|
|
318
|
+
const coordResult = await (0, coordinator_js_1.invokeCoordinator)(task, rejectionHistory, projectPath, coordExtra);
|
|
319
|
+
if (coordResult) {
|
|
320
|
+
const mustKeepOverride = activeMustImplement &&
|
|
321
|
+
task.rejection_count > activeMustImplement.rejection_count_watermark;
|
|
322
|
+
const normalizedGuidance = mustKeepOverride && !coordResult.guidance.includes('MUST_IMPLEMENT:')
|
|
323
|
+
? `${activeMustImplement.guidance}\n\nAdditional coordinator guidance:\n${coordResult.guidance}`
|
|
324
|
+
: coordResult.guidance;
|
|
325
|
+
coordinatorGuidance = normalizedGuidance;
|
|
326
|
+
coordinatorCache?.set(task.id, {
|
|
327
|
+
...coordResult,
|
|
328
|
+
guidance: normalizedGuidance,
|
|
329
|
+
});
|
|
330
|
+
(0, queries_js_1.addAuditEntry)(db, task.id, task.status, task.status, 'coordinator', {
|
|
331
|
+
actorType: 'orchestrator',
|
|
332
|
+
notes: `[${coordResult.decision}] ${normalizedGuidance}`,
|
|
333
|
+
});
|
|
334
|
+
if (!jsonMode) {
|
|
335
|
+
console.log(`\nCoordinator decision: ${coordResult.decision}`);
|
|
336
|
+
console.log('Coordinator guidance stored for both coder and reviewer.');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
if (!jsonMode) {
|
|
342
|
+
console.warn('Coordinator invocation failed, continuing without guidance:', error);
|
|
343
|
+
}
|
|
230
344
|
}
|
|
231
345
|
}
|
|
232
346
|
}
|
|
233
|
-
else if (cachedResult) {
|
|
347
|
+
else if (cachedResult && !activeMustImplement) {
|
|
234
348
|
coordinatorGuidance = cachedResult.guidance;
|
|
235
349
|
if (!jsonMode && task.rejection_count >= 2) {
|
|
236
350
|
console.log(`\nReusing cached coordinator guidance (decision: ${cachedResult.decision})`);
|
|
@@ -282,6 +396,7 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
282
396
|
const lastRejectionNotes = task.rejection_count > 0
|
|
283
397
|
? (0, queries_js_1.getTaskRejections)(db, task.id).slice(-1)[0]?.notes ?? undefined
|
|
284
398
|
: undefined;
|
|
399
|
+
const rejectionItemCount = countLatestOpenRejectionItems(lastRejectionNotes);
|
|
285
400
|
const context = {
|
|
286
401
|
task: {
|
|
287
402
|
id: task.id,
|
|
@@ -289,6 +404,7 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
289
404
|
description: task.title, // Use title as description for now
|
|
290
405
|
rejection_notes: lastRejectionNotes,
|
|
291
406
|
rejection_count: task.rejection_count,
|
|
407
|
+
rejection_item_count: rejectionItemCount,
|
|
292
408
|
},
|
|
293
409
|
coder_output: {
|
|
294
410
|
stdout: coderResult.stdout,
|
|
@@ -306,40 +422,57 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
306
422
|
}
|
|
307
423
|
catch (error) {
|
|
308
424
|
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) {
|
|
425
|
+
const orchestratorFailure = await classifyOrchestratorFailure(error, projectPath);
|
|
426
|
+
if (orchestratorFailure && !orchestratorFailure.retryable) {
|
|
315
427
|
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',
|
|
428
|
+
action: 'error',
|
|
429
|
+
reasoning: `FALLBACK: Non-retryable orchestrator failure (${orchestratorFailure.type})`,
|
|
333
430
|
commits: [],
|
|
334
|
-
next_status: '
|
|
431
|
+
next_status: 'failed',
|
|
335
432
|
metadata: {
|
|
336
433
|
files_changed: 0,
|
|
337
434
|
confidence: 'low',
|
|
338
|
-
exit_clean:
|
|
435
|
+
exit_clean: false,
|
|
339
436
|
has_commits: false,
|
|
340
437
|
}
|
|
341
438
|
});
|
|
342
439
|
}
|
|
440
|
+
else {
|
|
441
|
+
// Check if coder seems finished even if orchestrator failed
|
|
442
|
+
const isTaskComplete = hasCoderCompletionSignal(coderResult.stdout);
|
|
443
|
+
// Only count as having work if there are actual relevant uncommitted changes,
|
|
444
|
+
// changed files, or recent commits
|
|
445
|
+
const hasWork = hasRelevantChanges;
|
|
446
|
+
if (isTaskComplete && hasWork) {
|
|
447
|
+
orchestratorOutput = JSON.stringify({
|
|
448
|
+
action: has_uncommitted ? 'stage_commit_submit' : 'submit',
|
|
449
|
+
reasoning: 'FALLBACK: Orchestrator failed but coder signaled completion',
|
|
450
|
+
commits: commits.map(c => c.sha),
|
|
451
|
+
next_status: 'review',
|
|
452
|
+
metadata: {
|
|
453
|
+
files_changed: files_changed.length,
|
|
454
|
+
confidence: 'low',
|
|
455
|
+
exit_clean: true,
|
|
456
|
+
has_commits: commits.length > 0,
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
// Fallback to safe default: retry
|
|
462
|
+
orchestratorOutput = JSON.stringify({
|
|
463
|
+
action: 'retry',
|
|
464
|
+
reasoning: 'FALLBACK: Orchestrator failed, defaulting to retry',
|
|
465
|
+
commits: [],
|
|
466
|
+
next_status: 'in_progress',
|
|
467
|
+
metadata: {
|
|
468
|
+
files_changed: 0,
|
|
469
|
+
confidence: 'low',
|
|
470
|
+
exit_clean: true,
|
|
471
|
+
has_commits: false,
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
343
476
|
}
|
|
344
477
|
// STEP 5: Parse orchestrator output with fallback
|
|
345
478
|
const handler = new fallback_handler_js_1.OrchestrationFallbackHandler();
|
|
@@ -391,6 +524,90 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
391
524
|
}
|
|
392
525
|
}
|
|
393
526
|
}
|
|
527
|
+
// Contract-violation handling (structured-first, legacy-prefix fallback).
|
|
528
|
+
const legacyChecklistViolation = /^CHECKLIST_REQUIRED:/i.test(decision.reasoning || '');
|
|
529
|
+
const legacyRejectionResponseViolation = /^REJECTION_RESPONSE_REQUIRED:/i.test(decision.reasoning || '');
|
|
530
|
+
const contractViolation = decision.contract_violation
|
|
531
|
+
?? (legacyChecklistViolation ? 'checklist_required' : null)
|
|
532
|
+
?? (legacyRejectionResponseViolation ? 'rejection_response_required' : null);
|
|
533
|
+
if (contractViolation) {
|
|
534
|
+
const marker = contractViolation === 'checklist_required'
|
|
535
|
+
? CONTRACT_CHECKLIST_MARKER
|
|
536
|
+
: CONTRACT_REJECTION_RESPONSE_MARKER;
|
|
537
|
+
const reasonText = (decision.reasoning || '')
|
|
538
|
+
.replace(/^CHECKLIST_REQUIRED:\s*/i, '')
|
|
539
|
+
.replace(/^REJECTION_RESPONSE_REQUIRED:\s*/i, '')
|
|
540
|
+
.trim();
|
|
541
|
+
const cleanReason = reasonText || 'Required output contract not satisfied';
|
|
542
|
+
const consecutiveContractViolations = countConsecutiveTaggedOrchestratorEntries(db, task.id, marker, '[contract:') + 1;
|
|
543
|
+
if (consecutiveContractViolations >= MAX_CONTRACT_VIOLATION_RETRIES) {
|
|
544
|
+
decision = {
|
|
545
|
+
...decision,
|
|
546
|
+
action: 'error',
|
|
547
|
+
next_status: 'failed',
|
|
548
|
+
contract_violation: contractViolation,
|
|
549
|
+
reasoning: `${marker} ${cleanReason} (retry_limit ${consecutiveContractViolations}/${MAX_CONTRACT_VIOLATION_RETRIES})`,
|
|
550
|
+
metadata: {
|
|
551
|
+
...decision.metadata,
|
|
552
|
+
confidence: 'low',
|
|
553
|
+
exit_clean: false,
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
decision = {
|
|
559
|
+
...decision,
|
|
560
|
+
action: 'retry',
|
|
561
|
+
next_status: 'in_progress',
|
|
562
|
+
contract_violation: contractViolation,
|
|
563
|
+
reasoning: `${marker} ${cleanReason} (retry ${consecutiveContractViolations}/${MAX_CONTRACT_VIOLATION_RETRIES})`,
|
|
564
|
+
metadata: {
|
|
565
|
+
...decision.metadata,
|
|
566
|
+
confidence: 'medium',
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// Enforce orchestrator authority over weak/unsupported WONT_FIX claims.
|
|
572
|
+
const structuredOverrideItems = Array.isArray(decision.wont_fix_override_items)
|
|
573
|
+
? decision.wont_fix_override_items.filter(item => typeof item === 'string' && item.trim().length > 0)
|
|
574
|
+
: [];
|
|
575
|
+
const legacyWontFixOverrideMatch = (decision.reasoning || '').match(/WONT_FIX_OVERRIDE:\s*([\s\S]+)/i);
|
|
576
|
+
const fallbackLegacyItems = legacyWontFixOverrideMatch?.[1]
|
|
577
|
+
? legacyWontFixOverrideMatch[1]
|
|
578
|
+
.split('\n')
|
|
579
|
+
.map(line => line.trim())
|
|
580
|
+
.filter(Boolean)
|
|
581
|
+
: [];
|
|
582
|
+
const overrideItems = structuredOverrideItems.length > 0 ? structuredOverrideItems : fallbackLegacyItems;
|
|
583
|
+
if (overrideItems.length > 0) {
|
|
584
|
+
const mandatoryLines = overrideItems.map((item, idx) => `${idx + 1}. ${item}`);
|
|
585
|
+
const mandatoryGuidance = `MUST_IMPLEMENT:
|
|
586
|
+
${mandatoryLines.join('\n')}
|
|
587
|
+
|
|
588
|
+
This is a mandatory orchestrator override. Implement these changes before resubmitting.
|
|
589
|
+
Only use WONT_FIX if you provide exceptional technical evidence and the orchestrator explicitly accepts it.`;
|
|
590
|
+
const persistedNote = `${MUST_IMPLEMENT_MARKER}[rc=${task.rejection_count}] ${mandatoryGuidance}`;
|
|
591
|
+
(0, queries_js_1.addAuditEntry)(db, task.id, task.status, task.status, 'coordinator', {
|
|
592
|
+
actorType: 'orchestrator',
|
|
593
|
+
notes: persistedNote,
|
|
594
|
+
});
|
|
595
|
+
coordinatorCache?.set(task.id, {
|
|
596
|
+
success: true,
|
|
597
|
+
decision: 'guide_coder',
|
|
598
|
+
guidance: mandatoryGuidance,
|
|
599
|
+
});
|
|
600
|
+
decision = {
|
|
601
|
+
...decision,
|
|
602
|
+
action: 'retry',
|
|
603
|
+
next_status: 'in_progress',
|
|
604
|
+
reasoning: `${MUST_IMPLEMENT_MARKER} WONT_FIX override applied`,
|
|
605
|
+
metadata: {
|
|
606
|
+
...decision.metadata,
|
|
607
|
+
confidence: 'medium',
|
|
608
|
+
},
|
|
609
|
+
};
|
|
610
|
+
}
|
|
394
611
|
// STEP 6: Log orchestrator decision for audit trail
|
|
395
612
|
(0, queries_js_1.addAuditEntry)(db, task.id, task.status, decision.next_status, 'orchestrator', {
|
|
396
613
|
actorType: 'orchestrator',
|
|
@@ -399,14 +616,32 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
399
616
|
// STEP 7: Execute the decision
|
|
400
617
|
switch (decision.action) {
|
|
401
618
|
case 'submit':
|
|
402
|
-
|
|
619
|
+
{
|
|
620
|
+
const submissionCommitSha = (0, status_js_1.getCurrentCommitSha)(projectPath) || undefined;
|
|
621
|
+
if (!submissionCommitSha) {
|
|
622
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', 'Task failed: cannot submit to review without a commit hash');
|
|
623
|
+
if (!jsonMode) {
|
|
624
|
+
console.log('\n✗ Task failed (missing commit hash on submit)');
|
|
625
|
+
}
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'review', 'orchestrator', decision.reasoning, submissionCommitSha);
|
|
629
|
+
}
|
|
403
630
|
if (!jsonMode) {
|
|
404
631
|
console.log(`\n✓ Coder complete, submitted to review (confidence: ${decision.metadata.confidence})`);
|
|
405
632
|
}
|
|
406
633
|
break;
|
|
407
634
|
case 'stage_commit_submit':
|
|
408
635
|
if (!has_uncommitted) {
|
|
409
|
-
(0,
|
|
636
|
+
const submissionCommitSha = (0, status_js_1.getCurrentCommitSha)(projectPath) || undefined;
|
|
637
|
+
if (!submissionCommitSha) {
|
|
638
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', 'Task failed: cannot submit to review without a commit hash');
|
|
639
|
+
if (!jsonMode) {
|
|
640
|
+
console.log('\n✗ Task failed (missing commit hash on auto-commit skip path)');
|
|
641
|
+
}
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'review', 'orchestrator', 'Auto-commit skipped: no uncommitted changes', submissionCommitSha);
|
|
410
645
|
if (!jsonMode) {
|
|
411
646
|
console.log('\n✓ Auto-commit skipped (no uncommitted files) and submitted to review');
|
|
412
647
|
}
|
|
@@ -421,7 +656,15 @@ async function runCoderPhase(db, task, projectPath, action, jsonMode = false, co
|
|
|
421
656
|
cwd: projectPath,
|
|
422
657
|
stdio: 'pipe'
|
|
423
658
|
});
|
|
424
|
-
(0,
|
|
659
|
+
const submissionCommitSha = (0, status_js_1.getCurrentCommitSha)(projectPath) || undefined;
|
|
660
|
+
if (!submissionCommitSha) {
|
|
661
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', 'Task failed: auto-commit succeeded but commit hash could not be resolved');
|
|
662
|
+
if (!jsonMode) {
|
|
663
|
+
console.log('\n✗ Task failed (auto-commit hash could not be resolved)');
|
|
664
|
+
}
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'review', 'orchestrator', `Auto-committed and submitted (${decision.reasoning})`, submissionCommitSha);
|
|
425
668
|
if (!jsonMode) {
|
|
426
669
|
console.log(`\n✓ Auto-committed and submitted to review (confidence: ${decision.metadata.confidence})`);
|
|
427
670
|
}
|
|
@@ -455,6 +698,14 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
455
698
|
const config = (0, loader_js_1.loadConfig)(projectPath);
|
|
456
699
|
const multiReviewEnabled = (0, reviewer_js_1.isMultiReviewEnabled)(config);
|
|
457
700
|
const strict = config.ai?.review?.strict ?? true;
|
|
701
|
+
const submissionCommitSha = (0, queries_js_1.getLatestSubmissionCommitSha)(db, task.id);
|
|
702
|
+
if (!submissionCommitSha) {
|
|
703
|
+
(0, queries_js_1.updateTaskStatus)(db, task.id, 'failed', 'orchestrator', 'Task failed: submission commit hash missing for review phase');
|
|
704
|
+
if (!jsonMode) {
|
|
705
|
+
console.log('\n✗ Task failed (missing submission commit hash before review)');
|
|
706
|
+
}
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
458
709
|
let reviewerResult;
|
|
459
710
|
let reviewerResults = [];
|
|
460
711
|
// STEP 1: Invoke reviewer(s)
|
|
@@ -692,13 +943,17 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
692
943
|
}
|
|
693
944
|
catch (error) {
|
|
694
945
|
console.error('Orchestrator invocation failed:', error);
|
|
946
|
+
const orchestratorFailure = await classifyOrchestratorFailure(error, projectPath);
|
|
695
947
|
const handler = new fallback_handler_js_1.OrchestrationFallbackHandler();
|
|
696
948
|
const reviewerStdout = reviewerResult?.stdout ?? '';
|
|
697
949
|
const explicitDecision = handler.extractExplicitReviewerDecision(reviewerStdout);
|
|
950
|
+
const failureReason = orchestratorFailure
|
|
951
|
+
? `${orchestratorFailure.type}: ${orchestratorFailure.message}`
|
|
952
|
+
: summarizeErrorMessage(error);
|
|
698
953
|
if (explicitDecision) {
|
|
699
954
|
decision = {
|
|
700
955
|
decision: explicitDecision,
|
|
701
|
-
reasoning: `FALLBACK: Orchestrator failed but reviewer explicitly signaled ${explicitDecision.toUpperCase()}`,
|
|
956
|
+
reasoning: `FALLBACK: Orchestrator failed (${failureReason}) but reviewer explicitly signaled ${explicitDecision.toUpperCase()}`,
|
|
702
957
|
notes: reviewerStdout,
|
|
703
958
|
next_status: explicitDecision === 'approve' ? 'completed' :
|
|
704
959
|
explicitDecision === 'reject' ? 'in_progress' :
|
|
@@ -715,7 +970,7 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
715
970
|
else {
|
|
716
971
|
decision = {
|
|
717
972
|
decision: 'unclear',
|
|
718
|
-
reasoning:
|
|
973
|
+
reasoning: `FALLBACK: Orchestrator failed (${failureReason}), retrying review`,
|
|
719
974
|
notes: 'Review unclear, retrying',
|
|
720
975
|
next_status: 'review',
|
|
721
976
|
metadata: {
|
|
@@ -776,7 +1031,7 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
776
1031
|
description: followUp.description,
|
|
777
1032
|
sectionId: task.section_id,
|
|
778
1033
|
referenceTaskId: task.id,
|
|
779
|
-
referenceCommit:
|
|
1034
|
+
referenceCommit: submissionCommitSha,
|
|
780
1035
|
requiresPromotion,
|
|
781
1036
|
depth: nextDepth,
|
|
782
1037
|
});
|
|
@@ -795,7 +1050,7 @@ async function runReviewerPhase(db, task, projectPath, jsonMode = false, coordin
|
|
|
795
1050
|
}
|
|
796
1051
|
}
|
|
797
1052
|
// STEP 6: Execute the decision
|
|
798
|
-
const commitSha =
|
|
1053
|
+
const commitSha = submissionCommitSha;
|
|
799
1054
|
switch (decision.decision) {
|
|
800
1055
|
case 'approve':
|
|
801
1056
|
(0, queries_js_1.approveTask)(db, task.id, 'orchestrator', decision.notes, commitSha);
|