flanders 0.1.0 → 0.2.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.
Files changed (60) hide show
  1. package/lib/ai/AiRunner.d.ts +24 -0
  2. package/lib/ai/AiRunner.js +100 -0
  3. package/lib/ai/AiSession.d.ts +32 -0
  4. package/lib/ai/AiSession.js +143 -0
  5. package/lib/ai/ClaudeAdapter.d.ts +12 -0
  6. package/lib/ai/ClaudeAdapter.js +345 -0
  7. package/lib/ai/CodexAdapter.d.ts +13 -0
  8. package/lib/ai/CodexAdapter.js +354 -0
  9. package/lib/ai/ToolAdapter.d.ts +52 -0
  10. package/lib/ai/ToolAdapter.js +2 -0
  11. package/lib/cli.js +56 -35
  12. package/lib/commands/Implement.d.ts +22 -8
  13. package/lib/commands/Implement.js +296 -171
  14. package/lib/commands/Install.d.ts +31 -3
  15. package/lib/commands/Install.js +649 -49
  16. package/lib/commands/InstallAvailabilityCheck.d.ts +8 -0
  17. package/lib/commands/InstallAvailabilityCheck.js +45 -0
  18. package/lib/commands/InstallModelProbe.d.ts +2 -0
  19. package/lib/commands/InstallModelProbe.js +74 -0
  20. package/lib/contexts.d.ts +5 -4
  21. package/lib/index.d.ts +5 -3
  22. package/lib/{PlanFile.d.ts → plan/PlanFile.d.ts} +8 -1
  23. package/lib/{PlanFile.js → plan/PlanFile.js} +44 -5
  24. package/lib/prompts/prompts.d.ts +48 -0
  25. package/lib/prompts/prompts.js +328 -0
  26. package/lib/prompts/skills.d.ts +3 -0
  27. package/lib/prompts/skills.js +463 -0
  28. package/lib/{Git.d.ts → system/Git.d.ts} +4 -2
  29. package/lib/{Git.js → system/Git.js} +63 -3
  30. package/lib/{ScriptRunner.d.ts → system/ScriptRunner.d.ts} +1 -1
  31. package/lib/system/ShellScriptContext.d.ts +36 -0
  32. package/lib/system/ShellScriptContext.js +70 -0
  33. package/lib/{fsUtils.d.ts → system/fsUtils.d.ts} +1 -1
  34. package/lib/{wait.d.ts → system/wait.d.ts} +1 -1
  35. package/lib/ui/BottomBlock.d.ts +9 -1
  36. package/lib/ui/BottomBlock.js +30 -14
  37. package/lib/ui/PromptHelper.d.ts +12 -0
  38. package/lib/ui/PromptHelper.js +32 -0
  39. package/lib/ui/TerminalSizeSource.d.ts +19 -0
  40. package/lib/ui/TerminalSizeSource.js +77 -0
  41. package/lib/ui/formatters.d.ts +14 -0
  42. package/lib/ui/formatters.js +65 -2
  43. package/lib/workspace/FlandersConfig.d.ts +23 -0
  44. package/lib/workspace/FlandersConfig.js +90 -0
  45. package/lib/workspace/SpecDiscovery.d.ts +12 -0
  46. package/lib/workspace/SpecDiscovery.js +40 -0
  47. package/lib/{Workspace.d.ts → workspace/Workspace.d.ts} +10 -2
  48. package/lib/{Workspace.js → workspace/Workspace.js} +52 -6
  49. package/package.json +3 -2
  50. package/lib/Claude.d.ts +0 -129
  51. package/lib/Claude.js +0 -387
  52. package/lib/ClaudeSession.d.ts +0 -34
  53. package/lib/ClaudeSession.js +0 -292
  54. package/lib/prompts.d.ts +0 -18
  55. package/lib/prompts.js +0 -221
  56. package/lib/skills.d.ts +0 -3
  57. package/lib/skills.js +0 -267
  58. /package/lib/{ScriptRunner.js → system/ScriptRunner.js} +0 -0
  59. /package/lib/{fsUtils.js → system/fsUtils.js} +0 -0
  60. /package/lib/{wait.js → system/wait.js} +0 -0
