Tracks implementation work for toggle. Each project corresponds to one PR.
The PRD source of truth lives in prds/phase-0-prd.md (notably §0.13 and §0.14).
Status Legend:
[x]Completed[-]In Progress[ ]Not Started[~]Won't fix / Invalid / False positive
Release plan: P01–P04 landed together as a single
v0.2.0release onmain.
Goal: Implement group:variant section markers and the four CLI variant
behaviors from PRD §0.13.3 — solo, pair-flip, activate, force-all — with
per-file scope. Errors when a group has 3+ variants and -S lacks a
:variant qualifier.
Out of Scope
- The
--pairvalidation flag (P02) - Cross-file consistency checks (P04)
- Scan output changes (P03)
- [P01-T01] Add
parse_id_parts(id) -> (group, Option<variant>)helper tosrc/core.rs - [P01-TS01] Unit tests in
tests/unit/core_tests.rscovering solo, pair, edge cases - [P01-T02] Add
discover_variants(content, group) -> Vec<SectionInfo>helper tosrc/core.rs - [P01-TS02] Unit tests for
discover_variants(no matches, 1, 2, 3 variants, mixed solo+variant) - [P01-T03] Add
toggle_variant_group(...)andactivate_variant(...)functions tosrc/core.rs - [P01-TS03] Unit tests covering pair-flip, activate, force-on-all, force-off-all, error on 3+
- [P01-T04] Route variant CLI arguments in
src/main.rs::toggle_sectionto the new core functions - [P01-TS04] Integration tests in
tests/integration.rsexercising each CLI form against a fixture file - [P01-T05] Add
tests/fixtures/variants.pywith a pair group and a 3-variant group - [P01-T06] Update CLI help text on
-Sto mention variant syntax - [P01-T07] Run
just dev(format, lint, test, build) and ensure clean - [P01-T08] Run
just dev, commit (no version bump yet)
# Pair flip — db has exactly 2 variants
$ toggle -S db tests/fixtures/variants.py
# Activate one variant
$ toggle -S db:postgres tests/fixtures/variants.py
# Force all variants of a group commented
$ toggle -S db --force on tests/fixtures/variants.py
# Error when group has 3+ variants and no qualifier
$ toggle -S cache tests/fixtures/variants.py
Error: group 'cache' has 3 variants; specify one with -S cache:<name>cargo testpasses including new unit + integration testsjust lintclean (clippy with-D warnings)just buildsucceeds
toggle -S db variants.pyswaps active/commented blockstoggle -S db:postgres variants.pyactivates postgres, comments others
See docs/superpowers/plans/2026-04-16-p01-section-variants-core.md
Goal: Add the --pair pre-execution guard from PRD §0.13.4 that errors
when the targeted group does not contain exactly 2 variants. No file
modifications occur on failure.
- [P02-T01] Add
--pairflag tosrc/cli.rs - [P02-TS01] Unit test that
--pairrequires-S <group>(no line ranges) - [P02-T02] Insert pre-execution validation in
src/main.rsbefore file mutations - [P02-TS02] Integration tests: success on 2-variant group; error on 1, 3, 4
- [P02-T03] Update README / help text
- [P02-T04] Run
just dev
Goal: Extend --scan per PRD §0.14.1–§0.14.2 and §0.14.4: group variants
under their parent group, infer the TYPE column (solo/pair/group), add
recursive summary mode, the --scan -S <id> detailed view, and JSON output
that nests variants under groups.
- [P03-T01] Extend
ScanSectionInfo(or addScanGroupInfo) insrc/core.rswith group/type/variant fields - [P03-TS01] Unit tests for type inference (solo / pair / group / mixed)
- [P03-T02] Update
print_scan_resultsandrun_scaninsrc/main.rs - [P03-TS02] Integration: per-file table, recursive summary,
--scan -S db - [P03-T03] Update JSON schema per §0.14.4
- [P03-TS03] Test for JSON output
- [P03-T04] Run
just dev
Goal: Add --scan --check per PRD §0.14.3 — read-only validation that
reports unclosed markers, pair groups with ≠2 variants, cross-file variant
mismatches, and duplicate IDs within a single file.
- [P04-T01] Add
--checkflag insrc/cli.rs(only valid with--scan) - [P04-T02] Add validation logic in
src/core.rsreturning a list ofCheckIssue { level, group, message } - [P04-TS01] Unit tests for each check (unclosed, pair-mismatch, file-mismatch, duplicate ID)
- [P04-T03] Wire
--checkoutput (table + JSON) intosrc/main.rs - [P04-TS02] Integration tests
- [P04-T04] Behavior with
--check --pair(only checks pair-inferred groups) - [P04-T05] Run
just dev
Goal: Add a --insert mode that wraps a single file's -l start:end block
in a toggle:start/toggle:end marker pair, leaving the body uncommented.
See docs/superpowers/specs/2026-05-29-marker-insert-strip-list-filters-design.md.
- [P05-T01]
core::insert_sectionhappy path + unit tests - [P05-T02]
insert_sectionguards (dup ID, bounds, bad id/desc) + unit tests - [P05-T03]
--insert/--descCLI flags, validation,run_insert+ integration tests - [P05-T04]
--to-end,--comment-style,--dry-runintegration tests - [P05-T05] README + PROJECTS.md +
just dev
Note: The marker roadmap (P05–P07) is complete. The C ABI library is numbered P08; release automation P09.
Goal: Add --remove -S <ID> to strip a named section, recursively with -R.
--remove-mode markers|commented|all (default commented): markers deletes
only the marker lines; commented also deletes fully-commented body lines but
keeps live code; all deletes the whole span. Refuses an ambiguous bare variant
group; --require-match exits non-zero on no match. Reuses --dry-run/--backup.
See docs/superpowers/specs/2026-05-29-marker-insert-strip-list-filters-design.md §P06.
Out of Scope
--remove --atomic(guarded with an error for now; reuse the atomic batch later)- Multi-line-delimited (
/* … */) "fully-wrapped body" detection (single-line prefix handled)
- [P06-T01]
core::remove_section(content, id, mode, comment_style) -> (String, usize)(3 modes, dup-IDs) - [P06-TS01] Unit tests: markers / commented / all / duplicate-IDs / not-found
- [P06-T02]
RemoveModeenum +--remove/--remove-mode/--require-matchincli.rs - [P06-T03]
run_removeinmain.rs(walk, variant-group refusal, write viaapply_changes, match counting) - [P06-T04] Validation + dispatch;
--removeconflicts +--atomicguard - [P06-TS02] Integration tests: each mode, recursive, require-match, not-found, variant refusal, dry-run
cargo test --workspacegreen (13 new tests; lib 99→104, integration 135→143)cargo clippy --workspace --all-targets -- -D warningsclean
Goal: Add --fields ids|files|lines to --list-sections to filter its text
output. Default lines keeps current behavior (backward compatible); --json
is unaffected. See docs/superpowers/specs/2026-05-29-marker-insert-strip-list-filters-design.md §P07.
- [P07-T01]
ListFieldsenum (clapValueEnum) +--fieldsarg incli.rs - [P07-T02] Guard:
--fieldsrequires--list-sections - [P07-T03] Branch
run_list_sectionstext output on the level (ids / files / lines) - [P07-TS01] Integration tests: each level,
lines== default,--jsonunchanged, requires-guard
cargo test --workspacegreen (5 new integration tests; integration 130 → 135)cargo clippy --workspace --all-targets -- -D warningsclean--fields linesoutput byte-identical to prior behavior
Goal: Add a togl-ffi workspace crate exposing an ABI-stable C library
(libtogl, static + shared) over togl-lib's string-core and introspection,
with a cbindgen-generated committed header, a C smoke test, and pkg-config.
Also renames the workspace crates to the togl-* convention (togl-lib, togl-cli).
See docs/superpowers/specs/2026-05-29-togl-c-abi-library-design.md and
docs/superpowers/plans/2026-05-29-togl-ffi-c-library.md.
Out of Scope
- Nix flake output and the nixpkgs
libtoglpackage (separate follow-on) - Python and TypeScript bindings (future projects consuming
libtogl)
- [P08-T01] Rename crates →
togl-lib/togl-clifortogl-*consistency - [P08-T02] Scaffold
togl-fficrate ([lib] name="togl", static+cdylib) →libtogl - [P08-T03] Error codes, panic guard, string + metadata lifecycle
- [P08-T04] Transform functions (toggle_comments, section toggle, activate_variant)
- [P08-T05] Introspection functions returning JSON (discover, scan, validate)
- [P08-T06] cbindgen build script + committed
include/togl.h - [P08-TS01] C smoke test linking and exercising
libtogl - [P08-T07] pkg-config template + C API README
cargo test --workspace --all-featurespasses (249 tests, incl. FFI unit + C smoke)cargo clippy --workspace --all-targets -- -D warningscleanlibtogl.a/libtogl.dylib+include/togl.hproduced
See docs/superpowers/plans/2026-05-29-togl-ffi-c-library.md
Goal: Add PR-driven, Conventional-Commit-based releases modeled on
py-launch-blueprint, adapted to the Rust workspace: release-please opens a
release PR (bumps [workspace.package].version + CHANGELOG.md); merging it
tags v* which triggers the existing release.yml binary build. commitlint
enforces Conventional Commits on PRs.
See docs/superpowers/specs/2026-05-29-release-please-commitlint-design.md.
Out of Scope
- A crates.io publish workflow (separate follow-on)
- Creating the GitHub App / repo secrets (manual; see RELEASE.md)
- [P09-T01]
release-please-config.json(simple + extra-files for Cargo.toml workspace version),.release-please-manifest.json - [P09-T02]
.github/workflows/release-please.yml(App/PAT auth, sync-cargo-lock job) - [P09-T03]
commitlint.yml+commitlint.config.mjs+commitlint.dependabot.config.mjs+package.json - [P09-T04]
RELEASE.md(flow, disabling, token setup, first-release cutover)
release-please-config.json/.release-please-manifest.jsonare valid JSONcommitlint.config.mjs/commitlint.dependabot.config.mjsparse as ES modulesactionlintclean on both new workflows
- Add
RELEASE_PLEASE_APP_ID+RELEASE_PLEASE_PRIVATE_KEY(GitHub App) orRELEASE_PLEASE_APP_TOKEN(PAT) secrets.
Goal: Make the CLI more ergonomic without breaking the existing flat-flag interface. Three additive layers, shipped in order:
- Declarative clap constraints — replace hand-rolled flag-combination
checks with clap
group/requires, so impossible combinations fail at parse time with consistent messages. - Subcommands —
toggle/scan/check/list/insert/removeexpose only the flags that apply to each operation. Additive: the legacy flat flags remain; each subcommand translates to the equivalent legacy argv and is re-parsed through the same pipeline, so the two forms cannot drift. The legacy form emits a TTY-only deprecation nudge. - stdin/stdout filter — accept
-/stdin and write to stdout sotoglcomposes as a Unix filter.
Out of Scope
- Removing the flat-flag interface (deprecate only; removal is a future major).
- A
recoversubcommand (the rare--recoverpath stays flat-flag only).
- [P10-T01] Option 2:
operationarg-group +requiresconstraints incli.rs - [P10-T02] Option 1:
Commandssubcommand enum +GlobalArgsflatten struct - [P10-T03] Option 1:
Commands::to_legacy_argvtranslation (clap owns defaults) - [P10-T04] Option 1:
main()subcommand bridge + TTY-only deprecation notice - [P10-TS01] Parity tests: each subcommand ≡ its flat-flag form (incl. a
defaulted
--remove-modecase to guard default drift); scoping + help/man render - [P10-T05] Option 3: stdin/stdout filter mode (
-/--stdin/--stdout, writer ops only; rejection set for--json/--atomic/--backup/etc.) - [P10-TS02] Option 3: stdin≡file parity + no-op byte-identity (trailing newline both ways) + rejection-set tests
cargo testgreen (all parity + existing suites)cargo clippy --all-targets -- -D warningscleantogl --help/--man/--completionsrender the subcommands under the aliased bin name
Goal: Resume publishing to crates.io (stuck at togl 0.2.3 since the
release pipeline only ships GitHub binaries). Reclaim the togl crate name for
the CLI and auto-publish the library + CLI on each release tag.
Decisions
- Rename the CLI package
togl-cli→togl(directory stayscrates/togl-cli; binaries staytoggle/togl) — continues the existingtogl0.2.3 line and keepscargo install toglworking (already in the README). - Publish
togl-lib(library) +togl(CLI).togl-ffiispublish = false(it's the C library, not a Rust dependency). - Inter-crate
togl-libdep gets aversion(required for publishing); the release workflow syncs it to the release tag before publishing.
Out of Scope
- Publishing
togl-ffito crates.io. - Switching release tooling (release-please/
nix-updatestay as-is).
- [P11-T01] Rename CLI package →
togl; addversiontotogl-libdep/dev-dep;togl-ffipublish = false; update-p togl-clirefs (flake, test script) - [P11-T02]
publish-cratesjob inrelease.yml(sync dep version → tag, publishtogl-libthentogl) - [P11-T03] Trusted Publishing (OIDC): job runs in
environment: crateswithid-token: write+rust-lang/crates-io-auth-action; no API-token secret - [P11-TS01]
cargo publish --dry-run(togl-lib full; togl manifest), full test suite +nix build .#toglgreen after the rename
- crates.io uses Trusted Publishing (no
CARGO_REGISTRY_TOKENsecret). Per crate (togl-lib,togl) add a Trusted Publisher: reposmorin/toggle, workflowrelease.yml, environmentcrates. (Done:togl-libbootstrapped with a manual0.0.0publish since crates.io TP needs the crate to exist first.) - First real publish fires on the next release tag (e.g. v0.6.0); crates.io versions jump 0.2.3 → that release (allowed — versions only need to increase).
Goal: Ship the togl CLI to PyPI (togl), npm (togl-cli +
@smorinlabs/togl-* platform packages), and Homebrew
(smorinlabs/tap/togl) from the existing tag-triggered release.yml,
with OIDC trusted publishing everywhere a registry supports it. Also
hardens release.yml (guards job: tag-on-main ancestry + tag==version,
deny-all top-level permissions, no-cancel concurrency) and installs
contributors-please.
Decisions (locked 2026-06-07)
- PyPI: maturin
bindings=binwheels (no sdist), packagetogl, bothtoggle+toglcommands; TestPyPI (envtestpypi, auto) → PyPI (envpypi, required reviewer); OIDC on both registries. - npm: esbuild-style — unscoped wrapper
togl-cli+ 4 platform packages@smorinlabs/togl-{linux-x64,darwin-x64,darwin-arm64,win32-x64}(linux = musl build); strict OIDC on all 5. - Homebrew: prebuilt-binary formula
Formula/togl.rbin smorinlabs/homebrew-tap, pushed with a short-lived App installation token minted in-job (TAP_PUSH_APP_*secrets; smorinlabs App installed on the tap) — revised 2026-07-15 from the originalHOMEBREW_TAP_TOKENPAT plan, eliminating the pattern's last long-lived credential. - Release archives renamed
toggle-<target>→togl-<target>and now contain both binaries.
Out of Scope
- Publishing the library to PyPI/npm (togl-ffi is C-ABI; no PyO3/napi).
- cargo-dist (rejected — would own release.yml; axo.dev wound down).
- [P12-T01] pyproject.toml (maturin bindings=bin) +
build-wheelsmatrix,publish-testpypi→publish-pypijobs - [P12-T02] npm/ scaffolding (wrapper + 4 platform pkgs) +
publish-npmjob (unpack tarballs, stamp versions, publish) - [P12-T03]
update-homebrewjob rendering Formula/togl.rb from darwin tarball checksums - [P12-T04] Harden release.yml:
guardsjob,permissions: {}, no-cancel concurrency; ship both bins intogl-<target>archives - [P12-T05] contributors-please: update-contributors.yml, .contributors.yml, CONTRIBUTORS.md markers
- [P12-TS01] First gated releases exercised the pipeline end-to-end: v0.5.1 shipped binaries/crates/PyPI/Homebrew (npm blocked on publisher config + non-idempotent publishes); v0.5.2 shipped all five destinations green in one run, wrapper install + architecture selection verified on darwin-arm64 with cross-platform tarball checks
- PyPI + TestPyPI: add trusted publisher per registry for
togl(ownersmorin, repotoggle, workflowrelease.yml, envpypi/testpypi). - npm: one-time placeholder publish of the 4 platform packages with a
temp classic Automation token (npm has no pending publishers), attach
trusted publisher (
smorin/toggle/release.yml/envnpm) on all 5 packages, revoke token. - Homebrew: install the smorinlabs App on smorinlabs/homebrew-tap, then
repo_secrets set --repo smorin/toggle --app tap-push.