@stilero/bankan 1.0.18 → 1.0.19
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stilero/bankan",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
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",
|
|
@@ -545,7 +545,7 @@ RISKS:
|
|
|
545
545
|
return prompt;
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
-
function buildImplementorPrompt(task, workspacePath) {
|
|
548
|
+
export function buildImplementorPrompt(task, workspacePath) {
|
|
549
549
|
const repoDir = workspacePath || task.repoPath;
|
|
550
550
|
const promptBody = getPromptBody('implementation');
|
|
551
551
|
let prompt = `You are an expert software engineer implementing a feature on a real codebase.
|
|
@@ -568,9 +568,9 @@ Instructions:
|
|
|
568
568
|
- You are already on branch ${task.branch} in ${repoDir}
|
|
569
569
|
${promptBody}
|
|
570
570
|
- Before signaling completion, ensure ALL changes are committed to git on branch ${task.branch}
|
|
571
|
-
- When fully complete and all changes are committed, output the completion block below with the
|
|
571
|
+
- When fully complete and all changes are committed, output the completion block below — replace {TASK_ID} with the actual TASK ID shown above:
|
|
572
572
|
=== IMPLEMENTATION RESULT START ===
|
|
573
|
-
=== IMPLEMENTATION COMPLETE
|
|
573
|
+
=== IMPLEMENTATION COMPLETE {TASK_ID} ===
|
|
574
574
|
=== IMPLEMENTATION RESULT END ===
|
|
575
575
|
- If you encounter a blocker you cannot resolve, output:
|
|
576
576
|
=== IMPLEMENTATION RESULT START ===
|
|
@@ -2,12 +2,14 @@ import { describe, expect, test, vi } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
buildAgentCommand,
|
|
5
|
+
buildImplementorPrompt,
|
|
5
6
|
cleanTerminalArtifacts,
|
|
6
7
|
extractImplementationResult,
|
|
7
8
|
extractPlannerPlanText,
|
|
8
9
|
extractReviewerReviewText,
|
|
9
10
|
sanitizeBranchName,
|
|
10
11
|
} from './orchestrator.js';
|
|
12
|
+
import { isImplementationPlaceholder } from './workflow.js';
|
|
11
13
|
|
|
12
14
|
describe('structured output extraction', () => {
|
|
13
15
|
test('planner extraction falls back to agent structured capture when the PTY tail lost the full block', () => {
|
|
@@ -265,6 +267,49 @@ SUMMARY: Stable review capture prevents timeout.
|
|
|
265
267
|
expect(result).not.toContain('{task.id}');
|
|
266
268
|
});
|
|
267
269
|
|
|
270
|
+
test('implementation extraction rejects echoed prompt with {TASK_ID} placeholder and finds real block', () => {
|
|
271
|
+
// After the fix, the prompt template uses {TASK_ID} instead of the
|
|
272
|
+
// interpolated task ID, so the streaming parser captures a block with
|
|
273
|
+
// {TASK_ID} which isImplementationPlaceholder correctly rejects.
|
|
274
|
+
const readCaptured = vi.fn(() => null);
|
|
275
|
+
const echoedBlock = `=== IMPLEMENTATION RESULT START ===
|
|
276
|
+
=== IMPLEMENTATION COMPLETE {TASK_ID} ===
|
|
277
|
+
=== IMPLEMENTATION RESULT END ===`;
|
|
278
|
+
const realBlock = `=== IMPLEMENTATION RESULT START ===
|
|
279
|
+
=== IMPLEMENTATION COMPLETE T-ABC123 ===
|
|
280
|
+
=== IMPLEMENTATION RESULT END ===`;
|
|
281
|
+
const agent = {
|
|
282
|
+
cli: 'claude',
|
|
283
|
+
getBufferString: vi.fn(() => 'noise'),
|
|
284
|
+
getStructuredBlock: vi.fn(() => echoedBlock),
|
|
285
|
+
getAllCapturedBlocks: vi.fn(() => [echoedBlock, realBlock]),
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const result = extractImplementationResult(agent, { readCapturedCodexMessage: readCaptured });
|
|
289
|
+
expect(result).toContain('IMPLEMENTATION COMPLETE T-ABC123');
|
|
290
|
+
expect(result).not.toContain('{TASK_ID}');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('implementation extraction returns null when only echoed {TASK_ID} block exists', () => {
|
|
294
|
+
// When the agent has only echoed the prompt and hasn't produced real
|
|
295
|
+
// output yet, extraction should return the placeholder (which the
|
|
296
|
+
// signal checker will then reject via isImplementationPlaceholder).
|
|
297
|
+
const readCaptured = vi.fn(() => null);
|
|
298
|
+
const echoedBlock = `=== IMPLEMENTATION RESULT START ===
|
|
299
|
+
=== IMPLEMENTATION COMPLETE {TASK_ID} ===
|
|
300
|
+
=== IMPLEMENTATION RESULT END ===`;
|
|
301
|
+
const agent = {
|
|
302
|
+
cli: 'claude',
|
|
303
|
+
getBufferString: vi.fn(() => echoedBlock),
|
|
304
|
+
getStructuredBlock: vi.fn(() => echoedBlock),
|
|
305
|
+
getAllCapturedBlocks: vi.fn(() => [echoedBlock]),
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const result = extractImplementationResult(agent, { readCapturedCodexMessage: readCaptured });
|
|
309
|
+
// Should return the placeholder block (caller checks isImplementationPlaceholder)
|
|
310
|
+
expect(result).toContain('{TASK_ID}');
|
|
311
|
+
});
|
|
312
|
+
|
|
268
313
|
test('implementation extraction falls back to buffer scan when structured capture is placeholder', () => {
|
|
269
314
|
const readCaptured = vi.fn(() => null);
|
|
270
315
|
const templateBlock = `=== IMPLEMENTATION RESULT START ===
|
|
@@ -285,6 +330,34 @@ SUMMARY: Stable review capture prevents timeout.
|
|
|
285
330
|
});
|
|
286
331
|
});
|
|
287
332
|
|
|
333
|
+
describe('implementation prompt echo safety', () => {
|
|
334
|
+
test('completion block in implementor prompt is detected as placeholder by isImplementationPlaceholder', () => {
|
|
335
|
+
// This is the core bug: the prompt template interpolates ${task.id} into
|
|
336
|
+
// the example completion block. When the CLI echoes the prompt, the
|
|
337
|
+
// streaming parser captures a block with the real task ID, and
|
|
338
|
+
// isImplementationPlaceholder fails to detect it as a template echo.
|
|
339
|
+
const task = {
|
|
340
|
+
id: 'T-4F66CF',
|
|
341
|
+
title: 'Reporting',
|
|
342
|
+
branch: 'feature/t-4f66cf-reporting',
|
|
343
|
+
plan: 'Add reporting feature',
|
|
344
|
+
};
|
|
345
|
+
const prompt = buildImplementorPrompt(task, '/tmp/workspace');
|
|
346
|
+
|
|
347
|
+
// Extract the completion block from the prompt the same way the
|
|
348
|
+
// streaming parser would when the CLI echoes the prompt.
|
|
349
|
+
const startMarker = '=== IMPLEMENTATION RESULT START ===';
|
|
350
|
+
const endMarker = '=== IMPLEMENTATION RESULT END ===';
|
|
351
|
+
const startIdx = prompt.indexOf(startMarker);
|
|
352
|
+
const endIdx = prompt.indexOf(endMarker, startIdx);
|
|
353
|
+
const echoedBlock = prompt.slice(startIdx, endIdx + endMarker.length);
|
|
354
|
+
|
|
355
|
+
// The echoed completion block from the prompt MUST be detected as a
|
|
356
|
+
// placeholder — otherwise the signal checker treats it as real completion.
|
|
357
|
+
expect(isImplementationPlaceholder(echoedBlock)).toBe(true);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
288
361
|
describe('sanitizeBranchName', () => {
|
|
289
362
|
test('strips garbage text appended by ANSI cursor collapse', () => {
|
|
290
363
|
expect(sanitizeBranchName('feature/t-a811ca-reporting FILES_TO_MODIFY:'))
|
package/server/src/workflow.js
CHANGED
|
@@ -61,6 +61,7 @@ export function isImplementationPlaceholder(resultText) {
|
|
|
61
61
|
if (typeof resultText !== 'string' || !resultText.trim()) return true;
|
|
62
62
|
const normalized = resultText.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
63
63
|
if (normalized.includes('{describe the blocker here}')) return true;
|
|
64
|
+
if (normalized.includes('{task_id}')) return true;
|
|
64
65
|
// The prompt template contains placeholder instruction text — if the
|
|
65
66
|
// captured block matches the template exactly it's an echo, not real output.
|
|
66
67
|
if (normalized.includes('output the completion block below with the placeholder replaced')) return true;
|
|
@@ -195,6 +195,13 @@ some random text without markers
|
|
|
195
195
|
=== IMPLEMENTATION RESULT END ===`;
|
|
196
196
|
expect(isImplementationPlaceholder(noise)).toBe(true);
|
|
197
197
|
});
|
|
198
|
+
|
|
199
|
+
test('echoed prompt with {TASK_ID} placeholder is detected as placeholder', () => {
|
|
200
|
+
const echoedWithPlaceholder = `=== IMPLEMENTATION RESULT START ===
|
|
201
|
+
=== IMPLEMENTATION COMPLETE {TASK_ID} ===
|
|
202
|
+
=== IMPLEMENTATION RESULT END ===`;
|
|
203
|
+
expect(isImplementationPlaceholder(echoedWithPlaceholder)).toBe(true);
|
|
204
|
+
});
|
|
198
205
|
});
|
|
199
206
|
|
|
200
207
|
describe('retry status resolution', () => {
|