flanders 0.3.0 → 0.5.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.
@@ -112,6 +112,34 @@ const CLAUDE_CROSS_FAMILY_ALIASES = [
112
112
  ];
113
113
  const CLAUDE_BACK_LABEL = "← back";
114
114
  const REVIEWER_INDEXED_RE = /^--reviewer-(\d+)-(tool|model|effort)=/;
115
+ const REVIEWER_OPTIONAL_INDEXED_RE = /^--reviewer-(\d+)-optional$/;
116
+ function validateWeightedFlagsForReviewerCount(reviewerMinimum, optionalReviewerIndices, reviewerCount) {
117
+ if (reviewerCount === 1) {
118
+ if (reviewerMinimum !== undefined) {
119
+ return "Invalid flag for a single-reviewer configuration: --reviewer-minimum. Weighted-review flags require two or more reviewers.\n";
120
+ }
121
+ if (optionalReviewerIndices !== undefined) {
122
+ const idx = optionalReviewerIndices[0];
123
+ const flag = idx === 1 ? "--reviewer-optional" : `--reviewer-${idx}-optional`;
124
+ return `Invalid flag for a single-reviewer configuration: ${flag}. Weighted-review flags require two or more reviewers.\n`;
125
+ }
126
+ return null;
127
+ }
128
+ if (reviewerMinimum !== undefined && (reviewerMinimum < 1 || reviewerMinimum > reviewerCount)) {
129
+ return `Invalid value for --reviewer-minimum: "${reviewerMinimum}". Must be an integer between 1 and ${reviewerCount}.\n`;
130
+ }
131
+ if (optionalReviewerIndices !== undefined) {
132
+ for (const idx of optionalReviewerIndices) {
133
+ if (idx > reviewerCount) {
134
+ return `Invalid reviewer flag: --reviewer-${idx}-optional references reviewer ${idx}, beyond the configured reviewer list of ${reviewerCount}.\n`;
135
+ }
136
+ }
137
+ }
138
+ if (reviewerMinimum === reviewerCount && optionalReviewerIndices !== undefined) {
139
+ return `Invalid flag combination: --reviewer-minimum equal to the reviewer count (${reviewerCount}) leaves no reviewer that can be optional, so it cannot be combined with --reviewer[-N]-optional.\n`;
140
+ }
141
+ return null;
142
+ }
115
143
  function parseInstallFlags(rawArgs) {
116
144
  var _a;
117
145
  const hasGlobal = rawArgs.includes("--global");
@@ -226,6 +254,38 @@ function parseInstallFlags(rawArgs) {
226
254
  }
227
255
  answers.reviewers = list;
228
256
  }
257
+ const optionalIndices = new Set();
258
+ for (const arg of rawArgs) {
259
+ if (arg === "--reviewer-optional") {
260
+ optionalIndices.add(1);
261
+ continue;
262
+ }
263
+ const match = arg.match(REVIEWER_OPTIONAL_INDEXED_RE);
264
+ if (match === null) {
265
+ continue;
266
+ }
267
+ const idx = Number(match[1]);
268
+ if (idx < 1) {
269
+ return { ok: false, diagnostic: `Invalid reviewer flag: "${arg}". --reviewer-N-optional requires N >= 1.\n` };
270
+ }
271
+ optionalIndices.add(idx);
272
+ }
273
+ if (optionalIndices.size > 0) {
274
+ answers.optionalReviewerIndices = [...optionalIndices].sort((a, b) => a - b);
275
+ }
276
+ const minimumRaw = extractFlagValue(rawArgs, "--reviewer-minimum");
277
+ if (minimumRaw !== undefined) {
278
+ if (!/^\d+$/.test(minimumRaw)) {
279
+ return { ok: false, diagnostic: `Invalid value for --reviewer-minimum: "${minimumRaw}". Expected a non-negative integer.\n` };
280
+ }
281
+ answers.reviewerMinimum = Number(minimumRaw);
282
+ }
283
+ if (answers.reviewers !== undefined) {
284
+ const diagnostic = validateWeightedFlagsForReviewerCount(answers.reviewerMinimum, answers.optionalReviewerIndices, answers.reviewers.length);
285
+ if (diagnostic !== null) {
286
+ return { ok: false, diagnostic };
287
+ }
288
+ }
229
289
  return { ok: true, answers };
