@a-t-h-i/bot-lobby 0.6.24 → 0.6.26
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 +11 -3
- package/package.json +1 -5
- package/prompts/designer.md +14 -0
- package/prompts/master.md +30 -7
- package/prompts/panel.md +21 -1
- package/prompts/planner.md +22 -3
- package/prompts/qa.md +23 -3
- package/prompts/quickfix.md +4 -0
- package/prompts/reviewer.md +17 -0
- package/prompts/worker.md +21 -6
- package/src/ask/tool.ts +3 -3
- package/src/classifier/hosts.ts +0 -11
- package/src/classifier/instance.ts +0 -5
- package/src/delivery/review.ts +1 -1
- package/src/execution/agent-runner.ts +0 -18
- package/src/execution/git.ts +77 -0
- package/src/execution/lint.ts +435 -0
- package/src/knowledge/compactor.ts +0 -70
- package/src/lobby/ask.ts +4 -17
- package/src/lobby/feed.ts +39 -4
- package/src/lobby/host.ts +4 -0
- package/src/lobby/issues.ts +0 -8
- package/src/lobby/planner.ts +54 -5
- package/src/lobby/pr-review.ts +2 -1
- package/src/lobby/pulls.ts +1 -44
- package/src/lobby/quickfix.ts +34 -1
- package/src/lobby/runtime.ts +20 -3
- package/src/lobby/service.ts +5 -1
- package/src/lobby/session-files.ts +1 -45
- package/src/lobby/split.ts +33 -1
- package/src/lobby/task-rows.ts +2 -0
- package/src/master/master.ts +3 -0
- package/src/master/research.ts +0 -16
- package/src/pi/commands.ts +25 -10
- package/src/pi/events.ts +12 -4
- package/src/pi/model-settings.ts +0 -1
- package/src/pi/plan-checklist.ts +98 -8
- package/src/pi/route.ts +0 -1
- package/src/pi/run-summary.ts +0 -57
- package/src/pi/switch.ts +69 -0
- package/src/pi/tools.ts +7 -0
- package/src/pi/ui.ts +2 -14
- package/src/schemas/agent.ts +0 -4
- package/src/schemas/configuration.ts +51 -3
- package/src/schemas/lint.ts +55 -0
- package/src/schemas/task.ts +5 -0
- package/src/state/budget.ts +49 -22
- package/src/state/changes.ts +8 -2
- package/src/state/metrics.ts +0 -1
- package/src/state/persistence.ts +0 -5
- package/src/state/work-time.ts +123 -0
- package/src/webui/api/index.ts +2 -6
- package/src/webui/api/knowledge.ts +11 -3
- package/src/webui/api/lobby.ts +13 -6
- package/src/webui/api/plan-facts.ts +36 -6
- package/src/webui/api/settings.ts +6 -1
- package/src/webui/api/tasks.ts +27 -6
- package/src/webui/command.ts +14 -3
- package/src/webui/dev/fake-service.ts +7 -0
- package/src/webui/dev/fixtures.ts +4 -1
- package/src/webui/protocol.ts +30 -4
- package/src/workflow/lint.ts +221 -0
- package/src/workflow/track.ts +12 -4
- package/src/workflow/workflow.ts +105 -21
- package/webui/dist/assets/index-C1IwecNS.css +2 -0
- package/webui/dist/assets/index-C4Ww36TW.js +105 -0
- package/webui/dist/build.json +58 -62
- package/webui/dist/index.html +2 -2
- package/webui/dist/assets/index-BSewK1QO.css +0 -2
- package/webui/dist/assets/index-CHQYSZXA.js +0 -105
package/README.md
CHANGED
|
@@ -17,14 +17,22 @@ The questionnaire and web tools are built in, so don't also install extensions w
|
|
|
17
17
|
|
|
18
18
|
## Use
|
|
19
19
|
|
|
20
|
-
1. In Pi, run `/bot-lobby add a login page`.
|
|
20
|
+
1. In Pi, run `/bot-lobby add a login page`. Bot-lobby turns on and the lobby opens in your browser.
|
|
21
|
+
A new pi session starts with bot-lobby off, as plain pi: `Ctrl+Shift+M` or `/bot-lobby on|off` switches it.
|
|
21
22
|
2. Answer the oracle's questions and approve its plan, or agree one with the panel in **Plan** first.
|
|
22
|
-
|
|
23
|
+
A plan opens with a short list of steps; the detail of each follows for whoever wants it.
|
|
24
|
+
3. Follow the agents in **Lobby**: the step being worked on reads `active · 1m 12s`, and each task shows
|
|
25
|
+
how long its agents have worked on it (idle time left out). Models, effort and time limits are in **Settings**.
|
|
23
26
|
|
|
24
27
|
Small changes go to **Quick fix**. `--task` forces a full task, `--fast` / `--full` picks the workflow,
|
|
25
28
|
and `--branch` / `--worktree` isolates it in git. A finished isolated task pushes its branch and tries
|
|
26
29
|
to open a PR through `gh`; nothing merges on its own.
|
|
27
30
|
|
|
31
|
+
After every worker step, before QA and at completion, the engine runs the project's own linter
|
|
32
|
+
(ESLint, Biome, Oxlint, Ruff, or a command you set) on the files the task touched. Only problems on
|
|
33
|
+
changed lines count, and QA judges any lint suppression a worker adds. **Settings → Linting** sets it
|
|
34
|
+
to off, advise or block. Visual questions come with mockups that expand and scroll side by side.
|
|
35
|
+
|
|
28
36
|
| Agent | Does |
|
|
29
37
|
| --- | --- |
|
|
30
38
|
| Oracle | Your Pi session: questions, plan, delegation and approvals |
|
|
@@ -38,7 +46,7 @@ to open a PR through `gh`; nothing merges on its own.
|
|
|
38
46
|
The lobby is keyboard-first, and every action button shows its key (`Archive E`, `Delete Del`).
|
|
39
47
|
`Alt+1`…`Alt+9` switch tabs, `j` / `k` move through lists, `/` focuses search or the message box,
|
|
40
48
|
`p` switches project, `d` flips the theme and `?` lists every key. Alt and Ctrl shortcuts can be
|
|
41
|
-
rebound with `lobby.keys`. In Pi, `Alt+G` toggles auto mode and `Ctrl+Shift+M`
|
|
49
|
+
rebound with `lobby.keys`. In Pi, `Alt+G` toggles auto mode and `Ctrl+Shift+M` turns bot-lobby on or off.
|
|
42
50
|
|
|
43
51
|
## Screenshots
|
|
44
52
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a-t-h-i/bot-lobby",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.26",
|
|
4
4
|
"description": "Structured multi-agent software engineering orchestrator for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
"@earendil-works/pi-tui": "0.87.0",
|
|
59
59
|
"@fontsource-variable/inter": "5.3.0",
|
|
60
60
|
"@playwright/test": "1.62.0",
|
|
61
|
-
"@shadcn/react": "0.3.1",
|
|
62
61
|
"@tailwindcss/vite": "4.3.3",
|
|
63
62
|
"@types/node": "^22.10.0",
|
|
64
63
|
"@types/react": "19.3.0",
|
|
@@ -72,11 +71,8 @@
|
|
|
72
71
|
"react": "19.3.0",
|
|
73
72
|
"react-dom": "19.3.0",
|
|
74
73
|
"react-markdown": "10.1.0",
|
|
75
|
-
"recharts": "3.10.1",
|
|
76
74
|
"remark-gfm": "4.0.1",
|
|
77
|
-
"shiki": "4.5.0",
|
|
78
75
|
"socket.io": "4.8.4",
|
|
79
|
-
"sonner": "2.0.8",
|
|
80
76
|
"tailwind-merge": "3.7.0",
|
|
81
77
|
"tailwindcss": "4.3.3",
|
|
82
78
|
"tw-animate-css": "1.4.0",
|
package/prompts/designer.md
CHANGED
|
@@ -106,6 +106,20 @@ over-animated.
|
|
|
106
106
|
- Always honor `prefers-reduced-motion`: keep the state change, drop the
|
|
107
107
|
movement.
|
|
108
108
|
|
|
109
|
+
## Show the options
|
|
110
|
+
|
|
111
|
+
When a visual decision is the user's to make (a layout, a screen, how a
|
|
112
|
+
component looks, a direction for the whole UI), show it rather than describe
|
|
113
|
+
it: ask with `ask_user_question` and give every option an `htmlPreview`, a
|
|
114
|
+
static mockup of that option — HTML and CSS only (no scripts, images, fonts or
|
|
115
|
+
links: the sandbox strips them), laid out for a page about 1200px wide, with
|
|
116
|
+
the real content, palette and proportions of what you would build, never grey
|
|
117
|
+
placeholder boxes. The user sees the focused option's mockup beside the
|
|
118
|
+
question, expands it and scrolls through all the options side by side, so
|
|
119
|
+
draw each option at the same fidelity. Ask before you build, and only about
|
|
120
|
+
what is theirs to choose; decide the rest yourself. In the planning panel,
|
|
121
|
+
draw the same mockups as fenced `mockup` blocks under your options.
|
|
122
|
+
|
|
109
123
|
## Accessibility
|
|
110
124
|
|
|
111
125
|
Accessibility is a core requirement: semantic HTML first (landmarks, headings in
|
package/prompts/master.md
CHANGED
|
@@ -128,6 +128,15 @@ and reason about the change inside it:
|
|
|
128
128
|
|
|
129
129
|
## User interaction
|
|
130
130
|
|
|
131
|
+
For anything the user will see (a UI request, a layout, a screen, a visual
|
|
132
|
+
direction), show the choices rather than describe them. When you ask about a
|
|
133
|
+
visual decision, use `ask_user_question` and give each option an
|
|
134
|
+
`htmlPreview` mockup (static HTML and CSS of that option, laid out for a page
|
|
135
|
+
about 1200px wide); the user expands the mockups and scrolls through the
|
|
136
|
+
options side by side. When the choice needs real design work, delegate it to
|
|
137
|
+
the designer, who asks with its own mockups. In planning, the panel draws
|
|
138
|
+
mockups under its options the same way.
|
|
139
|
+
|
|
131
140
|
Write the proposal as a short `- ` bullet list, one line per change, so the user
|
|
132
141
|
can see what will be done at a glance; do not dump the internal plan unless
|
|
133
142
|
asked. If the user
|
|
@@ -160,10 +169,15 @@ integrations. One file has one owner: never split a file between domains, and
|
|
|
160
169
|
never give DESIGN only the styling of something another domain built —
|
|
161
170
|
whoever builds a visual thing owns how it looks.
|
|
162
171
|
|
|
163
|
-
Write the plan
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
172
|
+
Write the plan for the user to skim first and read in full only if they want
|
|
173
|
+
to. It opens with `## Steps`: a numbered list, one short plain line per step,
|
|
174
|
+
in the order they happen — what gets done, in a few words (`Add the work timer
|
|
175
|
+
to the task`), never how: no file paths, code, flags or jargon. That list is
|
|
176
|
+
the user's checklist, so keep it to the steps themselves. The detail follows
|
|
177
|
+
under `## Details`, one `### Step N: <the same words>` section per step, and
|
|
178
|
+
then the rest of the plan (contracts, risks, notes). Open each `implement` task
|
|
179
|
+
with its step number (`Step 3: ...`, or `Steps 3-4: ...` when one delegation
|
|
180
|
+
covers several) so the checklist tracks progress exactly.
|
|
167
181
|
|
|
168
182
|
## Briefing the agents
|
|
169
183
|
|
|
@@ -219,9 +233,9 @@ Rules for the brief:
|
|
|
219
233
|
a few actions. Split anything larger into consecutive steps in the same
|
|
220
234
|
`implement` call rather than leaving the agent to sequence it. Prefer more
|
|
221
235
|
explicit detail to fewer, larger chunks.
|
|
222
|
-
-
|
|
223
|
-
files, its actions and its done criteria, so the brief is the step
|
|
224
|
-
explicit, never a new decision.
|
|
236
|
+
- Each step's detail section is written to the same standard: it names the
|
|
237
|
+
step's files, its actions and its done criteria, so the brief is the step
|
|
238
|
+
made explicit, never a new decision.
|
|
225
239
|
- Scout and research instructions ask specific questions with the answer
|
|
226
240
|
format you want (paths, names, versions, yes/no plus evidence), and say what
|
|
227
241
|
you will do with the answer.
|
|
@@ -331,6 +345,15 @@ more round, or leave it blocked); never loop QA past that on your own. When the
|
|
|
331
345
|
user tells you to finish although QA has not passed, call `action=complete`:
|
|
332
346
|
the engine asks them to confirm, then completes the task.
|
|
333
347
|
|
|
348
|
+
The engine also lints the files this task's agents touched, after every
|
|
349
|
+
step, before QA and at completion, and adds a **Lint** line to the report.
|
|
350
|
+
Only problems on lines the task changed count. Send the new ones to the domain
|
|
351
|
+
that touched the file, as a fix step that fixes the cause and never silences
|
|
352
|
+
the rule. In block mode new lint errors hold completion until they are fixed
|
|
353
|
+
or the user accepts the work as it is (`/bot-lobby accept`); in advise mode
|
|
354
|
+
mention what is left to the user. When lint could not run, say so; it holds
|
|
355
|
+
nothing.
|
|
356
|
+
|
|
334
357
|
Not every change in the tree is this task's. Worker and QA reports end with
|
|
335
358
|
who changed each file, from bot-lobby's record of every agent's edits:
|
|
336
359
|
**planned** (this task's workers), **quick fix** (the user's own direct
|
package/prompts/panel.md
CHANGED
|
@@ -31,6 +31,15 @@ questions and the user's answers, and the oracle's current draft plan.
|
|
|
31
31
|
repository, put that option first with `(Recommended)` after its label. The
|
|
32
32
|
user can always type their own answer instead, so do not add an "Other"
|
|
33
33
|
option.
|
|
34
|
+
- When a question is about something the user will see (a layout, a
|
|
35
|
+
screen, a component's look, a flow, a visual direction), draw it: under
|
|
36
|
+
each option put a fenced `mockup` block with a mockup of that option —
|
|
37
|
+
static HTML with a `<style>` element (no scripts, images, fonts or links:
|
|
38
|
+
the sandbox strips them), laid out for a page about 1200px wide, at most
|
|
39
|
+
30,000 characters. Show the real content and proportions of the thing being
|
|
40
|
+
decided, not grey boxes. The user sees each as a
|
|
41
|
+
thumbnail beside the question and expands them to scroll through the
|
|
42
|
+
options side by side. DESIGN draws for every visual question; any seat may.
|
|
34
43
|
- Read the draft's Assumptions: if one the oracle made for your seat is
|
|
35
44
|
wrong, say so under Notes and ask about it again.
|
|
36
45
|
- If an answer from the user is vague or conflicts with what you see in the
|
|
@@ -49,10 +58,21 @@ OPEN or READY
|
|
|
49
58
|
1. The question, ending with a question mark?
|
|
50
59
|
- Short label — what choosing it means
|
|
51
60
|
- Another label — what choosing it means
|
|
61
|
+
2. A question about what the user will see?
|
|
62
|
+
- Short label — what choosing it means
|
|
63
|
+
```mockup
|
|
64
|
+
<style>…</style>
|
|
65
|
+
<div class="page">…</div>
|
|
66
|
+
```
|
|
67
|
+
- Another label — what choosing it means
|
|
68
|
+
```mockup
|
|
69
|
+
…
|
|
70
|
+
```
|
|
52
71
|
|
|
53
72
|
(Two to four options per question, labels of one to five words, no
|
|
54
73
|
recommendation unless the answer is quite obvious, and then only that option
|
|
55
|
-
carries `(Recommended)`.
|
|
74
|
+
carries `(Recommended)`. A `mockup` block goes under the option it shows.
|
|
75
|
+
Omit Questions when READY.)
|
|
56
76
|
|
|
57
77
|
## Notes
|
|
58
78
|
- …
|
package/prompts/planner.md
CHANGED
|
@@ -35,6 +35,15 @@ below.
|
|
|
35
35
|
quite obvious from the conversation or the repository, put that option first
|
|
36
36
|
with `(Recommended)` after its label. The user answers all of them together
|
|
37
37
|
in one dialog and can type their own answer, so never add an "Other" option.
|
|
38
|
+
- Show what the user will see. A member's option marked `[mockup]` has a
|
|
39
|
+
mockup drawn for it: keep that option's label when you relay the question
|
|
40
|
+
and the mockup goes with it (you need not repeat it). A visual question of
|
|
41
|
+
your own (a layout, a screen, a flow) gets a fenced `mockup` block under
|
|
42
|
+
each option, as below: static HTML with a `<style>` element (no scripts, images, fonts or links:
|
|
43
|
+
the sandbox strips them), laid out for a page about 1200px wide, at most
|
|
44
|
+
30,000 characters. Show the real content and proportions of the thing being
|
|
45
|
+
decided, not grey boxes. The user
|
|
46
|
+
expands the mockups and scrolls through the options side by side.
|
|
38
47
|
- The conversation may carry an **Already settled with the user** list:
|
|
39
48
|
questions the user answered (or left for you to decide). They are closed in
|
|
40
49
|
any wording, so never ask one again, not even rephrased; fold the answer
|
|
@@ -81,7 +90,14 @@ Three to six words naming the task.
|
|
|
81
90
|
1. [DEV] The most important open question?
|
|
82
91
|
- Short label — what choosing it means
|
|
83
92
|
- Another label — what choosing it means
|
|
84
|
-
2.
|
|
93
|
+
2. [DESIGN] A question about what the user will see?
|
|
94
|
+
- Short label — what choosing it means
|
|
95
|
+
```mockup
|
|
96
|
+
<style>…</style>
|
|
97
|
+
<div class="page">…</div>
|
|
98
|
+
```
|
|
99
|
+
- Another label — what choosing it means
|
|
100
|
+
3. …
|
|
85
101
|
|
|
86
102
|
(At most four questions, each tagged with its seat; two to four options per
|
|
87
103
|
question, labels of one to five words, `(Recommended)` only on an obvious one. Omit the Questions section when READY
|
|
@@ -90,6 +106,11 @@ or when you decided everything yourself.)
|
|
|
90
106
|
## Plan
|
|
91
107
|
The current draft, in Markdown:
|
|
92
108
|
|
|
109
|
+
### Steps
|
|
110
|
+
1. …
|
|
111
|
+
(first, so the user can skim the plan: one short plain line per step, in the
|
|
112
|
+
order they happen — what gets done in a few words, never how: no file paths,
|
|
113
|
+
code or jargon; the sections below carry the detail)
|
|
93
114
|
### Objective
|
|
94
115
|
### Scope and non-goals
|
|
95
116
|
### Acceptance criteria
|
|
@@ -99,6 +120,4 @@ The current draft, in Markdown:
|
|
|
99
120
|
(what the user decided for DEV, DESIGN, QA and RESEARCH, one bullet each)
|
|
100
121
|
### Assumptions
|
|
101
122
|
(what you decided without asking, each with its seat: "[QA] Test in evergreen browsers only")
|
|
102
|
-
### Steps
|
|
103
|
-
1. …
|
|
104
123
|
### Risks and open points
|
package/prompts/qa.md
CHANGED
|
@@ -30,11 +30,31 @@ Probe suspicions by reading, tracing and running the code (a one-off command or
|
|
|
30
30
|
script is fine), and report what you found as findings with evidence. A finding
|
|
31
31
|
you confirmed by running something is stronger than a test you added.
|
|
32
32
|
|
|
33
|
+
## Run as little as the change needs
|
|
34
|
+
|
|
35
|
+
Match what you run to what the change can break. The engine already lints the
|
|
36
|
+
touched files and you have its result, and the type checker covers types,
|
|
37
|
+
shapes and imports, so never run or write anything to check what they check.
|
|
38
|
+
|
|
39
|
+
- A trivial or non-destructive change (copy, docs, styling, layout, a config
|
|
40
|
+
value, an added optional field, a rename the type checker follows): read
|
|
41
|
+
the diff, run the typecheck of the touched package, and cite the engine's
|
|
42
|
+
lint result. Run no test suite.
|
|
43
|
+
- A small change to logic: run only the nearest tests for the files it
|
|
44
|
+
touches (one file, or one test name), never the whole suite.
|
|
45
|
+
- Run the full suite only when the change touches shared logic, data,
|
|
46
|
+
security, a public API or contract, or several packages.
|
|
47
|
+
|
|
48
|
+
Do not re-run a check the worker already ran on the same files with the same
|
|
49
|
+
result; cite it. Always pass a bash `timeout`.
|
|
50
|
+
|
|
33
51
|
## Write as few tests as possible
|
|
34
52
|
|
|
35
|
-
Tests cost tokens and add upkeep, so the default is to write none.
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
Tests cost tokens and add upkeep, so the default is to write none. Judge the
|
|
54
|
+
tests that exist for what the change touches; do not add to them to look
|
|
55
|
+
thorough. Never write a test for what the linter or the type checker already
|
|
56
|
+
catches (that a function exists, its types or shape, an unused import, a
|
|
57
|
+
style rule), and never a test for a change that cannot break behavior.
|
|
38
58
|
|
|
39
59
|
Write a new test only when one of these holds, and say which in your report:
|
|
40
60
|
|
package/prompts/quickfix.md
CHANGED
|
@@ -25,6 +25,10 @@ the change now, the way they would ask pi directly.
|
|
|
25
25
|
servers, watchers or background processes.
|
|
26
26
|
- Change files with `edit`/`write`, never through shell redirection or
|
|
27
27
|
`sed -i`.
|
|
28
|
+
- The engine lints the files you touched when you finish and shows the user
|
|
29
|
+
what it finds on the lines you changed. Run the project's linter on them
|
|
30
|
+
first and fix what it finds; never silence a rule with a disable comment or
|
|
31
|
+
a config change.
|
|
28
32
|
|
|
29
33
|
## Anything people look at
|
|
30
34
|
|
package/prompts/reviewer.md
CHANGED
|
@@ -18,6 +18,23 @@ Review requirements, the approved plan, the actual diff, affected files, tests,
|
|
|
18
18
|
security, accessibility where relevant, error handling, reliability,
|
|
19
19
|
performance where relevant, maintainability, and scope discipline.
|
|
20
20
|
|
|
21
|
+
## Lint
|
|
22
|
+
|
|
23
|
+
When your context has a Lint section, the engine ran the project's linter on
|
|
24
|
+
the files this task touched. Problems on lines the task changed are the
|
|
25
|
+
task's; what was already there is not. A lint pass is evidence, not
|
|
26
|
+
acceptance, and a lint failure in block mode already holds completion, so
|
|
27
|
+
report it only when it shows a real defect. The section also lists every lint
|
|
28
|
+
suppression and lint-config change the task added. Judge each one: a disable
|
|
29
|
+
comment, ignore entry or loosened rule without a reason that holds (in a
|
|
30
|
+
comment beside it, or the worker's report) is a `major` finding, and so is
|
|
31
|
+
code removed or bent only to satisfy a rule. You cannot edit, so name the file
|
|
32
|
+
and line and say what the fix should be.
|
|
33
|
+
|
|
34
|
+
When the change is trivial or non-destructive, the engine's lint result and a
|
|
35
|
+
typecheck are enough executed checks for a PASS; cite them under
|
|
36
|
+
Verification (`- engine lint on the touched files — no new problems`).
|
|
37
|
+
|
|
21
38
|
## Change provenance
|
|
22
39
|
|
|
23
40
|
The working tree can hold changes that are not this task's: the user makes
|
package/prompts/worker.md
CHANGED
|
@@ -30,18 +30,33 @@ section of your output instead and continue with the rest of the work.
|
|
|
30
30
|
|
|
31
31
|
## Testing
|
|
32
32
|
|
|
33
|
-
Run the targeted
|
|
34
|
-
touched
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
Run only the targeted checks for what you changed: the typecheck of the
|
|
34
|
+
touched package and the nearest tests for the files you touched; QA decides
|
|
35
|
+
whether more is needed. A trivial or non-destructive change (copy, styling,
|
|
36
|
+
layout, docs, a config value) needs the typecheck and the linter, no tests.
|
|
37
|
+
Write as few tests as possible: add one only for a breaking change, for a bug
|
|
38
|
+
fix whose regression would be silent, or for behavior that could turn out
|
|
39
|
+
unpredictable (concurrency, ordering, time, untrusted input), or when your
|
|
40
|
+
brief asks for tests. The linter and the type checker already catch unused
|
|
41
|
+
code, wrong types, hook rules and style, so never write a test for what they
|
|
42
|
+
catch. Never pad a change with tests for the sake of coverage.
|
|
39
43
|
|
|
40
44
|
- Always pass a bash `timeout` to tests and builds (for example 300 seconds).
|
|
41
45
|
- Never start dev servers, watch mode or other long-running processes, and
|
|
42
46
|
never leave background processes behind.
|
|
43
47
|
- Change files with `edit`/`write`, not through shell redirection or `sed -i`.
|
|
44
48
|
|
|
49
|
+
## Lint
|
|
50
|
+
|
|
51
|
+
After every step the engine lints the files you touched with the project's
|
|
52
|
+
own linter and reports what it finds on the lines you changed. Run that linter
|
|
53
|
+
on your files before you report, and fix what it finds at the cause. Never
|
|
54
|
+
silence it: no new disable comments (`eslint-disable`, `@ts-ignore`, `noqa`,
|
|
55
|
+
`biome-ignore` and the like), no loosened rules, ignore lists or lint config,
|
|
56
|
+
no code deleted or bent just to make a rule pass. Every one of them is listed
|
|
57
|
+
for QA, who reads it as a finding unless it carries a reason that holds. When
|
|
58
|
+
a rule is wrong for your change, leave it failing and say why under Notes.
|
|
59
|
+
|
|
45
60
|
## Time budget
|
|
46
61
|
|
|
47
62
|
Work efficiently: read what you need, make the change, verify, report. If the
|
package/src/ask/tool.ts
CHANGED
|
@@ -22,9 +22,9 @@ export { ASK_TOOL };
|
|
|
22
22
|
|
|
23
23
|
const OptionSchema = Type.Object({
|
|
24
24
|
htmlPreview: Type.Optional(Type.Object({
|
|
25
|
-
html: Type.String({ maxLength: MAX_PREVIEW_HTML }),
|
|
25
|
+
html: Type.String({ maxLength: MAX_PREVIEW_HTML, description: "Static HTML of a mockup of this option; scripts, images, fonts and links are stripped." }),
|
|
26
26
|
css: Type.Optional(Type.String({ maxLength: MAX_PREVIEW_CSS })),
|
|
27
|
-
}, { additionalProperties: false })),
|
|
27
|
+
}, { additionalProperties: false, description: "A static HTML/CSS mockup of this option, laid out for a page about 1200px wide: shown as a thumbnail beside the question, which the user expands to scroll through every option's mockup. Give one to every option of a visual question (a layout, a screen, a component's look)." })),
|
|
28
28
|
label: Type.String({ maxLength: MAX_LABEL, description: `The option as the user sees and picks it: 1-5 words, at most ${MAX_LABEL} characters.` }),
|
|
29
29
|
description: Type.Optional(Type.String({ description: "What choosing it means: its trade-offs or consequences. Markdown." })),
|
|
30
30
|
preview: Type.Optional(Type.String({ description: "Markdown shown beside the options while this one is focused: a mockup, a code snippet, a diagram, a config. Only when seeing it helps the user compare." })),
|
|
@@ -45,7 +45,7 @@ export const AskParams = Type.Object({
|
|
|
45
45
|
const DESCRIPTION = [
|
|
46
46
|
"Ask the user one to four questions with options to pick from, when the answer would change what you do and you would otherwise guess.",
|
|
47
47
|
"Each question has 2-4 options in a neutral order, with no recommendation (mark one \"(Recommended)\", first, only when the answer is quite obvious); the user can pick one (or several with multiSelect), or answer in their own words.",
|
|
48
|
-
"Questions, descriptions and previews are Markdown.
|
|
48
|
+
"Questions, descriptions and previews are Markdown. For anything the user will see (a layout, a screen, a component's look), give every option an `htmlPreview`: a static HTML/CSS mockup the user can expand and scroll through side by side with the others. Give options a Markdown `preview` for a code snippet, a config or a text sketch; the focused option's preview shows beside the list. An option can also carry an `image` file (a screenshot, a rendered mockup).",
|
|
49
49
|
"Do not use it for yes/no confirmations of what you were already told to do, or for questions the conversation already answers.",
|
|
50
50
|
].join(" ");
|
|
51
51
|
|
package/src/classifier/hosts.ts
CHANGED
|
@@ -40,11 +40,6 @@ export const JEV_HOST_TABLE: Readonly<Record<JevHostId, JevHost>> = Object.freez
|
|
|
40
40
|
/** The hosts `auto` tries, in order: OpenCode's free Jev first. */
|
|
41
41
|
export const AUTO_ORDER: readonly JevHostId[] = ["opencode", "typesafe"];
|
|
42
42
|
|
|
43
|
-
/** How a host name reads in settings. */
|
|
44
|
-
export function hostLabel(name: JevHostName): string {
|
|
45
|
-
return name === "auto" ? "Auto (OpenCode's free Jev, else TypeSafe)" : JEV_HOST_TABLE[name].label;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
43
|
/** Resolves a pi provider's API key (stored credential, then its environment variable). */
|
|
49
44
|
export type KeySource = (piProvider: string) => Promise<string | undefined>;
|
|
50
45
|
|
|
@@ -122,12 +117,6 @@ export function keyHint(config: Pick<ClassifierConfig, "provider">): string {
|
|
|
122
117
|
return how(JEV_HOST_TABLE[config.provider]);
|
|
123
118
|
}
|
|
124
119
|
|
|
125
|
-
/** `ts_ab…cd`: enough to recognise which key is loaded, never the key. */
|
|
126
|
-
export function maskKey(key: string): string {
|
|
127
|
-
if (key.length <= 8) return "*".repeat(key.length);
|
|
128
|
-
return `${key.slice(0, 4)}…${key.slice(-4)}`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
120
|
/** One line on where a host's key comes from, for settings and `/bot-lobby config`. */
|
|
132
121
|
export function describeKey(host: JevHost, status: StatusSource | undefined, env: NodeJS.ProcessEnv = process.env): string {
|
|
133
122
|
const found = configuredProvider(host, status);
|
|
@@ -76,11 +76,6 @@ export function keyStatus(piProvider: string): KeyStatus | undefined {
|
|
|
76
76
|
return binding?.status(piProvider);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
/** Tests: use this classifier for the process (undefined restores the default). */
|
|
80
|
-
export function useClassifier(next: Classifier | undefined): void {
|
|
81
|
-
instance = next;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
79
|
/** Register the `typesafe` login entry and bind the classifier to each session's key store. */
|
|
85
80
|
export function registerClassifier(pi: ExtensionAPI, configDir: string): void {
|
|
86
81
|
registerJevProvider(pi);
|
package/src/delivery/review.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { readRules, type Rules } from "./rules.ts";
|
|
|
10
10
|
import type { DeliveryContext } from "./transport.ts";
|
|
11
11
|
|
|
12
12
|
export function localVerified(task: Task): boolean {
|
|
13
|
-
return task.state === "completed" && !task.qaWaiver && completionBlockers(task, pendingApprovals(task).length).length === 0
|
|
13
|
+
return task.state === "completed" && !task.qaWaiver && !task.lintWaiver && completionBlockers(task, pendingApprovals(task).length).length === 0
|
|
14
14
|
&& (!qaRequired(task) || task.qaVerdict === "pass");
|
|
15
15
|
}
|
|
16
16
|
export function newDelivery(task: Task, project: string): Delivery {
|
|
@@ -380,21 +380,3 @@ export async function mapConcurrent<TIn, TOut>(items: TIn[], limit: number, fn:
|
|
|
380
380
|
export async function runParallel(requests: AgentRequest[], limit: number, run: ProcessRunner = spawnPiProcess): Promise<AgentRun[]> {
|
|
381
381
|
return mapWithConcurrencyLimit(requests, limit, (request) => runAgent(request, run));
|
|
382
382
|
}
|
|
383
|
-
|
|
384
|
-
function lastOutput(results: AgentRun[]): string {
|
|
385
|
-
return results.length > 0 ? results[results.length - 1]!.output : "";
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/** Run agents in order, substituting `{previous}` with the prior output. */
|
|
389
|
-
export async function runSequential(requests: AgentRequest[], run: ProcessRunner = spawnPiProcess): Promise<AgentRun[]> {
|
|
390
|
-
const results: AgentRun[] = [];
|
|
391
|
-
for (const request of requests) {
|
|
392
|
-
const instruction = request.instruction.includes("{previous}")
|
|
393
|
-
? request.instruction.replaceAll("{previous}", lastOutput(results))
|
|
394
|
-
: request.instruction;
|
|
395
|
-
const result = await runAgent({ ...request, instruction }, run);
|
|
396
|
-
results.push(result);
|
|
397
|
-
if (result.status !== "success") break;
|
|
398
|
-
}
|
|
399
|
-
return results;
|
|
400
|
-
}
|
package/src/execution/git.ts
CHANGED
|
@@ -31,6 +31,15 @@ export async function headCommit(cwd: string): Promise<string | undefined> {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** The repository's top folder, or undefined (no git, not a repository). */
|
|
35
|
+
export async function repositoryTop(cwd: string): Promise<string | undefined> {
|
|
36
|
+
try {
|
|
37
|
+
return (await git(cwd, ["rev-parse", "--show-toplevel"])) || undefined;
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
/** The newest commit on HEAD made before `iso` (where a task started), or undefined. */
|
|
35
44
|
export async function commitBefore(cwd: string, iso: string): Promise<string | undefined> {
|
|
36
45
|
try {
|
|
@@ -137,3 +146,71 @@ export async function changedFiles(cwd: string, base?: string): Promise<{ top: s
|
|
|
137
146
|
return undefined;
|
|
138
147
|
}
|
|
139
148
|
}
|
|
149
|
+
|
|
150
|
+
/** What a set of files gained since a commit: which files are new, which lines changed, and the text added. */
|
|
151
|
+
export interface AddedLines {
|
|
152
|
+
/** Files new since `base` (untracked, or added): every line of them is new. */
|
|
153
|
+
whole: Set<string>;
|
|
154
|
+
/** Line numbers, in each file as it stands now, added or changed since `base`. */
|
|
155
|
+
lines: Map<string, Set<number>>;
|
|
156
|
+
/** The added text, line by line, bounded. */
|
|
157
|
+
text: Array<{ file: string; line: number; text: string }>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Most added lines kept as text; line numbers stay whole. */
|
|
161
|
+
const MAX_ADDED_TEXT = 20_000;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The lines `files` (repository-relative, under `top`) gained since `base`
|
|
165
|
+
* (HEAD when absent): from `git diff -U0`, plus every line of an untracked
|
|
166
|
+
* file. Undefined when git cannot say, so callers treat every line as new.
|
|
167
|
+
*/
|
|
168
|
+
export async function addedLines(top: string, base: string | undefined, files: readonly string[]): Promise<AddedLines | undefined> {
|
|
169
|
+
if (files.length === 0) return { whole: new Set(), lines: new Map(), text: [] };
|
|
170
|
+
try {
|
|
171
|
+
const [diff, untracked] = await Promise.all([
|
|
172
|
+
run("git", ["-c", "core.quotePath=false", "diff", "-U0", "--no-color", "--no-ext-diff", base ?? "HEAD", "--", ...files], { cwd: top, maxBuffer: MAX_BUFFER }).then((result) => result.stdout),
|
|
173
|
+
run("git", ["ls-files", "--others", "--exclude-standard", "-z", "--", ...files], { cwd: top, maxBuffer: MAX_BUFFER }).then((result) => result.stdout),
|
|
174
|
+
]);
|
|
175
|
+
const out: AddedLines = { whole: new Set(), lines: new Map(), text: [] };
|
|
176
|
+
let file: string | undefined;
|
|
177
|
+
let at = 0;
|
|
178
|
+
for (const line of diff.split("\n")) {
|
|
179
|
+
if (line.startsWith("+++ ")) {
|
|
180
|
+
const path = line.slice(4).replace(/^"|"$/g, "");
|
|
181
|
+
file = path === "/dev/null" ? undefined : path.replace(/^b\//, "");
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line);
|
|
185
|
+
if (hunk) {
|
|
186
|
+
at = Number(hunk[1]);
|
|
187
|
+
const count = hunk[2] === undefined ? 1 : Number(hunk[2]);
|
|
188
|
+
if (file && count > 0) {
|
|
189
|
+
const set = out.lines.get(file) ?? new Set<number>();
|
|
190
|
+
for (let number = at; number < at + count; number += 1) set.add(number);
|
|
191
|
+
out.lines.set(file, set);
|
|
192
|
+
}
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (file && line.startsWith("+") && !line.startsWith("+++")) {
|
|
196
|
+
if (out.text.length < MAX_ADDED_TEXT) out.text.push({ file, line: at, text: line.slice(1) });
|
|
197
|
+
at += 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
for (const path of untracked.split("\0").filter(Boolean)) {
|
|
201
|
+
out.whole.add(path);
|
|
202
|
+
try {
|
|
203
|
+
const full = join(top, path);
|
|
204
|
+
if (statSync(full).size > MAX_NEW_FILE_BYTES) continue;
|
|
205
|
+
const content = readFileSync(full, "utf8");
|
|
206
|
+
if (content.includes("\0")) continue;
|
|
207
|
+
content.split("\n").forEach((text, index) => { if (out.text.length < MAX_ADDED_TEXT) out.text.push({ file: path, line: index + 1, text }); });
|
|
208
|
+
} catch {
|
|
209
|
+
// Unreadable: its lines still count as new.
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
} catch {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
}
|