flanders 0.8.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/README.md +22 -6
- package/lib/ai/AiRunner.d.ts +3 -1
- package/lib/ai/AiRunner.js +2 -2
- package/lib/ai/AiSession.d.ts +3 -1
- package/lib/ai/AiSession.js +2 -0
- package/lib/ai/ClaudeAdapter.d.ts +2 -1
- package/lib/ai/ClaudeAdapter.js +42 -11
- package/lib/ai/CodexAdapter.js +19 -101
- package/lib/ai/ToolAdapter.d.ts +7 -3
- package/lib/ai/toolErrorClassification.d.ts +7 -0
- package/lib/ai/toolErrorClassification.js +78 -0
- package/lib/cli.js +8 -2
- package/lib/commands/Implement.d.ts +3 -0
- package/lib/commands/Implement.js +60 -16
- package/lib/commands/Install.d.ts +9 -4
- package/lib/commands/Install.js +251 -105
- package/lib/commands/skillArtifacts.js +40 -36
- package/lib/contexts.d.ts +1 -0
- package/lib/fastMode.d.ts +2 -0
- package/lib/fastMode.js +15 -0
- package/lib/plan/PlanFile.d.ts +3 -1
- package/lib/plan/PlanFile.js +8 -1
- package/lib/prompts/prompts.d.ts +0 -1
- package/lib/prompts/prompts.js +10 -15
- package/lib/prompts/skills.js +17 -15
- package/lib/toolNames.d.ts +1 -0
- package/lib/toolNames.js +4 -0
- package/lib/ui/BottomBlock.d.ts +11 -8
- package/lib/ui/BottomBlock.js +24 -3
- package/lib/ui/PromptHelper.d.ts +7 -0
- package/lib/ui/PromptHelper.js +22 -0
- package/lib/ui/formatters.d.ts +3 -2
- package/lib/ui/formatters.js +7 -2
- package/lib/workspace/FlandersConfig.d.ts +3 -1
- package/lib/workspace/FlandersConfig.js +5 -1
- package/package.json +3 -2
|
@@ -15,10 +15,10 @@ exports.SKILLS = [
|
|
|
15
15
|
const CLAUDE_SKILLS_SUBDIR = ".claude/skills";
|
|
16
16
|
const CODEX_PROMPTS_SUBDIR = ".codex/prompts";
|
|
17
17
|
function skillArtifactPath(scopeRoot, tool, skillName) {
|
|
18
|
-
if (tool === "
|
|
19
|
-
return (0, fsUtils_1.joinPath)(scopeRoot,
|
|
18
|
+
if (tool === "codex") {
|
|
19
|
+
return (0, fsUtils_1.joinPath)(scopeRoot, CODEX_PROMPTS_SUBDIR, `${skillName}.md`);
|
|
20
20
|
}
|
|
21
|
-
return (0, fsUtils_1.joinPath)(scopeRoot,
|
|
21
|
+
return (0, fsUtils_1.joinPath)(scopeRoot, CLAUDE_SKILLS_SUBDIR, skillName, "SKILL.md");
|
|
22
22
|
}
|
|
23
23
|
function skillArtifactPaths(scopeRoot, tool) {
|
|
24
24
|
return exports.SKILLS.map(skill => skillArtifactPath(scopeRoot, tool, skill.name));
|
|
@@ -38,29 +38,53 @@ function stripYamlFrontmatter(body) {
|
|
|
38
38
|
}
|
|
39
39
|
return body.slice(closerIndex + "\n---\n".length);
|
|
40
40
|
}
|
|
41
|
+
async function writeDirectorySkillTrio(fs, scopeRoot, tool, isDisposed) {
|
|
42
|
+
const skillsRoot = (0, fsUtils_1.joinPath)(scopeRoot, CLAUDE_SKILLS_SUBDIR);
|
|
43
|
+
const writtenPaths = [];
|
|
44
|
+
for (const skill of exports.SKILLS) {
|
|
45
|
+
if (isDisposed()) {
|
|
46
|
+
return { ok: false, diagnostic: null };
|
|
47
|
+
}
|
|
48
|
+
const skillFolder = (0, fsUtils_1.joinPath)(skillsRoot, skill.name);
|
|
49
|
+
try {
|
|
50
|
+
await fs.mkdir(skillFolder, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return { ok: false, diagnostic: `Cannot create destination: ${skillFolder}\n` };
|
|
54
|
+
}
|
|
55
|
+
const filePath = skillArtifactPath(scopeRoot, tool, skill.name);
|
|
56
|
+
try {
|
|
57
|
+
await fs.writeFile(filePath, skill.body);
|
|
58
|
+
writtenPaths.push(filePath);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return { ok: false, diagnostic: `Cannot write file: ${filePath}\n` };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { ok: true, writtenPaths };
|
|
65
|
+
}
|
|
41
66
|
async function writeSkillArtifacts(fs, scopeRoot, tool, isDisposed) {
|
|
42
67
|
for (const skill of exports.SKILLS) {
|
|
43
68
|
if (!skill.body) {
|
|
44
69
|
return { ok: false, diagnostic: `Skill "${skill.name}" has no content.\n` };
|
|
45
70
|
}
|
|
46
71
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
if (tool === "codex") {
|
|
73
|
+
const codexPromptsRoot = (0, fsUtils_1.joinPath)(scopeRoot, CODEX_PROMPTS_SUBDIR);
|
|
74
|
+
try {
|
|
75
|
+
await fs.mkdir(codexPromptsRoot, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return { ok: false, diagnostic: `Cannot create destination: ${codexPromptsRoot}\n` };
|
|
79
|
+
}
|
|
80
|
+
const writtenPaths = [];
|
|
50
81
|
for (const skill of exports.SKILLS) {
|
|
51
82
|
if (isDisposed()) {
|
|
52
83
|
return { ok: false, diagnostic: null };
|
|
53
84
|
}
|
|
54
|
-
const
|
|
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);
|
|
85
|
+
const filePath = skillArtifactPath(scopeRoot, "codex", skill.name);
|
|
62
86
|
try {
|
|
63
|
-
await fs.writeFile(filePath, skill.body);
|
|
87
|
+
await fs.writeFile(filePath, stripYamlFrontmatter(skill.body));
|
|
64
88
|
writtenPaths.push(filePath);
|
|
65
89
|
}
|
|
66
90
|
catch {
|
|
@@ -69,25 +93,5 @@ async function writeSkillArtifacts(fs, scopeRoot, tool, isDisposed) {
|
|
|
69
93
|
}
|
|
70
94
|
return { ok: true, writtenPaths };
|
|
71
95
|
}
|
|
72
|
-
|
|
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 };
|
|
96
|
+
return writeDirectorySkillTrio(fs, scopeRoot, tool, isDisposed);
|
|
93
97
|
}
|
package/lib/contexts.d.ts
CHANGED
package/lib/fastMode.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAST_CAPABLE_MODELS = void 0;
|
|
4
|
+
exports.modelSupportsFastMode = modelSupportsFastMode;
|
|
5
|
+
exports.FAST_CAPABLE_MODELS = [
|
|
6
|
+
"opus",
|
|
7
|
+
"opus[1m]",
|
|
8
|
+
"claude-opus-4-8",
|
|
9
|
+
"claude-opus-4-8[1m]",
|
|
10
|
+
"claude-opus-4-7",
|
|
11
|
+
"claude-opus-4-7[1m]"
|
|
12
|
+
];
|
|
13
|
+
function modelSupportsFastMode(model) {
|
|
14
|
+
return exports.FAST_CAPABLE_MODELS.includes(model);
|
|
15
|
+
}
|
package/lib/plan/PlanFile.d.ts
CHANGED
|
@@ -33,11 +33,13 @@ export declare function headingAnchor(headingText: string): string;
|
|
|
33
33
|
export declare function extractHeadingSection(content: string, anchor: string): string | null;
|
|
34
34
|
export declare function buildSpecFileContent(references: readonly LinkedReference[], fileContents: ReadonlyMap<string, string>): string;
|
|
35
35
|
export declare class PlanFile {
|
|
36
|
-
readonly path: string;
|
|
37
36
|
private _content;
|
|
38
37
|
private _fs;
|
|
38
|
+
private _path;
|
|
39
39
|
private constructor();
|
|
40
|
+
get path(): string;
|
|
40
41
|
static load(path: string, fs: FsContext): Promise<PlanFile>;
|
|
42
|
+
rename(newPath: string): Promise<void>;
|
|
41
43
|
parse(): PlanParseResult;
|
|
42
44
|
nextOpenTask(): PlanTask | null;
|
|
43
45
|
updateMetrics(lineNumber: number, metrics: TaskMetrics): Promise<void>;
|
package/lib/plan/PlanFile.js
CHANGED
|
@@ -206,14 +206,21 @@ function buildSpecFileContent(references, fileContents) {
|
|
|
206
206
|
}
|
|
207
207
|
class PlanFile {
|
|
208
208
|
constructor(path, _content, _fs) {
|
|
209
|
-
this.path = path;
|
|
210
209
|
this._content = _content;
|
|
211
210
|
this._fs = _fs;
|
|
211
|
+
this._path = path;
|
|
212
|
+
}
|
|
213
|
+
get path() {
|
|
214
|
+
return this._path;
|
|
212
215
|
}
|
|
213
216
|
static async load(path, fs) {
|
|
214
217
|
const content = await fs.readFile(path);
|
|
215
218
|
return new PlanFile(path, content, fs);
|
|
216
219
|
}
|
|
220
|
+
async rename(newPath) {
|
|
221
|
+
await this._fs.rename(this._path, newPath);
|
|
222
|
+
this._path = newPath;
|
|
223
|
+
}
|
|
217
224
|
parse() {
|
|
218
225
|
return parsePlan(this._content);
|
|
219
226
|
}
|
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
|
@@ -23,7 +23,7 @@ const workerToolchainRerunStep = `When a test-guarded regression argument cannot
|
|
|
23
23
|
const claimClassification = `${claimClassificationCore}
|
|
24
24
|
|
|
25
25
|
${workerToolchainRerunStep}`;
|
|
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.
|
|
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. 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
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.`;
|
|
28
28
|
const claimClassificationCitationFree = claimClassificationCore.replace(" per `rules/testing/assert-via-public-surface.md`", "");
|
|
29
29
|
function buildReviewerMethodology(s) {
|
|
@@ -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
|
-
languageFraming: "
|
|
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
|
}
|
|
@@ -207,7 +202,7 @@ Your output will be inspected by an adversarial reviewer immediately after you f
|
|
|
207
202
|
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.
|
|
208
203
|
5. A behavior rule from the behavior-rule list below whose \`.spec/flanders\` scope encloses the files your changes touch is not honored by the changes — in-scope behavior rules are mandatory whether or not the task links them.
|
|
209
204
|
|
|
210
|
-
Condition 4 causes most rejections in practice.
|
|
205
|
+
Condition 4 causes most rejections in practice. 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.
|
|
211
206
|
|
|
212
207
|
Procedure:
|
|
213
208
|
1. Read the task shown above and respect the obligations of every contract and rule it references exactly. You may consult those files, or the plan file for broader context, at your discretion.
|
|
@@ -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.
|
|
@@ -272,7 +267,7 @@ The plan file is at ${"<PLAN_PATH>"}; you may open it for broader context, but y
|
|
|
272
267
|
|
|
273
268
|
${"<TASK_TEXT>"}
|
|
274
269
|
|
|
275
|
-
|
|
270
|
+
Inspect the working-tree changes that the worker just produced.
|
|
276
271
|
|
|
277
272
|
${linkedReferenceDirective("<SPEC_PATH>")}
|
|
278
273
|
|
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
|
|
9
|
-
languageFraming: "
|
|
10
|
-
finalExclusion: `,
|
|
11
|
-
trailer: ""
|
|
8
|
+
subject: "the messages you address to the user",
|
|
9
|
+
languageFraming: "the resolved interaction language you are addressing the user in",
|
|
10
|
+
finalExclusion: `, and ${authoredArtifactExclusion}`
|
|
12
11
|
});
|
|
13
12
|
}
|
|
14
13
|
exports.planSkillBody = `---
|
|
@@ -31,7 +30,7 @@ The user invokes you as: /flanders-plan [<data>]
|
|
|
31
30
|
2. 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\` — which lists tracked files plus untracked-but-not-ignored files — 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 — 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.
|
|
32
31
|
|
|
33
32
|
**Behavior rules.** Before persisting the plan file, read every behavior rule whose \`.spec/flanders\` scope encloses the plan file you are about to write — the project-root \`.spec\` folder and any other \`.spec\` folder whose scope encloses the \`plans/\` target — and honor all of them. Behavior rules govern how you name and organize the plan file you author; an in-scope behavior rule is binding on that work, not advisory, and applies whether or not the request mentions it. This adds no new task-line link obligation: the plan-file format and its contract and rule links are unchanged.
|
|
34
|
-
3. **Clarification phase.** Ask the user clarifying questions only when the question targets one of three things: an implementation choice that shapes a task's observable outcome and 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, or a load-bearing runtime-behavior premise the plan would otherwise have to assert without backing (see the plan content rules below). A choice that affects only how a task's work is carried out internally, with no effect on any observable outcome its acceptance criteria pin, is not asked about: it is left for the implementer to resolve against the real code. 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.
|
|
33
|
+
3. **Clarification phase.** Ask the user clarifying questions only when the question targets one of three things: an implementation choice that shapes a task's observable outcome and 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, or a load-bearing runtime-behavior premise the plan would otherwise have to assert without backing (see the plan content rules below). A choice that affects only how a task's work is carried out internally, with no effect on any observable outcome its acceptance criteria pin, is not asked about: it is left for the implementer to resolve against the real code. 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. Before you start asking, accumulate every permitted question whose content does not depend on another question's answer and ask that whole set together in a single interaction rather than one question at a time; a question is held back only when it genuinely depends on an earlier answer, and is then asked in a later round that again batches the questions that have become independent. When your AI tool provides a facility for asking several questions at once, present the accumulated batch through that facility in a single interaction; when it provides no such facility, ask one question per turn. Multiple-choice questions are preferred when the answer space is bounded.
|
|
35
34
|
|
|
36
35
|
When the doubt is about how the code should be implemented, resolve it through one of two outcomes:
|
|
37
36
|
- **Cross-cutting convention** — the answer would apply to all future code of the same kind in the project and belongs in a \`.spec/rules\` folder. Surface the gap to the user and recommend creating the rule via /flanders-spec 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.
|
|
@@ -94,13 +93,13 @@ Tasks are written in the order they must be implemented, accounting for dependen
|
|
|
94
93
|
|
|
95
94
|
- The persisted plan is free of placeholders, contradictions with existing contracts or rules, acceptance criteria that leave a leaf task's observable outcome ambiguous, missing acceptance criteria on leaf tasks, and missing contract or rule links on leaf tasks.
|
|
96
95
|
- The persisted plan is internally self-consistent: its narrative — context, rationale, and any explanatory prose — does not contradict the obligations, verification approach, or any other statement made in its task bodies, and no task contradicts that narrative. Where the prose describes how something is tested or built, it matches what the tasks prescribe.
|
|
97
|
-
- Each leaf task's acceptance criteria pin the task's observable outcome — the behavior the result must exhibit through the surface a reader or a test can inspect — so that any two implementations satisfying them are observably equivalent. The plan does not dictate a task's internal mechanism beyond what an observable acceptance criterion or an explicitly required architectural property demands: a choice that affects only how the work is carried out internally, changing no observable outcome the acceptance criteria pin, is left for the implementer to resolve against the real code rather than fixed by the planner. When the planner needs a structural property for an architectural reason — a single source for some logic, the absence of duplication, a module boundary — it states that property as a required outcome the acceptance criteria assert rather than
|
|
96
|
+
- Each leaf task's acceptance criteria pin the task's observable outcome — the behavior the result must exhibit through the surface a reader or a test can inspect — so that any two implementations satisfying them are observably equivalent. The plan does not dictate a task's internal mechanism beyond what an observable acceptance criterion or an explicitly required architectural property demands: a choice that affects only how the work is carried out internally — which mechanism a task uses, and how its code and tests are organized across files and modules — changing no observable outcome the acceptance criteria pin, is left for the implementer to resolve against the real code rather than fixed by the planner. When the planner needs a structural property for an architectural reason — a single source for some logic, the absence of duplication, a module boundary — it states that property as a required outcome the acceptance criteria assert rather than fixing a specific internal mechanism, whether a code element to reuse or leave untouched or the files and modules its code and tests are placed in.
|
|
98
97
|
- Write each paragraph of prose in the plan as a single continuous line, however long; insert a line break only where markdown structure requires it — a blank line between paragraphs, a list item, a heading, a table row, or inside a fenced code block (whose contents you reproduce verbatim). Never break a paragraph across multiple lines to keep it within a maximum column width; the reader's editor wraps long lines for display.
|
|
98
|
+
- Use the fewest words that state each task, obligation, and explanation unambiguously, and write content — a sentence, a section, a cross-reference — only when it carries something not already carried by another task, an earlier sentence, or the reader's ordinary competence. Reach for more words only when fewer would leave an observable outcome ambiguous; task granularity itself follows the granularity rule below.
|
|
99
99
|
- Every task that creates, modifies, or removes code is grounded in the real state of the code it builds on — the current source, plus the changes any earlier task it depends on prescribes. Before writing the task, establish that state: read the current source for code that already exists, and consult the producing earlier task for code an earlier task in the plan creates or changes. Changing what the code does is the task's purpose and is allowed; what a task may not do is misstate the code it builds on — naming structure or behavior that code does not and will not have, or removing or rewriting code on a mistaken account of what it is for.
|
|
100
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.
|
|
101
101
|
- The plan only references contracts and rules that exist in the canonical state captured at invocation.
|
|
102
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.
|
|
103
|
-
- 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.
|
|
104
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.
|
|
105
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.
|
|
106
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.
|
|
@@ -162,7 +161,7 @@ Five categories, all mandatory; failure in any one is a FAIL. Each category is a
|
|
|
162
161
|
- Free of placeholders. No \`<TBD>\` or analogous task markers, no template-style blanks, no parenthetical "(to be decided)" deferrals.
|
|
163
162
|
- Free of contradictions with existing contracts or rules. No task pins behavior the canonical listings forbid.
|
|
164
163
|
- Internally self-consistent — no contradiction between the plan's narrative and its tasks. The plan's context, rationale, and explanatory prose do not contradict the obligations, verification approach, or any other statement in its task bodies, and no task contradicts that narrative. Where the prose describes how something is tested or built, it matches what the tasks prescribe.
|
|
165
|
-
- Acceptance criteria pin the observable outcome; the internal mechanism may be left to the implementer. Each leaf task's acceptance criteria pin the task's observable outcome precisely, such that two implementations satisfying them are observably equivalent. Acceptance criteria that leave the observable outcome open — satisfiable by implementations that differ in observable behavior — or hedge wording that defers the outcome are FAIL. This includes, non-exhaustively, hedge phrases such as: \`(or class)\`, \`(or function)\`, \`(or refactor in place if preferred)\`, \`pick the lower-friction option\`, \`pick the X that minimizes Y\`, \`suggested location\`, \`or — alternatively —\`, \`or — equivalently —\`, \`or equivalent\`, \`at the time of implementation\`, \`if the X exists, do Y; otherwise Z\`, \`either A or B — pick one\`, \`A or B (or some hybrid)\`, \`or, more strongly\`, \`or X if Y\`. An outcome-affecting choice that the request did not specify must be either closed to a single observable commitment in the task's acceptance criteria, or escalated by the skill to the user before the plan was drafted. A choice that affects only the task's internal mechanism — which helper to reuse, which internal structure, which implementation approach — and that changes no observable outcome the acceptance criteria pin is NOT a violation when left for the implementer to resolve; conversely, a task that freezes an internal mechanism that no observable acceptance criterion and no explicitly required architectural property needs is FAIL.
|
|
164
|
+
- Acceptance criteria pin the observable outcome; the internal mechanism may be left to the implementer. Each leaf task's acceptance criteria pin the task's observable outcome precisely, such that two implementations satisfying them are observably equivalent. Acceptance criteria that leave the observable outcome open — satisfiable by implementations that differ in observable behavior — or hedge wording that defers the outcome are FAIL. This includes, non-exhaustively, hedge phrases such as: \`(or class)\`, \`(or function)\`, \`(or refactor in place if preferred)\`, \`pick the lower-friction option\`, \`pick the X that minimizes Y\`, \`suggested location\`, \`or — alternatively —\`, \`or — equivalently —\`, \`or equivalent\`, \`at the time of implementation\`, \`if the X exists, do Y; otherwise Z\`, \`either A or B — pick one\`, \`A or B (or some hybrid)\`, \`or, more strongly\`, \`or X if Y\`. An outcome-affecting choice that the request did not specify must be either closed to a single observable commitment in the task's acceptance criteria, or escalated by the skill to the user before the plan was drafted. A choice that affects only the task's internal mechanism — which helper to reuse, which internal structure, how its code and tests are organized across files and modules, which implementation approach — and that changes no observable outcome the acceptance criteria pin is NOT a violation when left for the implementer to resolve; conversely, a task that freezes an internal mechanism that no observable acceptance criterion and no explicitly required architectural property needs is FAIL.
|
|
166
165
|
- Every leaf task carries an explicit acceptance-criteria section.
|
|
167
166
|
- Every leaf task carries the relevant contract link(s) as markdown links in project-root-relative namespace form — the link text is the file's listed namespace with no leading slash, and the target is that same namespace prefixed with a single leading slash.
|
|
168
167
|
- Every leaf task carries the relevant rule link(s) as markdown links in project-root-relative namespace form — the link text is the file's listed namespace with no leading slash, and the target is that same namespace prefixed with a single leading slash. When a rule's enforcement is bound to a specific scope, that scope is referenced alongside the file path.
|
|
@@ -190,7 +189,7 @@ If the validator wants to show its work, it does so in the body of its response
|
|
|
190
189
|
When the validator returns FAIL, enter the triage-then-fix loop:
|
|
191
190
|
|
|
192
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.
|
|
193
|
-
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
|
|
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.
|
|
194
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.
|
|
195
194
|
4. Rewrite the plan file in place, addressing every enumerated issue.
|
|
196
195
|
5. Re-launch the validator (a new subagent in a fresh session when the subagent host is available) over the rewritten file.
|
|
@@ -208,6 +207,10 @@ After the final validator returns PASS, print a summary in chat containing:
|
|
|
208
207
|
- The plan file's total line count.
|
|
209
208
|
- The total number of detected tasks.
|
|
210
209
|
|
|
210
|
+
## After completion: implementing the plan
|
|
211
|
+
|
|
212
|
+
After the final validator returns PASS and you print the summary above, tell the user in chat that the plan you produced is implemented from the command line by running \`flanders implement\` against it. This message is informational and final: you state that implementation path and end the run. You do not ask the user whether to proceed, and you do not offer to launch, nor launch, any AI-tool skill — including /flanders-work — to implement the plan; carrying out a plan is the job of the \`flanders implement\` command, not of an in-session skill.
|
|
213
|
+
|
|
211
214
|
## Output language
|
|
212
215
|
|
|
213
216
|
Write the plan file in the same natural language as the input request, unless the user says otherwise.
|
|
@@ -239,7 +242,7 @@ The user invokes you as: /flanders-spec [<data>]
|
|
|
239
242
|
|
|
240
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.
|
|
241
244
|
|
|
242
|
-
Contracts are the public surface of the scope they belong to.
|
|
245
|
+
Contracts are the public surface of the scope they belong to.
|
|
243
246
|
|
|
244
247
|
## What a rule is
|
|
245
248
|
|
|
@@ -249,13 +252,11 @@ Bundles of related rules (for example, the multiple obligations that make up SOL
|
|
|
249
252
|
|
|
250
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.
|
|
251
254
|
|
|
252
|
-
Rules are immovable once written unless the user explicitly asks for a change.
|
|
253
|
-
|
|
254
255
|
## What a behavior rule is
|
|
255
256
|
|
|
256
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.
|
|
257
258
|
|
|
258
|
-
|
|
259
|
+
Contracts, rules, and behavior rules are all immovable once written unless the user explicitly asks for a change.
|
|
259
260
|
|
|
260
261
|
## Contract, rule, or behavior rule: how the skill classifies and places
|
|
261
262
|
|
|
@@ -270,11 +271,12 @@ For every obligation in the request, the skill decides whether it is a contract,
|
|
|
270
271
|
**Behavior rules.** Before persisting any file, read every behavior rule whose \`.spec/flanders\` scope encloses each file you are about to write — the \`.spec\` folder you write the file into and every parent \`.spec\` folder up to the project root — 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.
|
|
271
272
|
|
|
272
273
|
**Rename sweep.** When the run renames, relocates, or removes a term that can recur across the corpus beyond the files it is editing — a folder name, a path segment, a flag, an identifier, a fixed string, or a namespace convention — 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.
|
|
273
|
-
4. **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
|
|
274
|
+
4. **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 in batches: before you start asking, accumulate every question whose content does not depend on another question's answer and ask that whole set together in a single interaction rather than one question at a time, holding a question back only when it genuinely depends on an earlier answer and asking it in a later round that again batches the questions that have become independent. When your AI tool provides a facility for asking several questions at once, present the accumulated batch through that facility in a single interaction; when it provides no such facility, ask 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.
|
|
274
275
|
5. **Drafting phase.** Before persisting any file:
|
|
275
276
|
- Present the planned file layout — 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 — as a structured summary, and wait for user approval or redirection.
|
|
276
277
|
- Once the layout is approved, persist every resulting file in a single batch without any further per-file or per-section confirmation step.
|
|
277
278
|
- 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.
|
|
279
|
+
- Use the fewest files and the fewest words that state each obligation unambiguously: write a file, a section, a sentence, or a cross-reference only when it carries something not already carried elsewhere — by another file, by a sentence already written, or by the reader's ordinary competence — and reach for more files or more words only when fewer would leave an obligation ambiguous or would fuse genuinely separable concerns into one place.
|
|
278
280
|
- Do not write historical, transitional, or migration content into the contracts and rules you produce. A spec file states only the present spec — 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.
|
|
279
281
|
- State each obligation as the behavior the code performs — 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 — "does not…", "never…", "must not…" — 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 — 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.
|
|
280
282
|
- Write every cross-reference to another spec file as a markdown link in project-root-relative namespace form: the link text is the referenced file's namespace — its path relative to the project root, with no leading slash — and the target is that same namespace prefixed with a single leading slash, so the link resolves against the project root from a referencing file at any depth and never as a path computed relative to the referencing file's own location. When the relevant obligation lives in a specific section or line range, name that section or line range in the link text and point the target's fragment at it.
|
|
@@ -360,7 +362,7 @@ If the validator wants to show its work, it does so in the body of its response
|
|
|
360
362
|
When the validator returns FAIL, enter the triage-then-fix loop:
|
|
361
363
|
|
|
362
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.
|
|
363
|
-
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
|
|
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.
|
|
364
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.
|
|
365
367
|
4. Rewrite the affected file(s) in place, addressing every enumerated issue.
|
|
366
368
|
5. Re-launch the validator (a new subagent in a fresh session when the subagent host is available) over the rewritten file(s).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const TOOL_NAMES: readonly ["claude", "codex"];
|
package/lib/toolNames.js
ADDED
package/lib/ui/BottomBlock.d.ts
CHANGED
|
@@ -9,20 +9,20 @@ export type BottomBlockIO = {
|
|
|
9
9
|
export type Activity = "implementing" | "reviewing" | "building" | "testing";
|
|
10
10
|
export type HeaderFields = {
|
|
11
11
|
indexLabel?: string | null;
|
|
12
|
+
phaseMessage?: string | null;
|
|
12
13
|
iteration?: number | null;
|
|
13
14
|
activity?: Activity | null;
|
|
14
15
|
taskNumber?: string | null;
|
|
15
16
|
title?: string | null;
|
|
16
17
|
};
|
|
18
|
+
export type MetricsPairFields = {
|
|
19
|
+
tokens: number;
|
|
20
|
+
anchorMs?: number;
|
|
21
|
+
baseSeconds: number;
|
|
22
|
+
};
|
|
17
23
|
export type MetricsFields = {
|
|
18
|
-
task?:
|
|
19
|
-
|
|
20
|
-
seconds: number;
|
|
21
|
-
};
|
|
22
|
-
plan?: {
|
|
23
|
-
tokens: number;
|
|
24
|
-
seconds: number;
|
|
25
|
-
};
|
|
24
|
+
task?: MetricsPairFields;
|
|
25
|
+
plan?: MetricsPairFields;
|
|
26
26
|
};
|
|
27
27
|
export type WaitKind = "rate-limit";
|
|
28
28
|
export type FooterState = {
|
|
@@ -58,6 +58,8 @@ export declare class BottomBlock {
|
|
|
58
58
|
private _countdownTimer;
|
|
59
59
|
private _unsubResize;
|
|
60
60
|
private _prevLineWidths;
|
|
61
|
+
private _metricsPausedAtMs;
|
|
62
|
+
private _metricsPauseAccumMs;
|
|
61
63
|
constructor(_io: BottomBlockIO, _time: TimeContext, _random: RandomContext);
|
|
62
64
|
mount(): void;
|
|
63
65
|
isFinalized(): boolean;
|
|
@@ -76,5 +78,6 @@ export declare class BottomBlock {
|
|
|
76
78
|
private _drawBlock;
|
|
77
79
|
private _renderHeader;
|
|
78
80
|
private _renderMetrics;
|
|
81
|
+
private _resolvePair;
|
|
79
82
|
private _renderFooter;
|
|
80
83
|
}
|
package/lib/ui/BottomBlock.js
CHANGED
|
@@ -31,6 +31,8 @@ class BottomBlock {
|
|
|
31
31
|
this._countdownTimer = null;
|
|
32
32
|
this._unsubResize = null;
|
|
33
33
|
this._prevLineWidths = null;
|
|
34
|
+
this._metricsPausedAtMs = null;
|
|
35
|
+
this._metricsPauseAccumMs = 0;
|
|
34
36
|
}
|
|
35
37
|
mount() {
|
|
36
38
|
if (this._disposed)
|
|
@@ -64,6 +66,7 @@ class BottomBlock {
|
|
|
64
66
|
if (this._disposed || this._finalized)
|
|
65
67
|
return;
|
|
66
68
|
this._metrics = fields;
|
|
69
|
+
this._metricsPauseAccumMs = 0;
|
|
67
70
|
if (this._mounted) {
|
|
68
71
|
this._clearBlock();
|
|
69
72
|
this._drawBlock();
|
|
@@ -73,6 +76,13 @@ class BottomBlock {
|
|
|
73
76
|
if (this._disposed || this._finalized)
|
|
74
77
|
return;
|
|
75
78
|
this._cancelTimers();
|
|
79
|
+
if (state.kind === "waiting") {
|
|
80
|
+
this._metricsPausedAtMs = this._time.now();
|
|
81
|
+
}
|
|
82
|
+
else if (this._metricsPausedAtMs !== null) {
|
|
83
|
+
this._metricsPauseAccumMs += this._time.now() - this._metricsPausedAtMs;
|
|
84
|
+
this._metricsPausedAtMs = null;
|
|
85
|
+
}
|
|
76
86
|
this._footer = state;
|
|
77
87
|
if (state.kind === "working") {
|
|
78
88
|
this._animFrame = 0;
|
|
@@ -213,11 +223,22 @@ class BottomBlock {
|
|
|
213
223
|
this._prevLineWidths = [cols, (0, formatters_1.stripAnsi)(header).length, (0, formatters_1.stripAnsi)(metrics).length, (0, formatters_1.stripAnsi)(footer).length];
|
|
214
224
|
}
|
|
215
225
|
_renderHeader(cols) {
|
|
216
|
-
var _a, _b, _c, _d, _e;
|
|
217
|
-
return (0, formatters_1.formatHeaderLine)((_a = this._header.indexLabel) !== null && _a !== void 0 ? _a : null, (_b = this._header.
|
|
226
|
+
var _a, _b, _c, _d, _e, _f;
|
|
227
|
+
return (0, formatters_1.formatHeaderLine)((_a = this._header.indexLabel) !== null && _a !== void 0 ? _a : null, (_b = this._header.phaseMessage) !== null && _b !== void 0 ? _b : null, (_c = this._header.iteration) !== null && _c !== void 0 ? _c : null, (_d = this._header.activity) !== null && _d !== void 0 ? _d : null, (_e = this._header.taskNumber) !== null && _e !== void 0 ? _e : null, (_f = this._header.title) !== null && _f !== void 0 ? _f : null, cols);
|
|
218
228
|
}
|
|
219
229
|
_renderMetrics(cols) {
|
|
220
|
-
return (0, formatters_1.formatMetricsLine)(this._metrics.task, this._metrics.plan, cols);
|
|
230
|
+
return (0, formatters_1.formatMetricsLine)(this._resolvePair(this._metrics.task), this._resolvePair(this._metrics.plan), cols);
|
|
231
|
+
}
|
|
232
|
+
_resolvePair(pair) {
|
|
233
|
+
var _a;
|
|
234
|
+
if (!pair)
|
|
235
|
+
return undefined;
|
|
236
|
+
if (pair.anchorMs === undefined) {
|
|
237
|
+
return { tokens: pair.tokens, seconds: pair.baseSeconds };
|
|
238
|
+
}
|
|
239
|
+
const evalNow = (_a = this._metricsPausedAtMs) !== null && _a !== void 0 ? _a : this._time.now();
|
|
240
|
+
const activeMs = Math.max(0, evalNow - pair.anchorMs - this._metricsPauseAccumMs);
|
|
241
|
+
return { tokens: pair.tokens, seconds: Math.floor(activeMs / 1000) + pair.baseSeconds };
|
|
221
242
|
}
|
|
222
243
|
_renderFooter(cols) {
|
|
223
244
|
switch (this._footer.kind) {
|
package/lib/ui/PromptHelper.d.ts
CHANGED
|
@@ -10,5 +10,12 @@ export type AskTextArgs = Readonly<{
|
|
|
10
10
|
placeholder?: string;
|
|
11
11
|
default?: string;
|
|
12
12
|
}>;
|
|
13
|
+
export type AskMultiChoiceArgs = Readonly<{
|
|
14
|
+
header: string;
|
|
15
|
+
question: string;
|
|
16
|
+
options: readonly ChoiceOption[];
|
|
17
|
+
selected?: readonly ChoiceOption[];
|
|
18
|
+
}>;
|
|
13
19
|
export declare function askChoice(ask: AskContext, args: AskChoiceArgs, output?: OutputContext): Promise<ChoiceOption>;
|
|
20
|
+
export declare function askMultiChoice(ask: AskContext, args: AskMultiChoiceArgs, output?: OutputContext): Promise<readonly ChoiceOption[]>;
|
|
14
21
|
export declare function askText(ask: AskContext, args: AskTextArgs): Promise<string>;
|
package/lib/ui/PromptHelper.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.askChoice = askChoice;
|
|
4
|
+
exports.askMultiChoice = askMultiChoice;
|
|
4
5
|
exports.askText = askText;
|
|
5
6
|
function abortError() {
|
|
6
7
|
const e = new Error("Aborted");
|
|
@@ -23,6 +24,27 @@ async function askChoice(ask, args, output) {
|
|
|
23
24
|
}
|
|
24
25
|
return answer.picked[0];
|
|
25
26
|
}
|
|
27
|
+
async function askMultiChoice(ask, args, output) {
|
|
28
|
+
var _a;
|
|
29
|
+
const selectedLabels = new Set(((_a = args.selected) !== null && _a !== void 0 ? _a : []).map(o => o.label));
|
|
30
|
+
const defaultIndexes = [];
|
|
31
|
+
for (let i = 0; i < args.options.length; i++) {
|
|
32
|
+
if (selectedLabels.has(args.options[i].label)) {
|
|
33
|
+
defaultIndexes.push(i);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const [answer] = await ask.askChoices([{
|
|
37
|
+
header: args.header,
|
|
38
|
+
question: args.question,
|
|
39
|
+
options: args.options,
|
|
40
|
+
multiSelect: true,
|
|
41
|
+
defaultIndexes: defaultIndexes.length > 0 ? defaultIndexes : undefined
|
|
42
|
+
}], output);
|
|
43
|
+
if (!answer || answer.picked.length === 0) {
|
|
44
|
+
throw abortError();
|
|
45
|
+
}
|
|
46
|
+
return answer.picked;
|
|
47
|
+
}
|
|
26
48
|
async function askText(ask, args) {
|
|
27
49
|
const prompt = args.placeholder
|
|
28
50
|
? `${args.question} (${args.placeholder}): `
|
package/lib/ui/formatters.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ToolName } from "../ai/ToolAdapter";
|
|
1
2
|
export declare const CYAN = "\u001B[36m";
|
|
2
3
|
export declare const YELLOW = "\u001B[33m";
|
|
3
4
|
export declare const MAGENTA = "\u001B[35m";
|
|
@@ -23,7 +24,7 @@ export declare function formatDateTime(date: Date): string;
|
|
|
23
24
|
export declare function truncateToWidth(text: string, cols: number): string;
|
|
24
25
|
export declare function formatTokens(n: number): string;
|
|
25
26
|
export declare function formatActiveTime(seconds: number): string;
|
|
26
|
-
export declare function formatHeaderLine(indexLabel: string | null, iteration: number | null, activity: string | null, taskNumber: string | null | undefined, title: string | null, cols: number): string;
|
|
27
|
+
export declare function formatHeaderLine(indexLabel: string | null, phaseMessage: string | null, iteration: number | null, activity: string | null, taskNumber: string | null | undefined, title: string | null, cols: number): string;
|
|
27
28
|
export type MetricsPair = {
|
|
28
29
|
tokens: number;
|
|
29
30
|
seconds: number;
|
|
@@ -32,7 +33,7 @@ export declare function formatMetricsLine(task: MetricsPair | undefined, plan: M
|
|
|
32
33
|
export declare function formatSnapshotHeader(indexLabel: string, iteration: number, taskNumber: string | undefined, title: string): string;
|
|
33
34
|
export declare function formatSnapshotMetrics(taskTokens: number, taskSeconds: number, planTokens: number, planSeconds: number): string;
|
|
34
35
|
export declare function formatSnapshotBlock(indexLabel: string, iteration: number, taskNumber: string | undefined, title: string, taskTokens: number, taskSeconds: number, planTokens: number, planSeconds: number, cols: number): string;
|
|
35
|
-
export type ReviewerTool =
|
|
36
|
+
export type ReviewerTool = ToolName;
|
|
36
37
|
export type ReviewerState = "running" | "waiting" | "pass" | "fail";
|
|
37
38
|
export type ReviewerEntry = {
|
|
38
39
|
tool: ReviewerTool;
|