Skip to content

fix(source-control): resolve babysit worktree pruner main-checkout without hard ghq dependency - #741

Merged
kyle-sexton merged 2 commits into
mainfrom
fix/438-babysit-pruner-ghq-dependency
Jul 20, 2026
Merged

fix(source-control): resolve babysit worktree pruner main-checkout without hard ghq dependency#741
kyle-sexton merged 2 commits into
mainfrom
fix/438-babysit-pruner-ghq-dependency

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Summary

The babysit-prs engine-backed worktree pruner (prune_babysit_worktrees.py) hard-depended on ghq — the plugin author's personal repo-layout tool — to resolve a linked worktree's main checkout. A consumer without ghq hit a hard RuntimeError ("install ghq or set ghq.root") on every --apply removal, and even the ghq.root fallback baked in ghq's <root>/github.com/owner/repo layout assumption. ghq is an undeclared prerequisite: it appears nowhere in the README's "runs on git, gh, jq" self-contained claim, violating the repo's docs/PLUGIN-PHILOSOPHY.md "declare every required CLI at the point of use and in the README" and graceful-degrade rules. This bit engine-backed (Python) worker/autopilot runs.

Fix

repo_path() now resolves the main checkout natively from the linked worktree's own gitdir/commondir pointer via git -C <worktree> rev-parse --git-common-dir:

  • standard clone → the shared git directory is the main working tree's .git, so its parent is the checkout git worktree commands run from;
  • bare-clone hub → no working tree, so the git directory itself is where those commands run.

