diff --git a/CHANGELOG.md b/CHANGELOG.md index ebef729..9cf749f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## Unreleased +**Operator status / help UX (scannable situation room)** + +**Changed** +- `/do-work status` (unscoped): archive rows in the situation-room table are capped to the **15 newest** REQ ids (lexicographic / zero-padded `REQ-NNN` order). Totals still count every archived REQ. When more exist, output includes `_… and N more archived_` and points operators at `status UR-NNN` or `.do-work/archive/`. **Consumer impact:** scripts that scrape unscoped status for a full archive inventory will miss older rows — scope with `UR-NNN` or read `archive/` directly. +- Archive rows always render Status **`done`** (bucket location is authoritative; stale `**Status:**` headers in archive files are ignored in the table). **Consumer impact:** parsers that expected archive-row status to mirror the file header will now always see `done`. +- Idle unscoped projects (backlog=0, working=0, archive>0) print a short “no live work” scan cue above the table; empty projects suggest `/do-work start "…"`. +- `/do-work` help next steps: empty backlog no longer suggests `capture` for URs whose REQs already live in `archive/` (drained project); capture is only suggested for open URs with zero REQs anywhere. +- Agent/docs copy for status and primary-loop commands updated (`agents/status.md`, `agents/help.md`, `docs/commands.md`, `docs/getting-started.md`, `SKILL.md`). + **Hub-only skill install (UR-044 / REQ-278)** **Changed** diff --git a/SKILL.md b/SKILL.md index beae051..b5b70e6 100644 --- a/SKILL.md +++ b/SKILL.md @@ -18,6 +18,19 @@ Start → Go. (Or granular: Intake → Capture → Verify → Run.) Work-item storage is pluggable (`tracker.backend`: **markdown** default, or **linear**). Runtime/git (worktrees, merges, state locks, `config.yml`) always stay local. +## Primary loop + +Most days you only need these: + +| Command | What it does | +|---------|-------------| +| `/do-work start [brief]` | Record a brief and build the REQ backlog (ideate on by default; auto-installs). | +| `/do-work go [UR-NNN]` | Verify coverage, then audit + run when confidence ≥ threshold (default 90%). | +| `/do-work status [UR-NNN]` | Live situation room: in-flight, backlog, recent done, coverage. | +| `/do-work` | Help + suggested next steps for this project. | + +Flags for start/go (`--no-ideate`, `--force`, `--auto-fix`, …) are in the full table below. + ## Quick Reference | Command | What it does | @@ -102,7 +115,7 @@ Full multi-backend deep dive: [references/tracker.md](references/tracker.md). **No dual-write.** With `tracker.backend: linear`, Linear is the **only** work-item store. Agents must not mirror URs/REQs into local markdown as a second source of truth, and must not fall back to markdown when Linear fails (hard-stop instead). After idle migration (`/do-work upgrade migrate`), historical `.do-work/user-requests/` and `archive/` trees remain on disk as **read-only history** — work-item ops ignore them. -**Linear hierarchy:** **UR = Project Milestone** on shared `product_project` (default `do-work`); REQs = Issues with that milestone. Not Initiatives (MCP has no Initiative create tools). +**Linear hierarchy:** **UR = Project Milestone** on a **shared product Project** per local product (`tracker.linear.product_project` — name or UUID; **default empty**). Resolve: explicit `product_project` → `project.name` → git-root basename; `ensure_product_container` create-if-missing + **always persist UUID**. Never invent skill name `do-work` for empty config (example name for this skill repo only). REQs = Issues with that milestone. Not Initiatives (MCP has no Initiative create tools). **Linear commit / branch** (when `backend: linear`): subject uses Linear issue id only (`feat(ENG-123): …`); footer `Issue:` / `UR:` / `Output:`; branch/worktree `req/` (dir hard-defaults lowercase). Markdown backend still uses `feat(REQ-NNN): …` with `REQ:` / `UR:` archive paths — see [references/concepts.md](references/concepts.md#commit-convention). diff --git a/agents/capture.md b/agents/capture.md index 28a60e1..e85385c 100644 --- a/agents/capture.md +++ b/agents/capture.md @@ -849,7 +849,7 @@ If `config.next_steps.enabled` is `true` **and** this agent is running standalon 2. **"Run Go"** — Skip to verify + run in one shot 3. **"Skip"** — End the interaction -If `config.next_steps.enabled` is `false`, missing, or this agent is running as a delegate inside start: output "Next step: run verify to check coverage, or run the loop to start executing." and stop. +If `config.next_steps.enabled` is `false`, missing, or this agent is running as a delegate inside start: output `Next step: /do-work go UR-NNN to verify coverage and run.` (substitute the real UR number) and stop. --- diff --git a/agents/config.md b/agents/config.md index ecbe69a..f0acc99 100644 --- a/agents/config.md +++ b/agents/config.md @@ -114,7 +114,10 @@ tracker: team_key: "" # optional alternate resolve (e.g. team key string) default_assignee_id: "" # human operator; set on issue create when configured # Shared Linear Project that holds all UR milestones + Issues (not one Project per UR). - product_project: "do-work" # name or UUID; default "do-work" (or project.name when set) + # Empty default — NOT the skill name. Resolve when backend=linear (Load Config step 9): + # explicit product_project (name|UUID) wins; if empty → project.name; if that empty → + # git-root directory basename; ensure_product_container create-if-missing; persist UUID. + product_project: "" # Human-facing Project Milestone name for each UR. ur_milestone_name_pattern: "{ur_id}: {title}" # Deprecated aliases (still accepted if new keys missing): @@ -177,11 +180,18 @@ routing: [] - If a **top-level section is entirely missing** from the file (e.g. `next_steps:` does not appear), append the full section block — including all keys, default values, and inline comments — to the end of the file. - If a **top-level section exists but is missing individual keys** (e.g. `log:` exists but `batch_size` is absent), append the missing keys with their default values to that section. This applies to nested-map keys too — e.g. if `log:` exists but `log.max_chars` is absent, append it with its default map (`{x: 280, linkedin: 1300}`) and inline comment. - - **Never overwrite existing values.** If a key exists in the file, keep the user's value regardless of what the default says. For nested maps, treat presence of the parent key as "existing" — if `log.max_chars:` is present, do not overwrite any of its entries or add missing platform entries, even if the default template has more. + - **Never overwrite existing values.** If a key exists in the file, keep the user's value regardless of what the default says. For nested maps, treat presence of the parent key as "existing" — if `log.max_chars:` is present, do not overwrite any of its entries or add missing platform entries, even if the default template has more. In particular, never replace a non-empty `tracker.linear.product_project` (name or UUID) with the template empty default or with the skill name `do-work`. - If **no keys are missing**, do not write to the file. Skip this step silently. - If keys were added, report: `Config updated: added [list of added keys/sections]` -5. Keep the final merged values (file values + defaults for anything still missing) in context for subsequent steps. +4b. **Seed `project.name` from directory basename when empty (install / first load).** After create (step 3) or migrate (step 4): + + - Let `name` = current `project.name` (treat missing, null, empty, or whitespace-only as empty). + - If `name` is **non-empty** → leave it alone; do **not** overwrite. + - If `name` is **empty** → set `project.name` to the **git-root directory basename** (the basename of the detected project root from startup) and **write** that value to `{project}/.do-work/config.yml`. + - This runs on create and on every load where `project.name` is still blank (e.g. operator cleared it, or an older template left it empty). It never replaces a deliberate non-empty name. + +5. Keep the final merged values (file values + defaults for anything still missing, plus any seed from step 4b) in context for subsequent steps. 6. **Resolve tracker backend (markdown-default).** After the merged config is in context, set the effective work-item backend: @@ -267,9 +277,22 @@ routing: [] **Do not** fall back to process CWD, hub paths (`~/.agents/skills/do-work`, `~/.claude/skills/do-work`), or invent a path from `DO_WORK_SKILL_ROOT` / other env vars when inherit markers fail. **Do** inherit a **valid** `$SKILL_ROOT` already set in this turn's context (see inherit rule above). +9. **Resolve and bind `tracker.linear.product_project` when effective backend is `linear`.** Run after step 7 validations pass and the Linear backend docs are loaded — **before** any work-item CRUD that needs the product Project. When effective backend is **`markdown`**, skip this step entirely (`product_project` is inert). + + **Resolve order (name/lookup key only — does not rewrite an already-set value):** + + 1. Let `pp` = current `tracker.linear.product_project` (missing, null, or whitespace-only → treat as empty). + 2. If `pp` is **non-empty** (name **or** UUID) → **lookup key = `pp`**. Explicit config wins. Do **not** replace it with `project.name`, the git-root basename, or the skill name `do-work`. Existing product UUIDs (and explicit names) are left untouched by this fallback chain. + 3. If `pp` is **empty** → lookup key = `project.name` when that is non-empty; else the **git-root directory basename** (same basename as step 4b). Never fall back to a hard-coded skill name. + 4. Call port op **`ensure_product_container`** with that lookup key: resolve the Linear Project by name or UUID; **create-if-missing** when the key is a name and no Project matches. + 5. On success, **always persist** the resolved Project **UUID** back to `tracker.linear.product_project` in `{project}/.do-work/config.yml` and in the in-memory config. If the file already stores that same UUID, skip the write (idempotent). + 6. On failure (unresolved after create attempt, MCP missing, permission error) → **hard-stop** with operator instructions; never invent a product Project and never silent-fallback to markdown. + + **Rewrite rules:** The empty → `project.name` → basename chain runs **only** when `product_project` is truly empty. It must never overwrite an explicit existing value. The only write after a non-empty start is ensure's **UUID bind** (e.g. name → UUID once resolved). After a true empty state, ensure binds and step 5 persists the UUID so subsequent loads take the explicit-UUID path. + **Phase-agent contract:** every phase agent that touches work items follows the **Tracker load path** (config → resolve `tracker.backend` → `port.md` → `agents/tracker/.md` → only named port ops). The shared load path is defined once here and in `agents/tracker/port.md`; each phase agent restates a short copy so a missing wire cannot cause split-brain storage. Every phase agent that invokes skill `lib/` scripts also depends on step 8 (`$SKILL_ROOT` / `{skill-root}`) from this same Load Config block. -**Never fail or stop because of a missing or incomplete config file** (steps 1–5). If config creation or migration fails for any reason, proceed with in-memory defaults (including `tracker.backend: markdown`). **Exceptions (deliberate hard-stops, not config-file completeness problems):** step 7 Linear validation (and missing `linear.md`) when the operator has opted into `backend: linear`; step 8 skill-root resolve when walk-up (or inherit) cannot determine an absolute skill install root. +**Never fail or stop because of a missing or incomplete config file** (steps 1–5, including 4b seed). If config creation or migration fails for any reason, proceed with in-memory defaults (including `tracker.backend: markdown`). **Exceptions (deliberate hard-stops, not config-file completeness problems):** step 7 Linear validation (and missing `linear.md`) and step 9 product_project ensure/bind when the operator has opted into `backend: linear`; step 8 skill-root resolve when walk-up (or inherit) cannot determine an absolute skill install root. --- @@ -277,7 +300,7 @@ routing: [] | Key | Type | Default | Description | |-----|------|---------|-------------| -| `project.name` | string | `""` | Project display name | +| `project.name` | string | `""` (seeded from git-root directory basename when empty on create/first load — Load Config step 4b; never overwrites a non-empty name) | Project display name. Also the preferred empty-`product_project` fallback when `backend: linear` (Load Config step 9). | | `layers` | list of strings | `[]` | Project's declared layers for gap-aware capture. Capture and verify check that REQs cover each declared layer. Empty = opt out (feature briefs will halt until declared or `--no-layers` is passed). | | `log.enabled` | boolean | `true` | Whether the log step runs after Go | | `log.platforms` | list | `[]` | Platforms to generate draft posts for (e.g. `[x, linkedin]`) | @@ -313,7 +336,9 @@ routing: [] | `tracker.linear.team_id` | string | `""` | Linear team UUID. **Required when `backend: linear`** unless `team_key` alone resolves the team. Empty + unresolvable team_key → hard-fail (do not guess). Consumers: `agents/tracker/linear.md`, Load Config step 7. | | `tracker.linear.team_key` | string | `""` | Optional alternate team resolve (Linear team key string). Used when `team_id` is empty. Consumers: `agents/tracker/linear.md`, Load Config step 7. | | `tracker.linear.default_assignee_id` | string | `""` | Human operator Linear user id set as issue **assignee** on create when non-empty. Agents claim via workflow status + claim comments — they do not steal assignee. Consumers: `agents/tracker/linear.md` create/claim ops. | -| `tracker.linear.project_name_pattern` | string | `"do-work/{ur_id}"` | Pattern for per-UR Linear Project name. `{ur_id}` is the sequential UR slug (e.g. `UR-007`). Consumers: `agents/tracker/linear.md` intake/list. | +| `tracker.linear.product_project` | string | `""` | Shared Linear Project (**name or UUID**) that holds all UR Project Milestones + Issues — **not** one Project per UR. **Default is empty**, not the skill name `do-work`. When `backend: linear`, Load Config step 9 resolve order: (1) explicit non-empty `product_project` (name\|UUID) wins and is never replaced by the empty-fallback chain; (2) if empty/missing → `project.name`; (3) if that empty → git-root directory basename; (4) `ensure_product_container` create-if-missing; (5) **always persist** the resolved Project **UUID** back to this key. Explicit existing values (including a bound UUID) are left alone by the fallback chain; only ensure's UUID bind may update the field after a true empty (or name→UUID bind). Consumers: `agents/tracker/linear.md`, `ensure_product_container`, intake/`create_ur`. | +| `tracker.linear.ur_milestone_name_pattern` | string | `"{ur_id}: {title}"` | Human-facing Project Milestone name pattern for each UR. `{ur_id}` is the sequential UR slug; `{title}` is the brief title. Consumers: `agents/tracker/linear.md` create_ur / list_urs. | +| `tracker.linear.project_name_pattern` | string | `"do-work/{ur_id}"` | **Deprecated** pattern for per-UR Linear Project name (ignored for UR home; URs are Project Milestones on `product_project`). Kept for migrate compatibility. Consumers: legacy notes only. | | `tracker.linear.initiative_title_pattern` | string | `"{ur_id}: {title}"` | Pattern for Initiative title. `{title}` is the human-facing brief title. Consumers: `agents/tracker/linear.md` intake. | | `tracker.linear.status_map.backlog` | string | `"Todo"` | Team workflow state name for unclaimed/backlog REQs. **Hard-fail** if this state is missing on the team when `backend: linear` — rename the team state or override this key. Consumers: claim/list/status ops in `agents/tracker/linear.md`. | | `tracker.linear.status_map.in_progress` | string | `"In Progress"` | Team workflow state for claimed/in-progress REQs. Same missing-state hard-fail as other status_map keys. Consumers: claim/heartbeat/resume. | diff --git a/agents/go.md b/agents/go.md index e164fa8..ec42e0d 100644 --- a/agents/go.md +++ b/agents/go.md @@ -161,7 +161,7 @@ If `config.next_steps.enabled` is `true`: The go agent is a top-level orchestrator — it is never a delegate, so no suppression logic is needed. Sub-agents (verify, run, log) must suppress their own AskUserQuestion prompts when running inside go. -If `config.next_steps.enabled` is `false` or missing: skip the AskUserQuestion and stop. +If `config.next_steps.enabled` is `false` or missing: output `Next step: /do-work status` (or `/do-work start "…" for new work) and stop. --- diff --git a/agents/help.md b/agents/help.md index 65c4a46..b4d5cd2 100644 --- a/agents/help.md +++ b/agents/help.md @@ -88,14 +88,34 @@ path-unit backlog detected: top-level path REQs define reachable flows; child RE **If URs exist but backlog is empty:** +Do **not** treat every empty-backlog project the same. Distinguish URs that still need decomposition from a drained project whose REQs already live in `archive/` (or Linear done): + +1. Find the **most recent** `UR-NNN` under `user-requests/` (highest N). +2. Check whether **any** REQ for that UR exists in backlog, `working/`, or `archive/` (markdown: `**UR:** UR-NNN` on REQ files; Linear: `list_reqs_for_ur`). +3. Also scan older open URs for any with **zero** REQs anywhere — those still need capture. + +**A — Latest UR has no REQs yet (or any open UR has zero REQs):** + +``` +Suggested next steps: + /do-work capture UR-NNN — Decompose the request into tasks + /do-work go UR-NNN — Verify and run after capture + /do-work start "describe your feature or task" — Record a new brief instead +``` + +Prefer the **oldest** zero-REQ open UR for the capture line when more than one exists; otherwise use the latest UR. Replace `UR-NNN` with that real number. + +**B — Backlog empty and all open URs already have REQs (drained / archive-only):** + ``` Suggested next steps: - /do-work capture UR-NNN — Decompose the latest request into tasks - /do-work go UR-NNN — Verify and run for a specific request /do-work start "describe your feature or task" — Record a new brief + /do-work status — Review the situation room ``` -Replace `UR-NNN` with the most recent UR number. +Do **not** suggest `capture` for a UR that already has REQs in archive — that re-decomposes finished work and confuses operators. + +Then (when the 4-suggestion cap allows) still add retro / close from the heuristics below. **If `runs/` has entries but `.do-work/state/calibration.md` does not exist:** @@ -107,7 +127,7 @@ Suggest retro alongside other applicable suggestions (do not replace them — ad **If archived path-unit REQs exist for a UR but that UR has no `closure.md`:** -Suggest close alongside other applicable suggestions (add it when this condition is true and the 4-suggestion cap allows). Use the most recently completed UR: +Suggest close alongside other applicable suggestions (add it when this condition is true and the 4-suggestion cap allows). Use the most recently completed UR that still needs closure: ``` /do-work close UR-NNN — Walk path-unit entry points end-to-end and write the UR closure report diff --git a/agents/status.md b/agents/status.md index 305a788..4339626 100644 --- a/agents/status.md +++ b/agents/status.md @@ -54,6 +54,8 @@ bash {skill-root}/lib/synth-status.sh [UR-NNN] # passes the optional scope Print stdout verbatim to the user. +Unscoped output prioritizes live work: backlog + working list fully; archive is capped to recent completed rows with a note when more exist. Scope with `UR-NNN` to list every matching archived REQ. Archive rows always show Status `done` even if a file header is stale. + If `$SKILL_ROOT/lib/synth-status.sh` is missing, report `"$SKILL_ROOT/lib/synth-status.sh not found — cannot render status."` and stop. Then render a proof-backed status view. Glob REQ files in backlog, `working/`, and `archive/` (respecting `UR-NNN` scope when provided), and run: diff --git a/agents/tracker/linear.md b/agents/tracker/linear.md index c68cb1e..d2da673 100644 --- a/agents/tracker/linear.md +++ b/agents/tracker/linear.md @@ -26,7 +26,7 @@ Do **not** load this file for ordinary work-item ops when backend is `markdown` ``` Team (config) -└── Project product_project (default "do-work") — shared for all URs +└── Project product_project — one shared product Project per local product (not per UR) ├── Project Milestone (UR) — §9.1 └── Issue (REQ) — attached to that UR milestone └── Sub-issue (layer child) @@ -34,15 +34,16 @@ Team (config) | Entity | Naming / config | |--------|-----------------| -| Product Project | `tracker.linear.product_project` (default `do-work`) — **shared** | +| Product Project | `tracker.linear.product_project` — **shared** name or UUID; **default empty**. Resolve via config chain (explicit `product_project` → `project.name` → git-root basename); `ensure_product_container` create-if-missing + **always persist UUID**. Never fall through to skill name `do-work` for empty config. Example for this skill repo only: name `do-work`. | | UR | **Project Milestone** on that project; name `ur_milestone_name_pattern` (default `{ur_id}: {title}`) | | REQ | **Linear issue id only** (e.g. `ENG-123`) — no parallel `REQ-NNN` | | Issue scope | product Project + UR Project Milestone membership | ### Hard rules (hierarchy) + 1. **No Initiative-as-UR** — MCP has no reliable Initiative create path; URs are Project Milestones. -2. **`product_project` is shared** — do not create `do-work/{UR-id}` Projects per UR as the UR container. +2. **`product_project` is shared per local product** — do not create per-UR Projects (including `do-work/{UR-id}` patterns) as the UR container. 3. **Atomic `create_ur`** — product Project ensure + milestone create; no partial UR; hard-stop on failure. 4. **Rediscover, never invent** — every op begins with `search_tool`; hard-stop if tools missing. 5. **No dual-write** — Linear is sole work-item store while `backend: linear`. @@ -142,7 +143,7 @@ HARD STOP: Linear tracker backend is configured but Linear MCP is not usable. do-work will not fall back to markdown work-item storage while tracker.backend is "linear". No issues, Initiative-as-UR entities, or local REQ/UR substitutes were invented. -What failed: +What failed: Fix — connect Linear MCP (from Linear skill setup): @@ -164,9 +165,17 @@ Fix — connect Linear MCP (from Linear skill setup): - grok mcp enable linear - grok mcp doctor linear -4. Team config (when MCP works but team fails): +4. Team + product Project config (when MCP works but team/project fails): - Set tracker.linear.team_id (UUID) and/or tracker.linear.team_key in .do-work/config.yml - - Set tracker.linear.product_project (default name `do-work`) when the shared project is not yet resolved + - product_project resolve (Load Config step 8 / ensure_product_container): explicit + tracker.linear.product_project (name|UUID) if set; else project.name; else git-root + directory basename. Default product_project is empty — never invent skill name `do-work` + for empty config. ensure_product_container create-if-missing (name path) and always + persists the Project UUID back to tracker.linear.product_project. + - Empty-name failure: product_project, project.name, and basename all empty/unusable → + set project.name or product_project explicitly; do not invent a name; do not markdown-fallback + - Multi-match failure: more than one team Project shares the target name → set + tracker.linear.product_project to the desired Project UUID (names are ambiguous) - Do not guess a team 5. status_map (when team loads but a workflow state name is missing): @@ -186,6 +195,8 @@ use /do-work resume or unblock after MCP recovers (port: leave claimed). | `search_tool` returns no Linear tools | Hard stop + setup steps above | | MCP offline / unauthenticated mid-session | Hard stop; if already claimed → leave claimed | | Team id/key unresolved | Hard stop; do not guess | +| `product_project` empty-name after resolve chain | Hard stop; set `project.name` or `product_project`; **no** skill-name invent; **no** markdown fallback | +| `product_project` multi-match by name on team | Hard stop; require UUID in `tracker.linear.product_project` | | `product_project` unresolved / uncreatable | Hard stop | | Any `status_map` value missing on team workflow | Hard stop + rename / override instructions | | Milestone / issue create tools missing for `create_ur` / `create_req` | Hard stop; **no** Initiative-as-UR substitute; **no** markdown dual-write | diff --git a/agents/tracker/port.md b/agents/tracker/port.md index c2bf8c4..6b20667 100644 --- a/agents/tracker/port.md +++ b/agents/tracker/port.md @@ -196,7 +196,7 @@ Names freeze intent. Exact field shapes and store sequences live in each backend | Op | Intent | |----|--------| -| `ensure_product_container` | Team/product labeling ready; no single product Project required | +| `ensure_product_container` | Product/team container ready (markdown: dirs; Linear: shared product Project create/bind + persist UUID) | | `create_ur` | Record intake brief | | `read_ur` | Load brief (+ ideate if present) | | `list_urs` | Enumerate URs for prompts/status | @@ -232,9 +232,9 @@ Each op lists **intent**, **preconditions**, and **notes**. Inputs/outputs are c | | | |---|---| -| **Intent** | Ensure the product/team container for work items is ready (markdown: `.do-work/` dirs; Linear: team resolvable / labels ready — **no** single long-lived product Project required). | +| **Intent** | Ensure the product/team container for work items is ready. **Markdown:** local `.do-work/` dirs. **Linear:** team resolvable; **create or bind** the shared product Project when missing (`product_project` resolve chain + list/create + **persist UUID**); optional labels ready. | | **Preconditions** | Config loaded; backend resolved. For Linear: team resolvable or hard-stop. | -| **Notes** | Idempotent. Does not create a UR or REQ. | +| **Notes** | Idempotent. Does not create a UR or REQ. Linear never falls through to skill name `do-work` for empty `product_project`. Multi-match by name and empty-name failures hard-stop (no markdown substitute store). | #### `create_ur` diff --git a/docs/HOW-IT-WORKS.md b/docs/HOW-IT-WORKS.md index 1fc4456..e5e5c4e 100644 --- a/docs/HOW-IT-WORKS.md +++ b/docs/HOW-IT-WORKS.md @@ -49,13 +49,15 @@ Work items (URs, REQs, decisions, verify/close reports, run notes) go through a ``` Team (config team_id / team_key) -└── Product Project (tracker.linear.product_project, default "do-work") +└── Product Project (tracker.linear.product_project — one shared Project per local product) ├── Project Milestone (UR brief / ideate / verify / close) │ └── Issue (REQ / path-unit) ± sub-issues (layer children) └── Project Milestone (next UR) └── Issue … ``` +**`product_project` resolve (default empty):** explicit `tracker.linear.product_project` (name|UUID) if set; else `project.name`; else git-root directory basename. Then `ensure_product_container` create-if-missing and **always persists** the Project UUID back to config. Empty config never falls through to the skill name `do-work` — that name is only an example when this skill's own repo is the local product. + **Why milestones, not Initiatives:** official Linear MCP exposes Project Milestone create/list/get, but not Initiative create/list. do-work therefore homes each UR on a **Project Milestone**. REQs use **Linear issue ids** only (e.g. `ENG-123`). `UR-NNN` remains the UR-milestone slug. diff --git a/docs/commands.md b/docs/commands.md index 95ccb53..e2c83ad 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -163,7 +163,7 @@ Does **not** run the verify confidence gate (unlike `go`). ### `/do-work status [UR-NNN]` -Read-only situation room: REQs, claimers (`hostname.pid`), heartbeats, deadlock warnings, coverage rollup. Optional UR scope. +Read-only situation room: live REQs first (working + backlog), claimers (`hostname.pid`), heartbeats, deadlock warnings, coverage rollup. Unscoped archive is capped to recent completed rows; pass `UR-NNN` to list every matching archived REQ. Use whenever something looks stuck or you are running parallel workers. diff --git a/docs/getting-started.md b/docs/getting-started.md index 5d3697f..0946a20 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -116,6 +116,7 @@ tracker: linear: team_id: "" # required UUID — or set team_key team_key: "" # optional alternate team resolve + # product_project: "" # empty by default — resolve → project.name → basename; ensure binds UUID # status_map / labels / claim marker: defaults in agents/config.md ``` @@ -124,6 +125,7 @@ tracker: 1. Connect **Linear MCP** in your agent host (API key preferred: `LINEAR_API_KEY` + MCP URL `https://mcp.linear.app/mcp`). Details: [Troubleshooting → Linear tracker backend](troubleshooting.md#linear-tracker-backend). 2. Set a real `team_id` or `team_key` — agents hard-stop if the team cannot be resolved (they never guess). 3. Confirm team workflow states match `tracker.linear.status_map` defaults (`Todo` / `In Progress` / `Canceled` / `Done`) or override the map. +4. Product Project is **per local product**, not a universal Project named `do-work`. Leave `product_project` empty (default) to resolve via `project.name` → git-root basename, or set a name/UUID explicitly. First ensure create-if-missing and **persists the UUID**. **Rules that matter day one:** @@ -161,7 +163,7 @@ Flags: /do-work status UR-001 ``` -Read-only situation room: backlog vs working vs archive, claimers, heartbeats, deadlock warnings, coverage rollup. +Read-only situation room: backlog vs working first, recent completed (archive capped when unscoped), claimers, heartbeats, deadlock warnings, coverage rollup. ## How you know it worked diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index a849942..0aaf277 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -313,7 +313,7 @@ Use dry-run first when offered. After cutover: no dual-write; historical markdow **Cause:** Older skill text required Initiative + per-UR Project. Current hierarchy uses **Project Milestones** for URs (Linear MCP has milestone CRUD, not Initiative create). -**Fix:** Use skill version with Milestone-as-UR (`agents/tracker/linear.md` § Hierarchy). Ensure `tracker.linear.product_project` is set (default `do-work`) and milestone tools appear in `search_tool "linear milestone"`. +**Fix:** Use skill version with Milestone-as-UR (`agents/tracker/linear.md` § Hierarchy). Ensure the shared product Project resolves: set `tracker.linear.product_project` (name|UUID) **or** leave it empty so resolve uses `project.name` → git-root basename, then `ensure_product_container` create-if-missing + persists UUID. Do **not** expect a universal default Project named `do-work` (that name is only an example for this skill repo). Confirm milestone tools appear in `search_tool "linear milestone"`. --- diff --git a/lib/synth-status.sh b/lib/synth-status.sh index c11bd24..126b111 100755 --- a/lib/synth-status.sh +++ b/lib/synth-status.sh @@ -13,9 +13,9 @@ # Sources: # - `.do-work/REQ-*.md` (Status: backlog) # - `.do-work/working/REQ-*.md` (Status: in-progress / stopped) -# - `.do-work/archive/REQ-*.md` (Status: done; only shown when -# present so the snapshot reflects -# completed work in this run) +# - `.do-work/archive/REQ-*.md` (Status: always rendered as done; +# unscoped runs cap archive rows so +# the situation room stays scannable) # 3. Empty case: a single "no REQs" message in place of the table when there # are zero REQs across all three buckets. # 4. Footer: deadlock warnings from `lib/deadlock-check.sh` if executable; @@ -51,6 +51,9 @@ ARCHIVE_DIR="$DOWORK/archive" CONFIG="$DOWORK/config.yml" DEFAULT_THRESHOLD=300 FOOTPRINT_MAX=60 +# Unscoped archive rows shown in the table (totals still count all archived). +# Scoped runs (`synth-status.sh UR-NNN`) list every matching archive row. +ARCHIVE_CAP=15 # Resolve sibling lib scripts via $0's directory so the script works from any # cwd inside the project. @@ -238,7 +241,8 @@ md_cell() { # Bucket: backlog | working | archive # Status column is derived from the file's own `**Status:**` field so that # `stopped` is rendered correctly when a worker has marked a working slot -# stopped. +# stopped — except archive rows, which always display as `done` (location is +# authoritative; stale in-progress headers in archive used to confuse operators). render_row() { local path="$1" local bucket="$2" @@ -259,8 +263,12 @@ render_row() { return 0 fi - status="$F_STATUS" - [ -z "$status" ] && status="—" + if [ "$bucket" = "archive" ]; then + status="done" + else + status="$F_STATUS" + [ -z "$status" ] && status="—" + fi layer="$F_LAYER" [ -z "$layer" ] && layer="—" @@ -383,8 +391,13 @@ fi printf '**Totals:** backlog=%d, working=%d, archived=%d\n\n' \ "$BACKLOG_N" "$WORKING_N" "$ARCHIVE_N" +# Idle project with history (unscoped only): no live work. Lead with a scan cue. +if [ -z "$UR_FILTER" ] && [ "$BACKLOG_N" -eq 0 ] && [ "$WORKING_N" -eq 0 ] && [ "$ARCHIVE_N" -gt 0 ]; then + printf '_No live work (backlog empty, nothing in-flight). Showing recent completed REQs only._\n\n' +fi + if [ "$TOTAL_N" -eq 0 ]; then - printf '_no REQs found in backlog, working, or archive._\n' + printf '_no REQs found in backlog, working, or archive. Next: `/do-work start "your brief"`._\n' else # Table header. printf '| REQ | UR | Status | Layer | Claimer | Heartbeat-age | Deps-status | Footprint |\n' @@ -403,9 +416,33 @@ else done fi if [ "$ARCHIVE_N" -gt 0 ]; then - for f in "${ARCHIVE_FILES[@]}"; do - render_row "$f" "archive" - done + # Unscoped: cap archive rows (newest REQ ids last in glob → take tail). + # Scoped (UR-NNN): list every matching archive row — the filter already + # narrows the set and operators asked for that UR deliberately. + if [ -n "$UR_FILTER" ]; then + for f in "${ARCHIVE_FILES[@]}"; do + render_row "$f" "archive" + done + else + archive_shown=0 + archive_start=0 + if [ "$ARCHIVE_N" -gt "$ARCHIVE_CAP" ]; then + archive_start=$(( ARCHIVE_N - ARCHIVE_CAP )) + fi + i=0 + for f in "${ARCHIVE_FILES[@]}"; do + if [ "$i" -ge "$archive_start" ]; then + render_row "$f" "archive" + archive_shown=$(( archive_shown + 1 )) + fi + i=$(( i + 1 )) + done + if [ "$ARCHIVE_N" -gt "$ARCHIVE_CAP" ]; then + hidden=$(( ARCHIVE_N - archive_shown )) + printf '\n_… and %d more archived. Scope with `/do-work status UR-NNN`, or inspect `.do-work/archive/`._\n' \ + "$hidden" + fi + fi fi fi diff --git a/lib/tests/synth-status.test.sh b/lib/tests/synth-status.test.sh index 2a033e6..283444f 100755 --- a/lib/tests/synth-status.test.sh +++ b/lib/tests/synth-status.test.sh @@ -282,6 +282,70 @@ if [ "$ELAPSED" -gt 10 ]; then fi teardown_fixture +# ---------------------------------------------------------------------- +# Case 8: unscoped archive is capped; totals still count all archived. +# ---------------------------------------------------------------------- +CURRENT_CASE="archive-cap" +CASES=$((CASES + 1)) +setup_fixture +i=1 +while [ "$i" -le 20 ]; do + # Zero-pad so glob order matches numeric order (REQ-01 … REQ-20). + id="$(printf 'REQ-%03d' "$i")" + write_archive_req "$TMP/.do-work/archive/${id}-a.md" "$id" "UR-020" + i=$((i + 1)) +done +run_synth +assert_eq "0" "$RC" "$CURRENT_CASE rc=0" +assert_contains "archived=20" "$STDOUT" "$CURRENT_CASE totals count all 20" +assert_contains "No live work" "$STDOUT" "$CURRENT_CASE idle cue" +assert_contains "and 5 more archived" "$STDOUT" "$CURRENT_CASE cap note" +assert_contains "REQ-020" "$STDOUT" "$CURRENT_CASE newest archive row shown" +assert_not_contains "REQ-001" "$STDOUT" "$CURRENT_CASE oldest archive row hidden" +teardown_fixture + +# ---------------------------------------------------------------------- +# Case 9: archive bucket always shows status done (stale header ignored). +# ---------------------------------------------------------------------- +CURRENT_CASE="archive-status-done" +CASES=$((CASES + 1)) +setup_fixture +cat > "$TMP/.do-work/archive/REQ-900-stale-header.md" <; brief, ideate, verify, close └── Issue (REQ) — on product Project, attached to UR milestone └── Sub-issue (layer child) ``` +**Product Project naming:** `tracker.linear.product_project` defaults to **empty** (not skill name `do-work`). Resolve order is documented under `ensure_product_container` / `agents/config.md` Load Config step 8. Example for the do-work skill repo itself may still use name `do-work`. + **No Initiative-as-UR.** Path-milestone mode (M1/M2) is a *cursor + Issue markers* on the UR milestone — see [linear-path-milestones.md](linear-path-milestones.md). --- @@ -75,8 +77,9 @@ On **read/update**: if the marker is missing, treat as template parse failure | `**UR-id:**` | Sequential `UR-NNN` slug only (not a Linear entity id) | Resolve UR; `list_urs` | | `**Class:**` | Intake classification (feature / …) | Capture, status | | `**Created:**` | ISO date `YYYY-MM-DD` at create | Display | -| `**Product-project:**` | Shared product Project name (`product_project`, default `do-work`) | Resolve product Project | +| `**Product-project:**` | Shared product Project **display name** after ensure (from bound Project; not a hard-coded skill default) | Display; prefer id for resolve | | `**Product-project-id:**` / `**Milestone-id:**` | Linear UUIDs after ensure + milestone create | Prefer ids over names | + | `## Brief` | **Verbatim** intake — never overwrite on ideate/question | `read_ur` | | `## Clarifications` | `append_clarifications` appends Q&A; does not create REQs | Question, capture | | `## Ideate` | `append_ideate` writes/appends ideate body | Ideate, capture | @@ -204,9 +207,10 @@ When label tools are discoverable (create/list/attach), agents **must** keep lab |--------|---------| | UR slug | Sequential `UR-NNN` (UR Project Milestone metadata only) | | REQ | **Linear issue identifier only** (e.g. `ENG-123`) — never allocate `REQ-NNN` under Linear backend | -| Product Project | `tracker.linear.product_project` (default `do-work`) — shared for all URs | +| Product Project | `tracker.linear.product_project` — shared for all URs on this local product; **name or UUID**; empty default; resolve chain + ensure persist UUID (never invent skill name `do-work`) | | UR milestone name | `tracker.linear.ur_milestone_name_pattern` (default `{ur_id}: {title}`) | + ### Preflight (before first CRUD op in a session) 1. Config effective backend is `linear` (else do not use this file). @@ -219,34 +223,57 @@ When label tools are discoverable (create/list/attach), agents **must** keep lab | | | |---|---| -| **Intent** | Team resolvable; ensure shared **product Project** (`product_project`, default `do-work`); optional labels ready. | -| **Sequence** | Preflight steps 2–4. Resolve or create product Project by name/id from `tracker.linear.product_project` (default `do-work`). Optionally pre-create labels when tools exist. | -| **Failure** | Hard-stop; never create markdown `.do-work/` as substitute product container. Never invent Initiatives as UR containers. | +| **Intent** | Team resolvable; ensure the shared **product Project** for this local product (not per UR); optional labels ready. Create/bind when missing; **always persist** Project UUID to `tracker.linear.product_project`. | +| **Preconditions** | Preflight steps 2–4 done (MCP tools, team resolved, `status_map` validated). Config loaded (`agents/config.md`). | +| **Failure** | Hard-stop on empty-name, multi-match, tool missing, create/get failure. **Never** create markdown `.do-work/` as substitute product container. **Never** invent Initiatives as UR containers. **Never** fall through to skill name `do-work` for empty `product_project`. | + +**Agent sequence (executable):** + +1. **Resolve target name/id (lookup key)** — same chain as `agents/config.md` Load Config step 8; do **not** invent a different order: + 1. Let `pp` = `tracker.linear.product_project` (missing / null / whitespace-only → empty). + 2. If `pp` is **non-empty** (name **or** UUID) → **lookup key = `pp`**. Explicit config wins; do not replace with `project.name`, basename, or skill name `do-work`. + 3. If `pp` is **empty** → lookup key = `project.name` when non-empty; else **git-root directory basename**. + 4. If the final lookup key is still empty/whitespace → **hard-stop** (**empty-name**): instruct operator to set `project.name` or `tracker.linear.product_project` in `{project}/.do-work/config.yml`. Do **not** invent a name. Do **not** markdown-fallback. +2. **Rediscover project tools** — `search_tool` for Linear project surfaces (`"linear project"`, `"linear list projects"`, `"linear save project"` / create-project). Map hits to list/get/create. If list/get (and, for name create path, create/`save_project`) tools are undiscoverable → **hard-stop** (setup block). Never hard-code tool names; use qualified names + `input_schema` from search. +3. **Resolve or create on the team** + - **UUID path** (lookup key is already a Project UUID / id form): `use_tool` get-project (or list filtered by id). Found → use that Project. Not found → **hard-stop** (do not invent; do not create a Project whose name is the UUID string). + - **Name path** (lookup key is a display name): + 1. `use_tool` list-projects scoped to the **resolved team** (team id/key from preflight). Prefer `query` / name filter when the schema supports it; otherwise list and filter client-side. + 2. Keep **exact name matches** (case-sensitive unless the live tool documents otherwise) on that team. + 3. **Match count:** + - **0** → **create** via rediscovered create/save-project surface (`save_project` when discovered): `name` = lookup key; attach team with `addTeams` **or** `setTeams` = resolved team (schema requires at least one team). Capture returned Project UUID. + - **1** → **use** that Project (id + name). + - **>1** → **hard-stop** (**multi-match**): list matching ids/names; require operator to set `tracker.linear.product_project` to the desired **UUID**. Do not pick “first”; do not invent; no markdown fallback. +4. **Persist UUID** — **always** write the resolved Project **UUID** to `tracker.linear.product_project` in `{project}/.do-work/config.yml` and in-memory config. If the file already stores that same UUID, skip the write (idempotent). Prefer UUID over name for all subsequent ops in the session. +5. **Optional labels** — when create/list label tools exist, pre-create common labels (`labels.layer_prefix`, `size_prefix`, `path_unit`) for the team. Label failure is non-fatal for container ensure (body headers remain source of truth); project ensure itself must already have succeeded. +6. **Return** product Project **id (UUID)** + **name**. Cache for the session. + +**Does not:** create a UR or REQ; create a per-UR Linear Project; create Initiatives; write local UR/REQ markdown as the store. ### `create_ur` | | | |---|---| -| **Intent** | Record intake brief as a **UR Project Milestone** on the shared **product Project**. Does **not** create REQs. **Not** Initiative-as-UR. | +| **Intent** | Record intake brief as a **UR Project Milestone** on the shared **product Project**. Does **not** create REQs. **Not** Initiative-as-UR. **Not** a new Linear Project per UR. | | **Preconditions** | Preflight passed; `ensure_product_container` done; next `UR-NNN` slug allocatable. | | **Atomicity** | Product Project resolvable + Project Milestone create must succeed as one logical unit. **No partial UR.** | **Agent sequence:** -1. **Ensure product Project** — call **`ensure_product_container`** (resolve/create `tracker.linear.product_project`, default `do-work`). +1. **Ensure product Project** — call **`ensure_product_container`** first (resolve chain + list/create/bind + persist UUID). Do **not** restate a hard-coded product name here; do **not** create a per-UR Project. 2. **Allocate next `UR-NNN` slug** - `search_tool` for project milestones list tools (`"linear milestones"`, `"linear project milestones"`). - List milestones on the product Project; scan names / descriptions for `UR-*` / `**UR-id:** UR-*` / ``. - Pick next free sequential `UR-NNN`. 3. **Build body** - Milestone name: apply `tracker.linear.ur_milestone_name_pattern` (default `{ur_id}: {title}`, e.g. `UR-007: Add SSO`). - - Description: §9.1 template with verbatim brief; `**Product-project:**` + product project id; leave `**Milestone-id:**` empty until create returns it. + - Description: §9.1 template with verbatim brief; `**Product-project:**` + product project name; `**Product-project-id:**` from ensure; leave `**Milestone-id:**` empty until create returns it. 4. **Create Project Milestone** on the product Project - `search_tool "linear milestone"` / create-milestone surface. - If **no** milestone create tool is discovered → **hard-stop** (do **not** invent Initiative-as-UR; do **not** create a per-UR Project as a fake UR). - `use_tool` create with discovered schema (project id + name + description as required). - Record milestone id; patch `**Milestone-id:**` if update tools allow. -5. **Return** UR slug, product project id/name, milestone id/name. **Do not** write `.do-work/user-requests/UR-NNN/`. **Do not** create Linear Initiatives. +5. **Return** UR slug, product project id/name, milestone id/name. **Do not** write `.do-work/user-requests/UR-NNN/`. **Do not** create Linear Initiatives. **Do not** create a Linear Project per UR. ### `read_ur` diff --git a/references/tracker.md b/references/tracker.md index 4305802..3fab85d 100644 --- a/references/tracker.md +++ b/references/tracker.md @@ -20,8 +20,8 @@ Work items (URs, REQs, decisions, verify/close reports, run notes) are stored th |----------|------------------| | Team | `team_id` and/or `team_key` — **hard-fail** if neither resolves | | MCP | Linear MCP tools must be discoverable — **hard-fail** with skill setup instructions if not | -| Hierarchy | **UR = Project Milestone** on shared `product_project` (default `do-work`); REQs = Issues with that milestone. Not Initiatives (MCP has no Initiative create tools). | -| `product_project` | Shared Linear Project name/id for all URs (default `do-work`) | +| Hierarchy | **UR = Project Milestone** on shared product Project per local product; REQs = Issues with that milestone. Not Initiatives (MCP has no Initiative create tools). | +| `product_project` | Shared Linear Project (**name or UUID**) for all URs on this local product — **default empty** (not skill name `do-work`). Resolve: explicit `product_project` → `project.name` → git-root basename; `ensure_product_container` create-if-missing + **always persist UUID**. Example for this skill repo only: name `do-work`. | | `ur_milestone_name_pattern` | Default `{ur_id}: {title}` | | `status_map` | `backlog→Todo`, `in_progress→In Progress`, `stopped→Canceled`, `done→Done` — **hard-fail** if a mapped state is missing on the team (rename team state or override the map key) | | Labels | `Layer/`, `path-unit`, `Size/` prefixes |