schema_version: 1
prefix: QCTL
active: QCTL-001
queue:
- id: QCTL-001
title: Own the ledger contract as Rust types via schemars
scope: qctl
outcome: >-
The work-queue shape is declared once as Rust types. The committed
JSON Schema is generated from those types and still exact for IDEs.
blocked_by: []
acceptance:
- Ledger structs derive serde and schemars::JsonSchema, matching forkctl's contract types.
- schema/tasks.schema.json is generated in the qctl repo only (xtask or qctl schema write) and checked in CI.
- Consumers do not vendor a copy. tasks.yaml points $schema at a versioned stable URL.
- jsonschema still validates from the schema embedded in the qctl binary.
- Hand-written schema JSON is not edited as source.
notes: >-
Do not implement this until the type layout is settled. Graph rules
(unique ids, blocker order, active is queue[0]) stay as Rust checks,
not JSON Schema. Preferred URL shape is an immutable tag or release
asset, including schema_version (not /latest).
- id: QCTL-002
title: Mutate tasks.yaml without losing comments or key order
scope: qctl
outcome: >-
add, start, and archive change only the bytes they must. Comments,
quoting, and the $schema header survive.
blocked_by: [QCTL-001]
acceptance:
- A ledger with comments and a yaml-language-server header still has them after add/start/archive.
- serde dump is not the write path.
- check still passes on the mutated file.
- id: QCTL-003
title: "Publish native qctl so mise github: can install it"
scope: release
outcome: >-
A tagged release ships darwin-arm64 and linux-x64 binaries. The
tasks/q catalog pin is a real immutable ref.
blocked_by: [QCTL-001]
acceptance:
- GitHub Release assets exist for darwin-arm64 and linux-x64.
- examples/mise.toml pins that tag.
- mise run q -- check works from a clean consumer after mise install.
notes: >-
2026-08-17: the lane exists — CI, Version PR, publish, and both
tarballs, all through released verctl, same shape as ctl-core.
0.0.1 was cut by hand from this workstation, which is why it
shipped macos-arm64 only.
2026-08-18: 0.0.2 never shipped — its Version PR bumped Cargo.toml
and left Cargo.lock at 0.0.1, so Verify's `cargo package --locked`
hit a dirty tree. Fixed by declaring `[prepare]` here and moving to
verctl 0.0.3, which is the release that commits `[prepare].stage`
files. The nscloud note is withdrawn, not deferred: this repo is
public, so macos-latest/ubuntu-latest are the runners it wants, and
verctl 0.0.3 makes them a default rather than a hardcoded map.
Still open here: examples/mise.toml does not exist yet.
- id: QCTL-004
title: Point consumers at qctl check instead of copied Ajv tests
scope: cross-repo
outcome: >-
OMX seed, then kodus and pi-stuff, call qctl check. Copied
test/tasks-ledger.test.ts files can die once the pin is real.
blocked_by: [QCTL-003]
acceptance:
- ompex/oh-my-pi seed uses mise run q -- check.
- pi-stuff and kodus-ai have a follow-up row or are converted.
- No consumer still vendors ajv or a copied tasks.schema.json.
- Each consumer $schema URL is a pinned qctl tag/release, not latest.
- id: QCTL-006
title: Archive from commit trailers in a pre-push hook
scope: qctl
outcome: >-
A merged or pushed commit that closes a ledger id archives that
row. An active id cannot sit on main after its close trailer landed.
blocked_by: [QCTL-002]
acceptance:
- "Trailers Closes PREFIX-NNN and Completes: PREFIX-NNN in the commit body are the only syntax. Subject-line slogans do not count."
- "qctl hook install writes a pre-push (and optional commit-msg) hook that runs qctl close-from-git."
- "qctl close-from-git reads commits being pushed (or main..HEAD), archives matching queued ids with evidence = the commit SHA, and fails if active still names a closed id."
- "CI qctl check on default branch fails when a commit already on that branch closed an id that is still queued."
- "The hook does not amend. It writes tasks.yaml in the working tree and requires that file to be part of the push (or a follow-up commit). No hook skip flags."
- "Does not invent GitHub issue linking. Closes #12 stays GitHub's."
notes: >-
check now fails if a Closes/Completes trailer names a still-queued
id. Auto-archive of tasks.yaml still needs QCTL-002. VER-003 went
stale because check did not look at git and no commit had a trailer.
- id: QCTL-008
title: Adopt ctl-core in qctl and forkctl
scope: cross-repo
outcome: >-
qctl and forkctl flatten ctl-core flags and go()/View::show.
No third copy of styled help, color, or format.
blocked_by: []
acceptance:
- qctl main uses ctl_core::prelude (go, OutputArgs or ColorLong, formatdoc).
- forkctl drops its local help/color/format chassis in favor of ctl-core.
- state-sync is a follow-up or the same PR if the diff stays small.
- Pin is a ctl-core git rev or crates.io version, not a path dep on main.
notes: >-
Blocked in practice on ctl-core CTC-004 (harden). qctl cannot
name a foreign prefix in blocked_by. Do not wait for QCTL-001.
horizon:
- id: QCTL-007
title: Optional per-user daemon sharing one session and view
scope: ctl-core
kind: evaluation
outcome: >-
Multiple *ctl clients can attach to one local session the way
skill-cli does (Unix socket, NDJSON protocol, one dispatcher).
open: >-
skill-cli already has daemon + UDS + protocol crates. ctl-core
should reuse that shape only after pretty/JSON views are adopted
by at least one *ctl. Not a default feature.
notes: >-
Lower priority than clap chassis and schema-first views. Do not
pull tokio into ctl-core default features.
archive:
- id: QCTL-005
title: Start the shared CLI crate (short/long, help, negations)
scope: cross-repo
completed: "2026-08-17"
disposition: completed
outcome: >-
forkctl, qctl, and verctl share one clap chassis instead of
copying flag style.
evidence:
- "github.com/victor-software-house/ctl-core main 02c7e9f"
- "verctl ver-004-version-pr consumes ctl-core via git rev"
- "prelude, Render, formatdoc, FormatArgs/ColorLong, View"