230
290
  }
231
291
  class Install {
@@ -590,6 +650,95 @@ class Install {
590
650
  idx++;
591
651
  }
592
652
  }
653
+ if (suppliedReviewers === undefined) {
654
+ const diagnostic = validateWeightedFlagsForReviewerCount(answers.reviewerMinimum, answers.optionalReviewerIndices, reviewers.length);
655
+ if (diagnostic !== null) {
656
+ contexts.output.writeError(diagnostic);
657
+ return 1;
658
+ }
659
+ }
660
+ let minimumReviews;
661
+ const reviewerConfigs = [];
662
+ if (reviewers.length === 1) {
663
+ minimumReviews = 1;
664
+ reviewerConfigs.push({ ...reviewers[0], optional: false });
665
+ }
666
+ else {
667
+ const reviewerCount = reviewers.length;
668
+ if (answers.reviewerMinimum !== undefined) {
669
+ minimumReviews = answers.reviewerMinimum;
670
+ }
671
+ else {
672
+ if (this._disposed) {
673
+ return 1;
674
+ }
675
+ let chosen = null;
676
+ while (chosen === null) {
677
+ const entry = await promptText(contexts.ask, {
678
+ question: "Minimum reviewers that must run to a verdict in each review round",
679
+ placeholder: `1-${reviewerCount}, empty for ${reviewerCount}`
680
+ });
681
+ if (entry === null) {
682
+ return 1;
683
+ }
684
+ if (this._disposed) {
685
+ return 1;
686
+ }
687
+ const trimmed = entry.trim();
688
+ if (trimmed === "") {
689
+ chosen = reviewerCount;
690
+ }
691
+ else {
692
+ const parsed = Number(trimmed);
693
+ if (/^\d+$/.test(trimmed) && parsed >= 1 && parsed <= reviewerCount) {
694
+ chosen = parsed;
695
+ }
696
+ else {
697
+ contexts.output.write(`Enter an integer between 1 and ${reviewerCount}, or leave empty for ${reviewerCount}.\n`);
698
+ }
699
+ }
700
+ }
701
+ minimumReviews = chosen;
702
+ }
703
+ if (minimumReviews < reviewerCount) {
704
+ if (answers.optionalReviewerIndices !== undefined) {
705
+ const optionalSet = new Set(answers.optionalReviewerIndices);
706
+ for (let i = 0; i < reviewerCount; i++) {
707
+ reviewerConfigs.push({ ...reviewers[i], optional: optionalSet.has(i + 1) });
708
+ }
709
+ }
710
+ else {
711
+ for (let i = 0; i < reviewerCount; i++) {
712
+ if (this._disposed) {
713
+ return 1;
714
+ }
715
+ const reviewer = reviewers[i];
716
+ const modelLabel = reviewer.model === "" ? "default configured model" : reviewer.model;
717
+ const effortLabel = reviewer.effort === "" ? "default configured effort" : reviewer.effort;
718
+ const optionalOption = await promptChoice(contexts.ask, {
719
+ header: `Reviewer ${i + 1} optional`,
720
+ question: `Is reviewer ${i + 1} (${reviewer.tool} · ${modelLabel} · ${effortLabel}) optional?`,
721
+ options: [
722
+ { label: "no", description: "Required — always waits out its rate-limit waits; the round never completes without its verdict" },
723
+ { label: "yes", description: "Optional — reviews exactly like a required reviewer; the only difference is the round abandons it while it is in a rate-limit wait, once every required reviewer is in and the minimum is met" }
724
+ ]
725
+ });
726
+ if (!optionalOption) {
727
+ return 1;
728
+ }
729
+ if (this._disposed) {
730
+ return 1;
731
+ }
732
+ reviewerConfigs.push({ ...reviewer, optional: optionalOption.label === "yes" });
733
+ }
734
+ }
735
+ }
736
+ else {
737
+ for (let i = 0; i < reviewerCount; i++) {
738
+ reviewerConfigs.push({ ...reviewers[i], optional: false });
739
+ }
740
+ }
741
+ }
593
742
  const scopeRoot = mode === "global"
