feat(opencode): hide TODO paths from orchestrator (ADR-22)

In recent runs the orchestrator skipped @pm and edited TODO/ files
itself, despite the workflow.md anti-pattern warning. Root cause: the
workflow doc literally taught the orchestrator the path layout
(`./TODO/<ID>.md`), making self-help a discoverable shortcut.

Fix: remove the recipe. The orchestrator now never constructs or reads
any per-issue TODO path. All TODO operations go through @pm dispatches;
@pm returns the absolute file path of every issue it touches, and the
orchestrator captures and reuses those paths downstream.

- Phase 1 loses the TODO-existence and depends-on checks (former steps
  3 and 9 of the recent edit) — Phase 1 is now git/worktree-only.
- Phase 2 expands @pm's existing dispatch into a `Validate run
  prerequisites` operation that returns either {ok: true,
  issue_file_path, issue: {...}} or {ok: false, error_code, message}
  with error_code in {tracker_missing, issue_not_found,
  dependency_unmet, dependency_missing}. depends-on enforcement moves
  here.
- Phase 7 split_needed exit, Phase 9 TODO Update, Phase 9 Commit TODO
  Changes, and Failure Handler all reference @pm-returned paths or use
  `git add ./TODO/` blanketly (safe because Phase 1 verified clean tree
  and only @pm writes there during a run).
- pm.md gains a path-return rule: every read returns issue_file_path,
  every write returns the modified paths. Run-Prerequisite Output
  format documented with all four error codes.
- ADR-22 captures the rationale; routing matrix updates Phase 1/2 rows;
  pipeline diagram labels updated.

The fix is discoverability-only — no permission deny on TODO/, per
explicit user direction. The schema lives in agents/pm.md, which the
orchestrator does not load.

Refs: config/opencode/workflow-design.md ADR-22
This commit is contained in:
Harald Hoyer 2026-05-08 13:45:51 +02:00
parent 3e515d54eb
commit a3e0de6d04
3 changed files with 140 additions and 44 deletions

View file

@ -114,15 +114,59 @@ Statuses: `Todo`, `In Progress`, `Done`.
## Capabilities
You can:
- **View** an issue by ID — read `<TODO_DIR>/<ID>.md` and return its fields structured.
- **Validate run prerequisites** — given an issue ID, verify the TODO tracker is well-formed in this worktree (directory + `README.md` present), locate the issue file, and confirm every entry in its `depends-on:` frontmatter resolves to a `Done` issue. Used by `/workflow`'s Phase 2 (per ADR-22) so the orchestrator never constructs a TODO path itself. Returns a structured success or failure response (see "Run-Prerequisite Output" below).
- **View** an issue by ID — read `<TODO_DIR>/<ID>.md` and return its fields structured. **Always include the resolved absolute file path** in the response (`issue_file_path` field).
- **List** issues, optionally filtered by status / parent / label. Walk `<TODO_DIR>/*.md` (excluding `README.md`), parse frontmatter.
- **Create** an issue. Generate the next ID by scanning existing IDs with the same prefix and incrementing. Default `status: Todo`. Write `<TODO_DIR>/<NEW-ID>.md`. If the issue is top-level (`parent: null`), update `README.md` to add it under the caller-specified category. If the issue is a sub-issue (`parent: <PARENT-ID>`), update the parent file's `## Sub-issues` section.
- **Create** an issue. Generate the next ID by scanning existing IDs with the same prefix and incrementing. Default `status: Todo`. Write `<TODO_DIR>/<NEW-ID>.md`. If the issue is top-level (`parent: null`), update `README.md` to add it under the caller-specified category. If the issue is a sub-issue (`parent: <PARENT-ID>`), update the parent file's `## Sub-issues` section. **Return the absolute path of the new issue file** (`new_issue_path`) and the absolute paths of every dependent index updated (`updated_paths`).
- **Update status** in frontmatter. When status changes to/from `Done`, propagate the checkbox flip to:
- `README.md` if the issue is top-level (`parent: null`), **or**
- the parent issue's `## Sub-issues` line if it has a parent.
- **Add a comment** — append `- YYYY-MM-DD — <text>` to the issue's `## Comments` section (create the section if missing, just before EOF).
- **Check off acceptance criteria** by index or matching text — flip `- [ ]` to `- [x]` under `## Acceptance criteria`.
- **Edit** description or other body sections when explicitly requested.
Return the list of all paths modified by the operation.
- **Add a comment** — append `- YYYY-MM-DD — <text>` to the issue's `## Comments` section (create the section if missing, just before EOF). Return the modified path.
- **Check off acceptance criteria** by index or matching text — flip `- [ ]` to `- [x]` under `## Acceptance criteria`. Return the modified path.
- **Edit** description or other body sections when explicitly requested. Return the modified path.
**Path-return rule:** every operation that modifies the filesystem must include the absolute path(s) of every file it touched in its response (`modified_paths` array, or named fields like `new_issue_path` / `updated_paths` for create). Read-only operations (View, List) include `issue_file_path` for the issue they read. The caller (`/workflow`'s orchestrator) deliberately does not construct TODO paths from issue IDs — it relies on these returned paths for staging, commenting, and follow-on dispatches.
## Run-Prerequisite Output
The `Validate run prerequisites` capability returns one of two JSON shapes:
**Success:**
```json
{
"ok": true,
"issue_file_path": "/abs/path/to/TODO/<ID>.md",
"issue": {
"id": "...",
"title": "...",
"status": "Todo | In Progress | Done",
"parent": "... | null",
"labels": ["..."],
"depends_on": ["..."],
"description": "...",
"acceptance_criteria": [{"checked": false, "text": "..."}],
"sub_issues": [{"id": "...", "title": "...", "checked": false}]
}
}
```
**Failure:**
```json
{
"ok": false,
"error_code": "tracker_missing | issue_not_found | dependency_unmet | dependency_missing",
"message": "<one-line description suitable for the orchestrator to surface verbatim>"
}
```
Error code semantics:
- `tracker_missing``<TODO_DIR>/` or `<TODO_DIR>/README.md` is absent.
- `issue_not_found``<TODO_DIR>/<ID>.md` does not exist.
- `dependency_unmet` — the issue exists; one of its `depends-on:` entries is not yet `Done`. Include which dep ID and its current status in `message`.
- `dependency_missing` — the issue exists; one of its `depends-on:` entries refers to an issue that has no file at all. Include which dep ID in `message`.
Do **not** mutate state on failure — the validator is read-only.
You cannot:
- Delete issues. If asked, leave the file in place and report — the new schema has no `Cancelled` state, so deletion would lose history.
@ -138,11 +182,13 @@ Single-issue schema:
```json
{
"issue_file_path": "/abs/path/to/TODO/GAL-39.md",
"id": "GAL-39",
"title": "Implement a special stage type",
"status": "Done",
"parent": "GAL-38",
"labels": ["gameplay", "advanced-mechanics"],
"depends_on": ["GAL-37"],
"description": "…",
"sub_issues": [
{ "id": "GAL-40", "title": "…", "checked": true }
@ -157,7 +203,7 @@ Single-issue schema:
}
```
Omit fields whose corresponding sections are absent (`null` is fine for `parent`, but drop `sub_issues`/`acceptance_criteria`/`integration_test_hints`/`comments` entirely if the section isn't in the file).
`issue_file_path` is **always included** for any operation that reads or writes a single issue file (per the path-return rule above). Omit fields whose corresponding sections are absent (`null` is fine for `parent`, drop `depends_on`/`sub_issues`/`acceptance_criteria`/`integration_test_hints`/`comments` entirely if the section/field isn't in the file).
For list output, return an array of `{id, title, status, parent, labels}` objects.