@stilero/bankan 1.0.7 → 1.0.8
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/package.json +1 -1
- package/server/src/orchestrator.js +33 -9
- package/server/src/workflow.js +16 -0
- package/server/src/workflow.test.js +31 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stilero/bankan",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Run AI coding agents like a Kanban board. Plan, implement, review and ship code using parallel AI agents across your local repositories.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -7,7 +7,7 @@ import config, { loadSettings, getWorkspacesDir } from './config.js';
|
|
|
7
7
|
import store from './store.js';
|
|
8
8
|
import agentManager from './agents.js';
|
|
9
9
|
import bus from './events.js';
|
|
10
|
-
import { parseReviewResult, reviewShouldPass } from './workflow.js';
|
|
10
|
+
import { isReviewResultPlaceholder, parseReviewResult, reviewShouldPass } from './workflow.js';
|
|
11
11
|
|
|
12
12
|
const POLL_INTERVAL = 4000;
|
|
13
13
|
const SIGNAL_CHECK_INTERVAL = 2500;
|
|
@@ -354,15 +354,17 @@ ${task.plan}
|
|
|
354
354
|
Instructions:
|
|
355
355
|
${promptBody}
|
|
356
356
|
|
|
357
|
-
Output ONLY in this
|
|
357
|
+
Output ONLY a completed review block in this format.
|
|
358
|
+
Do not copy placeholder text. Replace every field with concrete findings from the actual diff.
|
|
359
|
+
Do not emit the review block until after you have run the required git diff commands and finished the review.
|
|
358
360
|
|
|
359
361
|
=== REVIEW START ===
|
|
360
|
-
VERDICT: PASS
|
|
362
|
+
VERDICT: PASS or FAIL
|
|
361
363
|
CRITICAL_ISSUES:
|
|
362
|
-
- none
|
|
364
|
+
- concrete issue, or none
|
|
363
365
|
MINOR_ISSUES:
|
|
364
|
-
-
|
|
365
|
-
SUMMARY:
|
|
366
|
+
- concrete issue, or none
|
|
367
|
+
SUMMARY: 2-3 concrete sentences summarising the review, including changed files and strengths
|
|
366
368
|
=== REVIEW END ===`;
|
|
367
369
|
}
|
|
368
370
|
|
|
@@ -721,6 +723,7 @@ async function onReviewComplete(agentId, taskId) {
|
|
|
721
723
|
const reviewText = getLastStructuredBlock(sourceText, '=== REVIEW START ===', '=== REVIEW END ===');
|
|
722
724
|
if (!reviewText) return;
|
|
723
725
|
const reviewResult = parseReviewResult(reviewText);
|
|
726
|
+
if (isReviewResultPlaceholder(reviewText, reviewResult)) return;
|
|
724
727
|
const shouldPass = reviewShouldPass(reviewResult);
|
|
725
728
|
|
|
726
729
|
store.updateTask(taskId, { review: reviewText });
|
|
@@ -1081,7 +1084,21 @@ function pollLoop() {
|
|
|
1081
1084
|
? hasCodexStructuredOutput(buf, '=== REVIEW END ===')
|
|
1082
1085
|
: buf.includes('=== REVIEW END ===');
|
|
1083
1086
|
if (reviewReady) {
|
|
1084
|
-
|
|
1087
|
+
const reviewer = agentManager.get(agent.id);
|
|
1088
|
+
const bufStr = reviewer?.getBufferString(100) || '';
|
|
1089
|
+
const captured = reviewer?.cli === 'codex' ? readCapturedCodexMessage(bufStr, { remove: false }) : null;
|
|
1090
|
+
const sourceText = captured || bufStr;
|
|
1091
|
+
const reviewText = getLastStructuredBlock(sourceText, '=== REVIEW START ===', '=== REVIEW END ===');
|
|
1092
|
+
const reviewResult = reviewText ? parseReviewResult(reviewText) : null;
|
|
1093
|
+
if (reviewText && reviewResult && !isReviewResultPlaceholder(reviewText, reviewResult)) {
|
|
1094
|
+
onReviewComplete(agent.id, taskId);
|
|
1095
|
+
} else {
|
|
1096
|
+
store.updateTask(taskId, {
|
|
1097
|
+
status: 'blocked',
|
|
1098
|
+
blockedReason: 'Reviewer exited after returning placeholder output',
|
|
1099
|
+
assignedTo: null,
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1085
1102
|
} else {
|
|
1086
1103
|
store.updateTask(taskId, {
|
|
1087
1104
|
status: 'blocked',
|
|
@@ -1147,8 +1164,15 @@ bus.on('agent:unexpected-exit', ({ agentId, taskId }) => {
|
|
|
1147
1164
|
? hasCodexStructuredOutput(buf, '=== REVIEW END ===')
|
|
1148
1165
|
: buf.includes('=== REVIEW END ===');
|
|
1149
1166
|
if (reviewReady) {
|
|
1150
|
-
|
|
1151
|
-
|
|
1167
|
+
const captured = agent.cli === 'codex' ? readCapturedCodexMessage(buf, { remove: false }) : null;
|
|
1168
|
+
const sourceText = captured || buf;
|
|
1169
|
+
const reviewText = getLastStructuredBlock(sourceText, '=== REVIEW START ===', '=== REVIEW END ===');
|
|
1170
|
+
const reviewResult = reviewText ? parseReviewResult(reviewText) : null;
|
|
1171
|
+
if (reviewText && reviewResult && !isReviewResultPlaceholder(reviewText, reviewResult)) {
|
|
1172
|
+
onReviewComplete(agentId, taskId);
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
authBlockedReason = 'Reviewer exited after returning placeholder output';
|
|
1152
1176
|
}
|
|
1153
1177
|
}
|
|
1154
1178
|
console.error(`[unexpected-exit] agent=${agentId} task=${taskId} last output:\n${buf.slice(-500)}`);
|
package/server/src/workflow.js
CHANGED
|
@@ -27,11 +27,13 @@ export function parseReviewResult(reviewText) {
|
|
|
27
27
|
const minorIssues = parseBulletList(
|
|
28
28
|
extractSection(reviewText, 'MINOR_ISSUES:', ['SUMMARY:', '=== REVIEW END ==='])
|
|
29
29
|
).filter(item => item.toLowerCase() !== 'none');
|
|
30
|
+
const summary = extractSection(reviewText, 'SUMMARY:', ['=== REVIEW END ===']);
|
|
30
31
|
|
|
31
32
|
return {
|
|
32
33
|
verdict,
|
|
33
34
|
criticalIssues,
|
|
34
35
|
minorIssues,
|
|
36
|
+
summary,
|
|
35
37
|
hasCriticalIssues: criticalIssues.length > 0,
|
|
36
38
|
};
|
|
37
39
|
}
|
|
@@ -40,6 +42,20 @@ export function reviewShouldPass(reviewResult) {
|
|
|
40
42
|
return reviewResult.verdict === 'PASS' || !reviewResult.hasCriticalIssues;
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
export function isReviewResultPlaceholder(reviewText, reviewResult = parseReviewResult(reviewText)) {
|
|
46
|
+
if (typeof reviewText !== 'string' || !reviewText.trim()) return true;
|
|
47
|
+
|
|
48
|
+
const normalized = reviewText.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
49
|
+
if (normalized.includes("(issue description, or 'none')")) return true;
|
|
50
|
+
if (normalized.includes('(2-3 sentences summarising the review)')) return true;
|
|
51
|
+
|
|
52
|
+
const summary = (reviewResult.summary || '').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
53
|
+
if (!summary) return true;
|
|
54
|
+
if (summary.includes('2-3 sentences summarising the review')) return true;
|
|
55
|
+
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
43
59
|
export function getLiveTaskAgent(task, agentManager) {
|
|
44
60
|
if (!task?.assignedTo) return null;
|
|
45
61
|
const agent = agentManager.get(task.assignedTo);
|
|
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
getLiveTaskAgent,
|
|
6
|
+
isReviewResultPlaceholder,
|
|
6
7
|
parseReviewResult,
|
|
7
8
|
reviewShouldPass,
|
|
8
9
|
stageToRetryStatus,
|
|
@@ -42,6 +43,36 @@ SUMMARY: A must-fix issue remains.
|
|
|
42
43
|
assert.equal(reviewShouldPass(result), false);
|
|
43
44
|
});
|
|
44
45
|
|
|
46
|
+
test('placeholder review template is rejected', () => {
|
|
47
|
+
const reviewText = `=== REVIEW START ===
|
|
48
|
+
VERDICT: PASS
|
|
49
|
+
CRITICAL_ISSUES:
|
|
50
|
+
- none
|
|
51
|
+
MINOR_ISSUES:
|
|
52
|
+
- (issue description, or 'none')
|
|
53
|
+
SUMMARY: (2-3 sentences summarising the review)
|
|
54
|
+
=== REVIEW END ===`;
|
|
55
|
+
|
|
56
|
+
const result = parseReviewResult(reviewText);
|
|
57
|
+
|
|
58
|
+
assert.equal(isReviewResultPlaceholder(reviewText, result), true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('concrete review output is not treated as placeholder', () => {
|
|
62
|
+
const reviewText = `=== REVIEW START ===
|
|
63
|
+
VERDICT: PASS
|
|
64
|
+
CRITICAL_ISSUES:
|
|
65
|
+
- none
|
|
66
|
+
MINOR_ISSUES:
|
|
67
|
+
- none
|
|
68
|
+
SUMMARY: Changed files: server/src/orchestrator.js, server/src/workflow.js. The review completion gate now rejects placeholder output and the branch otherwise looks consistent with existing task flow. Strengths: the change is narrowly scoped and adds regression coverage.
|
|
69
|
+
=== REVIEW END ===`;
|
|
70
|
+
|
|
71
|
+
const result = parseReviewResult(reviewText);
|
|
72
|
+
|
|
73
|
+
assert.equal(isReviewResultPlaceholder(reviewText, result), false);
|
|
74
|
+
});
|
|
75
|
+
|
|
45
76
|
test('retry ignores stale assigned agent process from another task', () => {
|
|
46
77
|
const task = {
|
|
47
78
|
id: 'T-123',
|