594
743
  ? contexts.platform.homedir()
595
744
  : options.projectRoot;
@@ -656,7 +805,8 @@ class Install {
656
805
  }
657
806
  const config = {
658
807
  worker: { tool: workerTool, model: workerModel, effort: workerEffort },
659
- reviewers
808
+ reviewers: reviewerConfigs,
809
+ minimumReviews
660
810
  };
661
811
  const configWrittenPath = await (0, FlandersConfig_1.write)(contexts.fs, {
662
812
  scope: mode,
@@ -9,6 +9,39 @@ function isCommandNotFound(combinedOutput) {
9
9
  const lower = combinedOutput.toLowerCase();
10
10
  return COMMAND_NOT_FOUND_MARKERS.some(marker => lower.includes(marker));
11
11
  }
12
+ function parseModelCatalog(stdout) {
13
+ let parsed;
14
+ try {
15
+ parsed = JSON.parse(stdout);
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
21
+ return null;
22
+ }
23
+ const models = parsed.models;
24
+ if (!Array.isArray(models)) {
25
+ return null;
26
+ }
27
+ const slugs = [];
28
+ for (const entry of models) {
29
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
30
+ return null;
31
+ }
32
+ const record = entry;
33
+ if (typeof record.slug !== "string" || typeof record.visibility !== "string") {
34
+ return null;
35
+ }
36
+ if (record.visibility === "list") {
37
+ slugs.push(record.slug);
38
+ }
39
+ }
40
+ if (slugs.length === 0) {
41
+ return { kind: "no-list" };
42
+ }
43
+ return { kind: "list", models: slugs };
44
+ }
12
45
  function probeModelList(script) {
13
46
  return new Promise(resolve => {
14
47
  const stdoutChunks = [];
@@ -48,52 +81,17 @@ function probeModelList(script) {
48
81
  });
49
82
  proc.on("exit", (code) => {
50
83
  const stdout = stdoutChunks.join("");
84
+ const catalog = parseModelCatalog(stdout);
85
+ if (catalog) {
86
+ settle(catalog);
87
+ return;
88
+ }
51
89
  const stderr = stderrChunks.join("");
52
90
  if (code === 127 || isCommandNotFound(stderr + stdout)) {
53
91
  notStarted(null);
54
92
  return;
55
93
  }
56
- if (code !== 0) {
57
- settle({ kind: "no-list" });
58
- return;
59
- }
60
- let parsed;
61
- try {
62
- parsed = JSON.parse(stdout);
63
- }
64
- catch {
65
- settle({ kind: "no-list" });
66
- return;
67
- }
68
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
69
- settle({ kind: "no-list" });
70
- return;
71
- }
72
- const models = parsed.models;
73
- if (!Array.isArray(models)) {
74
- settle({ kind: "no-list" });
75
- return;
76
- }
77
- const slugs = [];
78
- for (const entry of models) {
79
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
80
- settle({ kind: "no-list" });
81
- return;
82
- }
83
- const record = entry;
84
- if (typeof record.slug !== "string" || typeof record.visibility !== "string") {
85
- settle({ kind: "no-list" });
86
- return;
87
- }
88
- if (record.visibility === "list") {
89
- slugs.push(record.slug);
90
- }
91
- }
92
- if (slugs.length === 0) {
93
- settle({ kind: "no-list" });
94
- return;
95
- }
96
- settle({ kind: "list", models: slugs });
94
+ settle({ kind: "no-list" });
97
95
  });
98
96
  });
99
97
  }
@@ -27,6 +27,7 @@ export type PlanParseResult = Readonly<{
27
27
  }>;
28
28
  export declare const TASK_LINE: RegExp;
29
29
  export declare function parsePlan(content: string): PlanParseResult;
