qctl 0.0.2

Control in-repo YAML work queues
Documentation
# yaml-language-server: $schema=./schema/tasks.schema.json
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"