@warren-dean/code-kit 0.11.1 → 0.12.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +17 -0
- package/README.md +14 -5
- package/bin/code-kit.mjs +164 -4
- package/hooks/guard-bash.mjs +17 -0
- package/hooks/lib/harness.mjs +6 -0
- package/hooks/lib/next.mjs +19 -2
- package/hooks/lib/plan.mjs +16 -4
- package/hooks/lib/questions.mjs +140 -0
- package/hooks/lib/report.mjs +246 -0
- package/hooks/lib/rules.mjs +48 -11
- package/hooks/mod/art.mjs +2 -2
- package/hooks/mod/decisions.mjs +117 -3
- package/hooks/mod/panes.mjs +3 -1
- package/hooks/mod/register.mjs +242 -3
- package/hooks/mod/register.test.ts +344 -3
- package/hooks/mod/view.mjs +179 -4
- package/package.json +1 -1
- package/skills/init/SKILL.md +2 -1
- package/skills/next/SKILL.md +14 -0
- package/templates/agent.md +1 -0
- package/templates/claude-md.md +3 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"name": "code-kit",
|
|
10
10
|
"source": "./",
|
|
11
11
|
"description": "Keeps AI coding agents inside your project's rules, from idea to merged code: spec design, lanes, layer rules, protected paths with an approval log, spec-check and design gates, proof-before-finish checks, dispatch, review, build status and CI verification, configured per project from its docs",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.12.1",
|
|
13
13
|
"category": "development",
|
|
14
14
|
"tags": ["guardrails", "architecture", "lanes", "layers", "spec-driven", "ci", "multi-agent"],
|
|
15
15
|
"license": "MIT"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Keeps AI coding agents inside your project's rules, from idea to merged code: spec design, lanes, layer rules, protected paths with an approval log, spec-check and design gates, proof-before-finish checks, dispatch, review, build status and CI verification, configured per project from its docs",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Warren Langeveldt"
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
What changed in each release of code-kit, newest first. Versions follow the plugin manifest and the npm package `@warren-dean/code-kit`. Each release is also on [GitHub Releases](https://github.com/warrendeanlangeveldt/code-kit/releases).
|
|
4
4
|
|
|
5
|
+
## 0.12.1 (2026-10-13)
|
|
6
|
+
|
|
7
|
+
Ask about an approval before you give it.
|
|
8
|
+
|
|
9
|
+
- **Ask about it, in the Approve pane.** When a request isn't clear, ask what it's for right there. The answer comes from a side call over the lead's conversation and what code-kit knows of the request (what was asked for and for whom, why it was refused, its story, the limits it missed), and it stays in the pane: nothing is added to the lead's session. Follow-ups carry on the conversation. Before the lead has replied, it answers from code-kit's facts alone. Nothing is approved until you press Approve.
|
|
10
|
+
|
|
11
|
+
## 0.12.0 (2026-10-13)
|
|
12
|
+
|
|
13
|
+
What only you can decide reaches you, and waits until you answer.
|
|
14
|
+
|
|
15
|
+
- **Questions from the lead.** What the lead can't settle itself (a product choice, an account only you can create, wording only you can sign off) is asked with `code-kit ask`, and waits in the Decisions tab with the lead's options and what each does. Your answer, a press or your own words, is sent to the lead. A question can hold its story, which reads "waiting on you": nothing dispatches or reviews it, and the stories that depend on it wait, until you answer.
|
|
16
|
+
- **Your brief's open questions** are decisions too, each with **Answer…** (the lead records it in the brief) and **Later**.
|
|
17
|
+
- **What Claude Code itself refused.** A call Claude Code's own permission check refused, which code-kit can't approve past, waits in the band with **Put it in the prompt** (the command after `!`, for you to run), **Done it** and **Not now**.
|
|
18
|
+
- **Approval requests wait until answered.** They no longer vanish after 60 minutes; one nobody needs is dropped with **Not needed**, or by the lead with `code-kit requests drop`.
|
|
19
|
+
- **A card for each decision.** Two approvals waiting are two cards in the Decisions tab, each answered on its own.
|
|
20
|
+
- **Bug reports for code-kit.** With `harness.report.on` (off unless you turn it on; init asks), an agent that code-kit got in the way of drafts a report with only code-kit's own facts, your project's folders and email addresses taken out. You read it and send it to code-kit's GitHub with your own `gh` (a comment, if it's filed already), or get the link to file it, or discard it.
|
|
21
|
+
|
|
5
22
|
## 0.11.1 (2026-10-12)
|
|
6
23
|
|
|
7
24
|
The loop and the lead stop talking past each other, and approvals ask only what still needs you.
|
package/README.md
CHANGED
|
@@ -280,17 +280,21 @@ With Claude Code 2.1.287 or later, code-kit's mod shows the project in the sessi
|
|
|
280
280
|
- **The band** sits above the prompt. While the build is moving (the lead mid-turn, an agent at work, the loop started) it opens with the mission: **goal**, the milestone being built and how many of its stories are done; **now**, what the lead is doing and the loop's state, with **Pause** or **Resume**; **next**, the step the loop takes next; and **agents**, a line per agent at work with its current tool and how long since. With Context Graph installed, a line says when a lane edited files without understanding them, and only then. Beneath, labelled **you**, what only you can decide, each with the story it's for ("for ST-4 Booking form (BOOK-1)"), and **d** ("Why, and what each choice does") opens it as a card in the Decisions tab; usage and the plan's pause are labelled **note**. In a team (`team` in the config), it also names what of the team needs you, and only that: your stale claim (Keep or Release), a claim of yours changing the same files as someone else's, an intent touching what you own or lease (every intent, with Agree and Drop, for the tech lead), and a pull request in a lane you own. While the loop runs, a lane's finished work is the lead's to review and merge, so it never waits here; with the loop off, Review and Merge are offered to you. With nothing happening and nothing waiting, the band is gone. It shows:
|
|
281
281
|
- an agent's call held for you: installing a new package, writing or committing a protected file, or a lane changing the kit. Rather than stopping the agent, the call waits while the band asks ("web wants to install dayjs · 1:53"). **Approve** opens the confirmation and, once you confirm, the same call runs; **Refuse**, or no answer within `harness.hold.minutes` (2 unless set), refuses it as before. Under `autonomy: autonomous`, a call within `approvals.delegate` is approved as delegated without asking. Nothing is held under `claude -p` or the SDK, where nobody can answer;
|
|
282
282
|
- the lead loop, under `harness.autonomy`. `/code-kit:init` asks you how hands-off you want it. Until you've chosen, the loop's first step waits in the band ("code-kit can keep the build moving between your prompts") with **Keep going**, **Ask each time** and **Off**, and your answer is recorded as your setting. Once the lead has finished a turn, the loop keeps the build moving: with the lead idle and nothing typed in the prompt, it asks `code-kit next` and prompts the lead to dispatch the ready stories, review a finished one, or build its own, never the same step twice while nothing has changed, and never a story an agent is still on: one its lane agent is building, or a reviewer is reviewing, waits until that agent finishes ("waiting: ST-10 is still with its agent"). A step the lead was prompted with twice in a row and that is still next goes to you instead, in the band: "Merge lead/st-1: still next after 2 prompts. Ask the lead again?" with **Go**. **Pause** and **Resume** stop and start it. Under `propose`, each step waits in the band for **Go**; under `off`, the loop does nothing. A lane agent with no tool call for `harness.stall.nudgeMinutes` (5) is messaged, unless its work is done (it handed back, or its branch is in review or the merge queue); at `stall.restartMinutes` (10) the lead is asked to stop it and dispatch its story again, at most `stall.maxRestarts` (2) times, and then the band flags the lane. When every story is done, the band says so and the loop stops. With `harness.agents.reviewer.on`, a finished branch is first reviewed in the background by code-kit's reviewer agent, which the lead starts: it runs `verify` and the review checklist, read-only, and grades each finding blocker, concern or nit. The lead's review prompt then carries the findings, and a blocker means the branch is sent back. The pane shows each review, running with its clock or what it found. If the reviewer hasn't reported within 15 minutes, or the plan's 5-hour use is past `harness.background.pauseAtPercent`, the lead reviews without it. A branch the lead's review passes joins the merge queue (`code-kit queue`). Branches merge one at a time, in the order they passed, each verified against the base as it is then: the lead merges where `approvals.delegate.merge` is on under `autonomous`, otherwise the band shows the head with **Merge** for you. Who may merge is read from the config committed on the base, so a branch can't grant itself merges. There's no first merge for you: setting up commits the rules you approved straight onto the base, and in a project set up before that, the lead puts them there itself (`code-kit queue rules`) before its first merge. A merged lane's worktree is removed when it holds nothing uncommitted. A head that conflicts or fails verify goes back to its lane. The pane lists the queue, and lanes whose branch waits in it read `queued`;
|
|
283
|
-
- approvals agents have asked for: **Approve…** opens a confirmation with a reason prefilled from the request, which you keep or rewrite;
|
|
283
|
+
- approvals agents have asked for: **Approve…** opens a confirmation with a reason prefilled from the request, which you keep or rewrite. When it isn't clear what it's for, **Ask about it** in that pane: the answer comes from a side call over the lead's conversation and code-kit's facts about the request, shown in the pane and never added to the lead's session; follow-ups carry on the aside. A request waits until it's answered, its file is committed by someone who could, or it's dropped (at most a week); on its card, **Not needed** drops it;
|
|
284
|
+
- questions from the lead ("The lead asks: which weather provider?"): what it can't settle and only you can, asked with `code-kit ask`. **Answer…** opens it as a card with each of the lead's options and what it does, and **Something else…** for your own words; your answer is recorded and sent to the lead. A question can hold its story, which then reads `waiting on you`: nothing dispatches or reviews it, and the stories that depend on it wait, until you answer;
|
|
285
|
+
- your brief's open questions: **Answer…** sends your answer to the lead, which records it in the brief's decisions; **Later** puts one off until the next session;
|
|
286
|
+
- a call Claude Code's own permission check refused (its auto mode, a deny rule), which code-kit can't approve past: "Claude Code refused the lead's `pnpm db:generate`". **Put it in the prompt** puts a command in your prompt after `!` for you to run, **Done it** tells the lead you ran or allowed it, and **Not now** tells it to carry on with other work;
|
|
287
|
+
- with `harness.report.on`, a bug report for code-kit an agent drafted when code-kit got in its way: only code-kit's own facts (its version, the rule, what it said, the command), with the project's folders and email addresses taken out. **Read it** shows the exact text; **Send** files it on code-kit's GitHub with your own `gh` (a comment, if it's filed already; without `gh`, you get the link to file it), and **Discard** drops it;
|
|
284
288
|
- stories finished and waiting for review: **Review** asks the lead to run `/code-kit:review` on the branch, and **Merge** runs `verify`, checks included, then merges if it passes;
|
|
285
289
|
- a finish check failing in this session: **Lanes** opens the pane;
|
|
286
290
|
- a story using more than three times the median of the stories finished this session (once three have finished): **Lanes** opens the pane;
|
|
287
291
|
- background agents paused, once the plan’s 5-hour use passes `harness.background.pauseAtPercent` (80% unless set).
|
|
288
292
|
|
|
289
|
-
Each button has a digit: type it in an empty prompt to press it.
|
|
293
|
+
Each button has a digit: type it in an empty prompt to press it. The band keeps one line per kind ("2 approvals waiting"); the Decisions tab has a card for each decision.
|
|
290
294
|
|
|
291
295
|
- **`/lanes`** opens the Lanes pane. Its first line counts the stories by state (building, in review, queued, sent back, blocked, ready, merged), with the loop's state and the plan's 5-hour use; press a count to show only those lanes. Beneath it, whatever needs you (held calls, approvals, reviews, merges, stalled lanes, outliers) is pinned with its key. Tabs on **1** to **7**: **Lanes** opens on the loop as a card: its mode, the steps it took, the one running and the next. Then each lane on fixed columns: its name, state and story, its agent's current or last tool call with the time since beneath (amber once quiet, red when stalled), and its tokens; the lead reads orchestrating while its turn runs. A selected lane shows its stories as cards (state, requirements, what each waits on or its branch), then its agent and the paths it owns. **Decisions**, each decision that needs you as a card: what you asked for, the story and its requirements, what was happening, what's already settled, why the lead couldn't settle it, and each option with what it does; **Journal**, the build from your request to now in plain sentences, by day: the request and the decisions in the brief, the specification, each story handed out, sent back and merged, each approval and who gave it, each milestone finished, and the loop's steps this session, yours in yellow. Each lane has its own small pixel character, tinted in the lane's colour, whose pose shows its state: working (animated while its agent works), waiting on you, quiet, stalled, done or resting; the lead's wears a crown. Under each lane, its story's timeline on a shared time axis: building, then in review, with send-backs (↩), approvals (◆) and the merge (✓) marked where they fell; **Code map**, the whole project's folders by layer when no story is at work, or a story's files in a column per layer with the imports between them: a file fills with a lane's colour while that lane reads or edits it, and says whether its why (its Context Graph card) is written, out of date or not written yet; **j**/**k** select a file to see its card and what it imports and what imports it, and **o** opens it in Context Graph (box-drawing text in the terminal, an SVG on Desktop); **Trail**, every requirement in the specs as a cell by spec, its glyph and colour saying whether it's done and tested, done but untested, in progress, todo, without a story or removed, with a legend counting each. **h**/**l** and **j**/**k** move between cells and specs (or pick one from the list), showing the requirement's stories, their branches, the code they changed and the tests that name it; beneath the cells, the spec's trail drawn as a flow, each requirement to its stories, code and tests, with a missing link dashed in red; **Queue**, the merge queue; and **Usage**. **j** and **k** move between lanes, and the selected lane's acts are on **a** (approve), **r** (review), **s** (send back, with your reason), **m** (merge), **n** (nudge its agent) and **x** (stop it); **p** pauses or resumes the loop, and **f** filters the lanes by name, agent or story. **Enter** (or **o**) opens the selected lane's story: its requirements beside its spec-check report, the reviewer's findings by severity (each a way into the diff), `verify`'s problems, its usage and loop steps, and its diff against the base, file by file, with the acts its state allows. **b** or Escape goes back to the lanes. Inline (when the terminal can't dock it beside the transcript), it shows the counts, what needs you and a line per lane. It stays current within 2 seconds of a commit, a branch change, a plan edit or an agent starting or stopping. `/lanes` again, or Escape, closes it.
|
|
292
296
|
- **Refusals** a code-kit hook gives an agent are drawn as cards: what was refused, the rule, what to do, and Approve… where your approval would allow it. The full text is a press away.
|
|
293
|
-
- **`/harness`** opens the harness settings: how the lead loop acts, how long a call waiting on your approval is held, when a stalled lane is nudged and restarted, the reviewer agent and its model,
|
|
297
|
+
- **`/harness`** opens the harness settings: how the lead loop acts, how long a call waiting on your approval is held, when a stalled lane is nudged and restarted, the reviewer agent and its model, the use at which background agents pause, and whether agents draft bug reports for code-kit. Changing one asks your reason, writes `.claude/code-kit.json` and logs a `kit` approval. Lanes, paths and rules still change through `/code-kit:init`.
|
|
294
298
|
- **`/approvals`** lists the approvals waiting (one per approval and lane, with every file it covers), the approvals in force with minutes left, and every approval given, newest first: who gave it (you, or the lead within your delegated rules), its reason, and the files and branches it covered, merged or not. A request stops waiting once an approval is given for it, or its file is committed by someone who could; a read-only agent such as the reviewer never asks. **`/verify-branch`** runs `code-kit verify` on this branch. Neither calls the model.
|
|
295
299
|
|
|
296
300
|
Approving from the band records `(approved in the code-kit pane)` with your reason, and a merge from it says it was merged by the person from the pane. A settings change is logged as made in the pane. These happen only on your press: the hooks refuse `approve --via pane`, `merge --person` and `settings set` from every agent. Outside a project with `.claude/code-kit.json`, the mod draws nothing. Mods don't draw in the VS Code extension's chat panel, the Agent SDK or `claude -p`.
|
|
@@ -359,6 +363,11 @@ code-kit next [--base ref] [--json] # the step to take now, from the project'
|
|
|
359
363
|
code-kit trace <path>... [--json] # what a file is for: requirements, lane, layer rules
|
|
360
364
|
code-kit merge <branch> --delegated|--person [--into branch] # merge a branch that passes verify: the lead where approvals.delegate.merge is on, or the person
|
|
361
365
|
code-kit requests [--json] # approval requests still waiting, the approvals in force, and every approval given
|
|
366
|
+
code-kit requests drop <n>[,<n>…]|all [--reason "…"] # stop waiting on requests nobody needs answered (the lead, or Not needed in the pane)
|
|
367
|
+
code-kit ask "<question>" [--story ST-n] [--blocks] [--why "…"] [--option "Label: what it does"]... # the lead asks you what it can't settle; it waits in your Decisions tab
|
|
368
|
+
code-kit ask [--json] | ask answer <id> "<answer>" | ask withdraw <id> --reason "…" # the questions; your answer (given in chat, recorded by the lead); one settled another way
|
|
369
|
+
code-kit report "<title>" --what "…" [--expected …] [--command …] [--refusal …] [--rule …] # with harness.report.on, an agent drafts a bug report for code-kit
|
|
370
|
+
code-kit report [--json] | report send|discard <id> # the drafts; sending files it on code-kit's GitHub with your gh, which only you do
|
|
362
371
|
code-kit stops [--session id] [--json] # finish checks that refused an agent's last stop and still fail
|
|
363
372
|
code-kit sent-back <branch> [--reason "…"] # record a review's send-back: status shows the story as sent back until the branch moves on
|
|
364
373
|
code-kit settings [--json] # the harness settings in force, with their defaults
|
|
@@ -419,7 +428,7 @@ code-kit approve <name>... --reason "…" [--lane name] [--delegated | --via pan
|
|
|
419
428
|
| `setup` | no | The version of init's setup the project has (3 now). Without it, or lower, `next` names what the project is missing (the design gate, design stories before screens, a QA lane, a security reviewer) and starts no new story until re-running init adds it |
|
|
420
429
|
| `team` | no | Preview. For several people in one repository: `{ techLead, staleDays?, intents?, board?, provider? }`. `techLead` is the GitHub handle of the person who owns the plan, specs and contracts; `staleDays` is how many working days without a push make a claim stale (default 3); `intents` is the discussion category intents go in (default `Intents`; create it once in the repository's Settings › Discussions); `board` is `{ owner, number }`, a GitHub Project the plan is mirrored onto; `provider` is `"github"` (default) or `{ command, fallback? }` for another team provider, such as the code-kit teams hub; with `fallback: "github"`, claims go through GitHub whenever that provider can't be reached. See docs/specs/16-teams.md. |
|
|
421
430
|
| `branches.protected` | no | Default `["main", "master"]` |
|
|
422
|
-
| `harness` | no | What the mod's harness does in the session: `autonomy` (`autonomous`, `propose` or `off`; default `autonomous`), `hold.minutes` (how long a call waiting on your approval is held, 0 to 10; default 2), `stall.nudgeMinutes` and `stall.restartMinutes` (default 5 and 10), `stall.maxRestarts` (default 2), `agents.reviewer.on` (default `false`) and `agents.reviewer.model` (default the session's),
|
|
431
|
+
| `harness` | no | What the mod's harness does in the session: `autonomy` (`autonomous`, `propose` or `off`; default `autonomous`), `hold.minutes` (how long a call waiting on your approval is held, 0 to 10; default 2), `stall.nudgeMinutes` and `stall.restartMinutes` (default 5 and 10), `stall.maxRestarts` (default 2), `agents.reviewer.on` (default `false`) and `agents.reviewer.model` (default the session's), `background.pauseAtPercent` (default 80) and `report.on` (agents draft bug reports for code-kit, which you send or discard; default `false`). Change them with `/harness` or `code-kit settings set`. |
|
|
423
432
|
|
|
424
433
|
Add `.claude/approvals/`, `.claude/state/` and `.claude/code-kit.draft.json` to `.gitignore`, and `.claude/approval-log.jsonl merge=union` to `.gitattributes`, so merging branches that both logged changes keeps every entry. Commit `.claude/code-kit.baseline.json` if the project has one.
|
|
425
434
|
|
|
@@ -454,7 +463,7 @@ code-kit approve dep-dayjs --lane web --reason "Yes, add dayjs for ST-12"
|
|
|
454
463
|
|
|
455
464
|
### Requests
|
|
456
465
|
|
|
457
|
-
Every refusal that a person's approval would allow is recorded as a request in `.claude/state/requests.jsonl` in the main checkout: who was refused, the approvals that would allow it, the command or file, and why. A request stays open until approvals for all its names
|
|
466
|
+
Every refusal that a person's approval would allow is recorded as a request in `.claude/state/requests.jsonl` in the main checkout: who was refused, the approvals that would allow it, the command or file, and why. A request stays open until approvals for all its names answer it, its file is committed by someone who could, or it's dropped (`code-kit requests drop`), and at most a week. `code-kit requests` lists the open ones, numbered, and the approvals in force, with minutes left, and the code-kit mod shows them above the prompt.
|
|
458
467
|
|
|
459
468
|
`--via pane` and `merge --person` are the person's own acts, which the code-kit mod uses for a press in its pane. The hooks refuse both from every agent.
|
|
460
469
|
|
package/bin/code-kit.mjs
CHANGED
|
@@ -20,6 +20,13 @@
|
|
|
20
20
|
// merge <branch> --delegated|--person [--into branch] merge a branch that passes verify: the lead
|
|
21
21
|
// (approvals.delegate.merge) or the person (the code-kit pane)
|
|
22
22
|
// requests [--json] open approval requests and approvals in force
|
|
23
|
+
// requests drop <n>|all [--reason …] [--via pane] drop requests nobody needs answered any more
|
|
24
|
+
// ask "<question>" [--story ST-n] [--blocks] [--why …] [--option "Label: what it does"]...
|
|
25
|
+
// the lead asks the person what it can't settle; ask [--json] lists them
|
|
26
|
+
// ask answer <id> "<answer>" [--via pane] | ask withdraw <id> --reason …
|
|
27
|
+
// report "<title>" --what … [--expected …] [--command …] [--refusal …] [--rule …]
|
|
28
|
+
// with harness.report.on, an agent drafts a bug report for code-kit
|
|
29
|
+
// report [--json] | report send|discard <id> the person sends a draft to code-kit's GitHub, or not
|
|
23
30
|
// stops [--session id] [--json] finish checks that refused an agent's last stop, still failing
|
|
24
31
|
// sent-back <branch> [--reason …] records that a review sent the branch back at its current commit
|
|
25
32
|
// settings [--json] | settings set <key> <value> --reason … [--via pane]
|
|
@@ -51,6 +58,13 @@ import { diffConfigs, ownershipMoves } from '../hooks/lib/diff.mjs';
|
|
|
51
58
|
import { CODE, importsOf, layerOf, layerProblems, targetOf } from '../hooks/lib/layers.mjs';
|
|
52
59
|
import { ADAPTERS, activeAdapters, adapterFacts } from '../hooks/lib/adapters/index.mjs';
|
|
53
60
|
import { nextStep } from '../hooks/lib/next.mjs';
|
|
61
|
+
import { discardReport, draftReport, readReports, sendReport } from '../hooks/lib/report.mjs';
|
|
62
|
+
import {
|
|
63
|
+
answerQuestion,
|
|
64
|
+
askQuestion,
|
|
65
|
+
readQuestions,
|
|
66
|
+
withdrawQuestion,
|
|
67
|
+
} from '../hooks/lib/questions.mjs';
|
|
54
68
|
import { traceFile } from '../hooks/lib/trace.mjs';
|
|
55
69
|
import { buildStatus, specCheckRows } from '../hooks/lib/plan.mjs';
|
|
56
70
|
import { recordSentBack } from '../hooks/lib/reviews.mjs';
|
|
@@ -84,6 +98,7 @@ import {
|
|
|
84
98
|
approvalsInForce,
|
|
85
99
|
delegatedMark,
|
|
86
100
|
grantApprovals,
|
|
101
|
+
dropRequests,
|
|
87
102
|
openRequests,
|
|
88
103
|
ownerOf,
|
|
89
104
|
reviewerOf,
|
|
@@ -126,6 +141,15 @@ const staleDaysGiven = option('--stale-days');
|
|
|
126
141
|
const intentsGiven = option('--intents');
|
|
127
142
|
const ownersGiven = [];
|
|
128
143
|
for (let o = option('--owner'); o !== undefined; o = option('--owner')) ownersGiven.push(o);
|
|
144
|
+
const storyGiven = option('--story');
|
|
145
|
+
const whyGiven = option('--why');
|
|
146
|
+
const whatGiven = option('--what');
|
|
147
|
+
const expectedGiven = option('--expected');
|
|
148
|
+
const commandGiven = option('--command');
|
|
149
|
+
const refusalGiven = option('--refusal');
|
|
150
|
+
const ruleGiven = option('--rule');
|
|
151
|
+
const optionsGiven = [];
|
|
152
|
+
for (let o = option('--option'); o !== undefined; o = option('--option')) optionsGiven.push(o);
|
|
129
153
|
const FLAGS = [
|
|
130
154
|
'--write',
|
|
131
155
|
'--no-checks',
|
|
@@ -135,6 +159,7 @@ const FLAGS = [
|
|
|
135
159
|
'--fresh',
|
|
136
160
|
'--peek',
|
|
137
161
|
'--off',
|
|
162
|
+
'--blocks',
|
|
138
163
|
];
|
|
139
164
|
const flag = (name) => args.includes(name);
|
|
140
165
|
const writeBaseline = flag('--write');
|
|
@@ -911,16 +936,30 @@ function story(id) {
|
|
|
911
936
|
// Open approval requests and the approvals in force, for the person and the mod's band.
|
|
912
937
|
function requests() {
|
|
913
938
|
load();
|
|
939
|
+
// CARD-7: a request nobody needs answered any more is dropped: the person's Dismiss, or the lead.
|
|
940
|
+
if (rest[0] === 'drop') {
|
|
941
|
+
if (!rest[1])
|
|
942
|
+
die('code-kit requests drop <n>|all [--reason "…"]: n as `code-kit requests` numbers them.');
|
|
943
|
+
const r = dropRequests(resolve('.'), rest[1], {
|
|
944
|
+
by: via === 'pane' ? 'person' : 'lead',
|
|
945
|
+
reason: reason ?? null,
|
|
946
|
+
});
|
|
947
|
+
if (!r.ok) die(r.why);
|
|
948
|
+
return out(
|
|
949
|
+
`Dropped ${r.dropped.length} request${r.dropped.length === 1 ? '' : 's'}: ${r.dropped.map((d) => `${d.names.join(', ')} for ${d.lane ? `the ${d.lane} lane` : d.actor}`).join('; ')}.`,
|
|
950
|
+
);
|
|
951
|
+
}
|
|
914
952
|
const open = openRequests(resolve('.'));
|
|
915
953
|
const granted = approvalsInForce(resolve('.'));
|
|
916
954
|
// CARD-6: every approval given, newest first.
|
|
917
955
|
const history = approvalHistory(resolve('.')).slice(0, 100);
|
|
918
956
|
if (flag('--json')) return out(JSON.stringify({ open, inForce: granted, history }, null, 2));
|
|
919
957
|
out(open.length ? `${open.length} approval request(s) open:` : 'No approval requests open.');
|
|
920
|
-
|
|
958
|
+
open.forEach((r, i) =>
|
|
921
959
|
out(
|
|
922
|
-
` ${r.names.join(', ')} for ${r.lane ? `the ${r.lane} lane` : r.actor}: ${r.what} (${r.why})`,
|
|
923
|
-
)
|
|
960
|
+
` ${i + 1}. ${r.names.join(', ')} for ${r.lane ? `the ${r.lane} lane` : r.actor}: ${r.what} (${r.why}; asked ${r.at.slice(0, 16).replace('T', ' ')})`,
|
|
961
|
+
),
|
|
962
|
+
);
|
|
924
963
|
out(granted.length ? `\n${granted.length} approval(s) in force:` : '\nNo approvals in force.');
|
|
925
964
|
for (const a of granted)
|
|
926
965
|
out(
|
|
@@ -1613,6 +1652,121 @@ function intendCommand() {
|
|
|
1613
1652
|
);
|
|
1614
1653
|
}
|
|
1615
1654
|
|
|
1655
|
+
/**
|
|
1656
|
+
* ASK-1 to ASK-3: questions for the person. `ask "<question>"` records one (the lead's: the hooks refuse
|
|
1657
|
+
* it from lanes), waiting in the Decisions tab; `--blocks` holds its story until it's answered. `ask`
|
|
1658
|
+
* lists them; `ask answer` records the answer (the person's from the pane with --via pane, which the
|
|
1659
|
+
* hooks refuse from agents; otherwise the lead recording what the person said in chat); `ask withdraw`
|
|
1660
|
+
* takes one back once it no longer needs the person.
|
|
1661
|
+
*/
|
|
1662
|
+
function askCommand() {
|
|
1663
|
+
const root = resolve('.');
|
|
1664
|
+
const [sub, id, ...words] = rest;
|
|
1665
|
+
if (sub === 'answer') {
|
|
1666
|
+
const r = answerQuestion(root, id, {
|
|
1667
|
+
answer: words.join(' '),
|
|
1668
|
+
by: via === 'pane' ? 'person' : 'lead',
|
|
1669
|
+
via: via === 'pane' ? 'pane' : 'chat',
|
|
1670
|
+
});
|
|
1671
|
+
if (!r.ok) die(r.why);
|
|
1672
|
+
return out(
|
|
1673
|
+
`Recorded the answer to ${id}${via === 'pane' ? '' : ' (given in chat, recorded by the lead)'}: ${r.question.answer.text}${r.question.blocks ? ` ${r.question.story} no longer waits on the person.` : ''}`,
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
if (sub === 'withdraw') {
|
|
1677
|
+
if (!reason?.trim()) die('Say why it no longer needs the person, with --reason "…".');
|
|
1678
|
+
const r = withdrawQuestion(root, id, reason);
|
|
1679
|
+
if (!r.ok) die(r.why);
|
|
1680
|
+
return out(`Withdrew ${id}: ${reason.trim()}`);
|
|
1681
|
+
}
|
|
1682
|
+
const all = readQuestions(root);
|
|
1683
|
+
if (!rest.length) {
|
|
1684
|
+
if (flag('--json')) return out(JSON.stringify(all, null, 2));
|
|
1685
|
+
const open = all.filter((q) => q.state === 'open');
|
|
1686
|
+
out(
|
|
1687
|
+
open.length
|
|
1688
|
+
? `${open.length} question(s) wait for the person:`
|
|
1689
|
+
: 'No questions wait for the person.',
|
|
1690
|
+
);
|
|
1691
|
+
for (const q of open)
|
|
1692
|
+
out(
|
|
1693
|
+
` ${q.id}${q.story ? ` (${q.story}${q.blocks ? ', which waits on it' : ''})` : ''}: ${q.question}${q.options.length ? ` [${q.options.map((o) => o.label).join(' / ')}]` : ''}`,
|
|
1694
|
+
);
|
|
1695
|
+
const answered = all.filter((q) => q.state === 'answered').slice(-10);
|
|
1696
|
+
if (answered.length) out('\nAnswered lately:');
|
|
1697
|
+
for (const q of answered) out(` ${q.id}: ${q.question} → ${q.answer.text}`);
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
const question = rest.join(' ').trim();
|
|
1701
|
+
if (flag('--blocks') && !storyGiven) die('--blocks holds a story: name it with --story ST-n.');
|
|
1702
|
+
const r = askQuestion(root, {
|
|
1703
|
+
by: 'lead',
|
|
1704
|
+
question,
|
|
1705
|
+
why: whyGiven ?? null,
|
|
1706
|
+
story: storyGiven ?? null,
|
|
1707
|
+
blocks: flag('--blocks'),
|
|
1708
|
+
options: optionsGiven,
|
|
1709
|
+
});
|
|
1710
|
+
out(
|
|
1711
|
+
r.existed
|
|
1712
|
+
? `${r.question.id} is already open: ${r.question.question}`
|
|
1713
|
+
: `Asked ${r.question.id}. It waits in the person's Decisions tab${r.question.blocks ? `, and ${r.question.story} waits on it` : ''}; their answer is sent to you as a prompt. Carry on with other work meanwhile.`,
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* REPORT-1 to REPORT-3: bug reports for code-kit itself. An agent drafts one (with harness.report.on);
|
|
1719
|
+
* the person reads it and sends or discards it, from the pane or their terminal: the hooks refuse
|
|
1720
|
+
* `report send` and `report discard` from every agent.
|
|
1721
|
+
*/
|
|
1722
|
+
function reportCommand() {
|
|
1723
|
+
const root = resolve('.');
|
|
1724
|
+
const [sub, id] = rest;
|
|
1725
|
+
if (sub === 'send') {
|
|
1726
|
+
const r = sendReport(root, id);
|
|
1727
|
+
if (!r.ok) die(`${r.why}${r.link ? `\nOpen this to file it yourself: ${r.link}` : ''}`);
|
|
1728
|
+
return out(
|
|
1729
|
+
r.comment ? `Someone had filed it already; added it there: ${r.url}` : `Filed ${r.url}`,
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
if (sub === 'discard') {
|
|
1733
|
+
const r = discardReport(root, id);
|
|
1734
|
+
if (!r.ok) die(r.why);
|
|
1735
|
+
return out(`Discarded ${id}.`);
|
|
1736
|
+
}
|
|
1737
|
+
if (!rest.length) {
|
|
1738
|
+
const all = readReports(root);
|
|
1739
|
+
if (flag('--json')) return out(JSON.stringify(all, null, 2));
|
|
1740
|
+
const drafts = all.filter((r) => r.state === 'draft');
|
|
1741
|
+
out(
|
|
1742
|
+
drafts.length
|
|
1743
|
+
? `${drafts.length} report(s) wait for the person to send or discard:`
|
|
1744
|
+
: 'No reports waiting.',
|
|
1745
|
+
);
|
|
1746
|
+
for (const r of drafts) out(` ${r.id}: ${r.title}`);
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
const { config } = load();
|
|
1750
|
+
if (!config.harness.report.on)
|
|
1751
|
+
die(
|
|
1752
|
+
"Reporting code-kit's bugs is off (harness.report.on). Tell the person what went wrong instead: what you ran, what code-kit said, and what you expected.",
|
|
1753
|
+
);
|
|
1754
|
+
const r = draftReport(root, {
|
|
1755
|
+
title: rest.join(' '),
|
|
1756
|
+
what: whatGiven,
|
|
1757
|
+
expected: expectedGiven,
|
|
1758
|
+
command: commandGiven,
|
|
1759
|
+
refusal: refusalGiven,
|
|
1760
|
+
rule: ruleGiven,
|
|
1761
|
+
});
|
|
1762
|
+
if (!r.ok) die(r.why);
|
|
1763
|
+
out(
|
|
1764
|
+
r.existed
|
|
1765
|
+
? `${r.report.id} already reports this; it waits for the person.`
|
|
1766
|
+
: `Drafted ${r.report.id}. The person reads it in the Decisions tab and sends it to code-kit's GitHub, or discards it. Carry on: work around it if you can, and say so.`,
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1616
1770
|
/**
|
|
1617
1771
|
* The lead loop's pause (LOOP-6): `loop` says whether the person paused it; `loop pause|resume`, the
|
|
1618
1772
|
* person's act from the code-kit band, records it, so other tools' background work pauses with it.
|
|
@@ -1647,6 +1801,8 @@ const commands = {
|
|
|
1647
1801
|
queue,
|
|
1648
1802
|
team: teamCommand,
|
|
1649
1803
|
intend: intendCommand,
|
|
1804
|
+
ask: askCommand,
|
|
1805
|
+
report: reportCommand,
|
|
1650
1806
|
story: () => (rest.length === 1 ? story(rest[0]) : usage()),
|
|
1651
1807
|
map: () => (rest.length === 1 ? mapCommand(rest[0]) : usage()),
|
|
1652
1808
|
trail: trailCommand,
|
|
@@ -1672,7 +1828,11 @@ function usage() {
|
|
|
1672
1828
|
' | security [--base ref] [--branch name] [--json]\n' +
|
|
1673
1829
|
' | next [--base ref] [--json] | adapters | trace <path>... [--json]\n' +
|
|
1674
1830
|
' | approve <name>... --reason "…" [--lane name] [--config file]\n' +
|
|
1675
|
-
' | merge <branch> --delegated|--person [--into branch] | requests [--json]\n' +
|
|
1831
|
+
' | merge <branch> --delegated|--person [--into branch] | requests [--json] | requests drop <n>|all\n' +
|
|
1832
|
+
' | ask "<question>" [--story ST-n] [--blocks] [--why "…"] [--option "Label: effect"]... | ask [--json]\n' +
|
|
1833
|
+
' | ask answer <id> "<answer>" | ask withdraw <id> --reason "…"\n' +
|
|
1834
|
+
' | report "<title>" --what "…" [--expected …] [--command …] [--refusal …] [--rule …]\n' +
|
|
1835
|
+
' | report [--json] | report send|discard <id>\n' +
|
|
1676
1836
|
' | stops [--session id] [--json] | sent-back <branch> [--reason "…"]\n' +
|
|
1677
1837
|
' | settings [--json] | settings set <key> <value> --reason "…" [--via pane]\n' +
|
|
1678
1838
|
' | hold <id> [--minutes N] | hold <id> --answer approved|refused\n' +
|
package/hooks/guard-bash.mjs
CHANGED
|
@@ -77,6 +77,8 @@ const approves = runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+approve\b`));
|
|
|
77
77
|
const delegatedMerge = runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+(?:queue\s+)?merge\b`));
|
|
78
78
|
const queues = runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+queue\s+(?:add|drop)\b`));
|
|
79
79
|
const rulesOntoBase = runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+queue\s+rules\b`));
|
|
80
|
+
const asks = runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+ask\b\s+(?!--json\b)\S`));
|
|
81
|
+
const dropsRequests = runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+requests\s+drop\b`));
|
|
80
82
|
const commits = runs(cmd, new RegExp(String.raw`^${GIT}commit\b`));
|
|
81
83
|
if (error) {
|
|
82
84
|
if (commits) block(`Blocked: ${error}\nFix the config before committing.`);
|
|
@@ -163,6 +165,21 @@ if ((delegatedMerge && /\s--person\b/.test(cmd)) || (approves && /\s--via\b/.tes
|
|
|
163
165
|
block(
|
|
164
166
|
`Blocked: merging or approving as the person is the person's own act, from the code-kit pane or their terminal. Ask them, and stop.\nCommand: ${cmd}`,
|
|
165
167
|
);
|
|
168
|
+
// ASK-2, CARD-7: answering as the person, or dismissing their request, is theirs, from the pane.
|
|
169
|
+
if ((asks || dropsRequests) && /\s--via\b/.test(cmd))
|
|
170
|
+
block(
|
|
171
|
+
`Blocked: answering a question or dismissing a request in the person's name is their own act, from the code-kit pane or their terminal. Ask them, and stop.\nCommand: ${cmd}`,
|
|
172
|
+
);
|
|
173
|
+
// ASK-1: questions for the person, and taking back requests, go through the lead.
|
|
174
|
+
if ((asks || dropsRequests) && actor.kind !== 'lead')
|
|
175
|
+
block(
|
|
176
|
+
`Blocked: only the lead asks the person a question or drops an approval request. Write the question for the lead (what you need decided, the options and what each does), and stop.\nCommand: ${cmd}`,
|
|
177
|
+
);
|
|
178
|
+
// REPORT-3: sending a report to code-kit's GitHub, under the person's account, or discarding it, is theirs.
|
|
179
|
+
if (runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+report\s+(send|discard)\b`)))
|
|
180
|
+
block(
|
|
181
|
+
`Blocked: sending a bug report to code-kit's GitHub, or discarding it, is the person's own act, from the code-kit pane or their terminal. Tell them it's drafted, and carry on.\nCommand: ${cmd}`,
|
|
182
|
+
);
|
|
166
183
|
// Pausing and resuming the lead loop is the person's, from the band or their terminal.
|
|
167
184
|
if (runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+loop\s+(pause|resume)\b`)))
|
|
168
185
|
block(
|
package/hooks/lib/harness.mjs
CHANGED
|
@@ -57,6 +57,12 @@ export const SETTINGS = {
|
|
|
57
57
|
Number.isFinite(v) && v > 0 && v <= 100 ? null : 'must be a percentage from 1 to 100',
|
|
58
58
|
about: "The plan's 5-hour use at which background agents pause",
|
|
59
59
|
},
|
|
60
|
+
'report.on': {
|
|
61
|
+
default: false,
|
|
62
|
+
problem: (v) => (typeof v === 'boolean' ? null : 'must be true or false'),
|
|
63
|
+
about:
|
|
64
|
+
"Agents draft a bug report for code-kit's GitHub when code-kit itself gets in their way; you read each one and send it, or not",
|
|
65
|
+
},
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
const get = (obj, key) =>
|
package/hooks/lib/next.mjs
CHANGED
|
@@ -56,18 +56,21 @@ const hasMarkdown = (dir) => {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* { step, why, args, then, attention }. `step` is a skill (spec-design, init, check, dispatch,
|
|
59
|
+
* { step, why, args, then, attention, brief }. `brief`: the brief's open questions (ASK-4). `step` is a skill (spec-design, init, check, dispatch,
|
|
60
60
|
* review), `merge` (the head of the merge queue, where the lead merges), `wait` (lanes are
|
|
61
61
|
* building) or `done`. `base(config)` resolves the branch lanes merge into.
|
|
62
62
|
*/
|
|
63
63
|
export function nextStep(root, base) {
|
|
64
64
|
const attention = [];
|
|
65
|
+
// ASK-4: the brief's open questions, for the person's Decisions tab.
|
|
66
|
+
let briefQuestions = [];
|
|
65
67
|
const result = (step, why, extra = {}) => ({
|
|
66
68
|
step,
|
|
67
69
|
why,
|
|
68
70
|
args: '',
|
|
69
71
|
then: [],
|
|
70
72
|
attention,
|
|
73
|
+
brief: briefQuestions,
|
|
71
74
|
...extra,
|
|
72
75
|
});
|
|
73
76
|
const isRepo = git(root, 'rev-parse', '--is-inside-work-tree') === 'true';
|
|
@@ -82,8 +85,10 @@ export function nextStep(root, base) {
|
|
|
82
85
|
`The config is invalid, so nothing but the config can be written: ${error.split('\n')[0]}`,
|
|
83
86
|
);
|
|
84
87
|
const brief = readBrief(root, config);
|
|
85
|
-
if (brief?.openQuestions.length)
|
|
88
|
+
if (brief?.openQuestions.length) {
|
|
86
89
|
attention.push(`${brief.openQuestions.length} open question(s) in ${brief.path}.`);
|
|
90
|
+
briefQuestions = brief.openQuestions.map((question) => ({ question, path: brief.path }));
|
|
91
|
+
}
|
|
87
92
|
|
|
88
93
|
if (!config) {
|
|
89
94
|
if (existsSync(join(root, DRAFT_FILE)))
|
|
@@ -187,6 +192,12 @@ export function nextStep(root, base) {
|
|
|
187
192
|
const [review, building, blocked, fixes] = ['review', 'in progress', 'blocked', 'sent back'].map(
|
|
188
193
|
ids,
|
|
189
194
|
);
|
|
195
|
+
// ASK-3: a story whose question is open waits on the person; nothing dispatches or reviews it.
|
|
196
|
+
const waitingOnPerson = status.stories.filter((s) => s.state === 'waiting on you');
|
|
197
|
+
for (const s of waitingOnPerson)
|
|
198
|
+
attention.push(
|
|
199
|
+
`${s.id} waits on the person: ${s.waitingFor.question} (${s.waitingFor.id}, in their Decisions tab).`,
|
|
200
|
+
);
|
|
190
201
|
// A ready story with a gap (unknown lane, a requirement no spec defines) waits for the plan's fix.
|
|
191
202
|
const defined = new Set(status.requirements.map((r) => r.id));
|
|
192
203
|
const buildable = (s) =>
|
|
@@ -302,6 +313,12 @@ export function nextStep(root, base) {
|
|
|
302
313
|
'spec-design',
|
|
303
314
|
`${unbuildable.join(', ')} ${unbuildable.length === 1 ? 'is' : 'are'} next, but the plan has gaps to fix first.`,
|
|
304
315
|
);
|
|
316
|
+
// ASK-3: what's left waits on the person's answers, directly or through a dependency.
|
|
317
|
+
if (waitingOnPerson.length)
|
|
318
|
+
return result(
|
|
319
|
+
'wait',
|
|
320
|
+
`${waitingOnPerson.map((s) => `${s.id} waits on the person (${s.waitingFor.id})`).join('; ')}${blocked.length ? `; ${blocked.join(', ')} ${blocked.length === 1 ? 'waits' : 'wait'} on ${waitingOnPerson.length === 1 ? 'it' : 'them'}` : ''}. Carry on with anything else; their answer is sent to you.`,
|
|
321
|
+
);
|
|
305
322
|
if (blocked.length)
|
|
306
323
|
return result(
|
|
307
324
|
'spec-design',
|
package/hooks/lib/plan.mjs
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { execFileSync } from 'node:child_process';
|
|
8
8
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
|
+
import { waitingStories } from './questions.mjs';
|
|
10
11
|
import { sentBack } from './reviews.mjs';
|
|
11
12
|
|
|
12
13
|
export const REQUIREMENT = /\b(?!ST-)([A-Z][A-Z0-9]*-\d+)\b/g;
|
|
@@ -138,7 +139,8 @@ function worktrees(root) {
|
|
|
138
139
|
* `base` is in review; one level with it is in progress, or done once the plan had it in review.
|
|
139
140
|
* A branch still at the commit a review sent back is `sent back`, waiting for its lane's fix.
|
|
140
141
|
* Without a branch, the plan's status stands. A story is `blocked` while a story it depends on
|
|
141
|
-
* isn't done, whatever its branch holds; a todo story whose dependencies are done is `ready`.
|
|
142
|
+
* isn't done, whatever its branch holds; a todo story whose dependencies are done is `ready`. A story
|
|
143
|
+
* with an open question for the person that holds it (`code-kit ask --blocks`) is `waiting on you`.
|
|
142
144
|
*/
|
|
143
145
|
export function storyStates(root, stories, base) {
|
|
144
146
|
const trees = worktrees(root);
|
|
@@ -165,10 +167,16 @@ export function storyStates(root, stories, base) {
|
|
|
165
167
|
};
|
|
166
168
|
});
|
|
167
169
|
const done = new Set(states.filter((s) => s.state === 'done').map((s) => s.id));
|
|
170
|
+
// ASK-3: a story whose question for the person is open waits on them, whatever its branch holds.
|
|
171
|
+
const asked = waitingStories(root);
|
|
168
172
|
for (const s of states) {
|
|
169
173
|
if (s.state === 'done') continue;
|
|
170
174
|
s.waitingOn = s.dependsOn.filter((d) => !done.has(d));
|
|
171
|
-
|
|
175
|
+
s.waitingFor = asked.has(s.id)
|
|
176
|
+
? { id: asked.get(s.id).id, question: asked.get(s.id).question }
|
|
177
|
+
: null;
|
|
178
|
+
if (s.waitingFor) s.state = 'waiting on you';
|
|
179
|
+
else if (s.waitingOn.length) s.state = 'blocked';
|
|
172
180
|
else if (s.state === 'todo') s.state = 'ready';
|
|
173
181
|
}
|
|
174
182
|
return states;
|
|
@@ -216,7 +224,11 @@ export function buildStatus(root, config, base, files) {
|
|
|
216
224
|
let state = 'no story';
|
|
217
225
|
if (r.removed) state = 'removed';
|
|
218
226
|
else if (covering.length && covering.every((s) => s.state === 'done')) state = 'done';
|
|
219
|
-
else if (
|
|
227
|
+
else if (
|
|
228
|
+
covering.some((s) =>
|
|
229
|
+
['in progress', 'review', 'sent back', 'waiting on you', 'done'].includes(s.state),
|
|
230
|
+
)
|
|
231
|
+
)
|
|
220
232
|
state = 'in progress';
|
|
221
233
|
else if (covering.length) state = 'todo';
|
|
222
234
|
return { ...r, state, stories: covering.map((s) => s.id), tests: tested[r.id] ?? [] };
|
|
@@ -224,7 +236,7 @@ export function buildStatus(root, config, base, files) {
|
|
|
224
236
|
const drift = stories
|
|
225
237
|
.filter(
|
|
226
238
|
(s) =>
|
|
227
|
-
!['ready', 'blocked'].includes(s.state) &&
|
|
239
|
+
!['ready', 'blocked', 'waiting on you'].includes(s.state) &&
|
|
228
240
|
s.state !== s.status &&
|
|
229
241
|
!(s.state === 'sent back' && s.status === 'in progress'),
|
|
230
242
|
)
|