30
+ export declare function extractFullTaskText(content: string, taskLineNumber: number): string;
30
31
  export declare function parseLinkedPaths(content: string, taskLineNumber: number): TaskLinkedPaths;
31
32
  export declare class PlanFile {
32
33
  readonly path: string;
@@ -40,6 +41,7 @@ export declare class PlanFile {
40
41
  markDone(lineNumber: number, metrics: TaskMetrics): Promise<void>;
41
42
  markOpen(lineNumber: number, metrics: TaskMetrics): Promise<void>;
42
43
  linkedPaths(task: PlanTask): TaskLinkedPaths;
44
+ fullTaskText(task: PlanTask): string;
43
45
  planTotals(): TaskMetrics;
44
46
  private _rewriteTaskLine;
45
47
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PlanFile = exports.TASK_LINE = void 0;
4
4
  exports.parsePlan = parsePlan;
5
+ exports.extractFullTaskText = extractFullTaskText;
5
6
  exports.parseLinkedPaths = parseLinkedPaths;
6
7
  exports.TASK_LINE = /^(\s*[-*+]\s+)\[([ xX])\](\{[^}]*\})(\s.*)?$/;
7
8
  const MALFORMED_TASK_LINE = /^\s*[-*+]\s+\[[^\]]*\]\{/;
@@ -88,14 +89,12 @@ function parsePlan(content) {
88
89
  size: content.length
89
90
  };
90
91
  }
91
- const BACKTICK_PATH = /`([^`]+)`/g;
92
- function extractPathsFromLine(text) {
93
- const paths = [];
94
- let m;
95
- while ((m = BACKTICK_PATH.exec(text)) !== null) {
96
- paths.push(m[1]);
97
- }
98
- return paths;
92
+ const MARKDOWN_LINK = /\[[^\]]*\]\(([^)]+)\)/g;
93
+ const CONTRACTS_SEGMENT = ".spec/contracts/";
94
+ const RULES_SEGMENT = ".spec/rules/";
95
+ function detectNewline(content) {
96
+ const newlineMatch = /\r\n|\n/.exec(content);
97
+ return newlineMatch ? newlineMatch[0] : "\n";
99
98
  }
100
99
  function taskBodyLines(content, taskLineNumber) {
101
100
  const lines = content.split(/\r?\n/);
@@ -108,17 +107,30 @@ function taskBodyLines(content, taskLineNumber) {
108
107
  }
109
108
  return body;
110
109
  }
110
+ function extractFullTaskText(content, taskLineNumber) {
111
+ const lines = content.split(/\r?\n/);
112
+ const taskLine = lines[taskLineNumber - 1];
113
+ const body = taskBodyLines(content, taskLineNumber);
114
+ return [taskLine, ...body].join(detectNewline(content));
115
+ }
111
116
  function parseLinkedPaths(content, taskLineNumber) {
112
117
  const body = taskBodyLines(content, taskLineNumber);
113
- let contracts = [];
114
- let rules = [];
118
+ const contracts = [];
119
+ const rules = [];
115
120
  for (const line of body) {
116
- const trimmed = line.trimStart();
117
- if (trimmed.startsWith("Linked contracts:")) {
118
- contracts = extractPathsFromLine(trimmed.slice("Linked contracts:".length));
119
- }
120
- else if (trimmed.startsWith("Linked rules:")) {
121
- rules = extractPathsFromLine(trimmed.slice("Linked rules:".length));
121
+ let m;
122
+ while ((m = MARKDOWN_LINK.exec(line)) !== null) {
123
+ const path = m[1].split("#")[0].replace(/^\//, "");
124
+ if (path.includes(CONTRACTS_SEGMENT)) {
125
+ if (!contracts.includes(path)) {
126
+ contracts.push(path);
127
+ }
128
+ }
129
+ else if (path.includes(RULES_SEGMENT)) {
130
+ if (!rules.includes(path)) {
131
+ rules.push(path);
132
+ }
133
+ }
122
134
  }
123
135
  }
124
136
  return { contracts, rules };
@@ -156,6 +168,9 @@ class PlanFile {
156
168
  linkedPaths(task) {
157
169
  return parseLinkedPaths(this._content, task.line);
158
170
  }
171
+ fullTaskText(task) {
172
+ return extractFullTaskText(this._content, task.line);
173
+ }
159
174
  planTotals() {
160
175
  const { tasks } = parsePlan(this._content);
161
176
  let it = 0, ot = 0, t = 0;
@@ -168,8 +183,7 @@ class PlanFile {
168
183
  }
169
184
  async _rewriteTaskLine(lineNumber, metrics, flip) {
170
185
  validateMetricsInput(metrics);
171
- const newlineMatch = /\r\n|\n/.exec(this._content);
172
- const newline = newlineMatch ? newlineMatch[0] : "\n";
186
+ const newline = detectNewline(this._content);
173
187
  const lines = this._content.split(/\r?\n/);
174
188
  const idx = lineNumber - 1;
175
189
  const raw = lines[idx];
@@ -1,7 +1,6 @@
1
1
  export declare const enum Placeholders {
2
2
  PLAN_PATH = "<PLAN_PATH>",
3
- TASK_LINE = "<TASK_LINE>",
4
- TASK_TITLE = "<TASK_TITLE>",
3
+ TASK_TEXT = "<TASK_TEXT>",
5
4
  BUILD_SCRIPT_PATH = "<BUILD_SCRIPT_PATH>",
6
5
  TEST_SCRIPT_PATH = "<TEST_SCRIPT_PATH>",
7
6
  ERROR_LOG_PATH = "<ERROR_LOG_PATH>",
@@ -43,6 +42,5 @@ export declare const prompts: {
43
42
  detectBuildAndTest: string;
44
43
  worker: string;
45
44
  reviewer: string;
46
- prep: string;
47
45
  previousIterationBriefing: string;
48
46
  };
@@ -35,7 +35,7 @@ ${s.readOnlyParagraph}`;
35
35
  2. A contract referenced by ${s.specRef} is not honored.
