# os-backlog — component detail

_Leaf file of [../implementation-status.md](../implementation-status.md). Read on demand._

**Global os-backlog plugin** — `cc-os/plugins/os-backlog/` (git-tracked, 2026-07-10);
symlinked into `~/.claude/plugins/os-backlog`; PRD Forgejo issue #9, slices #10–#17.
Decision records: ADR-0023 (own plugin, not an os-vault extension), ADR-0025 (standard Ruby
plugin structure), ADR-0029 (column ownership — subject removed by ADR-0042), **ADR-0042
(2026-07-16, supersedes ADR-0033): Planka retired; git issues are the single tracker for
state and specs** (OpenSpec change `retire-planka-git-issues-only`).

## Component inventory (post-ADR-0042, v0.3.0)

- Purpose: the AI's process-management surface over git issues — deterministic tracker
  routing (`forgejo:<owner>/<repo>` | `github:<owner>/<repo>` | `repo:<path>`; `planka:`
  rejected fail-soft citing ADR-0042), mid-session capture as labeled issues, pull-only
  listing, cross-project filing via the global project index.
- State model: open/closed + labels. Ten canonical labels: priority `P0`–`P3`; autonomy
  `hitl`/`semi`/`afk-ready` (semi ships to `review` label and stays open; afk-ready closes
  after verification); state `next` (human-only — AI never applies/removes), `waiting`
  (+ blocker comment), `review`. No `doing`/`done` labels: in-progress = assignment/branch
  activity, done = closed. Recurring-convention issues (e.g. `jared/ops#1` biweekly audit)
  are commented per occurrence and never closed by the AI.
- Lib (Ruby): `tracker.rb` (grammar validation + `issues_destination`: `:git_issues` /
  `:repo_files` / `:unrouted`), `issues.rb` (2026-07-16 — capture/list dispatch: `tea` for
  forgejo, `gh` for github, `NNN-<slug>.md` frontmatter files for `repo:`; injected runner,
  no state labels reachable through `create`), `inspector.rb` (issue-shape classification),
  `config.rb`, `resolver.rb` (reduced to tracker-presence decision), `project_index.rb`,
  `wakeup.rb`.
- CLI: `bin/os-backlog` — `resolve`, `inspect`, `config-write`, `projects`, `issue-create`
  (`--title/--body/--priority`), `issues` (grouped next/waiting/review/other). Fail-soft
  one-liners throughout.
- Skills: `/os-backlog:capture` (issue-create; never state labels, never `next`),
  `/os-backlog:list` (pull-only), `/os-backlog:route` (destination proposal + idempotent
  ten-label ensure via tea/gh). SessionStart note rewritten 2026-07-16 (~444 tokens,
  down from ~687 measured): capture / routing / cross-project / human-only-`next` /
  working-state / autonomy / recurring rules.
- Tests: 88 runs / 203 assertions (2026-07-16) with fake CLI runners; no live API.
  Companion os-status `tracker-configured` check validates the reduced grammar and rejects
  `planka:` with the ADR-0042 advisory.

## Build and verification history

- **AFK slices shipped (2026-07-10):** #10 plugin scaffold + `board-ensure` (uniform
  lists/labels incl. new `semi` autonomy label), #11 `tracker` key in `.cc-os/config` +
  os-status check, #12 pure routing resolver CLI, #13 capture/list skills + card CLI, #15
  `card-triage` + `board-audit` named agents. Live verification same day: planka-api gem
  0.1.0 installed locally (`rake install`, rbenv 4.0.2; publish/versioning parked behind gem
  issue 13); `snapshot`, `board-ensure` (created the missing `semi` label on the cc-os
  board), `card-add` + `card-label` all succeeded against production Planka with bot
  credentials sourced from ovh-vps (`~/services/planka/bot-credentials.env`) in a subshell.
  Follow-up captured as a `hitl` Backlog card: get planka_bot into credvault (no import
  path — rotate vs. add import capability is a human decision).
