flanders 0.6.1 → 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.
@@ -3,6 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.workSkillBody = exports.specSkillBody = exports.planSkillBody = void 0;
4
4
  const PlanFile_1 = require("../plan/PlanFile");
5
5
  const prompts_1 = require("./prompts");
6
+ function skillVoiceSection(authoredArtifactExclusion) {
7
+ return (0, prompts_1.buildFlandersVoiceSection)({
8
+ subject: "the messages you address to the user — your questions, summaries, warnings, recommendations, and every other text you print in the conversation",
9
+ languageFraming: "in the resolved interaction language you are already addressing the user in",
10
+ finalExclusion: `, or ${authoredArtifactExclusion}`,
11
+ trailer: ""
12
+ });
13
+ }
6
14
  exports.planSkillBody = `---
7
15
  description: Produce a contract-aware work plan inside the project's plans/ folder.
8
16
  ---
@@ -23,13 +31,13 @@ The user invokes you as: /flanders-plan [<data>]
23
31
  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.
24
32
 
25
33
  **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.
26
- 3. **Clarification phase.** Ask the user clarifying questions only when the question targets one of three things: an implementation choice in the code the tasks will produce that the request does not specify, or a task-scope ambiguity you cannot reasonably infer from the request or from the canonical contracts and rules, or a load-bearing runtime-behavior premise the plan would otherwise have to assert without backing (see the plan content rules below). Any other doubt is resolved silently: pick the most reasonable default and proceed, documenting the choice in the relevant task's description when it is plan-local and load-bearing. Permitted questions are asked sequentially — one question per turn, multiple-choice preferred when the answer space is bounded.
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. Permitted questions are asked sequentially — one question per turn, multiple-choice preferred when the answer space is bounded.
27
35
 
28
36
  When the doubt is about how the code should be implemented, resolve it through one of two outcomes:
29
37
  - **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.
30
38
  - **Plan-local implementation choice** — the answer is specific to the requested work and does not generalize. The chosen answer is embedded in the relevant task's description and acceptance criteria, and is never promoted to a rule.
31
39
 
32
- The skill itself never writes to any \`.spec/rules\` or \`.spec/contracts\` folder. Rule creation, when the user elects it, happens through /flanders-spec as a separate, user-initiated act.
40
+ The skill itself never writes to any \`.spec/rules\`, \`.spec/contracts\`, or \`.spec/flanders\` folder. Rule creation, when the user elects it, happens through /flanders-spec as a separate, user-initiated act.
33
41
  4. **Drafting phase.** Once the clarification phase is complete, persist the plan file directly without presenting a layout summary, a section-by-section draft, or any other pre-write approval step. The user reviews the written plan file after the fact.
34
42
  5. Persist exactly one markdown file inside the project's plans/ folder. The filename must be descriptive of the plan's subject.
35
43
  6. Upon successful completion, print the summary described in the Summary section below. If the plan cannot be made compliant with the Plan content rules, do not declare complete: surface the issue along with the plan file path to the user in chat.
@@ -84,8 +92,10 @@ Tasks are written in the order they must be implemented, accounting for dependen
84
92
 
85
93
  ### Plan content rules
86
94
 
87
- - The persisted plan is free of placeholders, contradictions with existing contracts or rules, ambiguous task wording, missing acceptance criteria on leaf tasks, and missing contract or rule links on leaf tasks.
95
+ - 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.
88
96
  - 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 freezing a specific code element to reuse or to leave untouched.
98
+ - 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.
89
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.
90
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.
91
101
  - The plan only references contracts and rules that exist in the canonical state captured at invocation.
@@ -98,7 +108,7 @@ Tasks are written in the order they must be implemented, accounting for dependen
98
108
  - For every leaf task, link the relevant rule file or files the same way — a markdown link whose text is the file's listed namespace (its path relative to the project root, with no leading slash) and whose target is that namespace prefixed with a single leading slash. The planner MUST read every rule file it determines is relevant to the request before drafting the plan; reading the relevant rules is not optional. When a rule's enforcement is bound to a specific scope, reference that scope alongside the file path.
99
109
  - Rule selection per task is scope-driven, not topic-driven. Before listing the rule links for a leaf task, walk the rules listing and ask: which rule namespaces are in scope for the work this task actually performs? Use the namespace as the scope hint. Heuristics: a task that modifies or adds tests must link every applicable rule under a \`testing/\` subfolder; a task that creates or modifies anything with timers, listeners, controllers, child processes, or other async lifecycle must link every applicable rule under a \`disposables/\` subfolder; a task that changes terminal UI or live-region output must link every applicable rule under a \`ui/\` subfolder. Walk every namespace whose scope could plausibly apply, and pick every file whose obligation could be triggered by the task. Under-linking is costly: the downstream implementor is FAILed by the adversarial reviewer for any global rule that should have applied but was not applied, so when in doubt, link rather than omit.
100
110
  - Tasks are numbered hierarchically (1, 1.1, 1.2, 2, 2.1, ...) per the Plan file format section above.
101
- - No task may describe work that creates, modifies, deletes, or renames files inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder (the bounded checkbox/metrics update that the implement command holds is not available to tasks).
111
+ - No task may describe work that creates, modifies, deletes, or renames files inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, any \`.spec/flanders\` folder, or the \`plans/\` folder (the bounded checkbox/metrics update that the implement command holds is not available to tasks).
102
112
  - Never produce a plan that violates any contract or rule on the canonical lists.
103
113
 
104
114
  ## Post-write verification
@@ -146,13 +156,13 @@ Five categories, all mandatory; failure in any one is a FAIL. Each category is a
146
156
 
147
157
  2. Semantic dependency order. Tasks appear top-to-bottom in implementation order. The audit is semantic, not numeric: read each task's description and acceptance criteria and confirm that no task depends on work performed by a task that appears later in the document. A plan whose numbering is well-formed but whose dependencies flow upward is FAIL.
148
158
 
149
- 3. Spec-folder write boundary. No task (leaf or parent) describes work that creates, modifies, deletes, or renames any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. There is no exception for flipping checkboxes or rewriting metrics: those mutations are performed programmatically by the implement command and are never described by a task.
159
+ 3. Spec-folder write boundary. No task (leaf or parent) describes work that creates, modifies, deletes, or renames any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, any \`.spec/flanders\` folder, or the \`plans/\` folder. There is no exception for flipping checkboxes or rewriting metrics: those mutations are performed programmatically by the implement command and are never described by a task.
150
160
 
151
161
  4. Plan content rules. Verify the plan satisfies EACH of the following independently:
152
162
  - Free of placeholders. No \`<TBD>\` or analogous task markers, no template-style blanks, no parenthetical "(to be decided)" deferrals.
153
163
  - Free of contradictions with existing contracts or rules. No task pins behavior the canonical listings forbid.
154
164
  - 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.
155
- - Free of ambiguous task wording. Open-ended decisions deferred to the implementer 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 implementation choice that the request did not specify must be either (a) closed to a single concrete value in the task's description and acceptance criteria, or (b) escalated by the skill to the user before the plan was drafted — never left open for the worker to resolve.
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.
156
166
  - Every leaf task carries an explicit acceptance-criteria section.
157
167
  - 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.
158
168
  - 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.
@@ -179,7 +189,7 @@ If the validator wants to show its work, it does so in the body of its response
179
189
 
180
190
  When the validator returns FAIL, enter the triage-then-fix loop:
181
191
 
182
- 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 in the code the tasks will produce 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.
192
+ 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.
183
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 mechanics — 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.
184
194
  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.
185
195
  4. Rewrite the plan file in place, addressing every enumerated issue.
@@ -206,14 +216,16 @@ Write the plan file in the same natural language as the input request, unless th
206
216
 
207
217
  Every message you address to the user during the run — your clarifying questions, the recommendation to create a rule via /flanders-spec, the warnings printed when the project has no contracts or no rules, the end-of-run summary, and any other text you print in chat — 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 plan file you write.
208
218
 
219
+ ${skillVoiceSection("the plan file you author")}
220
+
209
221
  ## Missing contracts or rules
210
222
 
211
223
  If no \`.spec/contracts\` folder contains any file, warn the user in chat and produce a plan that includes whatever contracts the request implicitly requires before any implementation work. If no \`.spec/rules\` folder contains any file, warn the user in chat and proceed without rule references on the resulting tasks.`;
