Skip to content

feat(config): experimental worktrunk.config.* git-config project-config source - #3609

Draft
indexzero wants to merge 4 commits into
max-sixty:mainfrom
indexzero:feat/3454/claude-fable
Draft

feat(config): experimental worktrunk.config.* git-config project-config source#3609
indexzero wants to merge 4 commits into
max-sixty:mainfrom
indexzero:feat/3454/claude-fable

Conversation

@indexzero

Copy link
Copy Markdown

What this adds

An experimental second source for project configuration: worktrunk.config.* keys in git config.

$ git config worktrunk.config.post-start 'pnpm install'
$ git config worktrunk.config.list.url 'http://localhost:3000'

One mechanical rule governs the mapping. Strip worktrunk.config.; the remainder is the exact key path from .config/wt.toml. worktrunk.config.post-start is the top-level post-start hook. worktrunk.config.list.url is [list] url. No renamed keys, no parallel schema.

Why

.git/config is never committed, never pushed, never cloned. Configuration written there is private to one machine by construction — and shared across every linked worktree, because the local scope lives in the common git dir. That combination is what issue 3454 asks for, and what several earlier issues (650, 1077, 2818) kept circling: private, repo-local config without a new file format and without repo identities leaking into public dotfiles.

How selection works

All-or-nothing. When any worktrunk.config.* key exists, those keys are the complete project config; .config/wt.toml is ignored. There is no key-level merging between sources.

Two consequences follow, both deliberate:

  • Supersession is loud. When a project config file would otherwise load, a once-per-invocation warning names it, and a hint gives the diagnostic command: git config --show-scope --show-origin --get-regexp '^worktrunk\.config\.'. wt config show and wt hook show name the active source.
  • Parse failures are fatal. An invalid value errors with the schema's own message. Falling back to the file would silently change which hooks run.

What stays the same

  • Approval. Hooks and aliases from git config pass through the same approval gate as file-based project config. Git config can carry remotely-authored content through include/includeIf (a cloned dotfiles repo, for instance), so source alone is not a trust signal.
  • Precedence belongs to git. Keys are read from the already-cached git config --list -z map — zero new subprocesses. Local overrides global; conditional includes work; worktrunk never sees scopes.
  • Existing interfaces. .config/wt.toml, [projects."…"] overrides, and WORKTRUNK_PROJECT_CONFIG_PATH are untouched.

Limits in this first version

Values are strings, one per key. Fields needing other TOML types (step.copy-ignored.exclude, an array) are not expressible and error clearly when attempted. Keys must be lowercase — git's key model makes middle segments case-sensitive, and the schema's own spelling is lowercase. No write commands: git config already is the management interface.

Testing

Unit tests cover the key mapping: nesting, value/table conflicts, empty segments, loud failure on non-string fields. Integration tests cover source selection, the supersession warning firing exactly when a file is superseded, local-over-global precedence, include.path resolution, approval gating, and wt config show in human and JSON forms. The config docs page gained a "Private project config in git config" section, and all generated doc mirrors and help snapshots are refreshed.

…ig source