- **planka-api 0.2.0 typed-returns migration (2026-07-10, commit `8df0cb7`):** 0.2.0 returns
  `Planka::Types` value objects instead of parsed-JSON hashes. `board_ensurer.rb` and
  `cards.rb` rewritten on typed accessors; `Cards#snapshot` builds explicit string-keyed
  hashes from `BoardDetail#lists`/`#cards`/`#labels`/`#card_labels`. Test fakes rebuilt to
  return real `Planka::Types` records. JSON output stays byte-compatible, so skill/agent
  contracts are unchanged. Follow-up fix (issue #18, same day): the migration had silently
  dropped one field from the per-card snapshot hash; restored the full 19-key camelCase card
  key set plus `labels`, with a regression test pinning the key set including
  `listChangedAt`.
- **Live verification 0.2.0 (2026-07-10, issue #20):** local machine had no `PLANKA_*`
  env/credvault entry, so the plugin tree was rsynced to `ovh-vps:/tmp/os-backlog-verify`
  and exercised there with bot credentials sourced in a subshell (contents never read or
  printed). Results: tests 35 runs/0 failures on ovh-vps; `board-ensure cc-os` idempotent
  (twice `(existing)`, nothing created); card-add/card-label/card-comment/snapshot all
  correct with the full 20-key set; test card deleted via `Planka::Client#cards.delete`
  (the CLI has no delete subcommand) and confirmed gone; verification tree removed. No
  regressions vs. pre-0.2.0.
- **Slice 7 (2026-07-13, issue #16):** SessionStart injection note shipped
  (`hooks/session_start.py` + `hooks/hooks.json`, os-adr-style plugin-local hook): WHEN→THEN
  rules for capture / routing / promotion / column ownership / autonomy labels, ADR-0029
  semantics (the issue body's "afk-ready → Review" was wrong — caught in Codex review),
  rules only, zero board state, ~447 tokens. `to-issues` (now `to-tickets`; global skill,
  then at `~/.agents/skills/to-issues/`) gained a destination-tracker step reading the
  `.cc-os/config` tracker key; the `planka:`/unset default publishes slices as git issues
  on the repo remote + one Planka pointer card (spec/state boundary preserved — human gate
  decided 2026-07-13). `Backlog::Tracker.issues_destination` + 6 routing tests.
- **Board-id cache (2026-07-13, issue #22):** `Backlog::BoardResolver` with injected config
  read/write lambdas; `board_id=` cached in `.cc-os/config` beside `tracker=` (name stays
  source of truth; id honored only for `planka:` trackers and validated against the
  returned board name; 404/mismatch/any cache failure degrades to name lookup and rewrites
  the cache; write failures never fail the command). Comment-preserving `Config.set`
  mirrors os-status's writer. 14 new tests; suite 90 runs / 198 assertions / 0 failures.
- **Slice 8 (2026-07-13, issue #17):** Operations board created in the Dev project (board
  `1818308120145822783`) with uniform lists/labels; recurring card "Biweekly orchestration
  IRL audit (recurring)" (card `1818310715438531668`, P2 + semi, first progress comment
  `1818310889929966683`; recurrence contract: comment + move back to Backlog, never Done).
  Tracker-routing convention canonized as **ADR-0033** (Planka = state / git issues = specs,
  tracker key grammar, pointer + promotion rules; cites route/to-tickets/session-start
  rather than restating). os-context audit rubric gained category 8 `tracker-routing`
  (capture/route/promote compliance only). Bug found live and fixed: label color fallback
  only rescued `Planka::ValidationError`, but Planka rejects unknown colors as HTTP 400
  `Planka::BadRequestError` — fallback now rescues both (regression test; suite 91 runs /
  0 failures). os-status `/os-status:fix` step d reworded: invoke `/os-backlog:route`
  directly, never pre-ask "forgejo or planka?" (boundary rule uses both — surfaced by the
  servers-repo onboarding, cf. issue #26).
- **Issue-shape classification (2026-07-13, issue #26):** `inspect` now fetches lightweight
  remote-issue metadata (tea `--output json` with string-index/joined-labels normalization;
  gh `--json`; fail-soft → nil) and emits an always-present `issue_shape` key
  (`sequential-chain` / `flat-adhoc` / `mixed` / null). Classification is a pure
  `Inspector.classify_issue_shape` (label allowlist with whole-token matching; cross-refs
  must hit the fetched set with dependency language within 40 chars; <3 issues or no
  metadata → null). Route SKILL.md step 2 rewritten as three branches with **split as the
  default** for spec-shaped issue sets (still exactly one tracker key — division of labor,
  not two destinations); step 3 got a never-re-trigger exception; boundary rule now cites
  ADR-0033 instead of restating it. 12 new tests incl. a CLI-contract test; suite 103 runs /
  212 assertions / 0 failures.
- **Cross-project filing (2026-07-13, issue #27, ADR-0034):** `Backlog::ProjectIndex` —
  derived global index at `~/.cc-os/projects.json` (atomic temp+rename writes, corrupt/
  missing → empty, `CC_OS_HOME` override), upserted fail-soft by config-write (canonical
  path = realpath of the directory the config was written into — NOT the git toplevel, so
  umbrella subprojects index as themselves; corrected 2026-07-13 review), queried via new
  `projects [filter]` CLI subcommand (filter matches name, tracker, or path). Discoverer template + tea/gh/card-add mechanics in
  `skills/route/references/cross-project-filing.md`; one CROSS-PROJECT rule line added to
  the SessionStart note. 9 new tests; suite 112 runs / 233 assertions / 0 failures. Live
  smoke: cc-os row present in the real index.
- **Wakeup design spike (2026-07-13, issue #28, ADR-0035/0036):** poll + tmux direction
  chosen at the human gate. `Backlog::Wakeup` + `bin/wakeup-poll` thin pilot (dry-run
  default, `--spawn` to create `cc-<project>-issue<N>` tmux sessions and persist
  `~/.cc-os/wakeup-state.json`; opt-in `wakeup=true` in `.cc-os/config`; afk-ready → act,
  semi → triage-only, else notify-only into pull-only `~/.cc-os/wakeup-summary.md`). No
  cron installed; 6 tests; suite 128 runs / 0 failures. Follow-up recorded: single
  designated poller machine (state stays per-host); cadence/cron mechanism and a
  `config-write` wakeup key deferred until a first project opts in.
- **Review corrections (2026-07-13, cards #1818330518584820864 / #1818330528525321347):**
  post-ship review (Codex + verification pass) of the triage hook and project index:
  `Config#planka_board` (`tracker=planka:<board>` now counts everywhere, incl. the
  Resolver, not just the explicit `board` key); triage hook board resolution moved to pure
  `TriageCheck.board_name_for` honoring the umbrella rule (session cwd's `.cc-os/config`
  overrides the git root's; hook previously read only the root config); index keying fixed
  per above; hook timeout 8→15s; `CC_OS_DEBUG=1` surfaces swallowed hook errors on stderr.
  Known residual (captured as a Backlog card): board lookup is name-only across ALL Planka
  projects — duplicate board names in different projects can resolve to the wrong board.
  Suite 138 runs / 295 assertions / 0 failures.
- **Planka retirement rework (2026-07-16, ADR-0042, OpenSpec change
  `retire-planka-git-issues-only`):** after a week live, all real work flowed through git
  issues while boards held only the plugin's own build cards — Planka retired. Deleted:
  `board_ensurer`/`board_resolver`/`board_spec`/`cards`/`triage_check` (+ triage hook),
  both agents, planka-api gem wiring, `FakePlankaClient`, nine card/board CLI subcommands.
  Added: `issues.rb` + `issue-create`/`issues` CLI (live-verified; fixed tea field
  `assignees` — `assignee` is not a valid tea list field). Skills + SessionStart note +
  `/to-issues` + os-status fix skill rewritten to the single-tracker model. Migration:
  all 9 boards snapshotted (JSON + comments) + Postgres dump on ovh-vps; snapshot archived
  in new private `jared/ops` Forgejo repo (canonical labels; recurring audit issue
  `jared/ops#1`); cc-os non-Done cards → issues jared/cc-os#61–70 (3 skipped: pointer to
  closed #25, duplicate of open #8, obsoleted board-lookup card); ovh-prod + ruby-gems
  strays → jared/ops#2–3; `planka` board's 18 cards obsolete (gem build history, covered
  by snapshot); `tracker=` rewritten in all 6 indexed projects (no `planka:` values
  remain). Suite 88 runs / 203 assertions; os-status 80 tests.
- **Human gates closed (2026-07-16):** philly per-card pass done — 4 repo cards →
  jared/llf-schema#7–10, 18 client cards → jared/ops#4–21, guardrails pin closed (vault
  note is the record). Decommission done same day: bot creds deleted, Planka
  containers/cron/service dir removed from ovh-vps (snapshot + pg dump + service configs
  kept at `~/planka-final-snapshot-2026-07-16/`), github.com/jaredswanson/planka archived
  read-only with an ADR-0042 README banner.
- **Outstanding:** #14 residual (onboard one more project — operational hitl); wakeup
  rollout (first opt-in project + poller machine + cadence); jared/cc-os#74 — fold
  ADR-0025 compliance into os-backlog (bin/wakeup-poll second entry point, README gem
  deps), filed by the standards audit.
