verctl 0.0.3

Stack-agnostic version PRs from Changesets-format fragments
Documentation
# yaml-language-server: $schema=https://github.com/victor-software-house/qctl/releases/download/v0.0.1/tasks.schema.json
schema_version: 1
prefix: VER
active: VER-011

queue:
  - id: VER-011
    title: Finish the Changesets-shaped release loop
    scope: verctl
    outcome: >-
      Fragment in, Version PR, exact-SHA publish, per-package CHANGELOG,
      lockfile, already-published, optional natives. No hand-edited
      versions.
    blocked_by: []
    acceptance:
      - prepare writes versions + per-package CHANGELOG and consumes fragments.
      - Version PR is the only version-authority path (CI / --pr).
      - "`[prepare].after` plus stage globs; unexpected dirty files fail."
      - publish is overridable recipes; cargo and bun are stock examples.
      - Lockfile is committed; cargo publish --locked works after bump.
      - Pretty is ctl-core ColorMode tables; tests use --color never.
      - Native assets only when [assets] is set; PR CI stays one runner.
      - Asset jobs are named by target id; cargo/bun inherit stdio.
    notes: >-
      Original product goal. Do not name the private release-skill in
      repo docs. Rust and bun are stock examples, not the architecture.
      Adopt ctl-core fixture trees (CTC-006) instead of more fs::write
      + indoc copies. 2026-08-17: status/check/prepare are ColorMode
      tables; publish proves changelog headings + default-branch
      history (no commit-subject sniff); GitHub notes come from each
      package CHANGELOG. Hand-edit guard shipped as VER-012 / PR #9.
      Still open for archive: lockfile-after-bump (VER-013).

  - id: VER-015
    title: Asset runners are configuration, not a hardcoded map
    scope: verctl
    outcome: >-
      Each [assets] target names the runner that builds it. The plan
      emits what the repo declared, so a public repo stays on free
      hosted minutes and a private one can point at its own labels.
    blocked_by: []
    acceptance:
      - A machine is declared once as [runners.NAME] with labels; [ci.NAME] names several, [assets.NAME] exactly one.
      - verctl ci and verctl assets print what resolved and the matrix carries it.
      - A public repo needs no config to keep macos-latest / ubuntu-latest, and no [ci] to keep one verify job.
      - A declared self-hosted or nscloud label set reaches runs-on unchanged, in order.
      - An undeclared machine name fails listing the declared ones; a label is never accepted as a name.
      - Tests cover default, override, multi-label, fan-out naming, empty list, and a partial undeclared target.
    notes: >-
      2026-08-17: found while adopting the lane in qctl (qctl#3). Two
      claims first written here were wrong and are corrected: a per-target
      `runner` override already existed, and a job can be told its runner
      by an earlier job. The real defects are that `runner` is one string
      where GitHub takes a label set, that CI has no configuration surface
      at all, and that an unknown id with a partial record is accepted and
      reaches `cargo build --target ""`. Shape settled after several wrong
      turns, recorded so they are not retried: anonymous list rows needing
      an `id` field (`[[ci.jobs]]`, `runners = [{ labels = [...] }]`) were
      rejected as ceremony — names are table headers instead; a `runners`
      list on an asset target was rejected because two machines would
      overwrite one tarball, which the shape now makes unsayable; and one
      identical shape per phase was abandoned once it put `os`/`arch` on a
      job while its labels sat a level below. A name→labels registry is
      deliberately not the alias table rejected earlier: an alias made an
      invented word look like a resolved label, whereas a machine name
      resolves against `[runners]` or fails and never reaches GitHub.
      M1 (generating ci.yml) and M3 (a repo variable) were considered
      and rejected for M2, the plan-job bootstrap the publish lane already
      uses; M3 would move the value out of verctl.toml entirely.
      Asked why `plan` is not configurable, the inventory settled the law:
      verctl decides how many jobs there are, the workflow file decides
      where a fixed job runs. Unary jobs (`plan`, `crate`, `pin`,
      `prepare`) all have literal machines; only `verify` and `assets`,
      whose count the repo decides, read config. A static YAML file cannot
      declare N jobs without knowing N — that fan-out is what config buys,
      and the machine rides along on it. So `[release]` is dropped, not
      deferred: publish and pin are inherently unary, nothing decides their
      count, and a self-hosted-only repo is already served by editing three
      literals in workflows it owns. `verctl check --ci` was rejected with
      it — verifying those literals needs a YAML dependency this crate does
      not have, to check a value the law puts there on purpose.

horizon:
  - id: VER-016
    title: The plan job should not install a compiler to parse TOML
    scope: verctl
    kind: evaluation
    outcome: >-
      The ci.yml bootstrap costs what reading verctl.toml costs, and
      verify stops waiting on a toolchain it does not share.
    open: >-
      Measured on run 32078158505: plan took 33s, of which 21s was mise
      plus rust-cache, and verify did not start until 37s in. It needs
      verctl and verctl.toml, not cargo. The tarball is the fix and it
      is gated, not undecided: `git show v0.0.2:src/ci.rs` does not
      resolve, so the published binary has no `ci` verb and
      MISE_ENV=release cannot plan until a release carries VER-015.
      Flip the plan job then and delete its toolchain steps. Rejected
      meanwhile: a stable-rust plan job, which trades a second
      toolchain and a cold cache for ~13s; and one rust-cache key
      across plan and verify, since rust-cache saves only when the key
      is absent, so plan's dev-binary target dir would land first and
      leave verify a thinner cache than its own. Efficiency only — the
      runner contract itself is settled (VER-015).

  - id: VER-013
    title: Stock lockfile refresh after cargo bumps
    scope: verctl
    kind: evaluation
    outcome: >-
      After a cargo version write, Cargo.lock is regenerated so
      publish --locked does not fail.
    open: >-
      Decide whether this is default after for the cargo driver or a
      [prepare].after the repo opts into.

  - id: VER-014
    title: First-party composite Action packages
    scope: verctl
    kind: evaluation
    outcome: >-
      action.yml packages get floating major tags without npm.
    open: Need a first-party Action that is not this repo's own actions/.

  - id: VER-010
    title: Stock JS publish for npm, pnpm, and yarn
    scope: verctl
    kind: evaluation
    outcome: >-
      package.json still publishes through one table of argv. Bun stays
      the default. npm/pnpm/yarn are extra rows, not new verbs or
      Action commands.
    open: >-
      Need a consumer that is not Bun. Infer from packageManager /
      lockfile the same way detect.rs already does for install. Action
      stays `verctl publish`.

  - id: VER-008
    title: Generic regex version writer
    scope: verctl
    kind: evaluation
    outcome: Packages that are not Cargo or package.json can still bump via a declared pattern.
    open: Need a real third manifest (not a hypothetical) before adding a regex writer.

  - id: VER-006
    title: Conventional Commits synthesize the same fragments
    scope: verctl
    kind: deferred
    outcome: An opt-in mode writes .changeset/*.md from commit types.
    open: Default stays off. Fragments remain the human input.

  - id: VER-007
    title: OIDC publish backends for cargo and bun
    scope: verctl
    kind: deferred
    outcome: Public npm uses trusted publishing, not NPM_TOKEN.
    open: bun publish --tolerate-republish already ships. OIDC is the remaining auth path.

archive:
  - id: VER-012
    title: Block hand-edited versions on the default branch
    scope: verctl
    completed: "2026-08-17"
    disposition: completed
    outcome: >-
      A pre-push or check fails when a declared manifest version
      differs from origin/main outside version-packages.
    evidence:
      - "github.com/victor-software-house/verctl/pull/9"
      - "474cace on main"
      - "verctl check --versions plus mise run versions on pre-push"
      - "merge-base of HEAD vs origin/HEAD, GITHUB_BASE_REF, main, master"
      - "only version-packages exempt; CI does not skip; tests inject Skip"

  - id: VER-005
    title: "Publish native verctl so mise github: can install it"
    scope: release
    completed: "2026-08-17"
    disposition: completed
    outcome: >-
      crates.io plus GitHub Release assets for darwin-arm64 and linux-x64.
    evidence:
      - "crates.io verctl 0.0.1"
      - "github.com/victor-software-house/verctl/releases/tag/v0.0.1"
      - "verctl_0.0.1_linux_x64.tar.gz and verctl_0.0.1_macos_arm64.tar.gz"
      - "mise x github:victor-software-house/verctl@0.0.1 -- verctl --version"

  - id: VER-009
    title: Keep verctl on ctl-core after the chassis hardens
    scope: verctl
    completed: "2026-08-17"
    disposition: completed
    outcome: >-
      verctl stays on ctl-core prelude/View/formatdoc. No local help
      or color copy comes back.
    evidence:
      - "ctl-core = \"0.0.3\" on crates.io"
      - "ColorMode tables; no path = \"../ctl-core\""

  - id: VER-004
    title: Open or update the Version PR
    scope: verctl
    completed: "2026-08-17"
    disposition: completed
    outcome: >-
      One prepare-release PR consumes fragments, commits version files
      and CHANGELOG, and deletes the consumed .changeset files.
    evidence:
      - "github.com/victor-software-house/verctl/pull/7"
      - "crates.io verctl 0.0.1"
      - "actions/version-pr and actions/publish"

  - id: VER-003
    title: Write Cargo.toml and package.json versions
    scope: verctl
    completed: 2026-08-16
    disposition: completed
    outcome: >-
      prepare --no-pr applies the max bump per declared package to
      Cargo.toml and package.json.
    evidence:
      - verctl prepare --no-pr
      - cargo/npm are stock Path drivers (format + keys + after)
      - custom drivers via format+keys or read/write shell
      - tests/bump.rs (including declared keys and shell driver)

  - id: VER-002
    title: Render changelogs with minijinja
    scope: verctl
    completed: 2026-08-16
    disposition: completed
    outcome: Defaults are Jinja2 templates rendered by minijinja 2.x.
    evidence:
      - templates/changelog.jinja and dependency-changelog.jinja
      - tests/changelog.rs

  - id: VER-001
    title: Parse Changesets YAML fragments (quoted and unquoted)
    scope: verctl
    completed: 2026-08-16
    disposition: completed
    outcome: YAML fragments via gray_matter. status and check fail closed.
    evidence:
      - src/fragment.rs
      - tests/fragment.rs and tests/status.rs