flanders 0.9.0 → 0.10.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/lib/ai/ClaudeAdapter.js +36 -9
- package/lib/ai/CodexAdapter.js +11 -22
- package/lib/commands/Install.js +1 -0
- package/lib/prompts/prompts.d.ts +0 -1
- package/lib/prompts/prompts.js +6 -11
- package/lib/prompts/skills.js +6 -10
- package/package.json +1 -1
package/lib/ai/ClaudeAdapter.js
CHANGED
|
@@ -82,11 +82,11 @@ class ClaudeAdapterIterator {
|
|
|
82
82
|
this._capturedSessionId = null;
|
|
83
83
|
this._queue = [];
|
|
84
84
|
this._done = false;
|
|
85
|
-
this._error = null;
|
|
86
85
|
this._waitResolve = null;
|
|
87
86
|
this._abortListener = null;
|
|
88
87
|
this._pendingTerminal = null;
|
|
89
88
|
this._exitPromise = null;
|
|
89
|
+
this._retainedRateLimitInfo = null;
|
|
90
90
|
this._start();
|
|
91
91
|
}
|
|
92
92
|
_start() {
|
|
@@ -123,13 +123,24 @@ class ClaudeAdapterIterator {
|
|
|
123
123
|
});
|
|
124
124
|
proc.on("error", (e) => {
|
|
125
125
|
if (!this._done) {
|
|
126
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
126
127
|
this._done = true;
|
|
127
|
-
this.
|
|
128
|
+
this._queue.push({
|
|
129
|
+
type: "error",
|
|
130
|
+
retryable: false,
|
|
131
|
+
message: err.code === "ENOENT"
|
|
132
|
+
? "claude binary not found"
|
|
133
|
+
: err.message
|
|
134
|
+
});
|
|
128
135
|
this._wake();
|
|
129
136
|
}
|
|
130
137
|
exitResolve === null || exitResolve === void 0 ? void 0 : exitResolve();
|
|
131
138
|
});
|
|
132
|
-
proc.on("exit", () => {
|
|
139
|
+
proc.on("exit", (code, signal) => {
|
|
140
|
+
if (this._done) {
|
|
141
|
+
exitResolve === null || exitResolve === void 0 ? void 0 : exitResolve();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
133
144
|
if (stderrBuf) {
|
|
134
145
|
this._queue.push({
|
|
135
146
|
type: "output",
|
|
@@ -143,6 +154,20 @@ class ClaudeAdapterIterator {
|
|
|
143
154
|
this._queue.push(this._pendingTerminal);
|
|
144
155
|
this._pendingTerminal = null;
|
|
145
156
|
}
|
|
157
|
+
else if (signal) {
|
|
158
|
+
this._queue.push({
|
|
159
|
+
type: "error",
|
|
160
|
+
retryable: true,
|
|
161
|
+
message: `claude terminated by signal ${signal}`
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this._queue.push({
|
|
166
|
+
type: "error",
|
|
167
|
+
retryable: true,
|
|
168
|
+
message: `claude exited unexpectedly (code ${code} signal ${signal})`
|
|
169
|
+
});
|
|
170
|
+
}
|
|
146
171
|
if (!this._done) {
|
|
147
172
|
this._done = true;
|
|
148
173
|
}
|
|
@@ -150,10 +175,10 @@ class ClaudeAdapterIterator {
|
|
|
150
175
|
exitResolve === null || exitResolve === void 0 ? void 0 : exitResolve();
|
|
151
176
|
});
|
|
152
177
|
this._abortListener = () => {
|
|
178
|
+
this._done = true;
|
|
153
179
|
if (this._proc) {
|
|
154
180
|
this._proc.kill("SIGINT");
|
|
155
181
|
}
|
|
156
|
-
this._done = true;
|
|
157
182
|
this._wake();
|
|
158
183
|
};
|
|
159
184
|
if (this._args.abortSignal.aborted) {
|
|
@@ -182,6 +207,8 @@ class ClaudeAdapterIterator {
|
|
|
182
207
|
}
|
|
183
208
|
_handleLine(line) {
|
|
184
209
|
var _a, _b, _c, _d, _e, _f;
|
|
210
|
+
if (this._done)
|
|
211
|
+
return;
|
|
185
212
|
let parsed = null;
|
|
186
213
|
try {
|
|
187
214
|
parsed = JSON.parse(line);
|
|
@@ -201,6 +228,9 @@ class ClaudeAdapterIterator {
|
|
|
201
228
|
this._queue.push({ type: "session", id: parsed.session_id });
|
|
202
229
|
}
|
|
203
230
|
}
|
|
231
|
+
if (parsed.type === "rate_limit_event" && parsed.rate_limit_info) {
|
|
232
|
+
this._retainedRateLimitInfo = parsed.rate_limit_info;
|
|
233
|
+
}
|
|
204
234
|
if (parsed.type === "assistant" && ((_a = parsed.message) === null || _a === void 0 ? void 0 : _a.content)) {
|
|
205
235
|
for (const block of parsed.message.content) {
|
|
206
236
|
if (block.type === "tool_use" && typeof block.name === "string") {
|
|
@@ -263,11 +293,11 @@ class ClaudeAdapterIterator {
|
|
|
263
293
|
this._wake();
|
|
264
294
|
}
|
|
265
295
|
_classifyError(parsed) {
|
|
266
|
-
var _a, _b;
|
|
296
|
+
var _a, _b, _c;
|
|
267
297
|
const status = parsed.api_error_status;
|
|
268
298
|
const subtype = parsed.subtype;
|
|
269
299
|
const message = (_b = (_a = parsed.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : "unknown error";
|
|
270
|
-
const info = parsed.rate_limit_info;
|
|
300
|
+
const info = (_c = this._retainedRateLimitInfo) !== null && _c !== void 0 ? _c : parsed.rate_limit_info;
|
|
271
301
|
if (info || status === 429) {
|
|
272
302
|
if (info) {
|
|
273
303
|
const target = info.isUsingOverage && typeof info.overageResetsAt === "number"
|
|
@@ -313,9 +343,6 @@ class ClaudeAdapterIterator {
|
|
|
313
343
|
if (this._queue.length > 0) {
|
|
314
344
|
return { value: this._queue.shift(), done: false };
|
|
315
345
|
}
|
|
316
|
-
if (this._error) {
|
|
317
|
-
throw this._error;
|
|
318
|
-
}
|
|
319
346
|
if (this._done && this._queue.length === 0) {
|
|
320
347
|
this._cleanup();
|
|
321
348
|
if (this._exitPromise) {
|
package/lib/ai/CodexAdapter.js
CHANGED
|
@@ -40,13 +40,12 @@ class CodexAdapterIterator {
|
|
|
40
40
|
this._exitPromise = null;
|
|
41
41
|
this._sawTurnCompleted = false;
|
|
42
42
|
this._receivedAnyEvent = false;
|
|
43
|
-
this._fallbackAttempted = false;
|
|
44
43
|
this._usedResume = false;
|
|
45
|
-
this._start(
|
|
44
|
+
this._start();
|
|
46
45
|
}
|
|
47
|
-
_start(
|
|
46
|
+
_start() {
|
|
48
47
|
var _a, _b, _c;
|
|
49
|
-
const isResume =
|
|
48
|
+
const isResume = !!this._args.resumeSessionId;
|
|
50
49
|
this._usedResume = isResume;
|
|
51
50
|
const argv = this._buildArgv(isResume);
|
|
52
51
|
const spawnOptions = { stdio: "pipe" };
|
|
@@ -91,23 +90,6 @@ class CodexAdapterIterator {
|
|
|
91
90
|
exitResolve === null || exitResolve === void 0 ? void 0 : exitResolve();
|
|
92
91
|
return;
|
|
93
92
|
}
|
|
94
|
-
if (this._usedResume && !this._receivedAnyEvent && !this._fallbackAttempted) {
|
|
95
|
-
this._fallbackAttempted = true;
|
|
96
|
-
this._queue.push({
|
|
97
|
-
type: "output",
|
|
98
|
-
title: "Continuity lost",
|
|
99
|
-
subtitle: "",
|
|
100
|
-
details: "codex exec resume unavailable in installed CLI"
|
|
101
|
-
});
|
|
102
|
-
this._cleanup();
|
|
103
|
-
this._sawTurnCompleted = false;
|
|
104
|
-
this._receivedAnyEvent = false;
|
|
105
|
-
this._usedResume = false;
|
|
106
|
-
exitResolve === null || exitResolve === void 0 ? void 0 : exitResolve();
|
|
107
|
-
this._start(true);
|
|
108
|
-
this._wake();
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
93
|
if (this._sawTurnCompleted) {
|
|
112
94
|
this._queue.push({ type: "done" });
|
|
113
95
|
}
|
|
@@ -118,6 +100,13 @@ class CodexAdapterIterator {
|
|
|
118
100
|
message: `codex terminated by signal ${signal}`
|
|
119
101
|
});
|
|
120
102
|
}
|
|
103
|
+
else if (this._usedResume && !this._receivedAnyEvent) {
|
|
104
|
+
this._queue.push({
|
|
105
|
+
type: "error",
|
|
106
|
+
retryable: false,
|
|
107
|
+
message: "codex exec resume unavailable in installed CLI"
|
|
108
|
+
});
|
|
109
|
+
}
|
|
121
110
|
else {
|
|
122
111
|
this._queue.push({
|
|
123
112
|
type: "error",
|
|
@@ -130,10 +119,10 @@ class CodexAdapterIterator {
|
|
|
130
119
|
exitResolve === null || exitResolve === void 0 ? void 0 : exitResolve();
|
|
131
120
|
});
|
|
132
121
|
this._abortListener = () => {
|
|
122
|
+
this._done = true;
|
|
133
123
|
if (this._proc) {
|
|
134
124
|
this._proc.kill("SIGINT");
|
|
135
125
|
}
|
|
136
|
-
this._done = true;
|
|
137
126
|
this._wake();
|
|
138
127
|
};
|
|
139
128
|
if (this._args.abortSignal.aborted) {
|
package/lib/commands/Install.js
CHANGED
|
@@ -110,6 +110,7 @@ const CLAUDE_MODEL_FAMILIES = [
|
|
|
110
110
|
entries: [
|
|
111
111
|
{ label: "Latest Sonnet", value: "sonnet" },
|
|
112
112
|
{ label: "Latest Sonnet [1m context]", value: "sonnet[1m]" },
|
|
113
|
+
{ label: "Sonnet 5", value: "claude-sonnet-5" },
|
|
113
114
|
{ label: "Sonnet 4.6", value: "claude-sonnet-4-6" },
|
|
114
115
|
{ label: "Sonnet 4.6 [1m context]", value: "claude-sonnet-4-6[1m]" },
|
|
115
116
|
{ label: "Sonnet 4.5", value: "claude-sonnet-4-5" },
|
package/lib/prompts/prompts.d.ts
CHANGED
|
@@ -44,7 +44,6 @@ export interface FlandersVoiceParts {
|
|
|
44
44
|
subject: string;
|
|
45
45
|
languageFraming: string;
|
|
46
46
|
finalExclusion: string;
|
|
47
|
-
trailer: string;
|
|
48
47
|
}
|
|
49
48
|
export declare function buildFlandersVoiceSection(parts: FlandersVoiceParts): string;
|
|
50
49
|
export declare function flandersToneInstruction(reviewer: boolean): string;
|
package/lib/prompts/prompts.js
CHANGED
|
@@ -144,23 +144,18 @@ const citationFreeReviewerMethodology = buildReviewerMethodology(citationFreeRev
|
|
|
144
144
|
exports.reviewerMethodologyCore = `${citationFreeReviewerMethodology.changeSet}
|
|
145
145
|
|
|
146
146
|
${citationFreeReviewerMethodology.audit}`;
|
|
147
|
-
const
|
|
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.";
|
|
147
|
+
const voiceExclusionLead = "code, file paths, command lines, diagnostics, machine-read tokens, git commit messages";
|
|
150
148
|
function buildFlandersVoiceSection(parts) {
|
|
151
149
|
return `## Voice
|
|
152
150
|
|
|
153
|
-
|
|
151
|
+
Use a light Ned-Flanders touch in ${parts.subject}, only while ${parts.languageFraming} is English — deliver any other language plainly. Keep it out of ${voiceExclusionLead}${parts.finalExclusion}.`;
|
|
154
152
|
}
|
|
155
153
|
function flandersToneInstruction(reviewer) {
|
|
156
154
|
return buildFlandersVoiceSection({
|
|
157
155
|
subject: "your user-facing narration — the prose you stream as you work",
|
|
158
156
|
languageFraming: "the language you are narrating in",
|
|
159
157
|
finalExclusion: reviewer
|
|
160
|
-
? ",
|
|
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."
|
|
158
|
+
? ", and the violation entries you record in your error-log file"
|
|
164
159
|
: ""
|
|
165
160
|
});
|
|
166
161
|
}
|
|
@@ -249,19 +244,19 @@ ${flandersToneInstruction(false)}
|
|
|
249
244
|
|
|
250
245
|
## Available contracts
|
|
251
246
|
|
|
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.
|
|
247
|
+
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.
|
|
253
248
|
|
|
254
249
|
${"<CONTRACT_LIST>"}
|
|
255
250
|
|
|
256
251
|
## Available rules
|
|
257
252
|
|
|
258
|
-
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 under a \`testing/\` subfolder; if you touch timers, listeners, controllers, or any async lifecycle, open the rules under a \`disposables/\` subfolder; if you change terminal UI, open the rules under a \`ui/\` subfolder).
|
|
253
|
+
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 under a \`testing/\` subfolder; if you touch timers, listeners, controllers, or any async lifecycle, open the rules under a \`disposables/\` subfolder; if you change terminal UI, open the rules under a \`ui/\` subfolder).
|
|
259
254
|
|
|
260
255
|
${"<RULE_LIST>"}
|
|
261
256
|
|
|
262
257
|
## Available behavior rules
|
|
263
258
|
|
|
264
|
-
Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes you author are named, placed, and organized within the part of the project tree that the rule's \`.spec/flanders\` folder scopes. You must honor every behavior rule whose \`.spec/flanders\` scope encloses the files your changes touch. Like the global contract and rule lists above, in-scope behavior rules are mandatory whether or not the task links them
|
|
259
|
+
Each path below is a behavior rule's namespace. A behavior rule governs how the files and changes you author are named, placed, and organized within the part of the project tree that the rule's \`.spec/flanders\` folder scopes. You must honor every behavior rule whose \`.spec/flanders\` scope encloses the files your changes touch. Like the global contract and rule lists above, in-scope behavior rules are mandatory whether or not the task links them.
|
|
265
260
|
|
|
266
261
|
${"<BEHAVIOR_RULE_LIST>"}`,
|
|
267
262
|
reviewer: `You are the adversarial reviewer agent for the Flanders implement iteration loop.
|
package/lib/prompts/skills.js
CHANGED
|
@@ -5,10 +5,9 @@ const PlanFile_1 = require("../plan/PlanFile");
|
|
|
5
5
|
const prompts_1 = require("./prompts");
|
|
6
6
|
function skillVoiceSection(authoredArtifactExclusion) {
|
|
7
7
|
return (0, prompts_1.buildFlandersVoiceSection)({
|
|
8
|
-
subject: "the messages you address to the user
|
|
8
|
+
subject: "the messages you address to the user",
|
|
9
9
|
languageFraming: "the resolved interaction language you are addressing the user in",
|
|
10
|
-
finalExclusion: `,
|
|
11
|
-
trailer: ""
|
|
10
|
+
finalExclusion: `, and ${authoredArtifactExclusion}`
|
|
12
11
|
});
|
|
13
12
|
}
|
|
14
13
|
exports.planSkillBody = `---
|
|
@@ -101,7 +100,6 @@ Tasks are written in the order they must be implemented, accounting for dependen
|
|
|
101
100
|
- No task asserts, as settled fact, a runtime- or observable-behavior premise that its approach depends on and that cannot be confirmed by reading the source. Such a premise is either backed — by an existing contract or rule, an existing test, or a preceding task in the plan that establishes it executably — or escalated to the user during the clarification phase. A task does not remove, weaken, or replace existing code on the strength of an unbacked, unescalated runtime-behavior premise.
|
|
102
101
|
- The plan only references contracts and rules that exist in the canonical state captured at invocation.
|
|
103
102
|
- 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.
|
|
104
|
-
- 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.
|
|
105
103
|
- Write each leaf task with a detailed description and explicit acceptance criteria — the conditions that must be true once the task is implemented for it to be considered complete.
|
|
106
104
|
- 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.
|
|
107
105
|
- 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.
|
|
@@ -191,7 +189,7 @@ If the validator wants to show its work, it does so in the body of its response
|
|
|
191
189
|
When the validator returns FAIL, enter the triage-then-fix loop:
|
|
192
190
|
|
|
193
191
|
1. Triage each issue. For every issue enumerated in the FAIL report, classify it against the clarification-scope criteria of this skill's clarification phase — the same criteria that govern the initial clarification phase above: an implementation choice that shapes a task's observable outcome and that the request does not specify, a task-scope ambiguity the planner cannot reasonably infer from the request or the canonical contracts and rules, or a load-bearing runtime-behavior premise the plan would otherwise have to assert without backing. A validator FAIL never broadens what the skill may ask the user about; an unbacked runtime-behavior premise the validator flags is escalated to the user, never silently rewritten.
|
|
194
|
-
2. 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 cadence
|
|
192
|
+
2. 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 cadence the clarification phase above defines, scoped to the specific ambiguity at hand and never re-asking decisions the user has already given in this invocation.
|
|
195
193
|
3. For every other issue — placeholders, missing acceptance criteria, missing contract or rule links on a leaf task, hedge phrasing the planner can resolve by picking a concrete value, task ordering, hierarchical numbering, format-shape violations, and any other fix the skill is authorized to resolve on its own — apply in place without asking.
|
|
196
194
|
4. Rewrite the plan file in place, addressing every enumerated issue.
|
|
197
195
|
5. Re-launch the validator (a new subagent in a fresh session when the subagent host is available) over the rewritten file.
|
|
@@ -244,7 +242,7 @@ The user invokes you as: /flanders-spec [<data>]
|
|
|
244
242
|
|
|
245
243
|
A contract is a markdown document that describes the public behavior of the directory its \`.spec\` folder scopes — what code outside that directory relies on — 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.
|
|
246
244
|
|
|
247
|
-
Contracts are the public surface of the scope they belong to.
|
|
245
|
+
Contracts are the public surface of the scope they belong to.
|
|
248
246
|
|
|
249
247
|
## What a rule is
|
|
250
248
|
|
|
@@ -254,13 +252,11 @@ Bundles of related rules (for example, the multiple obligations that make up SOL
|
|
|
254
252
|
|
|
255
253
|
The 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.
|
|
256
254
|
|
|
257
|
-
Rules are immovable once written unless the user explicitly asks for a change.
|
|
258
|
-
|
|
259
255
|
## What a behavior rule is
|
|
260
256
|
|
|
261
257
|
A behavior rule is a markdown document that governs how Flanders' own commands and skills behave when they work in the project — how they name, place, organize, or otherwise produce the files and changes they author — as distinct from contracts and rules, which describe the host project's own code. Behavior rules live in \`.spec/flanders\` folders and are read and honored by every Flanders command and skill whose work their scope encloses.
|
|
262
258
|
|
|
263
|
-
|
|
259
|
+
Contracts, rules, and behavior rules are all immovable once written unless the user explicitly asks for a change.
|
|
264
260
|
|
|
265
261
|
## Contract, rule, or behavior rule: how the skill classifies and places
|
|
266
262
|
|
|
@@ -366,7 +362,7 @@ If the validator wants to show its work, it does so in the body of its response
|
|
|
366
362
|
When the validator returns FAIL, enter the triage-then-fix loop:
|
|
367
363
|
|
|
368
364
|
1. Triage each issue. For every issue enumerated in the FAIL report, classify it against the clarification-scope criteria of this skill's clarification phase — 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.
|
|
369
|
-
2. 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 cadence
|
|
365
|
+
2. 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 cadence the clarification phase above defines, scoped to the specific ambiguity at hand and never re-asking decisions the user has already given in this invocation.
|
|
370
366
|
3. For every other issue — 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 — apply in place without asking.
|
|
371
367
|
4. Rewrite the affected file(s) in place, addressing every enumerated issue.
|
|
372
368
|
5. Re-launch the validator (a new subagent in a fresh session when the subagent host is available) over the rewritten file(s).
|