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 past the build, test, and review gates 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.
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: the worker's output, the build and test output, each reviewer's output, and the `error.log` that briefed the final iteration. It's the whole story of what was tried and where each go-round fell short.
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
- this._workspace.preserveOnDispose();
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
- await this._writeErrorLog(ws, `git add -A failed (exit ${addResult.code})\n--- stdout ---\n${addResult.stdout}\n--- stderr ---\n${addResult.stderr}`);
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
- await this._writeErrorLog(ws, `git commit failed (exit ${commitResult.code})\n--- stdout ---\n${commitResult.stdout}\n--- stderr ---\n${commitResult.stderr}`);
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
- await this._writeErrorLog(ws, `build stage failed (exit ${result.code})\n--- stdout ---\n${result.stdout}\n--- stderr ---\n${result.stderr}`);
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
- await this._writeErrorLog(ws, `test stage failed (exit ${result.code})\n--- stdout ---\n${result.stdout}\n--- stderr ---\n${result.stderr}`);
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) {
@@ -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 hard-stop temporary folder: its per-iteration worker, build, test, and reviewer output logs, its briefing \`error.log\`, and its consolidated \`spec.md\`, together with 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.
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
- Every message you address to the user during the run is written in the natural language of the user's most recent message in the conversation. When the user switches the language they write in partway through the interaction, every subsequent message you address to the user follows the language of their latest message.
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flanders",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Flanders never breaks a rule",
5
5
  "main": "lib/",
6
6
  "types": "lib/types",