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.
- package/README.md +245 -0
- package/lib/Flanders.d.ts +2 -1
- package/lib/Flanders.js +7 -0
- package/lib/cli.js +10 -1
- package/lib/commands/Implement.d.ts +3 -1
- package/lib/commands/Implement.js +32 -23
- package/lib/commands/Install.d.ts +0 -1
- package/lib/commands/Install.js +131 -114
- package/lib/commands/Update.d.ts +19 -0
- package/lib/commands/Update.js +90 -0
- package/lib/commands/skillArtifacts.d.ts +18 -0
- package/lib/commands/skillArtifacts.js +93 -0
- package/lib/contexts.d.ts +1 -0
- package/lib/plan/PlanFile.d.ts +8 -5
- package/lib/plan/PlanFile.js +85 -16
- package/lib/prompts/prompts.d.ts +11 -1
- package/lib/prompts/prompts.js +42 -5
- package/lib/prompts/skills.d.ts +1 -1
- package/lib/prompts/skills.js +43 -17
- package/lib/ui/BottomBlock.d.ts +7 -3
- package/lib/ui/BottomBlock.js +47 -11
- package/lib/ui/PromptHelper.d.ts +2 -0
- package/lib/ui/PromptHelper.js +11 -2
- package/lib/ui/formatters.d.ts +8 -3
- package/lib/ui/formatters.js +67 -39
- package/lib/voiceVariants.d.ts +15 -0
- package/lib/voiceVariants.js +141 -0
- package/lib/workspace/FlandersConfig.d.ts +4 -3
- package/lib/workspace/FlandersConfig.js +18 -0
- package/lib/workspace/Workspace.d.ts +2 -0
- package/lib/workspace/Workspace.js +3 -1
- package/package.json +1 -1
package/lib/prompts/skills.js
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
|
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
|
-
-
|
|
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
|
|
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/
|
|
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
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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")}`;
|
package/lib/ui/BottomBlock.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/lib/ui/BottomBlock.js
CHANGED
|
@@ -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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
237
|
+
return (0, formatters_1.formatTerminalFooter)(this._footer.text, cols);
|
|
202
238
|
}
|
|
203
239
|
}
|
|
204
240
|
}
|
package/lib/ui/PromptHelper.d.ts
CHANGED
|
@@ -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>;
|
package/lib/ui/PromptHelper.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/lib/ui/formatters.d.ts
CHANGED
|
@@ -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" | "
|
|
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;
|