Any key under the worktrunk.config. prefix in git config now supplies
project configuration (max-sixty#3454). Strip the prefix; the remainder is the
exact .config/wt.toml key path. Selection is all-or-nothing: when any
key exists, the merged effective git config is the complete project
config, the file is ignored, and a warning names the superseded file.
Values are strings only; schema violations fail loudly with no fallback
to the file. Commands from this source pass through the same approval
gate as file-based project config.

Git owns precedence: keys come from the cached `git config --list -z`
map, so system/global/local scopes and includes resolve before worktrunk
sees them, at zero additional subprocess cost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@indexzero
indexzero force-pushed the feat/3454/claude-fable branch from 21b2343 to b2be351 Compare July 26, 2026 09:08
Findings from adversarial reviews of the two competing implementations,
applied per the reviewed merge-fix plan:

- Alias discovery routes through ProjectConfig::load, so git-config
  aliases appear in listings exactly when dispatch would run them.
- A WORKTRUNK_PROJECT_CONFIG_PATH override — any value, including the
  empty kill switch — disables the git source, enforced in the sole
  accessor so every consumer inherits the deferral by construction.
- wt config show propagates a failed git-config read instead of
  rendering the file as active while execution errors.
- wt --diagnose names the git-config source, its key names, and the
  superseded file, but never the values: diagnose output is routinely
  pasted into public bug reports and this source exists for private
  configuration.
- The supersession-warning latch is peeked before label resolution and
  set only on emit, so a no-file call cannot suppress a later
  born-superseded warning from wt config create --project.
- Provenance docstring softened: include/includeIf can carry
  remotely-sourced content, which is why the approval gate applies
  unchanged.
- Docs now state what the git source skips (file migration and its
  deprecation messaging — deprecated spellings may still deserialize),
  that per-worktree git config is unsupported, and describe the
  diagnostic command as listing matching keys rather than consumed
  values.
- wt config create --project warns when the new file is born
  superseded; wt step prune suppresses the branch-hooks annotation
  under the git source (git config is branch-independent); project
  config resolves from a bare root via the git source, with
  wt config approvals framing its answers from it.
- New tests: behavioral approval decline (hook must not run),
  includeIf resolution, both override-precedence forms, alias-listing
  consistency, born-superseded create, bare-root approvals, and
  diagnose value redaction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@indexzero
indexzero force-pushed the feat/3454/claude-fable branch from b2be351 to c2a50d0 Compare July 26, 2026 09:09

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing as a draft — flagging anything that looks worth a quick fix. Mark ready for a full review.

Nicely structured — the mechanical key mapping, the all-or-nothing selection, and the consistent "name the source, keep the approval gate" treatment across every consumer (config show, hook show, diagnostic, approvals, create, alias) all hang together. The diagnostic path deliberately omitting values (only naming keys + the superseded file) is a good call given diagnose output gets pasted into public bug reports.

One functional gap in the wt step prune skip-hint, noted inline: the git_source_active → invoking_project_bytes = None change doesn't actually suppress the (different hooks on branch) annotation the way its comment claims — it inverts it for the supersession case. Details on the line.

Comment thread src/commands/step/prune.rs
- wt config show snapshots for the two error renderings: a git-source
  value that violates the schema (Invalid config + gutter) and colliding
  key paths (conflict error), matching the file branch's treatment.
- wt step prune --dry-run under the git source exercises the suppressed
  branch-hooks baseline.
- A bare root without keys exercises the no-project-config guard and its
  path-free error.
- A repeat-call unit test exercises the supersession-warning latch peek.
- The diagnose Err arm collapses into the file fallback (best-effort
  surface; the file section already reports read failures) and the
  infallible-in-practice TOML render error map shrinks to a passthrough,
  removing two unreachable specialized handlers instead of pretending to
  test them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just the new commit since the last review — mostly test coverage, plus one behavior change in the diagnose path worth a second look (inline).

Comment thread src/diagnostic.rs Outdated
…r arm

Two review findings on the follow-up commits:

- wt step prune suppressed the "(different hooks on branch)" annotation
  by nulling the comparison baseline, which inverts it: a candidate with
  a committed .config/wt.toml compares Some(_) != None and flags the
  exact case where git-config hooks are identical across branches. The
  suppression now lives on the differs computation itself, pinned by a
  test with a committed file and an unapproved git-config pre-remove.

- The diagnose Err arm returns: a failed bulk-config read is reported
  as such instead of falling through and printing .config/wt.toml as
  the active source — the file section's "(read failed)" covers a
  different read, so the fall-through misattributed, in the report
  meant to diagnose exactly that failure. Its uncovered line is an
  honest, effectively untestable gap, and is documented as one.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants