flanders 0.11.0 → 0.12.0
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/README.md
CHANGED
|
@@ -254,10 +254,10 @@ If you really want the font to be kept at that size, just save the spec, and no
|
|
|
254
254
|
|
|
255
255
|
## Hard stop
|
|
256
256
|
|
|
257
|
-
Even the most neighborly run can run out of road. When Flanders can't get a single task
|
|
257
|
+
Even the most neighborly run can run out of road. When Flanders can't get a single task built, tested, reviewed, and committed within its five attempts, it doesn't keep flailing away — it calls a **hard stop**: the whole `implement` run ends right there and exits with a non-zero status.
|
|
258
258
|
|
|
259
259
|
It won't leave you guessing, though. Flanders prints an error that names the task that got stuck — its line number in the plan and its title — and points you at that run's temporary folder. Every other time Flanders exits it tidies that folder away, but on a hard stop it leaves it right where it is, on purpose, so you can have a look.
|
|
260
260
|
|
|
261
|
-
Inside you'll find the sessions from every attempt on the task
|
|
261
|
+
Inside you'll find the sessions from every attempt on the task — the worker's output, the build and test output, and each reviewer's output. Alongside them, each iteration that fell short leaves its error set down by the stage that tripped it: a build, test, or commit log for the iteration whose build, test, or commit stage failed, and — when it was the review that failed — one log per reviewer that recorded a violation. So the folder spells out plainly which stage failed in each iteration: it's the whole story of what was tried and where each go-round fell short.
|
|
262
262
|
|
|
263
263
|
And here's the neighborly part — you don't have to untangle it all yourself. Invoke **`/flanders-hard-stop-review`** in your AI coding tool and hand it that folder's path, and it reads back through the sessions, tells you why the run failed, and recommends how to relaunch `implement` so the stuck task finishes this time — mending the spec or the plan, or simply running it again.
|
|
@@ -38,6 +38,7 @@ export declare class Implement {
|
|
|
38
38
|
private _cancelledReviewers;
|
|
39
39
|
private _currentIndexLabel;
|
|
40
40
|
private _currentIteration;
|
|
41
|
+
private _retainedMaterializations;
|
|
41
42
|
private _currentTask;
|
|
42
43
|
private _taskStartedAt;
|
|
43
44
|
private _taskExcludedMs;
|
|
@@ -80,6 +81,7 @@ export declare class Implement {
|
|
|
80
81
|
private _runScript;
|
|
81
82
|
private _writeLog;
|
|
82
83
|
private _writeErrorLog;
|
|
84
|
+
private _materializeHardStopErrorLogs;
|
|
83
85
|
private _stringifyError;
|
|
84
86
|
private _finalizeBlock;
|
|
85
87
|
dispose(): Promise<void>;
|
|
@@ -83,6 +83,7 @@ class Implement {
|
|
|
83
83
|
this._cancelledReviewers = new Set();
|
|
84
84
|
this._currentIndexLabel = "";
|
|
85
85
|
this._currentIteration = 0;
|
|
86
|
+
this._retainedMaterializations = [];
|
|
86
87
|
this._currentTask = null;
|
|
87
88
|
this._taskStartedAt = 0;
|
|
88
89
|
this._taskExcludedMs = 0;
|
|
@@ -311,6 +312,7 @@ class Implement {
|
|
|
311
312
|
this._taskRateLimitStartedAt = null;
|
|
312
313
|
this._reviewPauseStartedAt = null;
|
|
313
314
|
this._taskTokens = { it: 0, ot: 0 };
|
|
315
|
+
this._retainedMaterializations = [];
|
|
314
316
|
this._otherTasksSeconds = plan.planTotals().t - task.metrics.t;
|
|
315
317
|
this._updateMetrics(plan);
|
|
316
318
|
let iteration = 0;
|
|
@@ -318,7 +320,15 @@ class Implement {
|
|
|
318
320
|
iteration++;
|
|
319
321
|
this._currentIteration = iteration;
|
|
320
322
|
if (iteration > MAX_ITER) {
|
|
321
|
-
|
|
323
|
+
try {
|
|
324
|
+
await this._materializeHardStopErrorLogs();
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
this._buffered.writeError(`Hard stop: could not materialize per-iteration error logs: ${this._stringifyError(e)}\n`);
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
this._workspace.preserveOnDispose();
|
|
331
|
+
}
|
|
322
332
|
this._buffered.writeError(`Hard stop: task at line ${task.line} ("${task.title}") exceeded ${MAX_ITER} iterations. Inspect logs at ${ws.root}.\n`);
|
|
323
333
|
return false;
|
|
324
334
|
}
|
|
@@ -382,13 +392,17 @@ class Implement {
|
|
|
382
392
|
const commitMessage = task.taskNumber ? `${task.taskNumber} ${task.title}` : task.title;
|
|
383
393
|
const addResult = await (0, Git_1.addAll)(this._contexts.script, this._contexts.time, this._gitOutputContext(), this._options.projectRoot);
|
|
384
394
|
if (addResult.code !== 0) {
|
|
385
|
-
|
|
395
|
+
const briefing = `git add -A failed (exit ${addResult.code})\n--- stdout ---\n${addResult.stdout}\n--- stderr ---\n${addResult.stderr}`;
|
|
396
|
+
this._retainedMaterializations.push({ path: ws.commitErrorLog(iteration), content: briefing });
|
|
397
|
+
await this._writeErrorLog(ws, briefing);
|
|
386
398
|
await revertCompletion();
|
|
387
399
|
continue;
|
|
388
400
|
}
|
|
389
401
|
const commitResult = await (0, Git_1.commit)(this._contexts.script, this._contexts.time, this._gitOutputContext(), this._options.projectRoot, commitMessage);
|
|
390
402
|
if (commitResult.code !== 0) {
|
|
391
|
-
|
|
403
|
+
const briefing = `git commit failed (exit ${commitResult.code})\n--- stdout ---\n${commitResult.stdout}\n--- stderr ---\n${commitResult.stderr}`;
|
|
404
|
+
this._retainedMaterializations.push({ path: ws.commitErrorLog(iteration), content: briefing });
|
|
405
|
+
await this._writeErrorLog(ws, briefing);
|
|
392
406
|
await revertCompletion();
|
|
393
407
|
continue;
|
|
394
408
|
}
|
|
@@ -478,7 +492,9 @@ class Implement {
|
|
|
478
492
|
this._updateMetrics(plan);
|
|
479
493
|
await this._writeLog(ws.buildLog(iteration), `--- stdout ---\n${result.stdout}\n--- stderr ---\n${result.stderr}`);
|
|
480
494
|
if (result.code !== 0) {
|
|
481
|
-
|
|
495
|
+
const briefing = `build stage failed (exit ${result.code})\n--- stdout ---\n${result.stdout}\n--- stderr ---\n${result.stderr}`;
|
|
496
|
+
this._retainedMaterializations.push({ path: ws.buildErrorLog(iteration), content: briefing });
|
|
497
|
+
await this._writeErrorLog(ws, briefing);
|
|
482
498
|
return false;
|
|
483
499
|
}
|
|
484
500
|
return true;
|
|
@@ -495,7 +511,9 @@ class Implement {
|
|
|
495
511
|
this._updateMetrics(plan);
|
|
496
512
|
await this._writeLog(ws.testLog(iteration), `--- stdout ---\n${result.stdout}\n--- stderr ---\n${result.stderr}`);
|
|
497
513
|
if (result.code !== 0) {
|
|
498
|
-
|
|
514
|
+
const briefing = `test stage failed (exit ${result.code})\n--- stdout ---\n${result.stdout}\n--- stderr ---\n${result.stderr}`;
|
|
515
|
+
this._retainedMaterializations.push({ path: ws.testErrorLog(iteration), content: briefing });
|
|
516
|
+
await this._writeErrorLog(ws, briefing);
|
|
499
517
|
return false;
|
|
500
518
|
}
|
|
501
519
|
return true;
|
|
@@ -591,18 +609,22 @@ class Implement {
|
|
|
591
609
|
if (this._disposed) {
|
|
592
610
|
return false;
|
|
593
611
|
}
|
|
612
|
+
const perFile = [];
|
|
613
|
+
for (let i = 0; i < reviewers.length; i++) {
|
|
614
|
+
if (outcomes[i] === "verdict") {
|
|
615
|
+
const content = await this._workspace.readReviewerErrorLog(i + 1);
|
|
616
|
+
perFile.push(content);
|
|
617
|
+
if (content.trim().length > 0) {
|
|
618
|
+
this._retainedMaterializations.push({ path: ws.reviewerErrorLogFor(iteration, i + 1), content });
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
594
622
|
if (failureCaught !== null) {
|
|
595
623
|
await this._persistMetrics(plan, task.line);
|
|
596
624
|
this._updateMetrics(plan);
|
|
597
625
|
await this._writeErrorLog(ws, `reviewer stage failed: ${this._stringifyError(failureCaught)}`);
|
|
598
626
|
return false;
|
|
599
627
|
}
|
|
600
|
-
const perFile = [];
|
|
601
|
-
for (let i = 0; i < reviewers.length; i++) {
|
|
602
|
-
if (outcomes[i] === "verdict") {
|
|
603
|
-
perFile.push(await this._workspace.readReviewerErrorLog(i + 1));
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
628
|
const aggregate = perFile.join("\n").replace(/^\s+|\s+$/g, "");
|
|
607
629
|
if (aggregate.length === 0) {
|
|
608
630
|
return true;
|
|
@@ -831,6 +853,12 @@ class Implement {
|
|
|
831
853
|
async _writeErrorLog(ws, content) {
|
|
832
854
|
await this._writeLog(ws.errorLog, content);
|
|
833
855
|
}
|
|
856
|
+
async _materializeHardStopErrorLogs() {
|
|
857
|
+
for (const { path, content } of this._retainedMaterializations) {
|
|
858
|
+
await this._contexts.fs.writeFile(path, content);
|
|
859
|
+
}
|
|
860
|
+
await this._workspace.clearErrorLog();
|
|
861
|
+
}
|
|
834
862
|
_stringifyError(e) {
|
|
835
863
|
var _a;
|
|
836
864
|
if (e instanceof Error) {
|
package/lib/prompts/skills.js
CHANGED
|
@@ -516,7 +516,7 @@ The user invokes you as: /flanders-hard-stop-review [<data>]
|
|
|
516
516
|
|
|
517
517
|
Your work is read-only, drawing only on the preserved hard-stop temporary folder, the plan file, and the project's spec corpus — not the AI tools' own session transcripts.
|
|
518
518
|
|
|
519
|
-
1. **Read the preserved evidence.** Read the preserved
|
|
519
|
+
1. **Read the preserved evidence.** Read the preserved folder's per-iteration worker, build, test, and reviewer output logs; the per-stage error logs the hard stop materializes — \`build.<iteration>.error.log\`, \`test.<iteration>.error.log\`, \`reviewer.<iteration>.<position>.error.log\`, and \`commit.<iteration>.error.log\` — making explicit which stage failed in each iteration and by which reviewer (the single briefing \`error.log\` has been removed at the hard stop); its consolidated \`spec.md\`; and each per-reviewer folder's \`error.log\`. From that evidence identify the task that hard-stopped — its plan-file line number and title — and the plan file the run was implementing.
|
|
520
520
|
|
|
521
521
|
2. **Ground the analysis in the project's specs.** Read the identified plan file and the contracts and rules the hard-stopped task references, consulting the wider spec corpus as far as the diagnosis needs.
|
|
522
522
|
|
|
@@ -540,9 +540,9 @@ After presenting the diagnosis, ask the user which skill to launch to carry out
|
|
|
540
540
|
|
|
541
541
|
You create, modify, delete, and rename no file of your own: not code, not a plan file, and no file inside any \`.spec/contracts\`, \`.spec/rules\`, or \`.spec/flanders\` folder. Every file change happens only through a skill you launch, under that skill's own write authority.
|
|
542
542
|
|
|
543
|
-
## Interaction language
|
|
543
|
+
## Interaction and reasoning language
|
|
544
544
|
|
|
545
|
-
|
|
545
|
+
Use one resolved language for both your reasoning and every message you address to the user, throughout the run. Resolve it, in order, from the natural language of the user's most recent message when that message carries a determinable natural language; otherwise from the plan file you identify, then the spec corpus you consult; otherwise the general most-recent-message resolution. Follow any mid-conversation language switch the user makes.
|
|
546
546
|
|
|
547
547
|
${(0, prompts_1.buildFlandersVoiceSection)({
|
|
548
548
|
subject: "the messages you address to the user",
|
|
@@ -13,6 +13,10 @@ export type WorkspacePaths = Readonly<{
|
|
|
13
13
|
reviewerOutputLog(iter: number, reviewerIndex: number): string;
|
|
14
14
|
reviewerErrorLog(reviewerIndex: number): string;
|
|
15
15
|
reviewerSpecFile(reviewerIndex: number): string;
|
|
16
|
+
buildErrorLog(iter: number): string;
|
|
17
|
+
testErrorLog(iter: number): string;
|
|
18
|
+
commitErrorLog(iter: number): string;
|
|
19
|
+
reviewerErrorLogFor(iter: number, position: number): string;
|
|
16
20
|
}>;
|
|
17
21
|
export interface PlatformContext {
|
|
18
22
|
isWindows(): boolean;
|
|
@@ -52,7 +52,11 @@ class Workspace {
|
|
|
52
52
|
reviewerLog(iter) { return (0, fsUtils_1.joinPath)(root, `reviewer.${iter}.log`); },
|
|
53
53
|
reviewerOutputLog(iter, reviewerIndex) { return (0, fsUtils_1.joinPath)(root, `reviewer.${iter}.${reviewerIndex}.log`); },
|
|
54
54
|
reviewerErrorLog(reviewerIndex) { return (0, fsUtils_1.joinPath)(reviewerRoots[reviewerIndex - 1], "error.log"); },
|
|
55
|
-
reviewerSpecFile(reviewerIndex) { return (0, fsUtils_1.joinPath)(reviewerRoots[reviewerIndex - 1], "spec.md"); }
|
|
55
|
+
reviewerSpecFile(reviewerIndex) { return (0, fsUtils_1.joinPath)(reviewerRoots[reviewerIndex - 1], "spec.md"); },
|
|
56
|
+
buildErrorLog(iter) { return (0, fsUtils_1.joinPath)(root, `build.${iter}.error.log`); },
|
|
57
|
+
testErrorLog(iter) { return (0, fsUtils_1.joinPath)(root, `test.${iter}.error.log`); },
|
|
58
|
+
commitErrorLog(iter) { return (0, fsUtils_1.joinPath)(root, `commit.${iter}.error.log`); },
|
|
59
|
+
reviewerErrorLogFor(iter, position) { return (0, fsUtils_1.joinPath)(root, `reviewer.${iter}.${position}.error.log`); }
|
|
56
60
|
};
|
|
57
61
|
}
|
|
58
62
|
async errorLogExists() {
|