flanders 0.6.1 → 0.8.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 +241 -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/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Motivation
|
|
2
|
+
|
|
3
|
+
Hi-diddly-ho, neighbor! I am a Node.js toolkit that helps avoid AI drifting. I was sick of the AI not reading the `CLAUDE.md`, avoiding rules and forgetting what I told it 3 prompts ago. You know that I, Ned Flanders, will never break a rule, so it was natural for me to spawn here at some point. I will help you define a specification, the public contracts and private rules, and then I will orchestrate the AI for you to implement and review anything without breaking the specification. I have no problem hitting the AI with a stick when it drifts.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [How it works](#how-it-works)
|
|
8
|
+
- [Requirements](#requirements)
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Updating](#updating)
|
|
11
|
+
- [Configuration](#configuration)
|
|
12
|
+
- [Usage](#usage)
|
|
13
|
+
- [A worked example](#a-worked-example)
|
|
14
|
+
|
|
15
|
+
## How it works
|
|
16
|
+
|
|
17
|
+
With Flanders you first define a specification that will be stored in a `.spec` folder alongside where the specification applies. Then Flanders will help you to create a plan divided into tasks, and via CLI you then implement the plan.
|
|
18
|
+
|
|
19
|
+
Flanders will implement one task at a time, and then will launch, in parallel, as many validators as you have configured. If something differs from the specification, they will notify the worker to review the problem and fix it.
|
|
20
|
+
|
|
21
|
+
I'm not gonna lie, it is token hungry, but that's what you get when you want to reduce specification drifting. The AI has to iterate and validate multiple times what it is doing to avoid the typical "Ah, you are right! You already told me that". Still, Flanders has rate limit detection and retries, so you can let an implementation run overnight while you sleep, without worrying about the limits.
|
|
22
|
+
|
|
23
|
+
The whole neighborly cycle runs **spec → plan → implement**:
|
|
24
|
+
|
|
25
|
+
1. Capture obligations and conventions as contracts and rules in the spec corpus (with `/flanders-spec`).
|
|
26
|
+
2. Derive an ordered work plan from them under `plans/` (with `/flanders-plan`).
|
|
27
|
+
3. Implement the plan task by task, gating each result through build, test, and adversarial review (with `flanders implement`).
|
|
28
|
+
|
|
29
|
+
For a small, self-contained change that doesn't need a whole plan, there's a friendly shortcut: **`/flanders-work`** implements the request directly and gates it through the same build, test, and adversarial review, all in one invocation.
|
|
30
|
+
|
|
31
|
+
Underneath, Flanders keeps a tidy little spec corpus alongside your code, and everything flows from it:
|
|
32
|
+
|
|
33
|
+
- **`.spec/contracts`** holds the public obligations a scope exposes — the promises its surface makes to the outside world.
|
|
34
|
+
- **`.spec/rules`** holds the internal conventions its code follows — the house rules it keeps for itself.
|
|
35
|
+
- **`.spec/flanders`** holds behavior rules — the obligations that govern how Flanders' own commands and skills behave while they work in your project.
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
A few neighborly things to have on hand before you start:
|
|
40
|
+
|
|
41
|
+
- **Node.js**.
|
|
42
|
+
- **A git repository** — the `implement` command requires the project to be a git repository.
|
|
43
|
+
- **A supported CLI AI coding tool** — currently Claude Code or Codex.
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
Setting Flanders up is a breeze, neighbor — just run:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
npm install flanders -g
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
and then:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
flanders install
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This is how Flanders first sets up on disk: it is the only command that writes the persistent `.flanders/` configuration that `implement` reads, and the way you publish the skills to a fresh scope.
|
|
60
|
+
|
|
61
|
+
### Scope
|
|
62
|
+
|
|
63
|
+
Flanders installs at one of two scopes, chosen with a pair of mutually exclusive flags:
|
|
64
|
+
|
|
65
|
+
- `--project` — the scope is the current working directory. Skills go into the project's AI-tool skill folders, and the `.flanders/` configuration is written at the project root.
|
|
66
|
+
- `--global` — the scope is your home directory. Skills go into the user-level AI-tool skill folders, and the `.flanders/` configuration is written at your home directory.
|
|
67
|
+
|
|
68
|
+
### What it writes
|
|
69
|
+
|
|
70
|
+
For each AI tool you select for skills, `install` writes one skill artifact per Flanders skill (`/flanders-spec`, `/flanders-plan`, and `/flanders-work`) into that tool's skill folder for the chosen scope:
|
|
71
|
+
|
|
72
|
+
| Tool | Project scope | Global scope |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| Claude Code | `.claude/skills/` | `~/.claude/skills/` |
|
|
75
|
+
| Codex CLI | `.codex/prompts/` | `~/.codex/prompts/` |
|
|
76
|
+
|
|
77
|
+
Select `both` and the artifacts are written for both tools, each into its own folder. Alongside the skills, the command writes the `.flanders/` configuration at the chosen scope (see [Configuration](#configuration)).
|
|
78
|
+
|
|
79
|
+
### Interactive prompts
|
|
80
|
+
|
|
81
|
+
Run it without flags and Flanders walks you through the setup, asking in this order:
|
|
82
|
+
|
|
83
|
+
1. **Skills tool** — `claude`, `codex`, or `both`.
|
|
84
|
+
2. **Scope** — `--project` or `--global`, each option labelled with the concrete destination path(s) for the skills tool you picked.
|
|
85
|
+
3. **Worker tool, model, and effort** — the AI the `implement` command's worker uses.
|
|
86
|
+
4. **Reviewer configuration** — an ordered list of one or more adversarial reviewers, each with its own tool, model, and effort. You can have any number of reviewers, for example Claude Opus, Claude Sonnet and Codex. You can even duplicate them if you think that one pass is not enough to detect a problem.
|
|
87
|
+
5. **Weighted-review configuration** — when two or more reviewers are configured, the minimum number of reviewers that must run to a verdict, and which reviewers are optional. Optional reviewers will not halt the implementation when they reach a rate limit.
|
|
88
|
+
|
|
89
|
+
And if a `.flanders/` configuration already lives at the scope you choose, neighbor, `install` reads it the moment you pick that scope and pre-selects your stored answers as the question defaults. Just press Enter straight through to reproduce your saved configuration just as it was.
|
|
90
|
+
|
|
91
|
+
### Flags
|
|
92
|
+
|
|
93
|
+
Every question has an equivalent command-line flag, so the whole setup can run without a single prompt:
|
|
94
|
+
|
|
95
|
+
**Scope** (mutually exclusive)
|
|
96
|
+
|
|
97
|
+
- `--project` — install into the current working directory.
|
|
98
|
+
- `--global` — install into your home directory.
|
|
99
|
+
|
|
100
|
+
**Skills and worker**
|
|
101
|
+
|
|
102
|
+
- `--skills-tool=<claude|codex|both>` — which AI tool(s) the skills are installed for.
|
|
103
|
+
- `--worker-tool=<claude|codex>` — which AI tool the `implement` worker uses.
|
|
104
|
+
- `--worker-model=<value>` — model the worker tool invokes; an empty value means "use the tool's default configured model".
|
|
105
|
+
- `--worker-effort=<value>` — reasoning effort the worker tool invokes; an empty value means "use the tool's default configured effort".
|
|
106
|
+
|
|
107
|
+
**Reviewers** — an ordered list, where reviewer 1 uses the unindexed names and reviewer `N` (2 or greater) carries the index:
|
|
108
|
+
|
|
109
|
+
- `--reviewer-tool=<claude|codex>` / `--reviewer-N-tool=<claude|codex>`
|
|
110
|
+
- `--reviewer-model=<value>` / `--reviewer-N-model=<value>`
|
|
111
|
+
- `--reviewer-effort=<value>` / `--reviewer-N-effort=<value>`
|
|
112
|
+
|
|
113
|
+
The reviewer indices must form a contiguous run starting at reviewer 1. Supplying any reviewer flag fixes the reviewer list to those indices and skips the "configure another reviewer?" prompt.
|
|
114
|
+
|
|
115
|
+
**Weighted review** — only meaningful with two or more reviewers:
|
|
116
|
+
|
|
117
|
+
- `--reviewer-optional` / `--reviewer-N-optional` — a presence flag that marks that reviewer optional; a reviewer with no such flag is required.
|
|
118
|
+
- `--reviewer-minimum=<value>` — the minimum number of reviewers that must run to a verdict each round, an integer between `1` and the number of configured reviewers.
|
|
119
|
+
|
|
120
|
+
A tool flag, or the `codex` effort flag, rejects a value outside its accepted set; model flags and the `claude` effort flag accept any value verbatim. Supplying a weighted-review flag with a single reviewer — or a `--reviewer-minimum` equal to the reviewer count together with any optional flag — is a usage error.
|
|
121
|
+
|
|
122
|
+
### Overwriting and output
|
|
123
|
+
|
|
124
|
+
Existing files at the destination — both skill artifacts and `.flanders/` configuration files — are overwritten silently, with no backup and no prompt, so preserving prior versions is up to your own version control. On success, the command prints the full list of files it wrote, one path per line.
|
|
125
|
+
|
|
126
|
+
## Updating
|
|
127
|
+
|
|
128
|
+
Updated the lib and itching for the freshest skills, neighbor? Just run:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
flanders update
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`update` takes no flags. It scans the four skill destinations `install` writes to — Claude Code and Codex CLI, each at project and global scope — and wherever it finds at least one Flanders skill artifact already in place, it rewrites the full `/flanders-spec`, `/flanders-plan`, and `/flanders-work` trio there with the current version. A destination where no Flanders skill artifact is present is left untouched, so `update` refreshes the installations you already have and never creates one where you had none.
|
|
135
|
+
|
|
136
|
+
## Configuration
|
|
137
|
+
|
|
138
|
+
The `install` command tucks your answers into a `.flanders/` folder so the command that consumes them — `implement` — knows just how you like things done. (`update` leaves this configuration untouched; it only refreshes the skills.) Where that folder lives depends on the scope you chose:
|
|
139
|
+
|
|
140
|
+
- **Project scope** — `.flanders/` at the project root.
|
|
141
|
+
- **Global scope** — `~/.flanders/` in your home directory.
|
|
142
|
+
|
|
143
|
+
### Which configuration wins
|
|
144
|
+
|
|
145
|
+
When a command reads the configuration, a project-scope `.flanders/` always takes precedence over a global one — and it's all or nothing, with no field-by-field merge between the two. So a project `.flanders/` is used in full when it's present; otherwise the global `~/.flanders/` is used.
|
|
146
|
+
|
|
147
|
+
## Usage
|
|
148
|
+
|
|
149
|
+
With Flanders installed, here's how to put it to work — running plans from the CLI and shaping them with the three skills.
|
|
150
|
+
|
|
151
|
+
### The three skills
|
|
152
|
+
|
|
153
|
+
- **`/flanders-spec`** — turns a free-form request into your contracts, rules, and behavior rules, written into the `.spec/contracts`, `.spec/rules`, and `.spec/flanders` folders.
|
|
154
|
+
- **`/flanders-plan`** — derives a single, ordered, specification-aware work plan from your request.
|
|
155
|
+
- **`/flanders-work`** — implements a small, self-contained request directly and gates it through build, test, and a single adversarial review, all in one invocation — no plan file and no commit.
|
|
156
|
+
|
|
157
|
+
Each skill takes the same optional `<data>` argument:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
/flanders-spec [<data>]
|
|
161
|
+
/flanders-plan [<data>]
|
|
162
|
+
/flanders-work [<data>]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
- Omit it, and the skill takes your request straight from the conversation.
|
|
166
|
+
- Give it a path to an existing file, and the skill reads that file as the input.
|
|
167
|
+
- Give it any other text, and the skill uses that text verbatim.
|
|
168
|
+
|
|
169
|
+
### Implementing a plan
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
flanders implement [plan]
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`implement` takes a plan from your `plans/` folder and carries it through from start to finish. Leave `[plan]` off and Flanders runs the single plan in `plans/` for you automatically. From there it works through each open task with the worker AI, gating every result through build, test, and adversarial review before marking that task complete in the plan — and it commits once per accepted task, so each step lands as its own neat little commit. The whole run is non-interactive: once started, it never stops to ask you anything, and it caps each task at five attempts before halting. The project must be a git repository: `implement` needs git and has no flag to turn it off.
|
|
176
|
+
|
|
177
|
+
When the plan implementation is finished, you can squash all the plan commits. They are ordered by task for easier identification.
|
|
178
|
+
|
|
179
|
+
### A typical workflow
|
|
180
|
+
|
|
181
|
+
Here's the neighborly path from a blank slate to shipped code:
|
|
182
|
+
|
|
183
|
+
1. **`flanders install`** — set Flanders up and deliver the skills.
|
|
184
|
+
2. **`/flanders-spec`** — capture your obligations and conventions as contracts, rules, and behavior rules.
|
|
185
|
+
3. **`/flanders-plan`** — derive an ordered work plan from them under `plans/`.
|
|
186
|
+
4. **`flanders implement`** — build the plan task by task, each result gated through build, test, and review.
|
|
187
|
+
|
|
188
|
+
And when a change is small enough that a whole plan would be overkill, **`/flanders-work`** is your shortcut — it carries that one request from request to reviewed finish without a plan or a commit.
|
|
189
|
+
|
|
190
|
+
## A worked example
|
|
191
|
+
|
|
192
|
+
Let's build a tiny web calculator that only multiplies and subtracts, neighbor — start to finish, the whole spec → plan → implement stroll.
|
|
193
|
+
|
|
194
|
+
1. **Set Flanders up** in your project and deliver the skills:
|
|
195
|
+
|
|
196
|
+
```sh
|
|
197
|
+
flanders install
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
2. **Capture the spec** with `/flanders-spec`. You describe what you want; the skill sorts each obligation into the right folder — public behavior into `.spec/contracts`, internal conventions into `.spec/rules`:
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
/flanders-spec A web calculator with exactly two operations — multiply and subtract — over two number inputs, showing the result. The operation buttons are teal, the result panel is white, and the page background is slate. Build the UI with React bundled by Vite, and use no other UI framework.
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
From that one request it writes, for example:
|
|
207
|
+
|
|
208
|
+
- a **functionality contract** under `.spec/contracts/` — the calculator offers exactly two operations, multiply and subtract, over two numeric inputs, and shows the result;
|
|
209
|
+
- a **colors contract** under `.spec/contracts/` — the operation buttons are teal, the result panel white, and the background slate;
|
|
210
|
+
- a **frameworks rule** under `.spec/rules/` — the UI is built with React bundled by Vite, and no other UI framework is introduced.
|
|
211
|
+
|
|
212
|
+
The skill shows you the planned layout first and writes the files once you approve.
|
|
213
|
+
|
|
214
|
+
3. **Derive the plan** with `/flanders-plan` — one ordered, specification-aware plan under `plans/`, each task linked back to the contracts and rules it satisfies:
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
/flanders-plan
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
4. **Build it** with `implement` — Flanders works each task with the worker AI, gates every result through build, test, and adversarial review, and lands one commit per accepted task:
|
|
221
|
+
|
|
222
|
+
```sh
|
|
223
|
+
flanders implement
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
When it finishes, the contracts are honored by code: a calculator that multiplies and subtracts and nothing else, in teal, white, and slate, built on the framework your rule pinned.
|
|
227
|
+
|
|
228
|
+
5. **Tweak it later** with the shortcut — a small change that doesn't need a whole plan:
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
/flanders-work make the result panel use a larger font
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
If you really want the font to be kept at that size, just save the spec, and no future work will ever break that specification:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
/flanders-spec make the result panel use a larger font
|
|
238
|
+
```
|
|
239
|
+
```
|
|
240
|
+
/flanders-work
|
|
241
|
+
```
|
package/lib/Flanders.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ImplementContexts } from "./commands/Implement";
|
|
2
2
|
import { InstallContexts } from "./commands/Install";
|
|
3
|
+
import { UpdateContexts } from "./commands/Update";
|
|
3
4
|
import type { AskContext, OutputContext } from "./contexts";
|
|
4
|
-
export type FlandersContexts = ImplementContexts & InstallContexts & Readonly<{
|
|
5
|
+
export type FlandersContexts = ImplementContexts & InstallContexts & UpdateContexts & Readonly<{
|
|
5
6
|
output: OutputContext;
|
|
6
7
|
ask: AskContext;
|
|
7
8
|
}>;
|
package/lib/Flanders.js
CHANGED
|
@@ -3,8 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Flanders = void 0;
|
|
4
4
|
const Implement_1 = require("./commands/Implement");
|
|
5
5
|
const Install_1 = require("./commands/Install");
|
|
6
|
+
const Update_1 = require("./commands/Update");
|
|
6
7
|
const USAGE = `usage: flanders <command> [arguments...]
|
|
7
8
|
install [--global | --project] install Claude Code skills
|
|
9
|
+
update refresh installed skills in place
|
|
8
10
|
implement [plan] run the iterative implementation loop`;
|
|
9
11
|
class Flanders {
|
|
10
12
|
constructor(args, _options, _contexts) {
|
|
@@ -26,6 +28,11 @@ class Flanders {
|
|
|
26
28
|
this._command = cmd;
|
|
27
29
|
return await cmd.result();
|
|
28
30
|
}
|
|
31
|
+
case "update": {
|
|
32
|
+
const cmd = new Update_1.Update(rest, { projectRoot: this._options.projectRoot }, this._contexts);
|
|
33
|
+
this._command = cmd;
|
|
34
|
+
return await cmd.result();
|
|
35
|
+
}
|
|
29
36
|
case "implement": {
|
|
30
37
|
const cmd = new Implement_1.Implement(rest, { projectRoot: this._options.projectRoot }, this._contexts);
|
|
31
38
|
this._command = cmd;
|
package/lib/cli.js
CHANGED
|
@@ -234,7 +234,8 @@ const ask = (() => {
|
|
|
234
234
|
for (let i = 0; i < q.options.length; i++) {
|
|
235
235
|
const o = q.options[i];
|
|
236
236
|
const marker = pickedLabels.has(o.label) ? "*" : " ";
|
|
237
|
-
|
|
237
|
+
const isDefault = q.defaultIndex === i;
|
|
238
|
+
out.write(` ${marker} ${i + 1}) ${o.label}${o.description ? ` — ${o.description}` : ""}${isDefault ? " (configured — press Enter)" : ""}\n`);
|
|
238
239
|
}
|
|
239
240
|
if (existing) {
|
|
240
241
|
const labels = existing.picked.map(p => p.label).join(", ");
|
|
@@ -258,6 +259,9 @@ const ask = (() => {
|
|
|
258
259
|
hints.push(q.multiSelect
|
|
259
260
|
? `[1-${q.options.length}, comma-separated; free-text OK]`
|
|
260
261
|
: `[1-${q.options.length}; free-text OK]`);
|
|
262
|
+
if (q.defaultIndex !== undefined) {
|
|
263
|
+
hints.push("Enter for configured");
|
|
264
|
+
}
|
|
261
265
|
if (idx > 0) {
|
|
262
266
|
hints.push("'-' back");
|
|
263
267
|
}
|
|
@@ -288,6 +292,11 @@ const ask = (() => {
|
|
|
288
292
|
}
|
|
289
293
|
continue;
|
|
290
294
|
}
|
|
295
|
+
if (raw === "" && q.defaultIndex !== undefined) {
|
|
296
|
+
answers[idx] = { picked: [q.options[q.defaultIndex]] };
|
|
297
|
+
idx++;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
291
300
|
const parsed = parseAnswer(raw, q.options.length, q.multiSelect);
|
|
292
301
|
if (!parsed) {
|
|
293
302
|
out.writeError("Invalid input. Pick a valid option number, type free-form text, or use '-' / '+' to navigate.\n");
|
|
@@ -55,10 +55,12 @@ export declare class Implement {
|
|
|
55
55
|
private _gitOutputContext;
|
|
56
56
|
private _runTask;
|
|
57
57
|
private _workerStage;
|
|
58
|
-
private
|
|
58
|
+
private _buildSpecContent;
|
|
59
59
|
private _buildStage;
|
|
60
60
|
private _testStage;
|
|
61
61
|
private _setReviewerState;
|
|
62
|
+
private _setReviewerWaiting;
|
|
63
|
+
private _writeReviewerEntry;
|
|
62
64
|
private _renderReviewingFooter;
|
|
63
65
|
private _setReviewerLogicalStatus;
|
|
64
66
|
private _evaluateReviewRoundCompletion;
|
|
@@ -13,6 +13,7 @@ const prompts_1 = require("../prompts/prompts");
|
|
|
13
13
|
const ScriptRunner_1 = require("../system/ScriptRunner");
|
|
14
14
|
const BottomBlock_1 = require("../ui/BottomBlock");
|
|
15
15
|
const formatters_1 = require("../ui/formatters");
|
|
16
|
+
const voiceVariants_1 = require("../voiceVariants");
|
|
16
17
|
const Workspace_1 = require("../workspace/Workspace");
|
|
17
18
|
const MAX_ITER = 5;
|
|
18
19
|
class LineBufferedBlock {
|
|
@@ -86,7 +87,7 @@ class Implement {
|
|
|
86
87
|
write: text => this._contexts.output.write(text),
|
|
87
88
|
columns: () => this._contexts.output.columns(),
|
|
88
89
|
onResize: listener => this._contexts.output.onResize(listener)
|
|
89
|
-
}, this._contexts.time);
|
|
90
|
+
}, this._contexts.time, this._contexts.random);
|
|
90
91
|
this._block = block;
|
|
91
92
|
block.mount();
|
|
92
93
|
this._buffered = new LineBufferedBlock(block);
|
|
@@ -141,7 +142,7 @@ class Implement {
|
|
|
141
142
|
if (initialParse.tasks.every(t => t.done)) {
|
|
142
143
|
this._block.setHeader({ indexLabel: `${totalTasks}/${totalTasks}` });
|
|
143
144
|
this._block.setMetrics({ plan: { tokens: planTotals.it + planTotals.ot, seconds: planTotals.t } });
|
|
144
|
-
this._buffered.write(
|
|
145
|
+
this._buffered.write(`${(0, voiceVariants_1.pickVariant)(voiceVariants_1.tasksCompletedPool, this._contexts.random)}\n`);
|
|
145
146
|
this._finalizeBlock("Done");
|
|
146
147
|
return 0;
|
|
147
148
|
}
|
|
@@ -193,7 +194,7 @@ class Implement {
|
|
|
193
194
|
return 1;
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
|
-
this._buffered.write(
|
|
197
|
+
this._buffered.write(`${(0, voiceVariants_1.pickVariant)(voiceVariants_1.allTasksCompletedPool, this._contexts.random)}\n`);
|
|
197
198
|
this._finalizeBlock("Done");
|
|
198
199
|
return 0;
|
|
199
200
|
}
|
|
@@ -371,7 +372,7 @@ class Implement {
|
|
|
371
372
|
taskText = "(The full task text and the contracts and rules it references were provided when this session began and remain available to you through session continuity; they are not repeated here.)";
|
|
372
373
|
}
|
|
373
374
|
else {
|
|
374
|
-
taskText = `(Your previous session for this task could not be resumed, so this is a fresh invocation. You are continuing work on the task at line ${task.line} of the plan file, titled "${task.title}".
|
|
375
|
+
taskText = `(Your previous session for this task could not be resumed, so this is a fresh invocation. You are continuing work on the task at line ${task.line} of the plan file, titled "${task.title}". The task text and its referenced content are not re-injected here: re-read that task from the plan file, and for the contracts and rules it references reread the consolidated spec.md the orchestrator left in the temporary folder at ${ws.specFile} — read that single file rather than reopening each referenced file — then address the previous-iteration briefing below.)`;
|
|
375
376
|
}
|
|
376
377
|
let prompt = prompts_1.prompts.worker
|
|
377
378
|
.split("<PLAN_PATH>").join(plan.path)
|
|
@@ -389,7 +390,8 @@ class Implement {
|
|
|
389
390
|
}
|
|
390
391
|
try {
|
|
391
392
|
if (iteration === 1) {
|
|
392
|
-
|
|
393
|
+
await this._contexts.fs.writeFile(ws.specFile, await this._buildSpecContent(plan, task));
|
|
394
|
+
prompt = `${prompt}\n\n${(0, prompts_1.linkedReferenceDirective)(ws.specFile)}`;
|
|
393
395
|
}
|
|
394
396
|
const { result, capturedOutput } = iteration === 1
|
|
395
397
|
? await this._runAi(this._config.worker.tool, this._config.worker.model, this._config.worker.effort, prompt)
|
|
@@ -411,19 +413,15 @@ class Implement {
|
|
|
411
413
|
return false;
|
|
412
414
|
}
|
|
413
415
|
}
|
|
414
|
-
async
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
for (const relPath of allPaths) {
|
|
422
|
-
const absPath = (0, fsUtils_1.joinPath)(this._options.projectRoot, relPath);
|
|
423
|
-
const content = await this._contexts.fs.readFile(absPath);
|
|
424
|
-
sections.push(`## ${relPath}\n\n${content}`);
|
|
416
|
+
async _buildSpecContent(plan, task) {
|
|
417
|
+
const references = plan.linkedReferences(task);
|
|
418
|
+
const fileContents = new Map();
|
|
419
|
+
for (const { path } of references) {
|
|
420
|
+
if (!fileContents.has(path)) {
|
|
421
|
+
fileContents.set(path, await this._contexts.fs.readFile((0, fsUtils_1.joinPath)(this._options.projectRoot, path)));
|
|
422
|
+
}
|
|
425
423
|
}
|
|
426
|
-
return
|
|
424
|
+
return (0, PlanFile_1.buildSpecFileContent)(references, fileContents);
|
|
427
425
|
}
|
|
428
426
|
async _buildStage(plan, taskLine, ws, iteration) {
|
|
429
427
|
if (!(await (0, fsUtils_1.isNonEmptyFile)(this._contexts.fs, ws.buildScript))) {
|
|
@@ -460,12 +458,22 @@ class Implement {
|
|
|
460
458
|
return true;
|
|
461
459
|
}
|
|
462
460
|
_setReviewerState(reviewerIdx, state) {
|
|
461
|
+
this._writeReviewerEntry(reviewerIdx, state, undefined);
|
|
462
|
+
}
|
|
463
|
+
_setReviewerWaiting(reviewerIdx, endTimeMs) {
|
|
464
|
+
this._writeReviewerEntry(reviewerIdx, "waiting", endTimeMs);
|
|
465
|
+
}
|
|
466
|
+
_writeReviewerEntry(reviewerIdx, state, endTime) {
|
|
463
467
|
if (!this._reviewerStates)
|
|
464
468
|
return;
|
|
465
469
|
const entry = this._reviewerStates[reviewerIdx];
|
|
466
470
|
if (!entry)
|
|
467
471
|
return;
|
|
468
|
-
|
|
472
|
+
const next = { tool: entry.tool, model: entry.model, effort: entry.effort, state };
|
|
473
|
+
if (endTime !== undefined) {
|
|
474
|
+
next.endTime = endTime;
|
|
475
|
+
}
|
|
476
|
+
this._reviewerStates[reviewerIdx] = next;
|
|
469
477
|
this._renderReviewingFooter();
|
|
470
478
|
}
|
|
471
479
|
_renderReviewingFooter() {
|
|
@@ -551,8 +559,9 @@ class Implement {
|
|
|
551
559
|
.split("<CONTRACT_LIST>").join(this._formatPathList(this._contractList))
|
|
552
560
|
.split("<RULE_LIST>").join(this._formatPathList(this._ruleList))
|
|
553
561
|
.split("<BEHAVIOR_RULE_LIST>").join(this._formatPathList(this._behaviorRuleList))
|
|
554
|
-
.split("<ERROR_LOG_PATH>").join(ws.reviewerErrorLog(reviewerNum))
|
|
555
|
-
|
|
562
|
+
.split("<ERROR_LOG_PATH>").join(ws.reviewerErrorLog(reviewerNum))
|
|
563
|
+
.split("<SPEC_PATH>").join(ws.reviewerSpecFile(reviewerNum));
|
|
564
|
+
await this._contexts.fs.writeFile(ws.reviewerSpecFile(reviewerNum), await this._buildSpecContent(plan, task));
|
|
556
565
|
const controller = new AbortController();
|
|
557
566
|
this._reviewerAbortControllers.set(idx, controller);
|
|
558
567
|
try {
|
|
@@ -570,10 +579,10 @@ class Implement {
|
|
|
570
579
|
void currentSession.dispose();
|
|
571
580
|
};
|
|
572
581
|
const callbacks = {
|
|
573
|
-
onLongWaitStart: () => {
|
|
582
|
+
onLongWaitStart: (_kind, endTimeMs) => {
|
|
574
583
|
if (this._disposed)
|
|
575
584
|
return;
|
|
576
|
-
this.
|
|
585
|
+
this._setReviewerWaiting(idx, endTimeMs);
|
|
577
586
|
this._setReviewerLogicalStatus(idx, "waiting");
|
|
578
587
|
},
|
|
579
588
|
onLongWaitEnd: () => {
|
|
@@ -614,7 +623,7 @@ class Implement {
|
|
|
614
623
|
continue;
|
|
615
624
|
}
|
|
616
625
|
const trimmed = (await this._workspace.readReviewerErrorLog(reviewerNum)).trim();
|
|
617
|
-
this._setReviewerState(idx, trimmed.length === 0 ? "
|
|
626
|
+
this._setReviewerState(idx, trimmed.length === 0 ? "pass" : "fail");
|
|
618
627
|
this._setReviewerLogicalStatus(idx, "done");
|
|
619
628
|
const verdictLine = trimmed.length === 0
|
|
620
629
|
? "Verdict: PASS"
|
|
@@ -10,7 +10,6 @@ export type InstallContexts = Readonly<{
|
|
|
10
10
|
export type InstallOptions = Readonly<{
|
|
11
11
|
projectRoot: string;
|
|
12
12
|
}>;
|
|
13
|
-
export declare function stripYamlFrontmatter(body: string): string;
|
|
14
13
|
type ReviewerFlagAnswers = Readonly<{
|
|
15
14
|
tool?: "claude" | "codex";
|
|
16
15
|
model?: string;
|