36
36
  3. A rule referenced by ${s.specRef} is not applied in the changes — you have the positive obligation to verify that every referenced rule is actively applied; a referenced rule that is not applied is FAIL.
37
37
  4. A contract or rule from the global lists above that you determine should have been applied but was not, even if not referenced by ${s.specRef}, is FAIL.
38
- 5. A behavior rule from the behavior-rule list above whose \`.docs/flanders\` scope encloses the files the working-tree changes touch is not honored by the changes, even if ${s.specRef} did not reference it, is FAIL.
38
+ 5. A behavior rule from the behavior-rule list above whose \`.spec/flanders\` scope encloses the files the working-tree changes touch is not honored by the changes, even if ${s.specRef} did not reference it, is FAIL.
39
39
 
40
40
  Exhaustiveness: do not stop at the first violation. Run every verification you are required to run and every additional check your judgment deems applicable, even after one of them has already produced a FAIL. The five conditions above and the ${s.critProtocolName} are executed in full on every invocation; encountering a violation in one of them does not exempt you from completing the rest. The goal is that a single review produces the complete list of fixes ${s.nextWorker} needs to apply.
41
41
 
@@ -149,21 +149,22 @@ If you cannot confidently determine how to build the project, leave the build sc
149
149
 
150
150
  ## Available rules
151
151
 
152
- Each path below is the rule's namespace. Before deciding the build or test commands, scan this list and open every rule whose scope governs how the project is built or how its tests are run — for example, any rule under a \`testing/\` or \`build/\` subfolder of a \`.docs/rules\` folder, or any rule that prescribes a specific runner, invocation form, required flag, or toolchain convention. Reading is not optional for rules whose scope matches build/test invocation. The commands you write must honor those rules: if a rule pins the test runner to a specific invocation form or required flag, the script you write must use that exact invocation.
152
+ Each path below is the rule's namespace. Before deciding the build or test commands, scan this list and open every rule whose scope governs how the project is built or how its tests are run — for example, any rule under a \`testing/\` or \`build/\` subfolder of a \`.spec/rules\` folder, or any rule that prescribes a specific runner, invocation form, required flag, or toolchain convention. Reading is not optional for rules whose scope matches build/test invocation. The commands you write must honor those rules: if a rule pins the test runner to a specific invocation form or required flag, the script you write must use that exact invocation.
153
153
 
154
154
  ${"<RULE_LIST>"}
155
155
 
156
156
  Git boundary: you must not execute any git command that modifies repository state. Read-only git commands (\`git status\`, \`git log\`, \`git show\`, \`git diff\`, \`git blame\`, \`git ls-files\`) are allowed if they help you understand the project; commits, staging, branches, tags, stashes, resets, restores, merges, rebases, edits under \`.git/\`, and any remote git operation are forbidden. See rules/ai/agents/no-git-writes.md for the full obligation.
157
157
 
158
- Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.docs/contracts\` folder, any \`.docs/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may write to them. See shared/spec-folder-write-authority.md for the full obligation.
158
+ Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may write to them. See shared/spec-folder-write-authority.md for the full obligation.
159
159
 
160
160
  ${foregroundBoundary}`,