@@ -1,292 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ClaudeSession = void 0;
4
- const Claude_1 = require("./Claude");
5
- const TOOL_RESULT_MAX_LINES = 5;
6
- const TOOL_INPUT_INLINE_MAX = 120;
7
- class ClaudeSession {
8
- constructor(_options, _contexts) {
9
- this._options = _options;
10
- this._contexts = _contexts;
11
- this._disposed = false;
12
- this._claude = null;
13
- this._printedAnyOutputThisRun = false;
14
- this._seenTextThisMessage = "";
15
- this._textOpen = false;
16
- }
17
- async run() {
18
- if (this._disposed) {
19
- throw new Error("ClaudeSession disposed");
20
- }
21
- this._resetState();
22
- const claude = new Claude_1.Claude({
23
- prompt: this._options.prompt,
24
- ...(this._options.cwd ? { cwd: this._options.cwd } : null),
25
- ...(this._options.initialSessionId != null ? { initialSessionId: this._options.initialSessionId } : null),
26
- ...(this._options.forkFromSessionId != null ? { forkFromSessionId: this._options.forkFromSessionId } : null),
27
- onEvent: event => this._onEvent(event),
28
- onStderr: chunk => this._contexts.output.writeError(chunk),
29
- onPermissionRequest: req => this._onPermissionRequest(req),
30
- onLongWaitStart: this._options.onLongWaitStart,
31
- onLongWaitEnd: this._options.onLongWaitEnd
32
- }, this._contexts.claude, this._contexts.time);
33
- this._claude = claude;
34
- try {
35
- return await claude.result();
36
- }
37
- finally {
38
- await claude.dispose();
39
- if (this._claude === claude) {
40
- this._claude = null;
41
- }
42
- }
43
- }
44
- async dispose() {
45
- var _a;
46
- if (this._disposed) {
47
- return;
48
- }
49
- this._disposed = true;
50
- await ((_a = this._claude) === null || _a === void 0 ? void 0 : _a.dispose());
51
- this._claude = null;
52
- }
53
- _resetState() {
54
- this._printedAnyOutputThisRun = false;
55
- this._seenTextThisMessage = "";
56
- this._textOpen = false;
57
- }
58
- async _onPermissionRequest(req) {
59
- if (req.tool_name === "AskUserQuestion") {
60
- return await this._handleAskUserQuestion(req.tool_input);
61
- }
62
- return {
63
- behavior: "allow",
64
- updatedInput: typeof req.tool_input === "object" && req.tool_input !== null ? req.tool_input : {}
65
- };
66
- }
67
- async _handleAskUserQuestion(toolInput) {
68
- const questions = extractQuestions(toolInput);
69
- if (questions.length === 0) {
70
- return {
71
- behavior: "allow",
72
- updatedInput: typeof toolInput === "object" && toolInput !== null ? toolInput : {}
73
- };
74
- }
75
- this._closeOpenTextLine();
76
- const askOptions = questions.map(q => ({
77
- header: q.header,
78
- question: q.question,
79
- options: q.options.map(o => ({ label: o.label, description: o.description })),
80
- multiSelect: q.multiSelect
81
- }));
82
- const allAnswers = await this._contexts.ask.askChoices(askOptions);
83
- if (this._disposed) {
84
- return { behavior: "deny", message: "session disposed", interrupt: true };
85
- }
86
- const answers = {};
87
- for (let i = 0; i < questions.length; i++) {
88
- const q = questions[i];
89
- const answer = allAnswers[i];
90
- const labels = answer.picked.map(p => p.label).join(", ");
91
- let composed;
92
- if (labels && answer.extra) {
93
- composed = `${labels}: ${answer.extra}`;
94
- }
95
- else if (labels) {
96
- composed = labels;
97
- }
98
- else if (answer.extra) {
99
- composed = answer.extra;
100
- }
101
- else {
102
- composed = "(no answer)";
103
- }
104
- answers[q.question] = composed;
105
- }
106
- return {
107
- behavior: "allow",
108
- updatedInput: { questions, answers }
109
- };
110
- }
111
- _onEvent(event) {
112
- var _a, _b, _c, _d, _e, _f;
113
- if ((_a = event.error) === null || _a === void 0 ? void 0 : _a.message) {
114
- this._closeOpenTextLine();
115
- this._contexts.output.writeError(`[claude error] ${event.error.message}\n`);
116
- this._printedAnyOutputThisRun = true;
117
- return;
118
- }
119
- const sse = event.type === "stream_event" && event.event ? event.event : event;
120
- if ((sse === null || sse === void 0 ? void 0 : sse.type) === "content_block_start" && ((_b = sse.content_block) === null || _b === void 0 ? void 0 : _b.type) === "text" && typeof sse.content_block.text === "string" && sse.content_block.text.length > 0) {
121
- this._emitText(sse.content_block.text);
122
- this._seenTextThisMessage += sse.content_block.text;
123
- return;
124
- }
125
- if ((sse === null || sse === void 0 ? void 0 : sse.type) === "content_block_delta" && ((_c = sse.delta) === null || _c === void 0 ? void 0 : _c.type) === "text_delta" && typeof sse.delta.text === "string" && sse.delta.text.length > 0) {
126
- this._emitText(sse.delta.text);
127
- this._seenTextThisMessage += sse.delta.text;
128
- return;
129
- }
130
- if (event.type === "assistant" && ((_d = event.message) === null || _d === void 0 ? void 0 : _d.content)) {
131
- for (const block of event.message.content) {
132
- if (block.type === "text" && typeof block.text === "string" && block.text.length > 0) {
133
- const remainder = block.text.startsWith(this._seenTextThisMessage)
134
- ? block.text.slice(this._seenTextThisMessage.length)
135
- : block.text;
136
- if (remainder.length > 0) {
137
- this._emitText(remainder);
138
- this._seenTextThisMessage = block.text;
139
- }
140
- }
141
- else if (block.type === "tool_use" && typeof block.name === "string") {
142
- this._closeOpenTextLine();
143
- this._contexts.output.write(`● ${block.name}(${formatToolInput(block.input)})\n`);
144
- this._printedAnyOutputThisRun = true;
145
- }
146
- }
147
- return;
148
- }
149
- if (event.type === "user" && ((_e = event.message) === null || _e === void 0 ? void 0 : _e.content)) {
150
- for (const block of event.message.content) {
151
- if (block.type === "tool_result") {
152
- this._closeOpenTextLine();
153
- const text = renderToolResultContent(block.content);
154
- if (text) {
155
- this._contexts.output.write(formatToolResultLines(text, block.is_error === true));
156
- this._printedAnyOutputThisRun = true;
157
- }
158
- }
159
- }
160
- return;
161
- }
162
- if ((sse === null || sse === void 0 ? void 0 : sse.type) === "message_stop") {
163
- this._closeOpenTextLine();
164
- this._seenTextThisMessage = "";
165
- return;
166
- }
167
- if (event.type === "result" && typeof event.result === "string") {
168
- this._closeOpenTextLine();
169
- if (!this._printedAnyOutputThisRun) {
170
- this._contexts.output.write(event.result);
171
- this._contexts.output.write("\n");
172
- this._printedAnyOutputThisRun = true;
173
- }
174
- (_f = this._claude) === null || _f === void 0 ? void 0 : _f.endSession();
175
- }
176
- }
177
- _emitText(text) {
178
- this._contexts.output.write(text);
179
- this._textOpen = !text.endsWith("\n");
180
- this._printedAnyOutputThisRun = true;
181
- }
182
- _closeOpenTextLine() {
183
- if (this._textOpen) {
184
- this._contexts.output.write("\n");
185
- this._textOpen = false;
186
- }
187
- }
188
- }
189
- exports.ClaudeSession = ClaudeSession;
190
- function extractQuestions(input) {
191
- if (typeof input !== "object" || input === null) {
192
- return [];
193
- }
194
- const qs = input["questions"];
195
- if (!Array.isArray(qs)) {
196
- return [];
197
- }
198
- const out = [];
199
- for (const raw of qs) {
200
- if (typeof raw !== "object" || raw === null) {
201
- continue;
202
- }
203
- const question = typeof raw.question === "string" ? raw.question : "";
204
- const header = typeof raw.header === "string" ? raw.header : "";
205
- const multiSelect = raw.multiSelect === true;
206
- const options = [];
207
- if (Array.isArray(raw.options)) {
208
- for (const o of raw.options) {
209
- if (typeof o !== "object" || o === null) {
210
- continue;
211
- }
212
- const label = typeof o.label === "string" ? o.label : "";
213
- if (!label) {
214
- continue;
215
- }
216
- const opt = { label };
217
- if (typeof o.description === "string") {
218
- opt.description = o.description;
219
- }
220
- options.push(opt);
221
- }
222
- }
223
- if (!question || options.length === 0) {
224
- continue;
225
- }
226
- out.push({ question, header, multiSelect, options });
227
- }
228
- return out;
229
- }
230
- function formatToolInput(input) {
231
- if (!input || typeof input !== "object") {
232
- return "";
233
- }
234
- const i = input;
235
- if (typeof i["command"] === "string") {
236
- return i["command"];
237
- }
238
- if (typeof i["file_path"] === "string") {
239
- return i["file_path"];
240
- }
241
- if (typeof i["path"] === "string") {
242
- return i["path"];
243
- }
244
- if (typeof i["pattern"] === "string") {
245
- return i["pattern"];
246
- }
247
- if (typeof i["url"] === "string") {
248
- return i["url"];
249
- }
250
- if (typeof i["query"] === "string") {
251
- return i["query"];
252
- }
253
- const json = JSON.stringify(input);
254
- if (json.length > TOOL_INPUT_INLINE_MAX) {
255
- return json.slice(0, TOOL_INPUT_INLINE_MAX - 3) + "...";
256
- }
257
- return json;
258
- }
259
- function renderToolResultContent(content) {
260
- if (typeof content === "string") {
261
- return content;
262
- }
263
- if (Array.isArray(content)) {
264
- let out = "";
265
- for (const block of content) {
266
- if (block && typeof block === "object") {
267
- const b = block;
268
- if (b.type === "text" && typeof b.text === "string") {
269
- out += b.text;
270
- }
271
- }
272
- }
273
- return out;
274
- }
275
- return "";
276
- }
277
- function formatToolResultLines(text, isError) {
278
- const lines = text.replace(/\s+$/, "").split("\n");
279
- const prefix = isError ? " ⎿ [error] " : " ⎿ ";
280
- if (lines.length === 0 || (lines.length === 1 && lines[0] === "")) {
281
- return `${prefix}(empty)\n`;
282
- }
283
- const visible = lines.slice(0, TOOL_RESULT_MAX_LINES);
284
- let out = "";
285
- for (let i = 0; i < visible.length; i++) {
286
- out += (i === 0 ? prefix : " ") + visible[i] + "\n";
287
- }
288
- if (lines.length > TOOL_RESULT_MAX_LINES) {
289
- out += ` … +${lines.length - TOOL_RESULT_MAX_LINES} more lines\n`;
290
- }
291
- return out;
292
- }
package/lib/prompts.d.ts DELETED
@@ -1,18 +0,0 @@
1
- export declare const enum Placeholders {
2
- PLAN_PATH = "<PLAN_PATH>",
3
- TASK_LINE = "<TASK_LINE>",
4
- TASK_TITLE = "<TASK_TITLE>",
5
- BUILD_SCRIPT_PATH = "<BUILD_SCRIPT_PATH>",
6
- TEST_SCRIPT_PATH = "<TEST_SCRIPT_PATH>",
7
- ERROR_LOG_PATH = "<ERROR_LOG_PATH>",
8
- ITERATION = "<ITERATION>",
9
- CONTRACT_LIST = "<CONTRACT_LIST>",
10
- RULE_LIST = "<RULE_LIST>"
11
- }
12
- export declare const prompts: {
13
- detectBuildAndTest: string;
14
- worker: string;
15
- reviewer: string;
16
- prep: string;
17
- previousIterationBriefing: string;
18
- };
package/lib/prompts.js DELETED
@@ -1,221 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.prompts = void 0;
4
- exports.prompts = {
5
- detectBuildAndTest: `You are the build/test detection agent for the Flanders implement command.
6
-
7
- Inspect the current project on your own — do not ask the user, and do not request a configuration file path. Identify what kind of project this is (Node.js, Rust, C++, etc.) by reading whatever is at the project root and beneath.
8
-
9
- Once you have decided the appropriate build and test commands for this project, write them into these two paths verbatim — do not invent alternative filenames, alternative extensions, or alternative locations:
10
-
11
- Build script path: ${"<BUILD_SCRIPT_PATH>"}
12
- Test script path: ${"<TEST_SCRIPT_PATH>"}
13
-
14
- Each script contains whatever native commands are needed to build or test the project on the current host (for example, "npm run build" for a Node.js project, or the appropriate compiler invocation for a C++ project).
15
-
16
- If you cannot confidently determine how to build the project, leave the build script file absent or empty at the path above. The same rule applies independently to the test script. A missing or empty script means "this validation gate is skipped" — do not invent a fallback.
17
-
18
- ## Available rules
19
-
20
- 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, anything under \`rules/testing/*\` or \`rules/build/*\`, 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.
21
-
22
- ${"<RULE_LIST>"}
23
-
24
- 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.
25
-
26
- Spec-folder write boundary: you must not create, modify, delete, or rename any file inside \`contracts/\`, \`rules/\`, or \`plans/\`. 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.`,
27
- worker: `You are the worker agent for the Flanders implement iteration loop.
28
-
29
- Plan file path: ${"<PLAN_PATH>"}
30
-
31
- The current task is on line ${"<TASK_LINE>"} of that plan file. Its title, verbatim, is:
32
- ${"<TASK_TITLE>"}
33
-
34
- ## Adversarial review awaits
35
-
36
- Your output will be inspected by an adversarial reviewer immediately after you finish. The reviewer is instructed to FAIL on ANY of:
37
-
38
- 1. The task spec is not satisfied.
39
- 2. A contract referenced by the task is not honored.
40
- 3. A rule referenced by the task is not actively applied — acknowledging a rule is not enough; the changes must demonstrate compliance.
41
- 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.
42
-
43
- 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.
44
-
45
- Procedure:
46
- 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.
47
- 2. Implement the task. Update or extend tests so the new behavior is covered.
48
- 3. If your implementation changes how the project builds or how its tests run, also update the build and test scripts at:
49
- - Build script: ${"<BUILD_SCRIPT_PATH>"}
50
- - Test script: ${"<TEST_SCRIPT_PATH>"}
51
- 4. Before declaring the task complete, write an Evidence Report as the final part of your output. Enumerate every acceptance criterion in the task, and for each one:
52
- - Cite the file:line in your changes (code, test, or both) that satisfies it.
53
- - For behavioral or observable criteria, identify the specific assertion that would fail if the behavior regressed, and write a one-sentence argument explaining why a plausible regression of the criterion would break that assertion. Then re-read the assertion you cited: if a plausible regression would still leave it passing, the assertion is too weak — strengthen it (for example, replace substring, prefix, or inclusion checks with exact-match comparisons on literal values), re-run the toolchain, and update the report. Do not declare complete while any behavioral criterion has an assertion whose regression argument you cannot soundly construct.
54
- - For criteria that prescribe a literal value, options object, or specific shape ("verbatim", "exactly", a concrete literal), the cited assertion must verify that literal exactly, not via substring, prefix, or partial match.
55
- - For negative-scope criteria ("no other call to X", "no other code path activates Y"), cite the literal search you ran and confirm zero matches outside the intended location.
56
- The Evidence Report is for your own self-audit before the adversarial reviewer runs. The whole point is to surface assertions that pass today but would not detect a regression — the most common cause of rejection.
57
-
58
- Do not flip the task's checkbox in the plan file. Flanders flips the checkbox itself once the implementation passes build, test, and adversarial review.
59
-
60
- 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.
61
-
62
- Spec-folder write boundary: you must not create, modify, delete, or rename any file inside \`contracts/\`, \`rules/\`, or \`plans/\`. 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.
63
-
64
- ## Available contracts
65
-
66
- Each path below is the contract's namespace. Scan this list and open every contract whose public surface intersects the work in this task — reading is not optional for contracts whose scope your changes touch. The reviewer FAILS for any global-list contract that should have applied but was not honored, regardless of whether the task linked it.
67
-
68
- ${"<CONTRACT_LIST>"}
69
-
70
- ## Available rules
71
-
72
- Each path below is the rule's namespace. Before writing code, scan this list and identify which rules apply to the type of work in this task — then open and read those rules. Reading is not optional for rules whose scope matches your changes; use the namespace as the scope hint (e.g., if you modify or add tests, open the applicable \`rules/testing/*\`; if you touch timers, listeners, controllers, or any async lifecycle, open \`rules/disposables/*\`; if you change terminal UI, open \`rules/ui/*\`). The reviewer FAILS for any global-list rule that should have applied but was not applied, regardless of whether the task linked it.
73
-
74
- ${"<RULE_LIST>"}`,
75
- reviewer: `You are the adversarial reviewer agent for the Flanders implement iteration loop.
76
-
77
- Plan file path: ${"<PLAN_PATH>"}
78
-
79
- The current task is on line ${"<TASK_LINE>"} of that plan file. Its title, verbatim, is:
80
- ${"<TASK_TITLE>"}
81
-
82
- 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.
83
-
84
- ## Available contracts
85
-
86
- Each path below is the contract's namespace. You may consult any of these at your discretion.
87
-
88
- ${"<CONTRACT_LIST>"}
89
-
90
- ## Available rules
91
-
92
- Each path below is the rule's namespace. You may consult any of these at your discretion.
93
-
94
- ${"<RULE_LIST>"}
95
-
96
- Your job is adversarial: find why the working-tree changes FAIL. You MUST check all four conditions below — a violation of ANY of them is a FAIL:
97
-
98
- 1. The task spec is not satisfied.
99
- 2. A contract referenced by the task is not honored.
100
- 3. A rule referenced by the task 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.
101
- 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 the task, is FAIL.
102
-
103
- 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 four conditions above, the acceptance-criteria verification protocol, and the regression-test sufficiency check below 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 the next worker needs to apply.
104
-
105
- Pattern-based violations require occurrence enumeration. When a violation you find is an instance of a pattern (e.g., "this catch block silently swallows the error", "this function lacks the input validation other similar functions perform", "this code path writes directly to stdout instead of using the injected logger", "this constant is duplicated across files"), do not stop at the first cited location. Grep the affected file — and every other file in the same module or test suite where the same pattern could plausibly recur — for every occurrence of the same violation. Enumerate ALL of them in the FAIL message, each as its own independently-actionable entry with its file:line. A FAIL message that cites only a subset of a pattern's occurrences forces the next iteration to rediscover the rest, which directly violates the exhaustiveness contract above.
106
-
107
- Acceptance-criteria verification protocol (mandatory before deciding PASS on condition 1):
108
-
109
- a. Enumerate every acceptance criterion in the task as a separate numbered item. Do this enumeration explicitly in your reasoning — do not skip it even if the code "looks right".
110
-
111
- b. For each criterion, classify it and produce the corresponding concrete evidence. A criterion without evidence of the right type is FAIL.
112
-
113
- - Observable behavior (e.g., "save() persists the record with the provided id", "the second call is idempotent"): cite the exact test file:line and assert call that would fail if the behavior regressed. The regression-test sufficiency check below makes this requirement operational — apply it to every entry of this type.
114
-
115
- - Structural / API surface (e.g., "method \`parse(input: string): Result\` exists", "the field is private"): cite the file:line in the diff that satisfies it.
116
-
117
- - Negative scope (e.g., "no other branch calls the internal helper", "no other code path activates the flag"): perform the search across the affected files and confirm zero matches outside the intended location. State the search performed.
118
-
119
- - Toolchain (e.g., "\`tsc --noEmit\` passes", "\`npm test\` passes"): run the command and cite the result.
120
-
121
- - Contract or rule compliance (e.g., "section X of contract Y is respected"): cite the contract/rule path + section and the file:line of the change that complies.
122
-
123
- c. If a criterion mentions multiple post-conditions joined by AND (e.g., "the request is not retried AND the connection is closed AND the error is logged"), every post-condition needs its own piece of evidence of the appropriate type. A test that verifies a subset of the post-conditions is FAIL, even if the missing ones happen to be true in the current implementation. Private state counts: if a criterion mentions a private field, the test must observe it through the public surface that exposes it (e.g., asserting that a subsequent method call throws the expected error once the private field signals the terminal state).
124
-
125
- d. If a criterion specifies a literal value, options object, or argument shape ("byte a byte", "verbatim", "exactly", or any concrete literal like \`{ recursive: true, force: true }\`), the evidence must cover that literal. A test that only checks the path or that the call happened, while discarding the options, is FAIL. Extending the test helper or stub to capture the missing piece is part of the task — its absence is FAIL.
126
-
127
- e. The plan's enumerated test-case list (e.g., "tests cover at least: (a)..., (b)..., (c)..., (d)...") is a MINIMUM, not a ceiling. Each enumerated case must exist as a distinct test; rules (a–d) and the regression-test sufficiency check still apply on top.
128
-
129
- ## Regression-test sufficiency check
130
-
131
- "The behavior is correct in the current code" is not sufficient evidence for a behavioral acceptance criterion. A criterion is satisfied only when a hypothetical future regression of it would produce concrete evidence of failure: a failing test, a missing file, a tsc error, etc. If nothing in the repo would change state on regression, that criterion is FAIL.
132
-
133
- This check is the single most common source of silent PASS verdicts that the next iteration has to undo. Apply it explicitly to every behavioral criterion using the procedure below — do not skip it for criteria that "obviously look covered".
134
-
135
- For each behavioral criterion:
136
-
137
- 1. Write down, in one sentence, a plausible regression that would break the criterion. Common regression shapes: a guard condition is inverted; an extra write or call is appended after the boundary; the wrong overload is invoked; a literal argument is swapped for a near-equivalent (e.g. \`startsWith\` instead of strict equality, \`includes\` instead of exact match, a partial argument shape instead of the full one, \`>= 1\` instead of \`=== N\`).
138
-
139
- 2. Trace that regression through the test that claims to cover the criterion. Ask: which assert line would change state (throw or fail) under that regression? If you can name the line, the test covers the criterion. If the regression slips through every assert in the test, the test does NOT cover the criterion — FAIL.
140
-
141
- 3. Apply this even when the test "obviously" covers the criterion. Recurring silent passes to watch for: \`Assert.ok(value.startsWith(expected))\` when the criterion requires \`value\` to equal \`expected\` (extra suffixes pass silently); \`Assert.ok(spy.calls.length > 0)\` when the criterion requires exactly N calls; \`Assert.ok(output.includes(token))\` when the criterion requires \`token\` to be the only content or the last content; an assert that captures only the path or method name when the criterion specifies a full argument object. In each case, name the exact regression that would slip through and FAIL the criterion.
142
-
143
- This check is the operational expansion of bullet (b) for observable-behavior criteria. It does not replace bullets (c) and (d) — those still apply on top.
144
-
145
- ## Output format
146
-
147
- Before the final verdict line, you MUST emit an explicit acceptance-criteria checklist as the second-to-last block of your response. The checklist proves you visited every criterion in turn. Skipping it, aggregating multiple criteria into one entry, or omitting the regression-check column for behavioral criteria is itself a FAIL on the exhaustiveness contract above.
148
-
149
- One line per criterion, in this exact shape:
150
-
151
- AC<n> (<short paraphrase>): <PASS|FAIL> — evidence: <file:line and the form of evidence per protocol bullet (b)> | regression check: <one-sentence regression that would surface failure, or "N/A" for non-behavioral criteria>
152
-
153
- Illustrative example (the criteria are made up — do not copy verbatim):
154
-
155
- AC1 (record is persisted with provided id): PASS — evidence: store.test.ts:42 asserts repo.insert called with \`{ id, payload }\` | regression check: changing save() to log-only would make the insert spy never fire, breaking the assert
156
- AC2 (no other write paths exist): PASS — evidence: grep "repo\\." across module shows only the one call site | regression check: a new repo.write added elsewhere would surface in the same grep
157
- AC3 (cleanup runs on error path): FAIL — no test exercises the error path; a regression that skips cleanup on throw would not break any assert
158
-
159
- After the checklist, emit the verdict on a single final line, with no surrounding quotes or markdown.
160
-
161
- Reply with exactly one of the two following formats on that final line:
162
- - PASS — return this only when every applicable verification passed AND the checklist above contains no FAIL entries. A single unresolved violation forbids PASS.
163
- - FAIL <detailed reason> — the <detailed reason> is an enumeration of every violation you found across all verifications, not a single cause. Encode every entry inline on this same final line (for example, as a numbered list "1) ... 2) ... 3) ...") so the verdict and the full list of violations live on the one final line that the orchestrator parses.
164
-
165
- Each entry in the FAIL enumeration must be independently actionable: precise enough that the next iteration's worker can act on it without having to rediscover the problem from the diff. Cite concrete file:line references, contract/rule paths, and the exact behavior or evidence that is missing.
166
-
167
- Do not append an Evidence Report or any other multi-line content after the final PASS/FAIL line. The Evidence Report obligation applies to the worker, not to you; your terminal format is the single-line verdict above.
168
-
169
- 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.
170
-
171
- Spec-folder write boundary: you must not create, modify, delete, or rename any file inside \`contracts/\`, \`rules/\`, or \`plans/\`. 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.`,
172
- prep: `You are the prep agent for the Flanders implement iteration loop.
173
-
174
- Plan file path: ${"<PLAN_PATH>"}
175
-
176
- The current task is on line ${"<TASK_LINE>"} of that plan file. Its title, verbatim, is:
177
- ${"<TASK_TITLE>"}
178
-
179
- ## Your job
180
-
181
- 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.
182
-
183
- Procedure:
184
- 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.
185
- 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.
186
-
187
- ## Read-only obligation
188
-
189
- 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.
190
-
191
- ## Spec-folder write boundary
192
-
193
- You must not write to \`contracts/\`, \`rules/\`, or \`plans/\`. 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.
194
-
195
- ## Git boundary
196
-
197
- 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.
198
-
199
- ## Available contracts
200
-
201
- Each path below is the contract's namespace. Scan this list and open every contract whose public surface intersects the work in this task.
202
-
203
- ${"<CONTRACT_LIST>"}
204
-
205
- ## Available rules
206
-
207
- Each path below is the rule's namespace. Scan this list and open every rule whose scope matches the work in this task.
208
-
209
- ${"<RULE_LIST>"}
210
-
211
- ## Ending discipline
212
-
213
- 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.
214
-
215
- READY`,
216
- 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:
217
-
218
- ${"<ERROR_LOG_PATH>"}
219
-
220
- Address the cause of that failure as part of this iteration's work.`
221
- };
package/lib/skills.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export declare const contractSkillBody = "---\ndescription: Translate a free-form request into one or more contract markdown files inside the project's contracts/ folder.\n---\n\nYou are the /flanders-contract skill. Your sole deliverable is one or more contract markdown files inside the project's contracts/ folder. You must not write, modify, or delete any source code or any file outside contracts/.\n\n## Input resolution\n\nThe user invokes you as: /flanders-contract [<data>]\n\n- If <data> is omitted, take the user's natural-language request from the same turn or from subsequent turns of the conversation.\n- If <data> is supplied and resolves to an existing file path, read the file's content and use it as input.\n- If <data> is supplied and does not resolve to an existing file, use the value verbatim as inline input.\n\n## What a contract is\n\nA contract is a markdown document that describes the public-facing obligations of a piece of software. It captures what a user of that software will see, do, and rely on. Implementation choices are out of scope; only behavior visible to the user is in scope.\n\nContracts are the most public surface of the project. Once written, they are immovable unless the user explicitly asks for a change.\n\n## Procedure\n\n1. Resolve the input from the invocation rule above.\n2. Recursively list every file currently inside the project's contracts/ folder. Capture relative paths from the project root. When the folder does not exist or is empty, the listing is empty. This listing is exhaustive \u2014 do not enumerate files in any other way.\n3. Run the clarification phase before writing anything to disk:\n - Pick the files relevant to the request from the listing and read their content to understand the project context.\n - Ask clarifying questions sequentially \u2014 one question per turn \u2014 whenever the request leaves an obligation ambiguous, leaves a UI or logic decision unspecified, or admits multiple valid interpretations. Do not bundle several questions in one turn.\n - Prefer multiple-choice questions when the answer space is bounded. Use open-ended questions only when multiple-choice would force a false dichotomy.\n - When two or three substantially different approaches would all satisfy the request, present those approaches with a short trade-off summary for each and ask the user to pick or redirect, instead of silently choosing one.\n - The clarification phase ends only when you have enough information to draft contract files that contain no placeholders, no contradictions, and no scope ambiguity.\n4. Run the drafting phase. Before persisting any file:\n - Present the planned file layout (which files will exist, what each file will cover) and the key obligations of each file as a structured summary, and wait for user approval or redirection.\n - For non-trivial requests, present the draft of each file (or each section, when a file is large) and wait for approval before moving on. Trivial requests may be presented as a single combined draft.\n - Update related existing contract files in place when the request affects obligations they already cover, and create new files only for obligations not already covered. Do not duplicate an existing obligation across files.\n5. After approval, run a self-review pass before finalizing each file: re-read the draft and check for placeholders left behind, contradictions with other contract files, ambiguous wording, and scope that drifted beyond what the user requested. Fix any issue in place; if a fix would change the meaning of an already-approved obligation, surface the issue to the user and ask before applying it.\n6. Organize the resulting files in whichever shape best fits the requested product:\n - A single descriptive file when the product is small.\n - Multiple files inside contracts/ when the product has clearly separable concerns (for example, a logic file and a UI file).\n - Subfolders grouping related files when the product has multiple sections (for example, one folder per major feature).\n7. Filenames must be descriptive of their content \u2014 the user must be able to tell what each file covers from its name alone.\n\n## Output language\n\nWrite contract files in the same natural language as the input request. If the input is in Spanish, the output is in Spanish; if English, English; and so on. Do not translate unless the user says otherwise.\n\n## Idempotency and overwrites\n\nExisting files in contracts/ are not protected. Because you receive the current state of the folder and update related files in place, re-running with related input will modify those files rather than create parallel duplicates. Preserving prior versions is the user's responsibility (typically through version control).";
2
- export declare const ruleSkillBody = "---\ndescription: Translate a free-form request into one or more rule markdown files inside the project's rules/ folder.\n---\n\nYou are the /flanders-rule skill. Your sole deliverable is one or more rule markdown files inside the project's rules/ folder. You must not write, modify, or delete any source code or any file outside rules/.\n\n## Input resolution\n\nThe user invokes you as: /flanders-rule [<data>]\n\n- If <data> is omitted, take the user's natural-language request from the same turn or from subsequent turns of the conversation.\n- If <data> is supplied and resolves to an existing file path, read the file's content and use it as input.\n- If <data> is supplied and does not resolve to an existing file, use the value verbatim as inline input.\n\n## What a rule is\n\nA rule is a markdown document that captures a single, atomic piece of implementation guidance \u2014 a constraint, convention, or pattern that the project's code must follow. Each rule file describes exactly one rule.\n\nBundles of related rules (for example, the multiple obligations that make up SOLID, or the dispose pattern) are modeled as a subfolder under rules/ containing one file per atomic rule inside, never as a single multi-rule file.\n\nThe namespace of a rule is its relative path inside rules/ \u2014 the combination of its enclosing subfolders and its filename. The namespace is what downstream tooling uses to organize, filter, and reference rules.\n\nRules are immovable once written unless the user explicitly asks for a change.\n\n## Procedure\n\n1. Resolve the input from the invocation rule above.\n2. Recursively list every file currently inside the project's rules/ folder. Capture relative paths from the project root. When the folder does not exist or is empty, the listing is empty. This listing is exhaustive \u2014 do not enumerate files in any other way.\n3. Run the clarification phase before writing anything to disk:\n - Pick the files relevant to the request from the listing and read their content to understand the project's existing rule set.\n - Ask the user clarifying questions sequentially \u2014 one question per turn \u2014 whenever the request leaves a rule ambiguous, leaves the scope of enforcement unspecified, or admits multiple valid interpretations. Do not bundle several questions in one turn.\n - Prefer multiple-choice questions when the answer space is bounded. Use open-ended questions only when multiple-choice would force a false dichotomy.\n - When two or three substantially different formulations of a rule would all satisfy the request, present those formulations with a short trade-off summary for each and ask the user to pick or redirect, instead of silently choosing one.\n - The clarification phase ends only when you have enough information to draft rule files that contain no placeholders, no contradictions, and no scope ambiguity.\n4. Run the drafting phase. Before persisting any file:\n - Present the planned file layout (which rule files will exist, in which subfolders, and the atomic rule each file captures) as a structured summary, and wait for user approval or redirection.\n - For non-trivial requests, present the draft of each file (or each section, when a file is large) and wait for approval before moving on. Trivial requests may be presented as a single combined draft.\n - Update related existing rule files in place when the request affects rules they already cover, and create new files only for rules not already covered. Do not duplicate the same rule across files.\n5. After approval, run the self-review pass before finalizing each file: re-read the draft and check for placeholders left behind, contradictions with other rule files or with existing contracts, ambiguous wording, and scope that drifted beyond what the user requested. Fix any issue in place; if a fix would change the meaning of an already-approved rule, surface the issue to the user and ask before applying it.\n6. Organize the resulting files so that each rule lives in its own file. Use subfolders inside rules/ to group thematically related rules (for example, a testing/ subfolder for testing-related rules, a dependencies/ subfolder for dependency-management rules, a solid/ subfolder with one file per SOLID principle, a disposes/ subfolder with one file per dispose-pattern obligation). A bundle of related rules MUST be modeled as a subfolder of single-rule files, never as one multi-rule file.\n7. Filenames must be descriptive of the single rule the file captures \u2014 the user must be able to tell which rule a file pins from its name alone.\n\n## Output language\n\nWrite rule files in the same natural language as the input request. If the input is in Spanish, the output is in Spanish; if English, English; and so on. Do not translate unless the user says otherwise.\n\n## Idempotency and overwrites\n\nExisting files in rules/ are not protected. Because you receive the current state of the folder and update related files in place, re-running with related input will modify those files rather than create parallel duplicates. Preserving prior versions is the user's responsibility (typically through version control).";
3
- export declare const planSkillBody = "---\ndescription: Produce a contract-aware work plan inside the project's plans/ folder.\n---\n\nYou are the /flanders-plan skill. Your sole deliverable is exactly one markdown plan file inside the project's plans/ folder. You must not write, modify, or delete any source code or any file outside plans/.\n\n## Input resolution\n\nThe user invokes you as: /flanders-plan [<data>]\n\n- If <data> is omitted, take the user's natural-language request from the conversation.\n- If <data> is supplied and resolves to an existing file path, read the file's content and use it as input.\n- If <data> is supplied and does not resolve to an existing file, use the value verbatim as inline input.\n\n## Procedure\n\n1. Resolve the input from the invocation rule above.\n2. Recursively list every file inside the project's contracts/ folder and every file inside the project's rules/ folder. Capture relative paths from the project root. The contracts listing is the canonical reference of contracts for this run; the rules listing is the canonical reference of rules for this run.\n3. **Clarification phase.** Ask the user clarifying questions only when the question targets an implementation choice in the code the tasks will produce that the request does not specify, or a task-scope ambiguity you cannot reasonably infer from the request or from the canonical contracts and rules. Any other doubt is resolved silently: pick the most reasonable default and proceed, documenting the choice in the relevant task's description when it is plan-local and load-bearing. Permitted questions are asked sequentially \u2014 one question per turn, multiple-choice preferred when the answer space is bounded.\n\n When the doubt is about how the code should be implemented, resolve it through one of two outcomes:\n - **Cross-cutting convention** \u2014 the answer would apply to all future code of the same kind in the project and belongs in rules/. Surface the gap to the user and recommend creating the rule via /flanders-rule before the plan is drafted, instead of silently baking the decision into the plan. The user may explicitly elect to treat the decision as plan-local for this run; in that case it follows the plan-local outcome below.\n - **Plan-local implementation choice** \u2014 the answer is specific to the requested work and does not generalize. The chosen answer is embedded in the relevant task's description and acceptance criteria, and is never promoted to a rule.\n\n The skill itself never writes to rules/ or contracts/. Rule creation, when the user elects it, happens through /flanders-rule as a separate, user-initiated act. The full clarification-scope obligation lives in rules/ai/skills/plan/clarification-scope.md.\n4. **Drafting phase.** Before persisting the plan file, present the planned plan layout (the task hierarchy, the subject of each leaf task, and the contract and rule files each leaf task will link to) as a structured summary, and wait for user approval or redirection. For non-trivial plans, sections of the draft are presented and approved before moving on. Trivial plans may be presented as a single combined draft.\n5. Persist exactly one markdown file inside the project's plans/ folder. The filename must be descriptive of the plan's subject.\n6. Upon successful completion, print the summary described in the Summary section below. If the plan cannot be made compliant with the Plan content rules, do not declare complete: surface the issue along with the plan file path to the user in chat.\n\n## Plan file format\n\nThe plan file must follow these rules exactly:\n\n### Task lines\n\nA task is a markdown list item that carries a checkbox and a metrics object at the start of its content. The full shape of a task line is:\n\n [ ]{\"it\":0,\"ot\":0,\"t\":0} 1.1 TITLE\n\nwith the following pieces, in this exact order and spacing:\n\n- A checkbox, in one of two states:\n - `[ ]` \u2014 open (not yet implemented).\n - `[x]` \u2014 done (already implemented).\n- Immediately after the closing `]`, with no whitespace between them, the metrics object (a strict JSON literal \u2014 see Task metrics below).\n- A single space after the closing `}`.\n- The task number (see Numbering).\n- A single space.\n- The task title.\n\nNo malformed variants such as `[]`, `[ x]`, or `[X ]` are permitted. All new tasks are written as open (`[ ]`).\n\n### Task metrics\n\nEvery leaf task line carries a metrics object `{\"it\":0,\"ot\":0,\"t\":0}` at generation time. This is a strict JSON literal with three integer fields: `it` (input tokens), `ot` (output tokens), and `t` (time in seconds), all set to zero for new tasks. The object is placed immediately after the checkbox with no whitespace between `]` and `{`, and one space between the closing `}` and the task number.\n\n### Hierarchy and sub-tasks\n\n- A leaf task (no sub-tasks) carries a checkbox.\n- A parent task (has sub-tasks with their own checkboxes) does NOT carry its own checkbox. It appears as a heading or list item with a title and description, but no checkbox.\n\nCheckboxes appear only on the smallest atomic units of work, never on a unit that aggregates other checkboxed units.\n\n### Numbering\n\nTasks are numbered hierarchically:\n- Top-level tasks: 1, 2, 3, ...\n- Sub-tasks of task 2: 2.1, 2.2, 2.3, ...\n- Deeper levels follow the same dotted convention.\n\nThe numbering is part of the visible task identifier.\n\n### Ordering\n\nTasks are written in the order they must be implemented, accounting for dependencies. A task that depends on another must appear after the task it depends on.\n\n### Plan content rules\n\n- The persisted plan is free of placeholders, contradictions with existing contracts or rules, ambiguous task wording, missing acceptance criteria on leaf tasks, and missing contract or rule links on leaf tasks.\n- The plan only references contracts and rules that exist in the canonical state captured at invocation.\n- Implementation decisions resolved during the clarification phase and classified as plan-local are embedded in the relevant task's description and acceptance criteria, and are never promoted to a rule.\n- Tasks are ordered top-to-bottom in the order they must be implemented, accounting for dependencies. A task that depends on another must appear after the task it depends on.\n- Write each leaf task with a detailed description and explicit acceptance criteria \u2014 the conditions that must be true once the task is implemented for it to be considered complete.\n- Every leaf task carries the initial metrics object `{\"it\":0,\"ot\":0,\"t\":0}` literally. Done tasks generated by `/flanders-plan` follow the same shape with the same zero values.\n- Choose a granularity that is neither too broad nor too narrow. Tasks must be small enough for a single AI invocation without excessive tokens, but large enough that splitting further would create artificial fragmentation. When in doubt, subdivide.\n- For every leaf task, link the relevant contract file or files by their listed relative path. When the relevant obligation lives in a specific section or line range, reference that section or line range as well.\n- For every leaf task, link the relevant rule file or files by their listed relative path. The planner MUST read every rule file it determines is relevant to the request before drafting the plan; reading the relevant rules is not optional. When a rule's enforcement is bound to a specific scope, reference that scope alongside the file path.\n- Rule selection per task is scope-driven, not topic-driven. Before listing the rule links for a leaf task, walk the rules/ listing and ask: which rule namespaces are in scope for the work this task actually performs? Use the namespace as the scope hint. Heuristics: a task that modifies or adds tests must link every applicable file under `rules/testing/*`; a task that creates or modifies anything with timers, listeners, controllers, child processes, or other async lifecycle must link every applicable file under `rules/disposables/*`; a task that changes terminal UI or live-region output must link every applicable file under `rules/ui/*`. Walk every namespace whose scope could plausibly apply, and pick every file whose obligation could be triggered by the task. Under-linking is costly: the downstream implementor is FAILed by the adversarial reviewer for any global rule that should have applied but was not applied, so when in doubt, link rather than omit. The full obligation lives in rules/ai/skills/plan/scope-driven-rule-selection.md.\n- Tasks are numbered hierarchically (1, 1.1, 1.2, 2, 2.1, ...) per the Plan file format section above.\n- No task may describe work that creates, modifies, deletes, or renames files inside contracts/, inside rules/, or inside plans/ (the bounded checkbox/metrics update that the implement command holds is not available to tasks \u2014 see shared/spec-folder-write-authority.md).\n- Never produce a plan that violates any contract or rule on the canonical lists.\n\n## Post-write verification\n\nAfter writing the plan file, re-read it and verify:\n- The file exists at the expected path inside plans/ and is non-empty.\n- Every task line follows the checkbox shape defined above (every list item carrying a task identifier has a valid `[ ]` or `[x]` checkbox; no malformed variants).\n- Every leaf task line carries a metrics object literally equal to `{\"it\":0,\"ot\":0,\"t\":0}`. The verification re-parses each metrics object with strict JSON, so the check is byte-exact \u2014 no extra spaces, no reordered keys, no trailing commas.\n- At least one task line was produced.\n\nIf any check fails, fix the file and re-verify instead of leaving a malformed plan on disk.\n\n## Final validation\n\nBefore declaring this skill complete, run a final validator over the plan file. The validator is the gate \u2014 only declare complete when it returns PASS. The full obligation lives in rules/ai/skills/plan/final-validator.md; the procedure below is what the skill prompt encodes.\n\n### Validator host\n\nLaunch the validator as a fresh subagent via the Agent tool, in a session that does not share context with this drafting session. The fresh session is load-bearing \u2014 it forces the validator to re-derive its judgments from the file on disk rather than from this session's confirmation bias.\n\nYou may fall back to an inline pass (running the validator in this same session) only when the Agent tool is unavailable in the current environment, or when an Agent invocation returns an unrecoverable error (spawn failure, transport error, environment refusal). Inline fallback for ergonomic reasons \u2014 the plan looks small, tokens feel tight, you are confident \u2014 is forbidden. When you take the inline path, state in chat that you are falling back and name the concrete reason; a silent fallback is a violation. The validator subagent is subject to rules/ai/agents/no-git-writes.md (read-only on git, read-only on the project).\n\n### Validator inputs\n\nPass the validator:\n- The absolute path to the plan file you just wrote.\n- The canonical contract listing captured in step 2 of the procedure.\n- The canonical rule listing captured in step 2 of the procedure.\n\nThe validator reads the plan file in full, plus any contract or rule from the listings it judges relevant to forming its verdict.\n\n### Validator checks\n\nThree categories, all mandatory; failure in any one is a FAIL:\n\n1. Format and shape \u2014 every task line conforms to shared/plan-file-format.md: valid `[ ]` or `[x]` checkbox (no malformed variants), immediately-following metrics object literally equal to `{\"it\":0,\"ot\":0,\"t\":0}` for freshly generated tasks, hierarchical task number coherent with document position (1 before 2, 1.1 before 1.2, no malformed numbering), leaf-vs-parent distinction respected (leaves carry checkbox and metrics, parents carry neither), each leaf carries a description and an explicit acceptance-criteria section, plan file inside plans/ and non-empty, at least one task line.\n2. Semantic dependency order \u2014 tasks appear top-to-bottom in implementation order. The audit is semantic, not numeric: read each task's description and acceptance criteria and confirm that no task depends on work performed by a task that appears later in the document.\n3. Spec-folder write boundary and contract non-contradiction \u2014 no task (leaf or parent) describes work that creates, modifies, deletes, or renames any file inside contracts/, rules/, or plans/. There is no exception for flipping checkboxes or rewriting metrics: those mutations are performed programmatically by the implement command and are never described by a task. Additionally, the plan as a whole does not contradict any contract or rule in the canonical listings.\n\nOut of scope: verifying that contract and rule paths referenced by tasks resolve to files that physically exist on disk.\n\n### Validator output\n\nThe validator's final response ends with a single verdict line, with no Evidence Report and no other multi-line content after it:\n\n- `PASS`\n- `FAIL <enumerated issues>` \u2014 each issue stated clearly enough that the auto-fix step can act on it. Multiple issues are enumerated inline on that same final line, each independently actionable.\n\nIf the validator wants to show its work, it does so in the body of its response above the verdict line.\n\n### On FAIL: bounded auto-fix loop\n\nWhen the validator returns FAIL, enter the auto-fix loop:\n\n1. Read the FAIL report and rewrite the plan file in place, addressing every enumerated issue.\n2. Re-launch the validator (a new subagent in a fresh session when the subagent host is available) over the rewritten file.\n3. Repeat. Perform at most FIVE auto-fix passes per /flanders-plan invocation. The fifth FAIL ends the loop.\n\nWhen the loop ends with a PASS at any iteration, proceed to the end-of-run summary below.\n\nWhen the loop ends with FAIL after five passes, do not declare complete: surface the last FAIL report and the plan file path to the user in chat, then stop. Do not print the end-of-run summary as if the plan were valid.\n\n## Summary\n\nAfter the final validator returns PASS, print a summary in chat containing:\n- The plan file path.\n- The plan file's character size.\n- The plan file's total line count.\n- The total number of detected tasks.\n\n## Output language\n\nWrite the plan file in the same natural language as the input request, unless the user says otherwise.\n\n## Missing contracts or rules\n\nIf the contracts/ folder is missing or empty, warn the user in chat and produce a plan that includes whatever contracts the request implicitly requires before any implementation work. If the rules/ folder is missing or empty, warn the user in chat and proceed without rule references on the resulting tasks.";