ghq is removed from ALLOWED_EXECUTABLES entirely, not retained as an optional path. Design call (per the issue's open question): native git-metadata resolution reads the actual checkout the worktree belongs to, which is strictly more correct than ghq's guess from a configured root plus an assumed layout — so a presence-gated ghq enhancement would add config surface with no correctness benefit. No README change is needed: ghq appeared only in this script (never in README/docs), so removing it makes the existing "runs on git, gh, jq" claim true rather than requiring a new prerequisite declaration.

Both resolution branches (standard and bare) were verified empirically against real linked worktrees before implementation.

Verification

Full stdlib-unittest suite + ruff, run from plugins/source-control/skills/babysit-prs/scripts:

python -m unittest discover -s tests -p 'test_*.py'
Ran 325 tests in 5.108s
OK

ruff check . tests
All checks passed!

New hermetic regression test tests/test_prune_babysit_worktrees.py (no gh, no ghq on PATH — any stray ghq call now raises "not in the caller's allowlist"):

  • RepoPathResolvesFromGitMetadata.test_linked_worktree_resolves_its_main_checkoutrepo_path resolves a linked worktree to its real main checkout from git metadata alone.
  • RemoveWorktreeIsHermetic.test_removes_a_clean_worktree_under_root_without_ghq — end-to-end removal via git worktree remove with only git present.
  • RemoveWorktreeIsHermetic.test_refuses_to_remove_a_worktree_outside_the_babysit_root — the path-containment safety guard still fires for a real worktree outside the declared root.

markdownlint-cli2 on the CHANGELOG: 0 errors. plugin.json valid, version bumped 0.15.10.15.2 (patch; internal bug fix, no config surface change).

Closes #438

Related

Co-authored-by: Claude Opus 4.8 noreply@anthropic.com

…thout hard ghq dependency

The engine-backed pruner resolved a linked worktree's main checkout by shelling
out to `ghq` (the plugin author's personal repo-layout tool) and raised a hard
RuntimeError for any consumer without it — an undeclared prerequisite absent from
the README's "runs on git, gh, jq" contract and a violation of the repo's
plugin-agnosticism rules.

Resolve the main checkout natively from the worktree's own gitdir/commondir
pointer via `git rev-parse --git-common-dir`, and drop `ghq` from the executable
allowlist entirely. Native resolution is strictly more correct than ghq's guess
from a configured root plus an assumed <root>/github.com/owner/repo layout, so no
optional ghq path is retained. Adds a hermetic regression test exercising
resolution and removal against a real linked worktree with no ghq on PATH.

Closes #438

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@kyle-sexton kyle-sexton added the automated Opened by automation. label Jul 20, 2026
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 37s —— View job


PR Review: fix(source-control): resolve babysit worktree pruner main-checkout without hard ghq dependency

Tasks

  • Gather context and understand the request
  • Read changed files
  • Review core fix in prune_babysit_worktrees.py
  • Review new test file
  • Review plugin.json and CHANGELOG.md
  • Post comprehensive review feedback

Summary

This is a clean, correct fix that eliminates a hard ghq dependency in the worktree pruner. The new repo_path() implementation is more correct than the original — it reads the actual checkout the worktree belongs to from git's own metadata rather than guessing from a configured root plus an assumed directory layout. The security-sensitive path-containment guard is preserved and tested. No blocking findings.


Core Logic: repo_path()

The old implementation shelled out to ghq list -p <owner>/<repo>, with a fallback to git config --get ghq.root + a hardcoded github.com/<owner>/<repo> suffix. Both paths were unreliable for consumers without ghq or using a non-github.com host. The new implementation:

proc = run(["git", "-C", str(worktree_path), "rev-parse", "--git-common-dir"])
common = Path(proc.stdout.strip())
if not common.is_absolute():
    common = worktree_path / common
common = common.resolve()
return common.parent if common.name == ".git" else common

repo_path — line 60

This is the canonical approach. --git-common-dir was added in git 2.5 (2015) and is stable. The relative-path guard (if not common.is_absolute()) is necessary — git returns a relative path when the worktree is inside the main checkout, and the absolute resolution via common.resolve() correctly dereferences symlinks. The .git-name heuristic correctly handles both cases:

  • Standard clone: --git-common-dir/path/to/repo/.gitcommon.name == ".git" → returns /path/to/repo
  • Bare-clone hub: --git-common-dir/path/to/repo.gitcommon.name != ".git" → returns /path/to/repo.git

Narrowing ALLOWED_EXECUTABLES to ("git", "gh") is the right call — it reduces attack surface and matches the README's stated contract ("Self-contained. Everything runs on git, gh (authenticated), jq").


Findings

🟡 Nit — Bare-clone branch has no automated test

The PR description says both resolution branches were "verified empirically against real linked worktrees before implementation," but the test suite only covers the standard-clone path (where common.name == ".git"). The else-branch (return common) for bare-clone hubs is exercised only manually.

test_prune_babysit_worktrees.py — line 54

A bare-clone test would follow the same pattern as test_linked_worktree_resolves_its_main_checkout, replacing git init with git init --bare. Not a merge blocker for a patch fix, but the else-branch is a real code path in any hub-style setup. Add a bare-clone test →

🟡 Nit — .git-name heuristic misfires on a bare repo literally named .git

prune_babysit_worktrees.py — line 75

return common.parent if common.name == ".git" else common uses the directory name to distinguish a standard clone's .git directory from a bare repo. If a bare repository is somehow named .git (non-standard but legal), the function would return the parent directory rather than the bare repo itself, causing git -C <main_repo> worktree remove to fail. Pathologically unlikely in practice. A more robust check would be to test whether common is a bare repo via git -C <common> rev-parse --is-bare-repository, but that's additional latency for a marginal edge case; documenting the assumption would also be sufficient.


What's working well

  • Tests are hermetic. No gh, no ghq on PATH during tests — stray calls raise immediately due to the allowlist, which is exactly the right regression guard.
  • Safety guard is preserved and tested. The path-containment check in remove_worktree (if allowed_root not in resolved.parents) is unchanged, and test_refuses_to_remove_a_worktree_outside_the_babysit_root confirms it fires on a real worktree.
  • Signature improvement. repo_path(worktree_path: Path) is strictly better than repo_path(owner, repo) — resolution is now derived from the worktree's actual git metadata rather than from name-based guessing.
  • Version and CHANGELOG are correct. Patch bump 0.15.1 → 0.15.2, clear entry, markdownlint clean.
  • PLUGIN-PHILOSOPHY alignment. Removing ghq makes the code match the README's "runs on git, gh, jq" contract rather than violating the "declare every required CLI" rule.

@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 21s —— View job


PR Review: fix(source-control): resolve babysit worktree pruner main-checkout without hard ghq dependency

Tasks

  • Gather context and understand the request
  • Read changed files
  • Review core fix in prune_babysit_worktrees.py
  • Review new test file
  • Review plugin.json and CHANGELOG.md
  • Post comprehensive review feedback

Summary

This is a clean, correct fix. The new repo_path() implementation is strictly more correct than the old one — it reads the actual checkout a worktree belongs to from git's own metadata rather than guessing from an external tool's configured root. The security-sensitive path-containment guard is preserved and tested. No blocking findings.


Core Logic: repo_path()

The old implementation (repo_path(owner, repo)) shelled out to ghq list -p <owner>/<repo>, with a fallback to git config --get ghq.root + a hardcoded github.com/<owner>/<repo> suffix, then raised RuntimeError if both failed. Any consumer without ghq hit that hard error on every --apply run.

The new implementation:

proc = run(["git", "-C", str(worktree_path), "rev-parse", "--git-common-dir"])
common = Path(proc.stdout.strip())
if not common.is_absolute():
    common = worktree_path / common
common = common.resolve()
return common.parent if common.name == ".git" else common

repo_path — line 60

--git-common-dir has been stable since git 2.5 (2015). The relative-path guard is necessary — git returns a relative path when the worktree is inside the main checkout — and common.resolve() correctly dereferences symlinks in both cases. The .git-name heuristic handles both standard and bare layouts:

  • Standard clone: --git-common-dir/path/to/repo/.gitcommon.name == ".git" → returns /path/to/repo
  • Bare-clone hub: --git-common-dir/path/to/repo.gitcommon.name != ".git" → returns /path/to/repo.git

Narrowing ALLOWED_EXECUTABLES from ("git", "gh", "ghq") to ("git", "gh") is correct — it reduces attack surface and makes the code match the README's stated contract.


Findings

🟡 Nit — Bare-clone branch has no automated test

The test suite only exercises the standard-clone path (where common.name == ".git"). The else-branch (return common) for bare-clone hubs is verified empirically but not in CI.

test_prune_babysit_worktrees.py — line 54

A bare-clone test would follow the same pattern as test_linked_worktree_resolves_its_main_checkout, replacing git init with git init --bare and asserting resolved == bare_repo_dir (not its parent). Not a merge blocker for a patch fix, but the else-branch is a real code path in any hub-style setup. Add a bare-clone test →

🟡 Nit — .git-name heuristic misfires on a bare repo literally named .git

prune_babysit_worktrees.py — line 75

return common.parent if common.name == ".git" else common uses the directory name to distinguish a standard clone's .git directory from a bare repo. A bare repository named .git (non-standard but legal) would cause the function to return the parent directory rather than the bare repo itself, producing a wrong git -C path in remove_worktree. Pathologically unlikely in practice. A doc comment noting the assumption (standard bare repos are not named .git) would be sufficient; an is-bare-repository check would cost an extra subprocess call for a marginal edge case.


What's working well

  • Test hermeticity is the right regression guard. ALLOWED_EXECUTABLES blocks any stray ghq call during tests — the allowlist itself is the regression guard for source-control: babysit worktree pruner hard-depends on ghq (undeclared, author-personal repo-layout tool) #438, not just the test assertions.
  • Safety guard preserved and tested. The path-containment check in remove_worktree (if allowed_root not in resolved.parents) is unchanged, and test_refuses_to_remove_a_worktree_outside_the_babysit_root confirms it fires on a real worktree outside the declared root and that the worktree survives.
  • Signature improvement. repo_path(worktree_path: Path) vs repo_path(owner, repo): name-based resolution required knowing the layout convention; git-metadata resolution reads the actual structure — no convention assumption possible.
  • Version and CHANGELOG are correct. Patch bump 0.15.1 → 0.15.2, clear entry, markdownlint clean.
  • PLUGIN-PHILOSOPHY alignment. Removing ghq entirely (rather than optional-path gating it) makes the README's "runs on git, gh, jq" contract true without requiring a new prerequisite declaration.
  • Python version compatibility. from datetime import UTC (Python 3.11+) in the main module means ignore_cleanup_errors=True (Python 3.10+) in the test's TemporaryDirectory is safe — no hidden version floor raised by the tests.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Classification of the two nits from the CI review (babysit worker pass; both bot review runs are duplicate re-runs of the same findings):

# Finding Classification Evidence Reacted
1 Bare-clone else-branch (return common) has no automated test VALID — fixing Added test_bare_hub_worktree_resolves_to_the_bare_repo_itself to RepoPathResolvesFromGitMetadata: clones a --bare hub, adds a linked worktree, asserts repo_path(wt) == bare.resolve() (the bare dir itself, not its parent). All 4 tests pass locally. 👍
2 .git-name heuristic misfires on a bare repo literally named .git VALID (defer) Pathological — standard bare repos are <name>.git, never .git; git itself refuses git clone --bare foo .git-style layouts in normal use. The docstring already documents the standard-vs-bare distinction; an is-bare-repository probe would add a subprocess per removal for a case that cannot arise in the babysit worktree topology. No code change. 👍

Destructive-correctness re-verified independently: the removal target is worktree.path (explicitly containment-guarded by allowed_root in resolved.parents), while repo_path() only supplies the -C repo context — a misresolved main_repo makes git worktree remove fail loudly rather than prune a wrong directory, and run(..., check=True) raises on an unresolvable worktree rather than silently skipping. No path to stranding or wrong-directory pruning.

The `repo_path` else-branch (`return common` when the git-common-dir is not
named `.git`) resolves a bare-clone hub to the bare repo directory itself.
That path had no CI coverage — only the standard-clone branch did. Add a
test that clones a `--bare` hub, adds a linked worktree, and asserts
`repo_path` returns the bare repo directory (not its parent).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 6s —— View job


Security Review: fix(source-control): resolve babysit worktree pruner main-checkout without hard ghq dependency

Tasks

  • Gather context and understand the request
  • Get PR diff and read changed files
  • Analyze prune_babysit_worktrees.py for security issues
  • Analyze new test file for security issues
  • Review plugin.json and CHANGELOG.md changes
  • Post security review findings

Overall Assessment

This PR reduces the attack surface rather than expanding it. Removing ghq from ALLOWED_EXECUTABLES narrows the set of processes the pruner can launch. The old repo_path(owner, repo) passed user-derived strings (directory-name-extracted owner/repo components) directly into a ghq list -p subprocess argument, which is eliminated entirely. No CRITICAL or IMPORTANT findings.


SUGGESTION — main_repo from git output is used as a -C path without containment validation

Confidence: LOW

prune_babysit_worktrees.py lines 136–145

def remove_worktree(worktree: Worktree, root: Path) -> None:
    main_repo = repo_path(worktree.path)          # path from git stdout, unchecked
    if not main_repo.exists():
        raise RuntimeError(...)
    resolved = worktree.path.resolve()
    allowed_root = root.resolve()
    if allowed_root not in resolved.parents:       # checks worktree.path only
        raise RuntimeError(...)
    run(["git", "-C", str(main_repo), "worktree", "remove", str(worktree.path)])

repo_path returns the output of git rev-parse --git-common-dir, resolved to an absolute path. That path is then passed as the -C argument — the directory git runs in — without any check that it falls within an expected boundary. The containment guard checks worktree.path (the target being removed), not main_repo (the git context used to issue the command).

Practical scenario: An attacker with write access to the worktree root could create a directory matching the WORKTREE_RE naming convention, point its .git/commondir to an unrelated git repository outside the declared root, and cause git -C <external_repo> worktree remove <target> to run in that external repo's context. The actual deletion is still bounded by the containment check on worktree.path, so no files outside root are deleted. However, git config options such as core.fsmonitor that can trigger executable invocations are evaluated from the working repo context (main_repo), not from the worktree being removed.

Why confidence is LOW: (a) git worktree remove is unlikely to trigger config-driven code execution in the way that git clone or git checkout can; (b) write access to root is the required precondition, which already implies substantial access; (c) the main_repo.exists() guard fails closed rather than silently continuing when the path is absent.

A low-cost mitigation would be a doc comment noting that main_repo is trusted to be a valid git repo because it was read from the worktree's own gitdir pointer and not from external input. A stricter mitigation would verify that main_repo is either an ancestor of or otherwise related to root before using it as a -C target.


What changed and why it's safer

Area Before After
Executable allowlist ("git", "gh", "ghq") ("git", "gh") — surface reduced
repo_path arguments (owner, repo) — strings extracted from directory names, passed into ghq list -p owner/repo (worktree_path: Path) — no user-derived strings in subprocess args
Resolution strategy External tool with assumed layout Git's own metadata — authoritative, no layout assumption
Failure mode RuntimeError ("install ghq") — hard crash for all consumers run(check=True) raises on unexpected git state rather than silently misbehaving
Test hermeticity No regression guard ALLOWED_EXECUTABLES in test process; any ghq call raises immediately

The path-containment guard in remove_worktree (if allowed_root not in resolved.parents) is unchanged from before this PR and correctly blocks symlink attacks (.resolve() follows symlinks before the check).

No GitHub Actions workflows, permission configurations, secret handling, or supply-chain dependencies changed in this PR.


@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 35s —— View job


PR Review: fix(source-control): resolve babysit worktree pruner main-checkout without hard ghq dependency

Tasks

  • Gather context and understand the request
  • Read updated test file and diff
  • Review bare-clone test added in response to prior nit
  • Post comprehensive review feedback

Summary

The sync commit (1c08c8a) adds the bare-clone test that was the only unresolved nit from the prior two review passes. Both nits from those reviews are now closed. No new findings. This PR is clean.


New test: test_bare_hub_worktree_resolves_to_the_bare_repo_itself

test_prune_babysit_worktrees.py — line 73

The implementation is correct:

  • make_bare_hub clones --bare from a one-commit source repo into hub.git — a real on-disk bare repository, not a mock.
  • add_worktree(bare, ...) runs git -C hub.git worktree add, which correctly creates a linked worktree from a bare repo (no working tree in the hub itself).
  • prune.repo_path(wt) then runs git -C <wt> rev-parse --git-common-dir. For a bare-cloned hub, that returns the bare repo path (hub.git), whose .name is "hub.git" — not ".git" — so the else-branch (return common) fires.
  • The assertion resolved == bare.resolve() confirms the bare repo directory is returned rather than its parent — which is the exact correctness guarantee remove_worktree needs for git -C <main_repo> worktree remove to resolve correctly.

The comment on the test correctly documents why returning the parent would be wrong (git -C <parent> worktree remove would fail), making the regression guard self-explanatory.


Nit classification recap (from Kyle's comment)

# Finding Resolution
Bare-clone else-branch has no automated test ✅ Fixed — test_bare_hub_worktree_resolves_to_the_bare_repo_itself
.git-name heuristic misfires on a bare repo literally named .git ✅ Deferred — pathological case (standard bare repos are <name>.git); docstring documents the assumption; is-bare-repository probe would add subprocess latency for a case unreachable in the babysit topology

Both dispositions are correct.


No new findings

The PR is ready to merge.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Follow-up: bare-clone hub test committed in 1c08c8a9 (finding #1). Pushed; CI re-triggered.

@kyle-sexton
kyle-sexton merged commit a2e28fd into main Jul 20, 2026
21 checks passed
@kyle-sexton
kyle-sexton deleted the fix/438-babysit-pruner-ghq-dependency branch July 20, 2026 20:42
kyle-sexton added a commit that referenced this pull request Jul 20, 2026
#752)

## Summary

Gates the `🤖 Generated with [Claude Code]…` PR-body line behind config,
matching the seam `/commit` already has for the commit trailer
(`trailer_policy`).

## Fix

Resolves a new `pr_body_attribution` key across the same three
`source-control.md` config layers `config-resolution.md` documents:
absent → the default line (unchanged current behavior — no existing
consumer sees a change), `none` → omit the line, any other value → that
literal line. Chosen as a **sibling** key rather than reusing
`trailer_policy` so opting out of the commit trailer doesn't silently
strip the PR-body line for existing consumers who only wanted one or the
other gated.

`create.md` §2.4.1 resolves the value at the model level and splices it
in as literal text alongside the existing `CLOSES_LINE` pattern —
preserving the same shell-injection-safety property (parameter expansion
of `"${VAR}"` does not re-evaluate the value; a `$(...)`-bearing custom
attribution string stays inert). `setup`'s interview, config template,
and `check` action render the new key; `SKILL.md` and
`config-resolution.md` document it. New evals pin both the
default-present and `none` opt-out paths.

**Known same-plugin version collision (sanctioned, not an error):** PR
#741 (issue #438, `fix/438-babysit-pruner-ghq-dependency`) is still open
and also bumps `source-control` to `0.15.2` from the same `0.15.1` base.
Per this repo's same-plugin serialization convention, this PR is opened
as DRAFT + `do-not-merge` and held until #741 merges, at which point it
needs a rebase + re-bump to whatever version `main` is at by then.

## Verification

- `jq empty` on `evals.json` and `plugin.json`: both valid JSON.
- `bash scripts/validate-plugins.sh`: full marketplace + all plugin
manifests validate, including `source-control`.
- New evals added (`evals/evals.json`) covering the default-present and
`none` opt-out paths — model-graded, will run under this repo's eval
pipeline.
- No existing test file exists for `create.md` itself (it's a reference
doc with bash snippets, not an executable script) — verification here is
manifest/JSON validity plus the evals; CI's full gate suite
(`plugin-gate`, `skill-quality-gate`, `changelog-parity-gate`, etc.) is
the remaining check.

Closes #439

## Related

- #438 / PR #741 — the sibling same-plugin PR this one is serialized
behind (version collision, see above)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Aug 9, 2026
…ne 4)

Lane 4 of the pocock-skills-v12-sync plan: Q10 resolved by the lane
interview; the user locked PORT, hardened, as a new single-capability
plugin.

- New plugin `wizard` 0.1.0, one skill `generate` (/wizard:generate,
  leaf named via naming tournament; grammar-clean, no naming-exception
  entry). Model-invoked with upstream's non-trigger fence kept ("Don't
  invoke this for steps the agent can perform itself").
- Hardened template.sh (the gating outcome of a completed security
  review, all conditions shipped): mandatory human read-and-approve of
  the full STAGES block before chmod +x; https-only open_url with the
  URL printed before dispatch (closes a Windows UNC/NTLM leak via
  explorer.exe); /dev/tty fail-closed prompts retiring a verified
  multi-line-paste confirm bypass and pause's EOF fail-open; quoted
  0600 .env writes + gitignore pre-flight assert + trap-cleaned atomic
  temp; repo-resolved/confirmed --repo-explicit gh writes with stderr
  surfaced into SKIPPED and empty values refused; key-name validation
  in every helper; readline on non-secret asks (upstream #741 fixed
  where safe); set_var via --body-file - (stdin, never argv).
- Scoping honesty fix: step 1 reads .env.example/README/workflows fully
  but takes key NAMES only from a live .env, never values; the skill
  states the secrets-and-context property honestly.
- Fresh-context static trace delegated in the skill's verify step per
  the fresh-eyes rules; bash -n/shellcheck stay deterministic gates.
- Records: SSOT attribution row + open-evaluations update, map row 18,
  PLAN.md Q10/lane-4 closure, MIGRATION-PLAYBOOK ACCEPT record for the
  model-generated-executable surface (deliberately breaks the
  statusline-shim no-templating precedent, mitigations recorded).
- Marketplace entry + regenerated CATALOG and skill cheat sheet.
- Gates: bash -n + shellcheck clean; skill-quality check PASS (0 warn);
  portability gate clean (gh sites declared portability-ok); typos and
  markdownlint clean; claude plugin validate + --strict catalog pass;
  functional smoke of the library helpers; fresh-context verifier
  subagent returned 25/25 PASS (its two non-blocking findings fixed:
  explorer.exe spurious warning, gitignore check now pre-flight).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated Opened by automation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

source-control: babysit worktree pruner hard-depends on ghq (undeclared, author-personal repo-layout tool)

1 participant