Triage of Vikunja P19 #228 and its c78, the gaps Jason's first PM request hit. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,244 @@
|
|||||||
|
Status: written by Sage, lead. It follows `docs/plans/BRIEF-TEMPLATE.md`.
|
||||||
|
It triages Vikunja P19 #228 (dogfood), which Mos filed from Jason's
|
||||||
|
first PM request after the PM started (talk fef3fa41, PM RESULT e300f8af,
|
||||||
|
about 2026-10-12T19:27Z; Mos sage-0100), and the three gaps Mos added in
|
||||||
|
#228 c78 from the same test (sage-0101). Three rows: the board read, the
|
||||||
|
requirement ids, and the role checkout. All three block S7 (row 42): the
|
||||||
|
PM can't carry a request to a task, or the coder work on the project,
|
||||||
|
until they land. Gitea issues #1583, #1584 and #1585.
|
||||||
|
|
||||||
|
# The PM's task flow on a real board (#228)
|
||||||
|
|
||||||
|
## PM task reads that fit a session
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
The PM's `list_tasks` tool takes no arguments
|
||||||
|
(`packages/harness/src/tools.mjs:116`, `obj({})`). The broker's `tasks`
|
||||||
|
verb returns every row of `task_current` with all its fields, description
|
||||||
|
included (`packages/bus/src/broker.mjs:313-329`, `:860`), and the MCP
|
||||||
|
server sends it as indented JSON (`packages/harness/src/mcp-server.mjs:40`,
|
||||||
|
`pi-extension.mjs:45`). On the Mosaic Stack board that reply was about
|
||||||
|
120 KB. Claude Code saved the oversized result to a file outside the
|
||||||
|
PM's workspace, and the run's file guard refused to read it. That refusal
|
||||||
|
is right; the reply is the defect. So the PM couldn't see the board.
|
||||||
|
|
||||||
|
### Owner and reviewer
|
||||||
|
|
||||||
|
Owner: Dewey, after row 81 lands. Reviewer: Darkwing.
|
||||||
|
|
||||||
|
### Files owned
|
||||||
|
|
||||||
|
- `packages/bus/src/broker.mjs`, the `tasks` verb and `#taskRows` only
|
||||||
|
- `packages/harness/src/tools.mjs`, `mcp-server.mjs`, `pi-extension.mjs`
|
||||||
|
- `packages/bus/README.md`, `packages/harness/README.md`, the sections
|
||||||
|
on the `tasks` verb and the tool replies
|
||||||
|
- tests under `packages/bus/tests/` and `packages/harness/tests/`
|
||||||
|
- `agents/dewey/work/queue-<row>/`
|
||||||
|
|
||||||
|
### What ships
|
||||||
|
|
||||||
|
- The broker's `tasks` verb takes optional arguments. With none, it
|
||||||
|
returns what it returns today, so the Console and `mosaic` are
|
||||||
|
unchanged. With any, it filters and pages: `done` (true, false; the
|
||||||
|
tool's default is false), `assignee` (a role), `label`, `requirement`,
|
||||||
|
`limit` (default 50, at most 200) and `after` (a task ref, for the next
|
||||||
|
page). A filtered reply is `{ tasks, total, next }`, and each task is
|
||||||
|
the short form: ref, title, done, bucket, assignee roles, labels,
|
||||||
|
priority, requirement and the last change time, with no description.
|
||||||
|
An unknown argument or a bad value refuses with `invalid-request`.
|
||||||
|
- One more argument, `task_ref`, returns that one task in full, as today's
|
||||||
|
row, so the PM reads a description only when it needs one.
|
||||||
|
- `list_tasks` exposes those arguments and says the default is open
|
||||||
|
tasks, 50 at a time. A `get_task` tool takes `task_ref`. Both are
|
||||||
|
typed for the pm role and the same holders as today.
|
||||||
|
- Every tool reply has a size cap, in `mcp-server.mjs` and
|
||||||
|
`pi-extension.mjs` both, set below the size at which the pinned Claude
|
||||||
|
CLI saves a tool result to a file. Dewey finds that size for the
|
||||||
|
version the bus unit runs (`MOSAIC_CLAUDE_BIN`, decision 91), records
|
||||||
|
how in the packet, and sets the cap with margin. Over the cap, the tool
|
||||||
|
returns an error naming the size and the cap and telling the session to
|
||||||
|
narrow the query, never a cut-off reply. Replies go out as compact
|
||||||
|
JSON, not indented.
|
||||||
|
- Tests: a board of 500 fixture tasks with long descriptions, where
|
||||||
|
`list_tasks` with defaults fits under the cap and pages to the end
|
||||||
|
with `next`; each filter; `task_ref`; the refusals; the cap error from
|
||||||
|
both servers; and the no-argument call still matching today's reply.
|
||||||
|
|
||||||
|
Suites: the bus, harness, webui and cli node suites, and every
|
||||||
|
`scripts/test-*.sh`.
|
||||||
|
|
||||||
|
### Out of scope
|
||||||
|
|
||||||
|
Loosening the run's file guard or adding the tool-results directory to
|
||||||
|
the workspace. The Console's task views. Any change to who may read
|
||||||
|
tasks. Server-side search over descriptions.
|
||||||
|
|
||||||
|
### Gate
|
||||||
|
|
||||||
|
Darkwing approves on the row's issue, naming the candidate manifest. The
|
||||||
|
suites above pass on Sage's gate rerun. The broker and harness change is
|
||||||
|
live at the next bus restart, which ends the PM session and launches a
|
||||||
|
new one (decision 109). Sage notifies Mos before the landing and sends
|
||||||
|
the commit after it; Mos restarts the bus and confirms the PM relaunched.
|
||||||
|
|
||||||
|
## The approved requirement ids as data
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
`task.create` needs a requirement id, and `verbs.mjs:187` checks only
|
||||||
|
its format, `REQ-[A-Z]+-[1-9][0-9]*`. Nothing tells the PM which ids
|
||||||
|
exist. The PRD (`docs/prd/mosaic-stack.md`, version 1.0, approved
|
||||||
|
2026-10-05, lead decision 61) holds 24 of them, but no tool reads it. So
|
||||||
|
the PM refused to create a task, and asked Jason in its RESULT instead
|
||||||
|
of raising a decision to his Inbox. In the same test it later created
|
||||||
|
P19 #229 and #230 under `REQ-QA-1`, which passes the format check and
|
||||||
|
isn't in the PRD.
|
||||||
|
|
||||||
|
Two more gaps from #228 c78 are words, not missing paths:
|
||||||
|
|
||||||
|
- `task_create`'s `request` says "Id of the message or human input this
|
||||||
|
task answers" (`tools.mjs:41`). `create()` accepts only a `human.input`
|
||||||
|
event id (`verbs.mjs:352`, `broker.mjs:460`), so the PM's message id
|
||||||
|
refused `request-not-found`. Each message `message.receive` returns
|
||||||
|
already carries that id as `request` (`broker.mjs:790-797`); the tool
|
||||||
|
doesn't say to use it.
|
||||||
|
- A blocking decision needs a task (`broker.mjs:596`, `task-required`),
|
||||||
|
and the PM read that as no Inbox path for a missing requirement. A
|
||||||
|
non-blocking decision needs none. `prd.approve` is gated
|
||||||
|
(`broker.mjs:29`), so it routes to the human. Nothing tells the PM
|
||||||
|
this.
|
||||||
|
|
||||||
|
### Owner and reviewer
|
||||||
|
|
||||||
|
Owner: Dewey, after the row above (both touch `broker.mjs`). Reviewer:
|
||||||
|
Filbert.
|
||||||
|
|
||||||
|
### Files owned
|
||||||
|
|
||||||
|
- `packages/tasks/requirements.json`, new
|
||||||
|
- `packages/tasks/src/requirements.mjs`, new, and
|
||||||
|
`packages/tasks/src/verbs.mjs`, the `task.create` requirement check only
|
||||||
|
- `packages/bus/src/broker.mjs`, a `requirements` read verb only
|
||||||
|
- `packages/harness/src/tools.mjs`, a `list_requirements` tool and
|
||||||
|
`task_create`'s `request` description only
|
||||||
|
- `roles/pm.md`, the paragraphs on creating tasks and raising decisions
|
||||||
|
only
|
||||||
|
- `packages/tasks/README.md`, `packages/bus/README.md`
|
||||||
|
- tests under `packages/tasks/tests/`, `packages/bus/tests/` and
|
||||||
|
`packages/harness/tests/`
|
||||||
|
- `agents/dewey/work/queue-<row>/`
|
||||||
|
|
||||||
|
### What ships
|
||||||
|
|
||||||
|
- `packages/tasks/requirements.json` lists the PRD version and path and
|
||||||
|
each id with a one-line title. A test reads the PRD's `**REQ-...**`
|
||||||
|
bullets and fails unless the list matches them exactly, ids and order.
|
||||||
|
A later PRD version is a new list in a reviewed commit, like the PRD.
|
||||||
|
- `requirements.mjs` checks the file's shape and loads it frozen, as
|
||||||
|
`fields.mjs` does for `fields.json` (row 61). A bad file refuses at
|
||||||
|
load.
|
||||||
|
- `task.create` refuses with `unknown-requirement` when the id isn't in
|
||||||
|
the list. The format check stays.
|
||||||
|
- The broker's `requirements` verb returns the list, a read for every
|
||||||
|
session that can create tasks. The `list_requirements` tool exposes it
|
||||||
|
to the pm role.
|
||||||
|
- `roles/pm.md`: before creating a task, pick the id from
|
||||||
|
`list_requirements`. When no id fits the request, raise a decision with
|
||||||
|
action `prd.approve`, `blocking` false and no `task_ref`, naming the
|
||||||
|
request and the nearest ids, and don't create the task; don't ask in
|
||||||
|
a RESULT.
|
||||||
|
- `task_create`'s `request` says it is the `request` field of the human
|
||||||
|
message being answered, not the message id. `pm.md` says the same.
|
||||||
|
- Tests: the PM's role session raises that `prd.approve` decision with
|
||||||
|
no task and it routes to the human; `task.create` with a message id
|
||||||
|
still refuses `request-not-found`, and with that message's `request`
|
||||||
|
it passes the request check.
|
||||||
|
|
||||||
|
Suites: the tasks, bus, harness, webui and cli node suites, and every
|
||||||
|
`scripts/test-*.sh`.
|
||||||
|
|
||||||
|
### Out of scope
|
||||||
|
|
||||||
|
Editing the PRD or adding ids. Mapping Jason's past requests to ids.
|
||||||
|
Showing the list in the Console. Letting a blocking decision go without a
|
||||||
|
task. What happens to #229 and #230; their id is the PM's and Jason's to
|
||||||
|
correct.
|
||||||
|
|
||||||
|
### Gate
|
||||||
|
|
||||||
|
Filbert approves on the row's issue, naming the candidate manifest. The
|
||||||
|
suites above pass on Sage's gate rerun. Live at the next bus restart and
|
||||||
|
PM launch, so the same decision-109 notices as the row above.
|
||||||
|
|
||||||
|
## A project checkout in each role workspace
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
A launched role session works in
|
||||||
|
`<dataRoot>/workspaces/<business>/<instance>/`
|
||||||
|
(`packages/seat/src/session.mjs:20,46`), which the launcher creates
|
||||||
|
empty (`packages/cli/src/launcher.mjs:159-160`). Nothing puts the
|
||||||
|
project there. In #228's test the coder's workspace held only a fresh
|
||||||
|
`.git` and its own new files, so it worked beside the source rather than
|
||||||
|
in it, and the PM asked Jason for a checkout (dad88d23). Slice 1's goal
|
||||||
|
is the stack building pieces of itself (`2026-10-04_slice-1.md`, line
|
||||||
|
20), which needs the coder in the project. The business file names each
|
||||||
|
project's `root` (`packages/business/src/business.mjs:170-177`); that is
|
||||||
|
the only source the launcher has.
|
||||||
|
|
||||||
|
### Owner and reviewer
|
||||||
|
|
||||||
|
Owner: Darkwing, after row 63 and ahead of row 88. Reviewer: Filbert.
|
||||||
|
|
||||||
|
### Files owned
|
||||||
|
|
||||||
|
- `packages/cli/src/launcher.mjs`, the workspace step in `start()` only
|
||||||
|
- `packages/seat/src/checkout.mjs`, new
|
||||||
|
- `packages/seat/README.md`, `packages/cli/README.md`, the workspace
|
||||||
|
paragraphs
|
||||||
|
- tests under `packages/seat/tests/` and `packages/cli/tests/`
|
||||||
|
- `agents/darkwing/work/queue-<row>/`
|
||||||
|
|
||||||
|
### What ships
|
||||||
|
|
||||||
|
- At every launch, before the bundle is built, the host makes sure each
|
||||||
|
project in the business file has a clone at
|
||||||
|
`<workspace>/<project id>/`. The host runs git, outside the session's
|
||||||
|
sandbox. A missing clone is `git clone --no-hardlinks <root>`, at the
|
||||||
|
root's checked-out branch. An existing clone with a clean tree on that
|
||||||
|
branch fast-forwards from its origin. Anything else (a dirty tree,
|
||||||
|
another branch, a fast-forward that fails) is left exactly as it is,
|
||||||
|
and the launch log records `checkout-kept` with the project id and the
|
||||||
|
reason. The host never resets, cleans, deletes or force-updates a
|
||||||
|
clone.
|
||||||
|
- A clone's origin is the local root path. No credential, token or
|
||||||
|
Gitea remote goes into it. The root is never written.
|
||||||
|
- A clone that can't be made refuses the launch with `checkout-failed`
|
||||||
|
and the project id, through the broker's `refuse()` like the other
|
||||||
|
launch steps. No partial clone is left behind.
|
||||||
|
- The bundle's session text names the clone's path, so the session
|
||||||
|
knows where the project is.
|
||||||
|
- Tests: a first launch clones; a second fast-forwards after a commit
|
||||||
|
to the root; a dirty clone and one on another branch are kept and
|
||||||
|
logged; an unreadable root refuses `checkout-failed` with nothing
|
||||||
|
left; the root's tree and refs are unchanged after each case; no
|
||||||
|
remote URL in a clone carries a credential.
|
||||||
|
|
||||||
|
Suites: the seat, cli, bus and harness node suites, and every
|
||||||
|
`scripts/test-*.sh`.
|
||||||
|
|
||||||
|
### Out of scope
|
||||||
|
|
||||||
|
How the coder's change reaches the reviewer and the repository: pushing
|
||||||
|
a working branch (`git.push.working`), the reviewer reading the coder's
|
||||||
|
change, and landing. That is the next row, which Sage briefs once this
|
||||||
|
one lands. Credentials in a clone. The PM's workspace contents beyond
|
||||||
|
the clone.
|
||||||
|
|
||||||
|
### Gate
|
||||||
|
|
||||||
|
Filbert approves on the row's issue, naming the candidate manifest. The
|
||||||
|
suites above pass on Sage's gate rerun. The launcher runs in the bus
|
||||||
|
host, so the change is live at the next bus restart: the same
|
||||||
|
decision-109 notices as the rows above.
|
||||||
Reference in New Issue
Block a user