161
161
  worker: `You are the worker agent for the Flanders implement iteration loop.
162
162
 
163
- Plan file path: ${"<PLAN_PATH>"}
163
+ The plan file is at ${"<PLAN_PATH>"}; you may open it for broader context.
164
164
 
165
- The current task is on line ${"<TASK_LINE>"} of that plan file. Its title, verbatim, is:
166
- ${"<TASK_TITLE>"}
165
+ ## Your task
166
+
167
+ ${"<TASK_TEXT>"}
167
168
 
168
169
  ## Adversarial review awaits
169
170
 
@@ -173,12 +174,12 @@ Your output will be inspected by an adversarial reviewer immediately after you f
173
174
  2. A contract referenced by the task is not honored.
174
175
  3. A rule referenced by the task is not actively applied — acknowledging a rule is not enough; the changes must demonstrate compliance.
175
176
  4. A contract or rule from the global lists below that the reviewer determines should have been applied but was not — even if the task did not reference it.
176
- 5. A behavior rule from the behavior-rule list below whose \`.docs/flanders\` scope encloses the files your changes touch is not honored by the changes — in-scope behavior rules are mandatory whether or not the task links them.
177
+ 5. A behavior rule from the behavior-rule list below whose \`.spec/flanders\` scope encloses the files your changes touch is not honored by the changes — in-scope behavior rules are mandatory whether or not the task links them.
177
178
 
178
179
  Condition 4 causes most rejections in practice. Rules whose scope matches your changes (testing rules when you touch tests, disposable rules when you touch async resources, UI rules when you change terminal output, etc.) are mandatory whether the task links them or not. Treat the global contract and rule lists below as part of your specification, not as optional reading. The reviewer will also enumerate every occurrence of a pattern violation, not just the first one, so partial compliance within a file is itself a FAIL.
179
180
 
180
181
  Procedure:
181
- 1. Open the plan file and find that line. Read the full task description and its acceptance criteria. You are not required to re-read the linked contracts and rules — on iteration 1 their content is already in context through the prep fork, and on later iterations it is preserved by your own session continuity. You may consult them at your discretion, but you must respect their obligations exactly.
182
+ 1. Read the task shown above and respect the obligations of every contract and rule it references exactly. You may consult those files, or the plan file for broader context, at your discretion.
182
183
  2. Implement the task. Update or extend tests so the new behavior is covered.
183
184
  3. If your implementation changes how the project builds or how its tests run, also update the build and test scripts at:
184
185
  - Build script: ${"<BUILD_SCRIPT_PATH>"}
@@ -209,7 +210,7 @@ Do not flip the task's checkbox in the plan file. Flanders flips the checkbox it
209
210
 
210
211
  Git boundary: you must not execute any git command that modifies repository state — no \`git add\`, \`git commit\`, \`git stash\`, \`git reset\`, \`git restore\`, \`git checkout -b\`, \`git branch\`, \`git tag\`, \`git rebase\`, \`git merge\`, \`git cherry-pick\`, no edits under \`.git/\`, and no remote git operations (\`fetch\`, \`pull\`, \`push\`). Read-only git commands (\`git status\`, \`git diff\`, \`git log\`, \`git show\`, \`git blame\`, \`git ls-files\`) are allowed when you need to inspect the repo. Leave your implementation as a dirty working tree — Flanders performs the commit itself once your changes pass build, test, and review. If your task seems to require a git write, stop and explain it in your final message instead of doing it. The full obligation lives in rules/ai/agents/no-git-writes.md.
211
212
 
212
- Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.docs/contracts\` folder, any \`.docs/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may write to them. See shared/spec-folder-write-authority.md for the full obligation.
213
+ Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may write to them. See shared/spec-folder-write-authority.md for the full obligation.
213
214
 
214
215
  ${foregroundBoundary}
215
216
 
@@ -227,17 +228,18 @@ ${"<RULE_LIST>"}
227
228
 
228
229
  ## Available behavior rules
229
230
 
230
- Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes you author are named, placed, and organized within the part of the project tree that the rule's \`.docs/flanders\` folder scopes. You must honor every behavior rule whose \`.docs/flanders\` scope encloses the files your changes touch. Like the global contract and rule lists above, in-scope behavior rules are mandatory whether or not the task links them; the reviewer FAILS for any in-scope behavior rule the changes do not honor.
231
+ Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes you author are named, placed, and organized within the part of the project tree that the rule's \`.spec/flanders\` folder scopes. You must honor every behavior rule whose \`.spec/flanders\` scope encloses the files your changes touch. Like the global contract and rule lists above, in-scope behavior rules are mandatory whether or not the task links them; the reviewer FAILS for any in-scope behavior rule the changes do not honor.
231
232
 
232
233
  ${"<BEHAVIOR_RULE_LIST>"}`,
