release: promote main to prod — v0.8.0 (betterleaks migration + ClawHub auto-publish + agent init --dry-run)#99
Merged
Merged
Conversation
Replace the secret-scanning binary in both Node and Python with betterleaks (github.com/betterleaks/betterleaks v1.1.2), maintained by the gitleaks authors. JSON report shape is unchanged, so the result-mapping logic survives; the wire-up changes are CLI subcommand (`detect --no-git -s` → `dir <path>`), release URL pattern, and checksum filename. Internal renames: - GitleaksScanner → BetterleaksScanner (node + python) - BinaryManager.get/is/verify/find/download_gitleaks → *_betterleaks - GITLEAKS_VERSION → BETTERLEAKS_VERSION - update-gitleaks command → update-betterleaks (with deprecated alias) User-facing back-compat: - `--with-gitleaks` accepted as alias of `--with-betterleaks` - `--engine gitleaks` accepted as alias of `--engine betterleaks` - `agent update-gitleaks` accepted as hidden alias Dropped the parenthetical in the `rafter secrets` help description so the "Secrets only" phrase no longer wraps mid-line at typer's default width. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Update README, CLAUDE.md, SKILL.md, llms.txt, shared-docs/CLI_SPEC.md, recipes/*, and the bundled python skill/agent resources to refer to Betterleaks (the gitleaks successor) as the canonical name. Each occurrence of the legacy flag/value/subcommand explicitly notes the back-compat alias so existing docs/scripts/agents that still say `gitleaks` keep working. Historical docs under docs/ (audits, code reviews, proposals, research) are left as-is — they describe state at a point in time. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ning) Functional fixes (4 reviewers reported): - Node BetterleaksScanner now falls back to PATH-installed binary (Homebrew users were silently demoted to regex; Python already did this). - agent verify/status soft-degrade when only legacy gitleaks is present — emit "run rafter agent update-betterleaks" hint instead of hard-failing. - Update remaining user-facing surfaces: action.yml, .pre-commit-hooks.yaml, Dockerfile (was `--with-gitleaks 2>/dev/null || true`), python/README.md, .github/copilot-instructions.md. - Python scanner now logs warnings on JSON/timeout errors (was silent FN). Supply-chain hardening: - Pin SHA256 hashes for the bundled BETTERLEAKS_VERSION (1.1.2) in source. Default install no longer trusts the release-page checksums.txt to authenticate itself; --version <other> still falls back to the upstream file (TOFU at install time). - Reject symlink/hardlink/device tar entries on extract — without this, a malicious release could ship `betterleaks` as a symlink to e.g. ~/.ssh/authorized_keys, and the subsequent chmod +x would mode-flip the target. Same defense for zip extraction (Unix mode bits in external_attr). Post-extract lstat confirms the result is a regular file. - Refuse non-https redirects (Node) and non-https final URLs (Python). - Validate `--version` against /^[A-Za-z0-9._-]+$/ to neutralize URL injection attempts like `1.1.2/../evil`. - Add `--` separator before user-supplied paths in betterleaks invocations so a path beginning with `-` isn't parsed as a flag. Aligns Node scanFile timeout with scanDirectory (60s, matches Python). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Parity: - Python BetterleaksScanner.getSeverity now inspects Tags (key+secret => critical, api => high, generic => medium) — matches Node behavior; same rule could classify differently across runtimes before this. - Tighten Node BetterleaksResult interface: git-history-only fields (Commit/Author/Email/Date/Message/Fingerprint) are absent on `dir` mode output, so mark them optional rather than lying about the contract. - Python User-Agent now uses rafter-cli __version__ instead of accidentally reusing BETTERLEAKS_VERSION. Tests for the legacy alias surface (no coverage before this — would have silently regressed on the next refactor): - node: --engine gitleaks (scan), --with-gitleaks (init), legacy gitleaks detection in agent status with the upgrade hint. - python: --engine gitleaks (secrets), --with-gitleaks (agent init). - python: tag-based severity (critical/high/medium) parametrized. - python: new test_binary_manager.py covers --version validation (URL-injection guards), pinned-hash table completeness for the bundled BETTERLEAKS_VERSION, and non-https refusal in _download_file. - node: matching --version validation tests in binary-manager.test.ts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The migration kept --with-gitleaks, --engine gitleaks, and the update-gitleaks subcommand as silent aliases. Per product direction, move off gitleaks entirely. Removed (now hard-error): - `rafter agent init --with-gitleaks` → "unknown option" - `rafter agent scan --engine gitleaks` → "Invalid engine" - `rafter agent baseline create --engine gitleaks` → same - `rafter agent update-gitleaks` (hidden alias) → "unknown command" - `gitleaks` value in mcp `scan_secrets` enum → schema rejection Kept (read-only legacy detection): - `rafter agent verify` and `rafter agent status` continue to probe for ~/.rafter/bin/gitleaks and gitleaks on PATH so users with leftover installs see "legacy gitleaks at X; run: rafter agent update-betterleaks" instead of a confusing "not found". This is the soft-degrade that prevents a verify hard-fail regression on upgrade. Docs scrubbed of "legacy alias accepted" copy across README, CLAUDE.md, llms.txt, shared-docs/CLI_SPEC.md, recipes/gemini-cli.md, and the bundled python skill cli-reference. Tests flipped from "alias is accepted" to "alias is rejected". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Audit run by 2 codex agents and 2 claude agents on the betterleaks-migration branch (post-alias-removal). Fixes: Bundled Node skill resources (P0, both reviewers flagged): The npm-bundled docs at node/resources/** still said "Gitleaks" and documented the removed `update-gitleaks` subcommand and `--with-gitleaks` flag — a clean install would land docs that recommend commands the CLI now hard-rejects. Mirrored the rename across: - node/resources/skills/rafter/SKILL.md - node/resources/skills/rafter/docs/cli-reference.md - node/resources/agents/rafter.md - node/resources/rafter-security-skill.md - node/.claude/skills/rafter/docs/cli-reference.md (dev-side mirror) - fixtures/vulnerable-repo/README.md (`--engine gitleaks` example) Python silent-failure bug (P1, codex-functional flagged): `_run_scan()` ignored the betterleaks subprocess return code, so any non-zero exit other than 1 (panic, OOM, malformed args) returned [] and the scan looked clean. Mirrored Node's contract: 0=clean, 1=findings, anything else => RuntimeError with stderr tail. Catches OSError on the subprocess call too. Windows .exe extension parity (P2, codex-functional flagged): Several legacy-detection / status / init paths hardcoded the binary name without `.exe`, so a managed `~/.rafter/bin/betterleaks.exe` (or leftover `gitleaks.exe`) was missed on Windows. - python/rafter_cli/commands/agent.py: agent init, _check_betterleaks, status output all now select binary name by sys.platform. - node/src/commands/agent/status.ts: same — derive `.exe` once and reuse. Test cleanup (P2, codex-functional flagged): node/tests/mcp-server.test.ts handler mock kept the `engineRaw === "gitleaks" ? "betterleaks" : engineRaw` normalization that production no longer has — removed so the test reflects current behavior. Copy polish (both reviewers flagged): - recipes/pre-commit.md sentence "21+ patterns via Betterleaks" wrongly attributed all 21 patterns to betterleaks. Now: "21+ built-in credential patterns plus optional Betterleaks integration". - shared-docs/SHOW_HN_DRAFT.md, drafts/show-hn/post.md, drafts/show-hn/faq.md marketing drafts updated to mention Betterleaks (kept gitleaks references where they're historical comparisons or the original FAQ question). CHANGELOG entry: Added an [Unreleased] entry for the full migration: scanner change, breaking removal of legacy aliases, soft-degrade detection, supply-chain hardening, and the alias-removal-as-breaking note. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Security hardening (Claude security reviewer flagged 3 real issues; Codex security review confirmed the layered hardening is otherwise solid): - Node download: add MAX_REDIRECTS=10 (was unbounded recursion on 30x loops), MAX_BYTES=200MB body cap with Content-Length precheck + per-chunk enforcement (was a 50GB-mirror DoS), and REQUEST_TIMEOUT_MS=60s socket timeout (was slow-loris hang). All in node/src/utils/binary-manager.ts downloadFile. - Python download: matching MAX_BYTES=200MB body cap. Python already had urllib's internal redirect cap and timeout=60. Simplicity (Claude simplicity reviewer): - Extract findLegacyGitleaks() onto BinaryManager (Node) and find_legacy_gitleaks() onto BinaryManager (Python). Was duplicated 3x per runtime (verify + status + agent init); now one canonical implementation each side, used everywhere. Drops the duplicated Windows .exe extension handling and the homedir traversal. - Inline _update_betterleaks_impl() back into update_betterleaks() — the wrapper existed for the (now-removed) update-gitleaks alias and was only called once. Functional correctness (Codex functional reviewer ran the full test suite, blew context before completing the manual matrix): - Confirmed: 73/73 node + 164/164 python tests pass. - Confirmed live: aliases hard-error correctly (--with-gitleaks, --engine gitleaks, update-gitleaks), legacy detection emits the upgrade hint identically across both runtimes, real download still works end-to-end after the hardening additions. Items deferred (low value or trade-off): - Re-verify on-disk binary hash before each scan (TOFU drift) — local malware threat outside our model. - Race on parallel agent init writing to same archive path — local DoS only, no security boundary. - Switch Node verify_betterleaks_verbose from execAsync (shell quoted) to execFile (argv) — fragile but trusted inputs only. - Drop post-extract lstat from Python tarball extract — kept for parity with Node where node-tar's filter typing is loose. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a publish-clawhub job to .github/workflows/publish.yml that publishes the rafter-security SKILL.md to https://clawhub.ai (the OpenClaw skill registry) after every prod-branch release. OpenClaw users can then install rafter via `clawhub skill install rafter-security` as an alternative to `rafter agent init --with-openclaw`. Workflow design: - Runs after publish-node so we have the canonical version output. - Stages node/resources/rafter-security-skill.md as /tmp/rafter-skill/rafter-security/SKILL.md (clawhub expects a directory containing SKILL.md). - Authenticates via CLAWHUB_TOKEN env var (stateless — no `clawhub login` persisting to disk). - Runs `clawhub skill publish ... --version <package-version> --owner raftersecurity`. Idempotent: same version+fingerprint → no-op; same version, changed content → fail loudly. - Verifies the publish landed via `clawhub skill show rafter-security`. Guards: - Skips when CLAWHUB_TOKEN secret isn't configured (forks). - Gated on `github.repository == 'Raftersecurity/rafter-cli'` so pushes to forks of prod don't unexpectedly try to publish. Version-sync enforcement (validate-release.yml): - Adds a pre-deploy check that `version:` in both Node and Python copies of rafter-security-skill.md matches the package version. Without this, a release could silently ship a stale ClawHub version if the maintainer forgot to bump the SKILL.md frontmatter. Recipe + release notes: - recipes/openclaw.md now documents both install paths (rafter CLI and `clawhub skill install`) with a note about the `requires.bins: [rafter]` gate (the rafter CLI must still be on PATH regardless of which install path users take). - The GitHub-release notes block in publish.yml now includes the `clawhub skill install rafter-security` install line. Pre-flight notes for first run: - The CLAWHUB_TOKEN secret needs to be configured on the repo before the next prod push. Until then the job logs a skip message; no release fails. - The `raftersecurity` ClawHub org handle must exist (or `--owner` needs adjusting). First run will surface either issue clearly. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Betterleaks migration
…y') (#96) The actual handle on clawhub.ai is `rafter`. Without this, the first real publish would have failed with an "owner not found" error. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Both pre-commit examples in README.md (lines 246 and 433) pinned rafter-cli at v0.7.1. Repo is at v0.7.9 since the rf-zfhj GitHub Action fix shipped. Stale pins make new adopters fall back to old behavior (missing the GA fix, the OpenClaw ClawHub-shape fix, the audit-log hash-chain hardening, etc.). Bumped both to v0.7.9 to match the latest published tag. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
… changes (#98) Closes the rf-v85b P0-1 review concern: `rafter agent init --all` edits up to 8 tools' global config files plus downloads betterleaks. Adds a `--dry-run` flag that prints every file path the command would create, modify, or download — without making any changes. Node + Python both ship a `printDryRunPlan` / `_print_dry_run_plan` helper. After detection + opt-in resolution (so the plan respects --all, --with-*, --local, and presence detection), the helper iterates the resolved want_* booleans and prints: - Always: ~/.rafter/config.json + bin/patterns dirs. - Per platform when enabled: header + WRITE lines with file path and short note (e.g. "PreToolUse + PostToolUse hooks merged", "rafter:start/end marker block", "shared with Codex"). - Betterleaks: DOWNLOAD line for the binary. - OpenClaw: WRITE for the new ClawHub-shaped SKILL.md, plus REMOVE for the legacy ~/.openclaw/skills/rafter-security.md (rf-zgwj migration). The plan is built from the same resolved booleans the install path uses, so listing matches what would actually run. The dry-run branch runs early in the action — before manager.initialize(), before any fs.write — so even ~/.rafter/config.json is NOT created in dry-run mode. Tests: - 3 Node tests in agent-commands.test.ts: writes no files, lists all 8 sections, betterleaks download line. Asserts ~/.rafter/config.json does NOT land. - 3 Python tests in TestDryRun: same coverage. All green. - Live smoke (Node + Python): --local --all --dry-run produces 39 WRITE lines, 0 DOWNLOADs, leaves cwd untouched. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
rafter ≥0.7.0 (since rf-0pch, 2026-04-27) wraps scan results as
{_note, scan_mode, triage_applied, results: [...]} instead of a bare
array. The composite action's count query [.[].matches[]] errored on
the new shape and silently fell through to "0", causing the Test
Composite Action 'detect secrets in fixture' job to fail with
"expected findings > 0, got 0" — even though the scanner correctly
detected the AKIA fixture.
Use a type-aware query that handles both the wrapped object (current)
and the bare array (older pinned versions).
Verified against both shapes locally; CI run #25598398886 log shows
the scanner already emits the correct match.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Surfaces the bead asked for were mostly already migrated. This pass mops up: - .pre-commit-hooks.yaml: 3 hook entries (rafter-scan, -node, -python) - fixtures/vulnerable-repo/README.md: demo commands - node/.claude/skills/* (stale dev copies): 9 SKILL.md / docs refs - node/src/commands/issues/from-scan.ts: --from-local help text - shared-docs/CLI_SPEC.md: baseline subcommand example Intentionally LEFT (alias plumbing, history, or test surfaces): tests exercising the alias, CHANGELOG/drafts/proposals, CLI_SPEC.md alias- documentation lines, internal source comments describing the alias mechanism, .github CI test that exercises the alias path. Subcommand was already hidden in Commander + Typer; alias still works. Discovered separate issue (filed as rc-mw0): node/.claude/skills/ has drifted ~2 weeks behind canonical node/resources/skills/. This pass only patches the 'scan local' lines — broader sync left to that bead. Also closes rc-hzf and dup rc-49q (Cut SessionStart hook): verified already done — no session-start command in Node or Python; init paths only install PreToolUse + PostToolUse. Remaining SessionStart code in components.ts / agent.py is migration cleanup that strips legacy entries from <=0.7.4 user installs and must stay. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Minor bump (not patch) because the betterleaks migration removed user-facing CLI surface — `--with-gitleaks`, `--engine gitleaks`, and `rafter agent update-gitleaks` now hard-error. On a 0.x line, removing documented CLI flags is the textbook MINOR trigger; burying it in a patch would mislead anyone scripting against those flags. Bumps: - node/package.json: 0.7.9 → 0.8.0 - python/pyproject.toml: 0.7.9 → 0.8.0 - node/resources/rafter-security-skill.md: 0.7.9 → 0.8.0 (ClawHub publish) - python/rafter_cli/resources/rafter-security-skill.md: 0.7.9 → 0.8.0 - recipes/openclaw.md example frontmatter: 0.7.9 → 0.8.0 CHANGELOG cleanup: - Move betterleaks bullets from [0.7.9] (where the PR #93 octopus merge parked them under main's `### Changed` heading) into the new [0.8.0] section. The published v0.7.9 (dc81574) does not contain betterleaks code; npm/PyPI 0.7.9 still ships gitleaks. - New [0.8.0] groups all unreleased bullets (rf-hrtd dry-run, ClawHub auto-publish) and adds three Fixed entries: rf-cfjc (action.yml jq parser, fixed in 941879f), #96 (ClawHub owner handle), rf-z6sv (#97, pre-commit rev pin bump). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tests/e2e-cli.test.ts asserted /secrets only/i (literal space) against the rafter secrets --help output. Commander wraps the description at terminal width (CI default 80 cols), splitting 'Secrets\nonly' across a newline. The literal-space regex didn't match; the test had been red on every push since the betterleaks merge (cbfe354). Allow any whitespace between the words. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…skills/ The dev copies at node/.claude/skills/ were ~2 weeks stale vs the shipped source-of-truth at node/resources/skills/. One commit (cdac956, 2026-04-26, v0.7.6 publish prep) rewrote SKILL.md framing and added rafter-skill-review in resources/ but never mirrored to .claude/. Today's purge (rc-dmp) patched the 'scan local' strings; this commit removes the duplicate tree entirely so the next 'forgot to mirror' can't happen. Root .gitignore already lists .claude/, so the deletion sticks naturally. Contributor workflow for dogfooding (documented in CONTRIBUTING.md): cd node && pnpm run build node dist/index.js agent init --with-claude-code --local This invokes our own published install path, populating node/.claude/skills/ from node/resources/skills/ on demand. Eats own dog food; no second tree to keep in sync. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…est suite Three fixes that unblock PR #99's red comprehensive-test-suite jobs and finish the rc-dmp 'scan local' → 'secrets' rename in test surfaces. 1. **test-comprehensive Verify build** (test-node + 6 cross-platform jobs): `node -e "import('./dist/index.js')..."` triggers `program.parse()` synchronously at module load. With no args, Commander 11 on Node 20 prints help and exits 1 before the `.then(() => 'Build OK')` runs. Pre-existing failure since 2026-04-26. Replaced with `node ./dist/index.js --version` — exercises the same build, exits 0 cleanly through a real Commander action. 2. **python test_suppresses_all_patterns_when_no_pattern_name**: test used `path_glob="node_modules/*"` and asserted it matches `"node_modules/pkg/index.js"`. The `*` glob doesn't cross `/` per standard glob semantics — the Node sister test in custom-patterns.test.ts uses `"node_modules/**"`. Implementation is correct; bumped the test to match Node. 3. **scan local → secrets in tests**: rc-dmp purged user-facing `scan local` references in product/docs but intentionally left tests exercising the alias. This pass migrates the scanner test calls to the canonical `secrets` command across: - node/tests/{e2e-cli,secret-scanning-e2e,cross-runtime-parity, formatter-commands,baseline}.test.ts - python/tests/{test_e2e_cli,test_agent_scan_history}.py The alias still works at runtime; coverage of the alias is consolidated into a dedicated back-compat block in each e2e file (`CLI e2e — \`scan local\` back-compat alias` / `TestScanLocalAliasBackCompat`) that pins three things: identical-output equivalence, routing, no deprecation warning. If `scan local` is ever formally removed, delete those blocks in the same commit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Raftersecurity
approved these changes
May 11, 2026
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second main→prod cut since #92 (v0.7.9). 16 commits land the betterleaks scanner migration (the reason for the minor bump on a 0.x line — removes the
--with-gitleaks/--engine gitleaks/update-gitleaksCLI surface), ClawHub auto-publish on every prod deploy, andrafter agent init --dry-run, plus three quality fixes.Headline changes since #92
detect --no-git -s→dir <path>), new release URL. Supply-chain hardening: pinned SHA256 hashes, symlink/hardlink/device entries rejected, https-only downloads,--versionregex-validated,--arg separator. Breaking:--with-gitleaks,--engine gitleaks,rafter agent update-gitleaksnow hard-error. Soft landing:verifyandstatusdetect leftover~/.rafter/bin/gitleaksand tell users to runrafter agent update-betterleaks.rafter agent init --dry-run— print every file path the command would create/modify/download without making any changes. Closes the rf-v85b P0-1 review concern.publish.ymlrunsclawhub skill publishafter every prod-branch deploy.validate-release.ymlenforces SKILL.mdversion:matches package version. OpenClaw users can install rafter viaclawhub skill install rafter-security.874a86a)scan localreferences —.pre-commit-hooks.yaml, fixtures README, dev skill copies,--from-localhelp,CLI_SPEC.mdbaseline example. Alias plumbing and tests retained for backward compat.Quality fixes in this PR
941879f) — Test Composite Action'sdetect secrets in fixturejob had been failing on every push since the betterleaks merge. The composite action's jq count query ([.[].matches[]] | length) errored on the wrapped JSON shape introduced in v0.7.7 (rf-0pch:{_note, scan_mode, triage_applied, results: [...]}) and silently fell through to"0". Scanner detection itself was working — pure jq parser bug. Now type-aware soversion:pins to either old or new rafter still work.rafter, notraftersecurity. Without this the first real ClawHub publish would have failed.rev:pins bumped from v0.7.1 → v0.7.9 (now stale-chasing v0.8.0 too, but that's the next bump).292e0eb) —tests/e2e-cli.test.ts--help advertises secrets-only scopewas matching/secrets only/iwith a literal space; Commander wraps the description at 80 cols, splittingSecrets\nonly. Test had been red on every push since cbfe354. Fixed regex to/secrets\s+only/i.CHANGELOG cleanup (in
07e1bd8)The PR #93 octopus merge parked the betterleaks bullets under main's
### Changedheading inside the[0.7.9]section in CHANGELOG.md — but the published v0.7.9 (dc81574) was tagged BEFORE PR #93 merged, so npm@rafter-security/cli@0.7.9and PyPIrafter-cli==0.7.9actually ship gitleaks. The release-bump commit moves the betterleaks bullets back into the new[0.8.0]section where they belong.Out of scope (filed for follow-up)
rafter skill install --platform openclawstill uses the pre-rf-zgwj flat path. Two install entrypoints write to different locations on the same platform.node/.claude/skills/has drifted ~2 weeks behind canonicalnode/resources/skills/; rc-dmp only patched thescan locallines.==0.7.9~30s after publish. Worth apip install --retries 5 --timeout 60or a 60s sleep before the smoke step.Test plan
Validate Releasegreen on292e0eb(test fix) → 1735 passing / 1 skipped (was 1735 passing / 1 failing pre-rf-ax2p)validate-versionsconfirms Node 0.8.0 == Python 0.8.0Test Composite Actiongreen on941879fafter rf-cfjc fix — fixture-with-AKIA detection now correctly reportsfinding-count=1[0.8.0]entry exists;[0.7.9]cleaned up to reflect what actually shipped atdc81574publish.ymlfiresPost-merge actions (will be done by jack)
v0.8.0on the merge commitv1to that tag (git push origin :v1; git tag v1 v0.8.0; git push origin v1)publish.ymlpush to npm (@rafter-security/cli@0.8.0) and PyPI (rafter-cli==0.8.0)🤖 Generated with Claude Code