212
224
  exports.specSkillBody = `---
213
- description: Translate a free-form request into one or more spec markdown files inside the project's .spec/contracts and .spec/rules folders.
225
+ description: Translate a free-form request into one or more spec markdown files inside the project's .spec/contracts, .spec/rules, and .spec/flanders folders.
214
226
  ---
215
227
 
216
- You 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.
228
+ You are the /flanders-spec skill. Your sole deliverable is one or more markdown files inside the project's \`.spec/contracts\`, \`.spec/rules\`, and \`.spec/flanders\` folders. You must not write, modify, or delete any source code or any file outside the project's \`.spec/contracts\`, \`.spec/rules\`, and \`.spec/flanders\` folders.
217
229
 
218
230
  ## Input resolution
219
231
 
@@ -239,9 +251,15 @@ The namespace of a rule is its path relative to the project root. The namespace
239
251
 
240
252
  Rules are immovable once written unless the user explicitly asks for a change.
241
253
 
242
- ## Contract vs rule: how the skill classifies and places
254
+ ## What a behavior rule is
255
+
256
+ 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
+ Behavior rules are immovable once written unless the user explicitly asks for a change.
259
+
260
+ ## Contract, rule, or behavior rule: how the skill classifies and places
243
261
 
244
- For 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 — 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 — the user reviews and approves them in the drafting phase before anything is persisted.
262
+ For every obligation in the request, the skill decides whether it is a contract, a rule, or a behavior rule and which \`.spec\` folder it belongs to: public behavior across a scope's boundary is a contract, internal implementation guidance is a rule, guidance that governs how Flanders' own commands and skills behave within a scope is a behavior rule, and the spec lands in the \`.spec\` folder of the lowest directory that encloses all the code its obligation governs — 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 contract is written to the chosen scope's \`.spec/contracts\` folder, a rule to its \`.spec/rules\` folder, and a behavior rule to its \`.spec/flanders\` 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 more than one kind 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 — the user reviews and approves them in the drafting phase before anything is persisted.
245
263
 
246
264
  ## Procedure
247
265
 
@@ -260,6 +278,7 @@ For every obligation in the request, the skill decides whether it is a contract
260
278
  - 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.
261
279
  - 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.
262
280
  - 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.
281
+ - Write each paragraph of prose 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.
263
282
  6. 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.
264
283
  7. Organize the resulting files in whichever shape best fits the content:
265
284
  - 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).
@@ -295,7 +314,7 @@ The validator reads the file(s) in full, plus any contract or rule from the list
295
314
 
296
315
  ### Validator checks
297
316
 
298
- Three 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.
317
+ Three 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. A file that landed in a \`.spec/flanders\` folder is audited by the non-contradiction category C only; categories A and B audit files in \`.spec/contracts\` and \`.spec/rules\` folders respectively.
299
318
 
300
319
  **A. Contract artifacts (each file written or updated under a \`.spec/contracts\` folder)**
301
320
 
@@ -373,9 +392,11 @@ Do not translate already-written content; the resolved language governs only the
373
392
 
374
393
  Every message you address to the user during the run — your clarifying questions, the approach trade-off summaries, the drafting-phase layout summary, and any other text you print in chat — 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.
375
394
 
395
+ ${skillVoiceSection("the contract and rule files you author")}
396
+
376
397
  ## Idempotency and overwrites
377
398
 
378
- Existing 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).`;
399
+ Existing files in the project's \`.spec/contracts\`, \`.spec/rules\`, and \`.spec/flanders\` folders are not protected. Because you receive the current state of those 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).`;
379
400
  exports.workSkillBody = `---
380
401
  description: Carry a single self-contained piece of work from request to reviewed completion in the current session, gating the result through one adversarial reviewer subagent, without authoring a plan or running the implement pipeline.
381
402
  ---
@@ -413,7 +434,7 @@ Proceed to the review only once the build and test gates have both passed; a gat
413
434
 
414
435
  ## Spec-folder write boundary
415
436
 
416
- Neither the work you perform nor the reviewer subagent creates, modifies, deletes, or renames any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, or the \`plans/\` folder. Those folders are the project's source of truth, governed by their own dedicated skills; consult them freely but never write to them.
437
+ Neither the work you perform nor the reviewer subagent creates, modifies, deletes, or renames any file inside any \`.spec/contracts\` folder, any \`.spec/rules\` folder, any \`.spec/flanders\` folder, or the \`plans/\` folder. Those folders are the project's source of truth, governed by their own dedicated skills; consult them freely but never write to them.
417
438
 
418
439
  ## The reviewer
419
440
 
@@ -429,17 +450,20 @@ You may fall back to running the review inline in this same session only when th
429
450
 
430
451
  ### The reviewer's prompt
431
452
 
432
- Assemble the reviewer subagent's prompt as four parts, in order:
453
+ Assemble the reviewer subagent's prompt as five parts, in order:
433
454
 
434
455
  1. A framing that states the spec under review is the user's request that you implemented, quoting or summarizing that request so the reviewer can measure the work against it.
435
456
  2. The available contracts, the available rules, and the available behavior rules as lists of their namespaces (each namespace is a path relative to the project root), discovered across the project's \`.spec\` folders, placed above the methodology so the methodology's references to "the global lists above" and "the behavior-rule list above" resolve.
436
457
  3. The absolute path of the error-log file you provisioned for this round — this is the "error-log file" the methodology refers to — with the instruction to record its verdict there.
437
458
  4. The methodology below, verbatim.
459
+ 5. The narration-only tone instruction below, verbatim.
438
460
 
439
- The methodology is self-contained: keep it that way and add no citation to any file inside the project's spec folders.
461
+ The methodology and the tone instruction are both self-contained: keep them that way and add no citation to any file inside the project's spec folders.
440
462
 
441
463
  ${prompts_1.reviewerMethodologyCore}
442
464
 
465
+ ${(0, prompts_1.flandersToneInstruction)(true)}
466
+
443
467
  The reviewer is inspection-only. Its prompt also states the boundaries every Flanders subagent honors:
444
468
 
445
469
  - **Git.** It runs only read-only git commands (\`git status\`, \`git diff\`, \`git log\`, \`git show\`, \`git blame\`, \`git ls-files\`) to inspect the change set, and never a command that mutates repository state — no \`git add\`, \`git commit\`, \`git stash\`, \`git reset\`, \`git restore\`, \`git checkout\`, \`git branch\`, \`git tag\`, \`git rebase\`, \`git merge\`, \`git cherry-pick\`, no edits under \`.git/\`, and no remote operations.
@@ -471,4 +495,6 @@ Then report completion to the user in chat.
471
495
 
472
496
  ## Interaction language
473
497
 
474
- Every message you address to the user during the run — your progress messages, the summary you print when the work is done or when you surface a review that keeps failing, and any other text you print in chat — 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 code you write: it governs only what you say to the user in the conversation, never the language or content of the code you produce.`;
498
+ Every message you address to the user during the run — your progress messages, the summary you print when the work is done or when you surface a review that keeps failing, and any other text you print in chat — 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 code you write: it governs only what you say to the user in the conversation, never the language or content of the code you produce.
499
+
500
+ ${skillVoiceSection("the code you write")}`;
@@ -1,4 +1,4 @@
1
- import type { TimeContext } from "../contexts";
1
+ import type { RandomContext, TimeContext } from "../contexts";
2
2
  import type { ReviewerEntry } from "./formatters";
3
3
  export type { ReviewerEntry, ReviewerState, ReviewerTool } from "./formatters";
4
4
  export type BottomBlockIO = {
@@ -38,12 +38,13 @@ export type FooterState = {
38
38
  reviewers: readonly ReviewerEntry[];
39
39
  } | {
40
40
  kind: "terminal";
41
- label: TerminalLabel;
41
+ text: string;
42
42
  };
43
43
  export type TerminalLabel = "Done" | "Hard stop" | "Interrupted" | "Failed";
44
44
  export declare class BottomBlock {
45
45
  private _io;
46
46
  private _time;
47
+ private _random;
47
48
  private _mounted;
48
49
  private _finalized;
49
50
  private _disposed;
@@ -51,11 +52,13 @@ export declare class BottomBlock {
51
52
  private _metrics;
52
53
  private _footer;
53
54
  private _animFrame;
55
+ private _workingLabel;
54
56
  private _animTimer;
57
+ private _labelTimer;
55
58
  private _countdownTimer;
56
59
  private _unsubResize;
57
60
  private _prevLineWidths;
58
- constructor(_io: BottomBlockIO, _time: TimeContext);
61
+ constructor(_io: BottomBlockIO, _time: TimeContext, _random: RandomContext);
59
62
  mount(): void;
60
63
  isFinalized(): boolean;
61
64
  setHeader(fields: HeaderFields): void;
@@ -67,6 +70,7 @@ export declare class BottomBlock {
67
70
  private _cancelTimers;
68
71
  private _startFooterTimer;
69
72
  private _scheduleAnimTick;
73
+ private _scheduleLabelTick;
70
74
  private _scheduleCountdownTick;
71
75
  private _clearBlock;
72
76
  private _drawBlock;
@@ -1,20 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BottomBlock = void 0;
4
+ const voiceVariants_1 = require("../voiceVariants");
4
5
  const formatters_1 = require("./formatters");
5
6
  const WAIT_HEADINGS = {
6
7
  "rate-limit": "Waiting rate limit"
7
8
  };
8
9
  const FRAMES = ["⣋", "⣙", "⣹", "⣸", "⣼", "⣴", "⣦", "⣧", "⣇", "⣏"];
9
10
  const FRAME_MS = 200;
11
+ const LABEL_MS = 9000;
10
12
  const AUTOWRAP_OFF = "\x1b[?7l";
11
13
  const AUTOWRAP_ON = "\x1b[?7h";
12
14
  const CR = "\r";
13
15
  const CLEAR_TO_END = "\x1b[J";
14
16
  class BottomBlock {
15
- constructor(_io, _time) {
17
+ constructor(_io, _time, _random) {
16
18
  this._io = _io;
17
19
  this._time = _time;
20
+ this._random = _random;
18
21
  this._mounted = false;
19
22
  this._finalized = false;
20
23
  this._disposed = false;
@@ -22,7 +25,9 @@ class BottomBlock {
22
25
  this._metrics = {};
23
26
  this._footer = { kind: "working" };
24
27
  this._animFrame = 0;
28
+ this._workingLabel = "";
25
29
  this._animTimer = null;
30
+ this._labelTimer = null;
26
31
  this._countdownTimer = null;
27
32
  this._unsubResize = null;
28
33
  this._prevLineWidths = null;
@@ -33,6 +38,9 @@ class BottomBlock {
33
38
  if (this._mounted)
34
39
  return;
35
40
  this._mounted = true;
41
+ if (this._footer.kind === "working") {
42
+ this._workingLabel = (0, voiceVariants_1.pickVariant)(voiceVariants_1.workingPool, this._random);
43
+ }
36
44
  this._unsubResize = this._io.onResize(() => {
37
45
  this._clearBlock();
38
46
  this._drawBlock();
@@ -68,6 +76,10 @@ class BottomBlock {
68
76
  this._footer = state;
69
77
  if (state.kind === "working") {
70
78
  this._animFrame = 0;
79
+ this._workingLabel = (0, voiceVariants_1.pickVariant)(voiceVariants_1.workingPool, this._random);
80
+ }
81
+ else if (state.kind === "reviewing") {
82
+ this._animFrame = 0;
71
83
  }
72
84
  if (this._mounted) {
73
85
  this._clearBlock();
@@ -93,7 +105,7 @@ class BottomBlock {
93
105
  return;
94
106
  this._finalized = true;
95
107
  this._cancelTimers();
96
- this._footer = { kind: "terminal", label };
108
+ this._footer = { kind: "terminal", text: (0, voiceVariants_1.pickVariant)(voiceVariants_1.terminalPools[label], this._random) };
97
109
  if (this._unsubResize) {
98
110
  this._unsubResize();
99
111
  this._unsubResize = null;
@@ -119,23 +131,36 @@ class BottomBlock {
119
131
  this._animTimer.cancel();
120
132
  this._animTimer = null;
121
133
  }
134
+ if (this._labelTimer) {
135
+ this._labelTimer.cancel();
136
+ this._labelTimer = null;
137
+ }
122
138
  if (this._countdownTimer) {
123
139
  this._countdownTimer.cancel();
124
140
  this._countdownTimer = null;
125
141
  }
126
142
  }
127
143
  _startFooterTimer() {
128
- if (this._footer.kind === "working") {
129
- this._scheduleAnimTick();
130
- }
131
- else if (this._footer.kind === "waiting") {
132
- this._scheduleCountdownTick();
144
+ switch (this._footer.kind) {
145
+ case "working":
146
+ this._scheduleAnimTick();
147
+ this._scheduleLabelTick();
148
+ break;
149
+ case "reviewing":
150
+ this._scheduleAnimTick();
151
+ break;
152
+ case "waiting":
153
+ this._scheduleCountdownTick();
154
+ break;
155
+ case "blank":
156
+ case "terminal":
157
+ break;
133
158
  }
134
159
  }
135
160
  _scheduleAnimTick() {
136
161
  this._animTimer = this._time.setTimeout(() => {
137
162
  this._animTimer = null;
138
- if (this._disposed || this._finalized || this._footer.kind !== "working")
163
+ if (this._disposed || this._finalized || (this._footer.kind !== "working" && this._footer.kind !== "reviewing"))
139
164
  return;
140
165
  this._animFrame = (this._animFrame + 1) % FRAMES.length;
141
166
  this._clearBlock();
@@ -143,6 +168,17 @@ class BottomBlock {
143
168
  this._scheduleAnimTick();
144
169
  }, FRAME_MS);
145
170
  }
171
+ _scheduleLabelTick() {
172
+ this._labelTimer = this._time.setTimeout(() => {
173
+ this._labelTimer = null;
174
+ if (this._disposed || this._finalized || this._footer.kind !== "working")
175
+ return;
176
+ this._workingLabel = (0, voiceVariants_1.pickVariant)(voiceVariants_1.workingPool, this._random, this._workingLabel);
177
+ this._clearBlock();
178
+ this._drawBlock();
179
+ this._scheduleLabelTick();
180
+ }, LABEL_MS);
181
+ }
146
182
  _scheduleCountdownTick() {
147
183
  this._countdownTimer = this._time.setTimeout(() => {
148
184
  this._countdownTimer = null;
@@ -188,7 +224,7 @@ class BottomBlock {
188
224
  case "blank":
189
225
  return "";
190
226
  case "working":
191
- return (0, formatters_1.formatWorkingFooter)(FRAMES[this._animFrame], cols);
227
+ return (0, formatters_1.formatWorkingFooter)(FRAMES[this._animFrame], this._workingLabel, cols);
192
228
  case "waiting": {
193
229
  const remaining = Math.max(0, this._footer.endTime - this._time.now());
194
230
  const dateStr = (0, formatters_1.formatDateTime)(new Date(this._footer.endTime));
@@ -196,9 +232,9 @@ class BottomBlock {
196
232
  return (0, formatters_1.formatWaitingFooter)(WAIT_HEADINGS[this._footer.waitKind], dateStr, countdown, cols);
197
233
  }
198
234
  case "reviewing":
199
- return (0, formatters_1.formatReviewingFooter)(this._footer.reviewers, cols);
235
+ return (0, formatters_1.formatReviewingFooter)(FRAMES[this._animFrame], this._footer.reviewers, cols, this._time.now());
200
236
  case "terminal":
201
- return `${formatters_1.ORANGE}${this._footer.label}${formatters_1.RESET}`;
237
+ return (0, formatters_1.formatTerminalFooter)(this._footer.text, cols);
202
238
  }
203
239
  }
204
240
  }
@@ -3,10 +3,12 @@ export type AskChoiceArgs = Readonly<{
3
3
  header: string;
4
4
  question: string;
5
5
  options: readonly ChoiceOption[];
6
+ defaultLabel?: string;
6
7
  }>;
7
8
  export type AskTextArgs = Readonly<{
8
9
  question: string;
9
10
  placeholder?: string;
11
+ default?: string;
10
12
  }>;
11
13
  export declare function askChoice(ask: AskContext, args: AskChoiceArgs, output?: OutputContext): Promise<ChoiceOption>;
12
14
  export declare function askText(ask: AskContext, args: AskTextArgs): Promise<string>;
@@ -8,11 +8,15 @@ function abortError() {
8
8
  return e;
9
9
  }
10
10
  async function askChoice(ask, args, output) {
11
+ const matchIndex = args.defaultLabel !== undefined
12
+ ? args.options.findIndex(o => o.label === args.defaultLabel)
13
+ : -1;
11
14
  const [answer] = await ask.askChoices([{
12
15
  header: args.header,
13
16
  question: args.question,
14
17
  options: args.options,
15
- multiSelect: false
18
+ multiSelect: false,
19
+ defaultIndex: matchIndex >= 0 ? matchIndex : undefined
16
20
  }], output);
17
21
  if (!answer || answer.picked.length === 0) {
18
22
  throw abortError();
@@ -23,10 +27,15 @@ async function askText(ask, args) {
23
27
  const prompt = args.placeholder
24
28
  ? `${args.question} (${args.placeholder}): `
25
29
  : `${args.question}: `;
30
+ let value;
26
31
  try {
27
- return await ask.askText(prompt);
32
+ value = await ask.askText(prompt);
28
33
  }
29
34
  catch {
30
35
  throw abortError();
31
36
  }
37
+ if (value === "" && args.default !== undefined) {
38
+ return args.default;
39
+ }
40
+ return value;
32
41
  }
@@ -2,6 +2,7 @@ export declare const CYAN = "\u001B[36m";
2
2
  export declare const YELLOW = "\u001B[33m";
3
3
  export declare const MAGENTA = "\u001B[35m";
4
4
  export declare const GREEN = "\u001B[32m";
5
+ export declare const RED = "\u001B[31m";
5
6
  export declare const BLUE = "\u001B[34m";
6
7
  export declare const DIM = "\u001B[2m";
7
8
  export declare const ORANGE = "\u001B[38;5;208m";
@@ -14,8 +15,10 @@ export type Segment = {
14
15
  color?: string;
15
16
  };
16
17
  export declare function renderSegments(segments: Segment[]): string;
18
+ export declare function segmentsWidth(segments: readonly Segment[]): number;
17
19
  export declare function renderSegmentsToWidth(segments: Segment[], cols: number): string;
18
20
  export declare function formatCountdown(remainingMs: number): string;
21
+ export declare function formatCompactCountdown(remainingMs: number): string;
19
22
  export declare function formatDateTime(date: Date): string;
20
23
  export declare function truncateToWidth(text: string, cols: number): string;
21
24
  export declare function formatTokens(n: number): string;
@@ -30,13 +33,15 @@ export declare function formatSnapshotHeader(indexLabel: string, iteration: numb
30
33
  export declare function formatSnapshotMetrics(taskTokens: number, taskSeconds: number, planTokens: number, planSeconds: number): string;
31
34
  export declare function formatSnapshotBlock(indexLabel: string, iteration: number, taskNumber: string | undefined, title: string, taskTokens: number, taskSeconds: number, planTokens: number, planSeconds: number, cols: number): string;
32
35
  export type ReviewerTool = "claude" | "codex";
33
- export type ReviewerState = "running" | "waiting" | "ok" | "fail";
36
+ export type ReviewerState = "running" | "waiting" | "pass" | "fail";
34
37
  export type ReviewerEntry = {
35
38
  tool: ReviewerTool;
36
39
  model: string;
37
40
  effort: string;
38
41
  state: ReviewerState;
42
+ endTime?: number;
39
43
  };
40
- export declare function formatReviewingFooter(reviewers: readonly ReviewerEntry[], cols: number): string;
41
- export declare function formatWorkingFooter(frame: string, cols: number): string;
44
+ export declare function formatReviewingFooter(frame: string, reviewers: readonly ReviewerEntry[], cols: number, nowMs: number): string;
45
+ export declare function formatWorkingFooter(frame: string, label: string, cols: number): string;
46
+ export declare function formatTerminalFooter(label: string, cols: number): string;
42
47
  export declare function formatWaitingFooter(heading: string, dateTime: string, countdown: string, cols: number): string;