233
234
  reviewer: `You are the adversarial reviewer agent for the Flanders implement iteration loop.
234
235
 
235
- Plan file path: ${"<PLAN_PATH>"}
236
+ The plan file is at ${"<PLAN_PATH>"}; you may open it for broader context, but you do not need to in order to find the task — the full task is provided in this prompt.
237
+
238
+ ## The task under review
236
239
 
237
- The current task is on line ${"<TASK_LINE>"} of that plan file. Its title, verbatim, is:
238
- ${"<TASK_TITLE>"}
240
+ ${"<TASK_TEXT>"}
239
241
 
240
- Read the task's full description, its acceptance criteria, every contract referenced by the task AND every rule referenced by the task. Inspect the working-tree changes that the worker just produced.
242
+ The task's full description, its acceptance criteria, and the full content of every contract and rule it references are provided to you directly — the referenced contracts and rules are injected inline at the end of this prompt (under "Linked reference content"). Inspect the working-tree changes that the worker just produced.
241
243
 
242
244
  ## Determining the worker's change set
243
245
 
@@ -257,7 +259,7 @@ ${"<RULE_LIST>"}
257
259
 
258
260
  ## Available behavior rules
259
261
 
260
- Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes the worker authored are named, placed, and organized within the part of the project tree that the rule's \`.docs/flanders\` folder scopes. You must verify that the working-tree changes honor every behavior rule whose \`.docs/flanders\` scope encloses the files they touch. Like the global contract and rule lists above, in-scope behavior rules are mandatory whether or not the task links them.
262
+ Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes the worker authored are named, placed, and organized within the part of the project tree that the rule's \`.spec/flanders\` folder scopes. You must verify that the working-tree changes honor every behavior rule whose \`.spec/flanders\` scope encloses the files they touch. Like the global contract and rule lists above, in-scope behavior rules are mandatory whether or not the task links them.
261
263
 
262
264
  ${"<BEHAVIOR_RULE_LIST>"}
263
265
 
@@ -265,61 +267,9 @@ ${implementReviewerMethodology.audit}
265
267
 
266
268
  Git boundary: you are an inspection-only agent. You must not execute any git command that modifies repository state — no \`git add\`, \`git commit\`, \`git stash\`, \`git reset\`, \`git restore\`, \`git checkout -b\`, \`git branch\`, \`git tag\`, no edits under \`.git/\`, and no remote git operations. Read-only git commands (\`git status\`, \`git diff\`, \`git log\`, \`git show\`, \`git blame\`, \`git ls-files\`) are allowed and are how you should inspect the worker's changes. The full obligation lives in rules/ai/agents/no-git-writes.md.
267
269
 
268
- Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.docs/contracts\` folder, any \`.docs/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may write to them. See shared/spec-folder-write-authority.md for the full obligation.
270
+ Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may write to them. See shared/spec-folder-write-authority.md for the full obligation.
269
271
 
270
272
  ${foregroundBoundary}`,
