flanders 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +245 -0
- package/lib/Flanders.d.ts +2 -1
- package/lib/Flanders.js +7 -0
- package/lib/cli.js +10 -1
- package/lib/commands/Implement.d.ts +3 -1
- package/lib/commands/Implement.js +32 -23
- package/lib/commands/Install.d.ts +0 -1
- package/lib/commands/Install.js +131 -114
- package/lib/commands/Update.d.ts +19 -0
- package/lib/commands/Update.js +90 -0
- package/lib/commands/skillArtifacts.d.ts +18 -0
- package/lib/commands/skillArtifacts.js +93 -0
- package/lib/contexts.d.ts +1 -0
- package/lib/plan/PlanFile.d.ts +8 -5
- package/lib/plan/PlanFile.js +85 -16
- package/lib/prompts/prompts.d.ts +11 -1
- package/lib/prompts/prompts.js +42 -5
- package/lib/prompts/skills.d.ts +1 -1
- package/lib/prompts/skills.js +48 -21
- package/lib/ui/BottomBlock.d.ts +7 -3
- package/lib/ui/BottomBlock.js +47 -11
- package/lib/ui/PromptHelper.d.ts +2 -0
- package/lib/ui/PromptHelper.js +11 -2
- package/lib/ui/formatters.d.ts +8 -3
- package/lib/ui/formatters.js +67 -39
- package/lib/voiceVariants.d.ts +15 -0
- package/lib/voiceVariants.js +141 -0
- package/lib/workspace/FlandersConfig.d.ts +4 -3
- package/lib/workspace/FlandersConfig.js +18 -0
- package/lib/workspace/Workspace.d.ts +2 -0
- package/lib/workspace/Workspace.js +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SKILLS = void 0;
|
|
4
|
+
exports.skillArtifactPath = skillArtifactPath;
|
|
5
|
+
exports.skillArtifactPaths = skillArtifactPaths;
|
|
6
|
+
exports.stripYamlFrontmatter = stripYamlFrontmatter;
|
|
7
|
+
exports.writeSkillArtifacts = writeSkillArtifacts;
|
|
8
|
+
const fsUtils_1 = require("../system/fsUtils");
|
|
9
|
+
const skills_1 = require("../prompts/skills");
|
|
10
|
+
exports.SKILLS = [
|
|
11
|
+
{ name: "flanders-spec", body: skills_1.specSkillBody },
|
|
12
|
+
{ name: "flanders-plan", body: skills_1.planSkillBody },
|
|
13
|
+
{ name: "flanders-work", body: skills_1.workSkillBody }
|
|
14
|
+
];
|
|
15
|
+
const CLAUDE_SKILLS_SUBDIR = ".claude/skills";
|
|
16
|
+
const CODEX_PROMPTS_SUBDIR = ".codex/prompts";
|
|
17
|
+
function skillArtifactPath(scopeRoot, tool, skillName) {
|
|
18
|
+
if (tool === "claude") {
|
|
19
|
+
return (0, fsUtils_1.joinPath)(scopeRoot, CLAUDE_SKILLS_SUBDIR, skillName, "SKILL.md");
|
|
20
|
+
}
|
|
21
|
+
return (0, fsUtils_1.joinPath)(scopeRoot, CODEX_PROMPTS_SUBDIR, `${skillName}.md`);
|
|
22
|
+
}
|
|
23
|
+
function skillArtifactPaths(scopeRoot, tool) {
|
|
24
|
+
return exports.SKILLS.map(skill => skillArtifactPath(scopeRoot, tool, skill.name));
|
|
25
|
+
}
|
|
26
|
+
function stripYamlFrontmatter(body) {
|
|
27
|
+
if (!body.startsWith("---\n") && !body.startsWith("---\r\n")) {
|
|
28
|
+
return body;
|
|
29
|
+
}
|
|
30
|
+
const newlineAfterOpener = body.indexOf("\n") + 1;
|
|
31
|
+
const closerIndex = body.indexOf("\n---\n", newlineAfterOpener);
|
|
32
|
+
if (closerIndex === -1) {
|
|
33
|
+
const closerCrlf = body.indexOf("\n---\r\n", newlineAfterOpener);
|
|
34
|
+
if (closerCrlf === -1) {
|
|
35
|
+
return body;
|
|
36
|
+
}
|
|
37
|
+
return body.slice(closerCrlf + "\n---\r\n".length);
|
|
38
|
+
}
|
|
39
|
+
return body.slice(closerIndex + "\n---\n".length);
|
|
40
|
+
}
|
|
41
|
+
async function writeSkillArtifacts(fs, scopeRoot, tool, isDisposed) {
|
|
42
|
+
for (const skill of exports.SKILLS) {
|
|
43
|
+
if (!skill.body) {
|
|
44
|
+
return { ok: false, diagnostic: `Skill "${skill.name}" has no content.\n` };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const writtenPaths = [];
|
|
48
|
+
if (tool === "claude") {
|
|
49
|
+
const claudeSkillsRoot = (0, fsUtils_1.joinPath)(scopeRoot, CLAUDE_SKILLS_SUBDIR);
|
|
50
|
+
for (const skill of exports.SKILLS) {
|
|
51
|
+
if (isDisposed()) {
|
|
52
|
+
return { ok: false, diagnostic: null };
|
|
53
|
+
}
|
|
54
|
+
const skillFolder = (0, fsUtils_1.joinPath)(claudeSkillsRoot, skill.name);
|
|
55
|
+
try {
|
|
56
|
+
await fs.mkdir(skillFolder, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { ok: false, diagnostic: `Cannot create destination: ${skillFolder}\n` };
|
|
60
|
+
}
|
|
61
|
+
const filePath = skillArtifactPath(scopeRoot, "claude", skill.name);
|
|
62
|
+
try {
|
|
63
|
+
await fs.writeFile(filePath, skill.body);
|
|
64
|
+
writtenPaths.push(filePath);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return { ok: false, diagnostic: `Cannot write file: ${filePath}\n` };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { ok: true, writtenPaths };
|
|
71
|
+
}
|
|
72
|
+
const codexPromptsRoot = (0, fsUtils_1.joinPath)(scopeRoot, CODEX_PROMPTS_SUBDIR);
|
|
73
|
+
try {
|
|
74
|
+
await fs.mkdir(codexPromptsRoot, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { ok: false, diagnostic: `Cannot create destination: ${codexPromptsRoot}\n` };
|
|
78
|
+
}
|
|
79
|
+
for (const skill of exports.SKILLS) {
|
|
80
|
+
if (isDisposed()) {
|
|
81
|
+
return { ok: false, diagnostic: null };
|
|
82
|
+
}
|
|
83
|
+
const filePath = skillArtifactPath(scopeRoot, "codex", skill.name);
|
|
84
|
+
try {
|
|
85
|
+
await fs.writeFile(filePath, stripYamlFrontmatter(skill.body));
|
|
86
|
+
writtenPaths.push(filePath);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return { ok: false, diagnostic: `Cannot write file: ${filePath}\n` };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { ok: true, writtenPaths };
|
|
93
|
+
}
|
package/lib/contexts.d.ts
CHANGED
package/lib/plan/PlanFile.d.ts
CHANGED
|
@@ -12,9 +12,9 @@ export type PlanTask = Readonly<{
|
|
|
12
12
|
done: boolean;
|
|
13
13
|
metrics: TaskMetrics;
|
|
14
14
|
}>;
|
|
15
|
-
export type
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
export type LinkedReference = Readonly<{
|
|
16
|
+
path: string;
|
|
17
|
+
anchor: string | null;
|
|
18
18
|
}>;
|
|
19
19
|
export type PlanParseResult = Readonly<{
|
|
20
20
|
tasks: readonly PlanTask[];
|
|
@@ -28,7 +28,10 @@ export type PlanParseResult = Readonly<{
|
|
|
28
28
|
export declare const TASK_LINE: RegExp;
|
|
29
29
|
export declare function parsePlan(content: string): PlanParseResult;
|
|
30
30
|
export declare function extractFullTaskText(content: string, taskLineNumber: number): string;
|
|
31
|
-
export declare function
|
|
31
|
+
export declare function parseLinkedReferences(content: string, taskLineNumber: number): LinkedReference[];
|
|
32
|
+
export declare function headingAnchor(headingText: string): string;
|
|
33
|
+
export declare function extractHeadingSection(content: string, anchor: string): string | null;
|
|
34
|
+
export declare function buildSpecFileContent(references: readonly LinkedReference[], fileContents: ReadonlyMap<string, string>): string;
|
|
32
35
|
export declare class PlanFile {
|
|
33
36
|
readonly path: string;
|
|
34
37
|
private _content;
|
|
@@ -40,7 +43,7 @@ export declare class PlanFile {
|
|
|
40
43
|
updateMetrics(lineNumber: number, metrics: TaskMetrics): Promise<void>;
|
|
41
44
|
markDone(lineNumber: number, metrics: TaskMetrics): Promise<void>;
|
|
42
45
|
markOpen(lineNumber: number, metrics: TaskMetrics): Promise<void>;
|
|
43
|
-
|
|
46
|
+
linkedReferences(task: PlanTask): LinkedReference[];
|
|
44
47
|
fullTaskText(task: PlanTask): string;
|
|
45
48
|
planTotals(): TaskMetrics;
|
|
46
49
|
private _rewriteTaskLine;
|
package/lib/plan/PlanFile.js
CHANGED
|
@@ -3,7 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PlanFile = exports.TASK_LINE = void 0;
|
|
4
4
|
exports.parsePlan = parsePlan;
|
|
5
5
|
exports.extractFullTaskText = extractFullTaskText;
|
|
6
|
-
exports.
|
|
6
|
+
exports.parseLinkedReferences = parseLinkedReferences;
|
|
7
|
+
exports.headingAnchor = headingAnchor;
|
|
8
|
+
exports.extractHeadingSection = extractHeadingSection;
|
|
9
|
+
exports.buildSpecFileContent = buildSpecFileContent;
|
|
7
10
|
exports.TASK_LINE = /^(\s*[-*+]\s+)\[([ xX])\](\{[^}]*\})(\s.*)?$/;
|
|
8
11
|
const MALFORMED_TASK_LINE = /^\s*[-*+]\s+\[[^\]]*\]\{/;
|
|
9
12
|
const HEADING_NUMBER = /^#{1,6}\s+(\d+(?:\.\d+)*)\b/;
|
|
@@ -92,6 +95,8 @@ function parsePlan(content) {
|
|
|
92
95
|
const MARKDOWN_LINK = /\[[^\]]*\]\(([^)]+)\)/g;
|
|
93
96
|
const CONTRACTS_SEGMENT = ".spec/contracts/";
|
|
94
97
|
const RULES_SEGMENT = ".spec/rules/";
|
|
98
|
+
const HEADING_LINE = /^(#{1,6})\s+(.+)$/;
|
|
99
|
+
const SPEC_FILE_LEAD = "The following is the consolidated content of every contract and rule this task references.";
|
|
95
100
|
function detectNewline(content) {
|
|
96
101
|
const newlineMatch = /\r\n|\n/.exec(content);
|
|
97
102
|
return newlineMatch ? newlineMatch[0] : "\n";
|
|
@@ -113,27 +118,91 @@ function extractFullTaskText(content, taskLineNumber) {
|
|
|
113
118
|
const body = taskBodyLines(content, taskLineNumber);
|
|
114
119
|
return [taskLine, ...body].join(detectNewline(content));
|
|
115
120
|
}
|
|
116
|
-
function
|
|
121
|
+
function parseLinkedReferences(content, taskLineNumber) {
|
|
117
122
|
const body = taskBodyLines(content, taskLineNumber);
|
|
118
|
-
const
|
|
119
|
-
const rules = [];
|
|
123
|
+
const references = [];
|
|
120
124
|
for (const line of body) {
|
|
121
125
|
let m;
|
|
122
126
|
while ((m = MARKDOWN_LINK.exec(line)) !== null) {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
const target = m[1];
|
|
128
|
+
const hashIndex = target.indexOf("#");
|
|
129
|
+
const anchor = hashIndex === -1 ? null : target.slice(hashIndex + 1);
|
|
130
|
+
const rawPath = hashIndex === -1 ? target : target.slice(0, hashIndex);
|
|
131
|
+
const path = rawPath.replace(/^\//, "");
|
|
132
|
+
if (path.includes(CONTRACTS_SEGMENT) || path.includes(RULES_SEGMENT)) {
|
|
133
|
+
references.push({ path, anchor });
|
|
128
134
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return references;
|
|
138
|
+
}
|
|
139
|
+
function headingAnchor(headingText) {
|
|
140
|
+
return headingText
|
|
141
|
+
.trim()
|
|
142
|
+
.toLowerCase()
|
|
143
|
+
.replace(/[^a-z0-9 _-]/g, "")
|
|
144
|
+
.replace(/ /g, "-");
|
|
145
|
+
}
|
|
146
|
+
function extractHeadingSection(content, anchor) {
|
|
147
|
+
const lines = content.split(/\r?\n/);
|
|
148
|
+
let startIdx = -1;
|
|
149
|
+
let level = 0;
|
|
150
|
+
for (let i = 0; i < lines.length; i++) {
|
|
151
|
+
const headingMatch = HEADING_LINE.exec(lines[i]);
|
|
152
|
+
if (headingMatch && headingAnchor(headingMatch[2]) === anchor) {
|
|
153
|
+
startIdx = i;
|
|
154
|
+
level = headingMatch[1].length;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (startIdx === -1) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
let endIdx = lines.length;
|
|
162
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
163
|
+
const headingMatch = HEADING_LINE.exec(lines[i]);
|
|
164
|
+
if (headingMatch && headingMatch[1].length <= level) {
|
|
165
|
+
endIdx = i;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return lines.slice(startIdx, endIdx).join(detectNewline(content));
|
|
170
|
+
}
|
|
171
|
+
function buildSpecFileContent(references, fileContents) {
|
|
172
|
+
const order = [];
|
|
173
|
+
const byPath = new Map();
|
|
174
|
+
for (const { path, anchor } of references) {
|
|
175
|
+
let unit = byPath.get(path);
|
|
176
|
+
if (!unit) {
|
|
177
|
+
unit = { whole: false, sections: [] };
|
|
178
|
+
byPath.set(path, unit);
|
|
179
|
+
order.push(path);
|
|
180
|
+
}
|
|
181
|
+
if (anchor === null) {
|
|
182
|
+
unit.whole = true;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const section = extractHeadingSection(fileContents.get(path), anchor);
|
|
186
|
+
if (section === null) {
|
|
187
|
+
unit.whole = true;
|
|
188
|
+
}
|
|
189
|
+
else if (!unit.sections.some(existing => existing.anchor === anchor)) {
|
|
190
|
+
unit.sections.push({ anchor, content: section });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const units = [];
|
|
194
|
+
for (const path of order) {
|
|
195
|
+
const unit = byPath.get(path);
|
|
196
|
+
if (unit.whole) {
|
|
197
|
+
units.push(`## ${path}\n\n${fileContents.get(path)}`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
for (const { anchor, content } of unit.sections) {
|
|
201
|
+
units.push(`## ${path}#${anchor}\n\n${content}`);
|
|
133
202
|
}
|
|
134
203
|
}
|
|
135
204
|
}
|
|
136
|
-
return
|
|
205
|
+
return [SPEC_FILE_LEAD, ...units].join("\n\n");
|
|
137
206
|
}
|
|
138
207
|
class PlanFile {
|
|
139
208
|
constructor(path, _content, _fs) {
|
|
@@ -165,8 +234,8 @@ class PlanFile {
|
|
|
165
234
|
async markOpen(lineNumber, metrics) {
|
|
166
235
|
return this._rewriteTaskLine(lineNumber, metrics, "open");
|
|
167
236
|
}
|
|
168
|
-
|
|
169
|
-
return
|
|
237
|
+
linkedReferences(task) {
|
|
238
|
+
return parseLinkedReferences(this._content, task.line);
|
|
170
239
|
}
|
|
171
240
|
fullTaskText(task) {
|
|
172
241
|
return extractFullTaskText(this._content, task.line);
|
package/lib/prompts/prompts.d.ts
CHANGED
|
@@ -7,8 +7,10 @@ export declare const enum Placeholders {
|
|
|
7
7
|
ITERATION = "<ITERATION>",
|
|
8
8
|
CONTRACT_LIST = "<CONTRACT_LIST>",
|
|
9
9
|
RULE_LIST = "<RULE_LIST>",
|
|
10
|
-
BEHAVIOR_RULE_LIST = "<BEHAVIOR_RULE_LIST>"
|
|
10
|
+
BEHAVIOR_RULE_LIST = "<BEHAVIOR_RULE_LIST>",
|
|
11
|
+
SPEC_PATH = "<SPEC_PATH>"
|
|
11
12
|
}
|
|
13
|
+
export declare function linkedReferenceDirective(specPath: string): string;
|
|
12
14
|
export interface ReviewerMethodologySurface {
|
|
13
15
|
changeSetIntro: string;
|
|
14
16
|
specRef: string;
|
|
@@ -38,6 +40,14 @@ export declare function buildReviewerMethodology(s: ReviewerMethodologySurface):
|
|
|
38
40
|
audit: string;
|
|
39
41
|
};
|
|
40
42
|
export declare const reviewerMethodologyCore: string;
|
|
43
|
+
export interface FlandersVoiceParts {
|
|
44
|
+
subject: string;
|
|
45
|
+
languageFraming: string;
|
|
46
|
+
finalExclusion: string;
|
|
47
|
+
trailer: string;
|
|
48
|
+
}
|
|
49
|
+
export declare function buildFlandersVoiceSection(parts: FlandersVoiceParts): string;
|
|
50
|
+
export declare function flandersToneInstruction(reviewer: boolean): string;
|
|
41
51
|
export declare const prompts: {
|
|
42
52
|
detectBuildAndTest: string;
|
|
43
53
|
worker: string;
|
package/lib/prompts/prompts.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.prompts = exports.reviewerMethodologyCore = void 0;
|
|
4
|
+
exports.linkedReferenceDirective = linkedReferenceDirective;
|
|
4
5
|
exports.buildReviewerMethodology = buildReviewerMethodology;
|
|
6
|
+
exports.buildFlandersVoiceSection = buildFlandersVoiceSection;
|
|
7
|
+
exports.flandersToneInstruction = flandersToneInstruction;
|
|
8
|
+
function linkedReferenceDirective(specPath) {
|
|
9
|
+
return `## Linked reference content
|
|
10
|
+
|
|
11
|
+
The full content of every contract and rule this task references has been consolidated into the file at ${specPath}. Read that file in full, from beginning to end, in as few passes as possible — ideally a single read — before you start.`;
|
|
12
|
+
}
|
|
5
13
|
const claimClassificationCore = `Classify every claim by ONE question: what kind of signal would soundly observe a plausible regression of the claim? Place the claim in exactly one of these three branches, and name the concrete observer the branch requires — an automated failure, an asserting test, or reviewer inspection.
|
|
6
14
|
|
|
7
15
|
- **Toolchain-guarded** — a plausible regression triggers an automated failure signal WITHOUT any new test being added: a build error, a type error, a linker error, a linter or other static-analysis error from a checker the project actually runs, an existing test failing, or a runtime crash on a code path the test suite already exercises. The evidence is a \`file:line\` citation in the change plus the name of the automated failure a regression would trigger. A linter signal qualifies only when the project actually runs that linter as part of its build or test flow.
|
|
@@ -16,6 +24,7 @@ const claimClassification = `${claimClassificationCore}
|
|
|
16
24
|
|
|
17
25
|
${workerToolchainRerunStep}`;
|
|
18
26
|
const foregroundBoundary = `Foreground execution boundary: you run every command you execute in the foreground and keep your turn active until that command finishes and its result is in hand. You must not start any command in the background and must not end your turn while a command you spawned is still running. This binds every command without exception — build scripts, test scripts, linters, and any other shell command; give a long-running command a tool timeout large enough to finish in the foreground rather than detaching it. Forbidden mechanisms include a tool call made with a background flag (for example \`run_in_background: true\`), shell-level detachment (a trailing \`&\`, \`nohup\`, \`setsid\`, \`disown\`, \`start\`, \`Start-Process\`, \`Start-Job\`), converting a timed-out foreground command into a background task, and ending your turn with a message that a spawned command is still running. The full obligation lives in rules/ai/agents/no-background-commands.md.`;
|
|
27
|
+
const specFolderWriteBoundary = `Spec-folder write boundary: you must not create, modify, delete, or rename any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, any \`.spec/flanders\` 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.`;
|
|
19
28
|
const claimClassificationCitationFree = claimClassificationCore.replace(" per `rules/testing/assert-via-public-surface.md`", "");
|
|
20
29
|
function buildReviewerMethodology(s) {
|
|
21
30
|
const changeSet = `You must derive ${s.changeSetIntro}:
|
|
@@ -41,11 +50,13 @@ Exhaustiveness: do not stop at the first violation. Run every verification you a
|
|
|
41
50
|
|
|
42
51
|
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.
|
|
43
52
|
|
|
53
|
+
Referenced-obligation enumeration. Before deciding conditions 2, 3, 4, and 5 are met, enumerate the discrete obligations of each contract and rule in scope — every contract and rule the work references, plus every corpus contract, rule, or behavior rule you judge should have applied — as separate items, and confirm each obligation is actively applied in the changes. A contract or rule that pins more than one discrete obligation — for example a required-exclusion list, a set of required surfaces, or several conditions stated in one section — is never satisfied by confirming the contract or rule "in general": each enumerated obligation is its own item with its own confirmation, and an obligation the changes leave unapplied, or that you never enumerated, is a violation. A reference whose obligations enumerate N discrete facts expands into N items.
|
|
54
|
+
|
|
44
55
|
${s.critProtocolHeading} (mandatory before deciding PASS on condition 1):
|
|
45
56
|
|
|
46
57
|
a. Enumerate every ${s.critRef} in ${s.specRef} as a separate numbered item. Do this enumeration explicitly in your reasoning — do not skip it even if the code "looks right".
|
|
47
58
|
|
|
48
|
-
b. For each enumerated ${s.critRefShort}, classify it by the regression-signal question and confirm ${s.ownerChangesEvidence} carry evidence of the type that classification requires. A ${s.critRefShort} lacking that evidence is FAIL.
|
|
59
|
+
b. For each enumerated ${s.critRefShort}, classify it by the regression-signal question and confirm ${s.ownerChangesEvidence} carry evidence of the type that classification requires. A ${s.critRefShort} lacking that evidence is FAIL. A spec element classified test-guarded is confirmed satisfied only when the named test's assertions cover every case and every fact the element requires: the existence of a test for the element is not enough, and a test that asserts some of the element's cases while leaving a required case unguarded does not satisfy it — the uncovered case is a violation, never waved through as holding "by inspection".
|
|
49
60
|
|
|
50
61
|
${s.taxonomy}
|
|
51
62
|
|
|
@@ -133,6 +144,26 @@ const citationFreeReviewerMethodology = buildReviewerMethodology(citationFreeRev
|
|
|
133
144
|
exports.reviewerMethodologyCore = `${citationFreeReviewerMethodology.changeSet}
|
|
134
145
|
|
|
135
146
|
${citationFreeReviewerMethodology.audit}`;
|
|
147
|
+
const voiceLocalization = "using that language and region's genuine established localization of the character rather than a word-for-word translation of his original-language manner. Because an established localization is regional, detect and match the regional idiom the user's own writing exhibits, and fall back to the most widely recognized localization of that language when the user's region cannot be determined. When you are addressing the user in a language other than English, the flavor never appears as the character's English-language manner; and when the language has no established localization of the character, or you cannot otherwise render it in that localization, the message is delivered plainly, with no flavor.";
|
|
148
|
+
const voiceExclusionLead = "The flavor lives only in flowing prose: it never appears in code, file paths, directory names, command lines, flag or option tokens, the factual content of a diagnostic or error message (the problem described, the path, the line number, and every other datum needed to act on it), any token another part of the tool reads programmatically, git commit messages";
|
|
149
|
+
const voiceTail = " — all of which stay exact and as actionable as before.";
|
|
150
|
+
function buildFlandersVoiceSection(parts) {
|
|
151
|
+
return `## Voice
|
|
152
|
+
|
|
153
|
+
Season ${parts.subject} — with a soft Ned-Flanders touch in every message: a gentle note of the character's warm, folksy, good-natured manner, so the voice is a steady, recognizable presence across the whole run rather than a rare flourish, the one exception being a message whose language has no established localization of the character, or that you cannot render in that localization, which is delivered plainly with no touch. Keep it light — typically a single touch per message, never on every line and never exaggerated — and never let the flavor change the substance, structure, or accuracy of anything you say. Render the flavor ${parts.languageFraming}, ${voiceLocalization} ${voiceExclusionLead}${parts.finalExclusion}${voiceTail}${parts.trailer}`;
|
|
154
|
+
}
|
|
155
|
+
function flandersToneInstruction(reviewer) {
|
|
156
|
+
return buildFlandersVoiceSection({
|
|
157
|
+
subject: "your user-facing narration — the prose you stream as you work",
|
|
158
|
+
languageFraming: "in the same language you are already narrating in",
|
|
159
|
+
finalExclusion: reviewer
|
|
160
|
+
? ", or the violation entries you record in your error-log file"
|
|
161
|
+
: "",
|
|
162
|
+
trailer: reviewer
|
|
163
|
+
? " The flavor never changes how you record your verdict: you still append every violation to your error-log file, an empty file still means a clean pass, and your verdict is never carried by your streamed output or your exit code."
|
|
164
|
+
: ""
|
|
165
|
+
});
|
|
166
|
+
}
|
|
136
167
|
exports.prompts = {
|
|
137
168
|
detectBuildAndTest: `You are the build/test detection agent for the Flanders implement command.
|
|
138
169
|
|
|
@@ -155,7 +186,7 @@ ${"<RULE_LIST>"}
|
|
|
155
186
|
|
|
156
187
|
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
188
|
|
|
158
|
-
|
|
189
|
+
${specFolderWriteBoundary}
|
|
159
190
|
|
|
160
191
|
${foregroundBoundary}`,
|
|
161
192
|
worker: `You are the worker agent for the Flanders implement iteration loop.
|
|
@@ -210,10 +241,12 @@ Do not flip the task's checkbox in the plan file. Flanders flips the checkbox it
|
|
|
210
241
|
|
|
211
242
|
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.
|
|
212
243
|
|
|
213
|
-
|
|
244
|
+
${specFolderWriteBoundary}
|
|
214
245
|
|
|
215
246
|
${foregroundBoundary}
|
|
216
247
|
|
|
248
|
+
${flandersToneInstruction(false)}
|
|
249
|
+
|
|
217
250
|
## Available contracts
|
|
218
251
|
|
|
219
252
|
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.
|
|
@@ -239,7 +272,9 @@ The plan file is at ${"<PLAN_PATH>"}; you may open it for broader context, but y
|
|
|
239
272
|
|
|
240
273
|
${"<TASK_TEXT>"}
|
|
241
274
|
|
|
242
|
-
The task's full description
|
|
275
|
+
The task's full description and its acceptance criteria are provided to you directly, and the full content of every contract and rule it references has been consolidated into a spec.md that you must read in full — see "Linked reference content" below. Inspect the working-tree changes that the worker just produced.
|
|
276
|
+
|
|
277
|
+
${linkedReferenceDirective("<SPEC_PATH>")}
|
|
243
278
|
|
|
244
279
|
## Determining the worker's change set
|
|
245
280
|
|
|
@@ -265,9 +300,11 @@ ${"<BEHAVIOR_RULE_LIST>"}
|
|
|
265
300
|
|
|
266
301
|
${implementReviewerMethodology.audit}
|
|
267
302
|
|
|
303
|
+
${flandersToneInstruction(true)}
|
|
304
|
+
|
|
268
305
|
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.
|
|
269
306
|
|
|
270
|
-
|
|
307
|
+
${specFolderWriteBoundary}
|
|
271
308
|
|
|
272
309
|
${foregroundBoundary}`,
|
|
273
310
|
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:
|
package/lib/prompts/skills.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const planSkillBody: string;
|
|
2
|
-
export declare const specSkillBody = "---\ndescription: Translate a free-form request into one or more spec markdown files inside the project's .spec/contracts and .spec/rules folders.\n---\n\nYou are the /flanders-spec skill. Your sole deliverable is one or more markdown files inside the project's `.spec/contracts` and `.spec/rules` folders. You must not write, modify, or delete any source code or any file outside the project's `.spec/contracts` and `.spec/rules` folders.\n\n## Input resolution\n\nThe user invokes you as: /flanders-spec [<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 behavior of the directory its `.spec` folder scopes \u2014 what code outside that directory relies on \u2014 stated abstractly, never naming internal symbols, internal data shapes, or paths inside a source directory; at the project-root `.spec` folder the boundary is the whole project, so its contracts capture what the end user sees, does, and relies on.\n\nContracts are the public surface of the scope they belong to. Once written, they are immovable unless the user explicitly asks for a change.\n\n## What a rule is\n\nA rule is a markdown document that captures a single, atomic piece of implementation guidance internal to the directory its `.spec` folder scopes \u2014 a constraint, convention, or pattern that the directory's code must follow. The rule is the atomic unit, not the file: each rule is a single atomic obligation, and a rule file holds one rule on its own, or several related rules as discrete atomic sections.\n\nBundles of related rules (for example, the multiple obligations that make up SOLID, or the dispose pattern) are modeled either as a subfolder under the scope's `.spec/rules` folder containing one file per atomic rule, or as a single file that groups those related rules as discrete atomic sections. The atomic unit is the rule, not the file; both shapes keep every rule atomic.\n\nThe namespace of a rule is its path relative to the project root. 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## Contract vs rule: how the skill classifies and places\n\nFor every obligation in the request, the skill decides whether it is a contract or a rule and which `.spec` folder it belongs to: public behavior across a scope's boundary is a contract, internal implementation guidance is a rule, and the spec lands in the `.spec` folder of the lowest directory that encloses all the code its obligation governs \u2014 an obligation governing one directory goes in that directory's `.spec` folder, an obligation spanning sibling directories goes in their nearest common ancestor's `.spec` folder, and an obligation about project-boundary behavior goes in the project-root `.spec` folder. A spec is a contract because code outside its scope depends on it, not because the end user observes it directly; only at the project root do those coincide. A single request may carry both kinds and may span several scopes; the skill writes each spec to its proper `.spec` folder in the same invocation. The classification and placement are the skill's own decisions, not questions put to the user \u2014 the user reviews and approves them in the drafting phase before anything is persisted.\n\n## Procedure\n\n1. Resolve the input from the invocation rule above.\n2. Discover every directory named `.spec` across the whole project tree at every depth, excluding every path the project's git ignore rules exclude (for example by enumerating with `git ls-files --cached --others --exclude-standard` \u2014 which lists tracked files plus untracked-but-not-ignored files \u2014 and dropping any candidate that sits under a git-ignored path, for example via `git check-ignore`); the files under each `.spec/contracts` subfolder form the canonical contracts listing and the files under each `.spec/rules` subfolder form the canonical rules listing; the files under each `.spec/flanders` subfolder form the behavior-rule listing, treating every file inside a `.spec/flanders` folder at any depth as a behavior rule; each file is identified by its namespace \u2014 its path relative to the project root, which for nested `.spec` folders includes the directories above the `.spec` folder, so files sharing a leaf filename in different `.spec` folders stay distinct. A missing or empty discovery \u2014 no `.spec` folder, or none containing any file \u2014 yields an empty canonical reference set. This is the canonical reference set for the run.\n3. Before drafting anything, read every file in the canonical reference set that is relevant to the request. Reading the relevant existing files is mandatory \u2014 a draft begun without having read them is invalid, regardless of your confidence. When in doubt, read rather than omit: a deliverable that contradicts or duplicates an unread file is invalid.\n\n **Behavior rules.** Before persisting any file, read every behavior rule whose `.spec/flanders` scope encloses each file you are about to write \u2014 the `.spec` folder you write the file into and every parent `.spec` folder up to the project root \u2014 and honor all of them. Behavior rules govern how you name, place, and organize the files you author; an in-scope behavior rule is binding on that work, not advisory, and applies whether or not the request mentions it.\n\n **Rename sweep.** When the run renames, relocates, or removes a term that can recur across the corpus beyond the files it is editing \u2014 a folder name, a path segment, a flag, an identifier, a fixed string, or a namespace convention \u2014 establish the full set of files to touch by searching the whole corpus (every contract and every rule) for the old term and inspecting every occurrence the search returns. The search is exhaustive over the corpus; it is not narrowed to the files you already planned to edit. Triage each occurrence individually into exactly one of two dispositions: an occurrence the rename must update, which the run edits; or an occurrence that is an intentional reference the rename leaves alone (for example a cross-reference to an unrelated file, or a deliberately unchanged example). An occurrence is never left unexamined on the grounds that its file looked irrelevant. Coverage is driven by the token, not by a judgment of which files are relevant: the set of files the run edits is the union of the occurrences the sweep shows must be updated, and a file the sweep surfaces that you had not planned to touch is added to the run.\n4. **Clarification phase.** Whenever the request leaves an obligation ambiguous, leaves a UI or logic decision unspecified, leaves a rule or its scope of enforcement unspecified, or admits multiple valid interpretations, ask the user clarifying questions sequentially \u2014 one question per turn. Prefer multiple-choice questions when the answer space is bounded. Use open-ended questions only when multiple-choice would force a false dichotomy. 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. The clarification phase ends only when you have enough information to draft files that contain no placeholders, no contradictions, and no scope ambiguity.\n5. **Drafting phase.** Before persisting any file:\n - Present the planned file layout \u2014 which files will exist, which `.spec` folder each falls in, which are contracts and which are rules (the classification and placement made visible), and the key obligations of each file \u2014 as a structured summary, and wait for user approval or redirection.\n - Once the layout is approved, persist every resulting file in a single batch without any further per-file or per-section confirmation step.\n - Update related existing 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 obligation across files, whether within a folder or across the two folders.\n - Do not write historical, transitional, or migration content into the contracts and rules you produce. A spec file states only the present spec \u2014 what the software does now and what the code must do now. Content recording what the spec used to be, what it replaces, what changed in this run, or any transitional framing (for example, \"replaces the former X\", \"previously Y\", a changelog of what this run changed) belongs in the commit message or pull-request description, not in a permanent spec file.\n - State each obligation as the behavior the code performs \u2014 what the software does and what the code must do. The set of things the code does not do is unbounded, so do not enumerate non-actions: satisfy a request to remove or stop a behavior by describing the resulting positive behavior, letting the removed behavior vanish by omission. Write an explicit prohibition \u2014 \"does not\u2026\", \"never\u2026\", \"must not\u2026\" \u2014 only when it is load-bearing, namely when BOTH conditions hold: (1) its absence is not already entailed by a positive obligation (a positive obligation stated exclusively, such as \"the only X is Y\", already excludes every alternative, so a prohibition restating that exclusion adds nothing); and (2) it guards a behavior a competent implementer reading only the positive spec would plausibly introduce \u2014 an attractive default they would reach for, or a behavior that falls inside a responsibility the component otherwise has. A prohibition that fails either condition is redundant and is not written.\n6. After approval, run a self-review pass before finalizing each file: re-read the draft and check for placeholders left behind, contradictions with the canonical reference set, ambiguous wording, and scope that drifted beyond what the user requested. Fix any issue in place; if a fix would change the meaning of content the user approved in the layout summary, surface the issue to the user and ask before applying it.\n7. Organize the resulting files in whichever shape best fits the content:\n - Within a `.spec/contracts` folder: a single descriptive file when the scope is small; multiple files when the scope has clearly separable concerns (for example, a logic file and a UI file); subfolders grouping related files when the scope has multiple sections (for example, one folder per major feature).\n - Within a `.spec/rules` folder: the rule is the atomic unit, not the file. A standalone file holds one isolated rule; a single file groups a cluster of related rules as discrete atomic sections; and a subfolder holds a file per rule (or per sub-cluster) when the scope spans several distinct clusters (for example, a testing/ subfolder for testing rules, a dependencies/ subfolder for dependency-management rules, a solid/ subfolder for the SOLID principles). A subfolder of single-rule files and a single file grouping related rules as sections are both valid; each rule stays atomic in either shape.\n8. Filenames must be descriptive of their content \u2014 the user must be able to tell what each contract file covers, and which rule or cluster of related rules each rule file pins, from the name alone.\n9. Before declaring complete, run the final validator over the persisted file(s). The validator is the gate \u2014 only declare complete when it returns PASS. The procedure is in the Final validation section below.\n10. After declaring the spec complete, recommend the next step and, at the user's choice, launch it, per the Recommending and launching the next step section below.\n\n## Final validation\n\nBefore declaring this skill complete, run a final validator over the persisted or updated file(s). The validator is the gate \u2014 only declare complete when it returns PASS.\n\nWhat a passing gate certifies: a pass certifies that the file(s) you wrote or updated in this run satisfy the validator's checks and do not contradict the corpus the validator inspected. It does not certify that the entire corpus is mutually consistent independent of this run's files \u2014 whole-corpus consistency is not re-verified on every run, and a passing gate is not a proof of it. Report a pass as a statement about this run's own output, never as a statement that the whole spec is globally sound.\n\n### Validator host\n\nLaunch the validator as a fresh subagent via the AI tool's subagent mechanism, 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(s) on disk rather than from this session's confirmation bias.\n\nThe subagent mechanism is tool-specific. In Claude Code, the host spawns the validator through the Agent tool. In Codex CLI, the host spawns it through whatever Codex documents as its subagent surface at the time of the run.\n\nYou may fall back to an inline pass (running the validator in this same session) only when the subagent mechanism is unavailable in the current environment, or when a subagent invocation returns an unrecoverable error (spawn failure, transport error, environment refusal). Inline fallback for ergonomic reasons \u2014 the artifact 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 is read-only on the project and does not run git mutations.\n\n### Validator inputs\n\nPass the validator:\n- The absolute path(s) to the file(s) you just wrote or updated, partitioned by folder, plus an explicit enumeration of which subset of the canonical listings is under audit in this run.\n- The canonical contracts listing captured in step 2 of the procedure.\n- The canonical rules listing captured in step 2 of the procedure.\n- When this run renamed, relocated, or removed a term that can recur across the corpus (per the Rename sweep obligation in the procedure above), the explicit list of those old term(s). The list is empty when the run changed no such term.\n- The verbatim text of the check categories below. The host MUST inline these categories in the validator's prompt \u2014 it does not just point the validator at a file by path, and it does not rely on the validator discovering them by transitive reading.\n\nThe validator reads the file(s) 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. Each category is audited independently and violations are enumerated exhaustively. The category set is selected by the folder each file landed in: category A applies to each file that landed in a `.spec/contracts` folder; category B applies to each file that landed in a `.spec/rules` folder; category C applies to every file written or updated in the run.\n\n**A. Contract artifacts (each file written or updated under a `.spec/contracts` folder)**\n\nA1. Format and shape. Every contract file written or updated lives inside a `.spec/contracts` folder, is non-empty, is markdown, has a filename descriptive of its content, and is organized as described in step 7 of the procedure.\n\nA2. Content rules. Verify the artifact satisfies EACH of the following independently:\n- Free of placeholders. No `<TBD>` or analogous task markers, no template-style blanks, no parenthetical \"(to be decided)\" deferrals.\n- Free of ambiguous wording. Open-ended phrasing \u2014 hedge phrases such as `may or may not`, `left to the implementer`, `pick one of`, `or equivalent`, `at the discretion of the user`, `or \u2014 alternatively \u2014`, `or X if Y`, or any formulation that leaves an obligation undefined \u2014 is FAIL. A contract obligation reads as a single concrete commitment, never as a choice the reader is invited to make.\n- Describes only public behavior across its scope's boundary \u2014 what code outside the directory its `.spec` folder scopes can rely on, stated abstractly, where for the project-root `.spec/contracts` that boundary is the end user. References to implementation details \u2014 names of specific classes, functions, libraries, modules, or frameworks; paths under src/, lib/, or any source folder; internal data shapes that consumers across the boundary do not directly observe; private helper or coordinator types; the existence of specific test files or runners; choices of HTTP client, ORM, database engine, build tool, or other tooling consumers do not directly interact with \u2014 are out of scope of a contract and are FAIL.\n- Free of historical or migration content. The contract states only the present spec \u2014 what the software does now. Content recording what the spec used to be, what it replaces, what changed in this run, or any transitional framing is FAIL.\n- No obligation is duplicated across files. When the request relates to obligations already covered by existing files, those files are updated rather than duplicated.\n\n**B. Rule artifacts (each file written or updated under a `.spec/rules` folder)**\n\nB1. Format and shape. Every rule file written or updated lives inside a `.spec/rules` folder, is non-empty, is markdown, and captures one or more atomic rules \u2014 one rule on its own, or several related rules as discrete atomic sections, where each rule pins exactly one obligation; a file is FAIL only when it fuses unrelated obligations into one non-atomic rule, or presents a section as a rule that is not itself atomic. Its filename is descriptive of the rule or cluster of related rules it pins, and bundles of related rules are modeled either as a subfolder containing one file per atomic rule or as a single file grouping those related rules as discrete atomic sections \u2014 both shapes are valid.\n\nB2. Content rules. Verify the artifact satisfies EACH of the following independently:\n- Free of placeholders. No `<TBD>` or analogous task markers, no template-style blanks, no parenthetical \"(to be decided)\" deferrals.\n- Scope of enforcement is explicit. The rule has a \"Who this applies to\" or equivalent section that names exactly which code, agents, surfaces, file patterns, or call sites the rule binds. An open-ended \"applies everywhere\" without enumeration of the actual surface is FAIL.\n- Free of ambiguous wording. Hedge phrasing that turns the obligation into a choice instead of a commitment \u2014 `may or may not`, `pick one of`, `or equivalent`, `left to the implementer`, `at the discretion of`, `or \u2014 alternatively \u2014`, `or X if Y` \u2014 is FAIL.\n- Free of historical or migration content. The rule states only the present spec. Content recording what the rule used to be, what it replaces, what changed in this run, or any transitional framing is FAIL.\n- No rule is duplicated across files. When the request relates to a rule already covered by an existing file, that file is updated rather than a parallel duplicate created.\n\n**C. Non-contradiction with the canonical corpus (every file written or updated in this run)**\n\nThe file(s) written or updated do not contradict any other contract in the project's contracts (the canonical contracts listing, spanning every `.spec/contracts` folder) and do not contradict any rule in the project's rules (the canonical rules listing, spanning every `.spec/rules` folder). A contradiction is an obligation pinned in two places with incompatible content. Tightening, extending, or qualifying an existing obligation in a way the existing text already allows is not a contradiction.\n\n**Renamed-term sweep.** For each old term the host passed (the terms this run renamed, relocated, or removed), the validator searches the whole corpus for that term and inspects every occurrence. An occurrence that is a stale, un-updated instance of the renamed term \u2014 a leftover that should have been changed in this run \u2014 is FAIL. An occurrence that is an intentional reference the rename correctly leaves alone is not a violation. The validator drives this check from the passed term(s), not from its own judgment of which files are relevant, so that a stale occurrence in a file the validator would not otherwise open is still caught. When the passed list is empty, this check is vacuously satisfied.\n\nOut of scope of the validator: verifying that paths referenced by a contract or rule physically resolve 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 triage-then-fix loop\n\nWhen the validator returns FAIL, enter the triage-then-fix loop:\n\n1. Triage each issue. For every issue enumerated in the FAIL report, classify it against the clarification-scope criteria of this skill's clarification phase \u2014 the same criteria that govern the initial clarification phase above: obligation ambiguous, UI or logic decision unspecified, rule or scope of enforcement unspecified, or multiple valid interpretations.\n2. For issues whose fix would commit the skill to an answer that, per the clarification phase, the user is the one who must give and that the user did not give in the initial clarification phase of this invocation: re-enter the clarification phase for that specific ambiguity before any rewrite. Re-entered clarification follows the same mechanics \u2014 one question per turn, multiple-choice preferred when bounded, no bundling. The re-entered phase is scoped to the specific ambiguity at hand and never re-asks decisions the user has already given in this invocation.\n3. For every other issue \u2014 formatting, naming, descriptive-filename violations, placeholders that do not require a user-level decision, and any other fix the skill is authorized to resolve on its own \u2014 apply in place without asking.\n4. Rewrite the affected file(s) in place, addressing every enumerated issue.\n5. Re-launch the validator (a new subagent in a fresh session when the subagent host is available) over the rewritten file(s).\n6. Repeat the cycle. Perform at most FIVE triage-then-fix passes per /flanders-spec invocation. The fifth FAIL ends the loop.\n\nWhen the loop ends with a PASS at any iteration, declare complete.\n\nWhen the loop ends with FAIL after five passes, do not declare complete: surface the last FAIL report and the file path(s) to the user in chat, then stop.\n\n## Recommending and launching the next step\n\nOnce you have declared the spec complete \u2014 the spec files persisted and the final validator returned PASS \u2014 offer to continue into the next step in the same session. Make this offer only on successful completion: when the bounded triage-then-fix loop exhausts without a PASS, surface the last FAIL report and stop, and make no such offer.\n\nAsk the user which skill to launch next: /flanders-plan, /flanders-work, or neither. Recommend one of them based on the implementation effort the spec you just wrote implies \u2014 recommend /flanders-work when the spec describes a single, small, self-contained change, and recommend /flanders-plan when the spec describes larger work that spans multiple obligations or scopes or needs an ordered, multi-step implementation. The user accepts the recommendation, chooses the other skill, or declines.\n\nWhen the user chooses /flanders-plan or /flanders-work, launch it by invoking it in the same session with no <data> argument, so the launched skill takes its input from the conversation \u2014 the original request together with the spec you just wrote. The run then proceeds under that skill; launching it leaves your own deliverable and write boundary unchanged, so you write only this run's spec files and never code or a plan file. When the user declines, end the run.\n\n## Output language\n\nResolve the natural language to write each spec file in by this priority order:\n\n1. When the request explicitly states a language to write in, write in that language.\n2. Otherwise, when at least one spec file already exists in the project, write in the language of those existing spec files, determined by inspecting a single existing spec file \u2014 reading more than one is unnecessary, since the corpus is kept in one language.\n3. Otherwise \u2014 when the request names no language and no spec file exists yet \u2014 write in the language the request itself is written in.\n\nDo not translate already-written content; the resolved language governs only the content you author in this run.\n\n## Interaction language\n\nEvery message you address to the user during the run \u2014 your clarifying questions, the approach trade-off summaries, the drafting-phase layout summary, and any other text you print in chat \u2014 is written in the natural language of the user's most recent message in the conversation. When the user switches the language they write in partway through the interaction, every subsequent message you address to the user follows the language of their latest message. This is resolved independently of the Output language above: it governs only what you say to the user in the conversation, never the language of the spec files you write.\n\n## Idempotency and overwrites\n\nExisting files in the project's `.spec/contracts` and `.spec/rules` folders are not protected. Because you receive the current state of both folders 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 specSkillBody: string;
|
|
3
3
|
export declare const workSkillBody: string;
|