271
- prep: `You are the prep agent for the Flanders implement iteration loop.
272
-
273
- Plan file path: ${"<PLAN_PATH>"}
274
-
275
- The current task is on line ${"<TASK_LINE>"} of that plan file. Its title, verbatim, is:
276
- ${"<TASK_TITLE>"}
277
-
278
- ## Your job
279
-
280
- Read the task and its reference material so the session is ready to be forked by the worker and reviewer agents. You do not implement anything — you are a read-only context-loading agent.
281
-
282
- Procedure:
283
- 1. Open the plan file and find the task at the line indicated above. Read its full description, acceptance criteria, and every contract and rule file the task references.
284
- 2. From the global lists below, read the full content of every additional contract or rule you judge relevant to the task, even if the task does not explicitly reference it. Err on the side of loading material that might be needed rather than skipping it.
285
-
286
- ## Read-only obligation
287
-
288
- You must not implement, modify, or write anything in the project. Do not use Edit, Write, or any Bash command that mutates project state. Your only job is to read and load context.
289
-
290
- ## Spec-folder write boundary
291
-
292
- You must not write to any \`.docs/contracts\` folder, any \`.docs/rules\` folder, or the \`plans/\` folder. These folders are governed by dedicated skills and the implement command's bounded checkpoint updates; no other agent may create, modify, delete, or rename files in them. See shared/spec-folder-write-authority.md for the full obligation.
293
-
294
- ## Git boundary
295
-
296
- You must not execute any git command that modifies repository state — no \`git add\`, \`git commit\`, \`git stash\`, \`git reset\`, \`git restore\`, \`git checkout -b\`, \`git branch\`, \`git tag\`, \`git rebase\`, \`git merge\`, \`git cherry-pick\`, no edits under \`.git/\`, and no remote git operations (\`fetch\`, \`pull\`, \`push\`). Read-only git commands (\`git status\`, \`git diff\`, \`git log\`, \`git show\`, \`git blame\`, \`git ls-files\`) are allowed when you need to inspect the repo. The full obligation lives in rules/ai/agents/no-git-writes.md.
297
-
298
- ${foregroundBoundary}
299
-
300
- ## Available contracts
301
-
302
- Each path below is the contract's namespace. Scan this list and open every contract whose public surface intersects the work in this task.
303
-
304
- ${"<CONTRACT_LIST>"}
305
-
306
- ## Available rules
307
-
308
- Each path below is the rule's namespace. Scan this list and open every rule whose scope matches the work in this task.
309
-
310
- ${"<RULE_LIST>"}
311
-
312
- ## Available behavior rules
313
-
314
- Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes Flanders authors are named, placed, and organized within the part of the project tree that the rule's \`.docs/flanders\` folder scopes; every behavior rule whose \`.docs/flanders\` scope encloses the files this task's work touches must be honored. Read all of those in-scope behavior rules now, so the worker and reviewer forked from this session honor them. Like the global contract and rule lists above, in-scope behavior rules are mandatory whether or not the task links them.
315
-
316
- ${"<BEHAVIOR_RULE_LIST>"}
317
-
318
- ## Ending discipline
319
-
320
- When you have finished reading all relevant material, end your reply with the word READY on its own line and no pending tool calls. The session must be in a forkable state.
321
-
322
- READY`,
323
273
  previousIterationBriefing: `This is iteration ${"<ITERATION>"} for this task. The previous iteration produced a problem to review before retrying. Read the full context written into the error log file at:
324
274
 
325
275
  ${"<ERROR_LOG_PATH>"}