Repository navigation
feat(config): project configuration with a committed .worktree.yml (#181) - #186
Conversation
… Schema (#181) Phase 1 of #181. Adds yaml 2.9.1 and zod 4.6.7, the v1 project-config schema in src/lib/project-config.ts with its generated docs/public/schema/v1.json and a drift test, and the loader: reads .worktree.yml from origin/HEAD (personal defaultSourceBranch as fallback, WORKTREE_PROJECT_CONFIG override), refuses secret keys and malformed files with exit 2 naming file, ref, key path and line, and warns on unknown and personal keys. Adds git helpers to resolve origin/HEAD and read a blob at a ref, and an untrimmed option on runCapturing so cited line numbers match the committed file. No consumer reads it yet. Gate 1: pnpm check, pnpm typecheck, pnpm --filter docs typecheck, pnpm build, pnpm test (1493 passed), pnpm docs:test (69 passed), test markers clean, all exit 0. Gate 2: reviewer subagent, FAIL then PASS after one loopback (unresolvable ref was silent, prototype-named keys bypassed validation, trimmed blob shifted line numbers).
…181) Phase 2 of #181. Team keys (defaultSourceBranch, the three branchPrefix keys, jira.host, clickup.workspaceId) now resolve flag, then git config, then the project file, then the built-in default, and verifyConfig accepts a key the file supplies. branch fetches once, before it reads the project file, so a bad file stops it with exit 2 before anything is created; a file-supplied jira.host is used without a prompt and a personal one overrides it. Adds the Project Configuration docs page and points Recommended Team Baseline, the Jira guide and the branch and config pages at it. Gate 1: pnpm check, pnpm typecheck, pnpm --filter docs typecheck (24 routes), pnpm build, pnpm test (1538 passed), pnpm docs:test (69 passed), test markers clean, all exit 0. Gate 2: reviewer subagent, FAIL then PASS WITH NOTES after one loopback (two pages still described jira.host as git-config only).
) Phase 3 of #181. Adds a pure branch-type rules engine (src/lib/branch-type.ts): ordered rules, first match wins, every field in a `when` must match, a list of values matches any, ids compare exactly and names case-insensitively, `otherwise` sets the kind for an unmatched issue and without it there is no prefix. A rule naming a field the issue lacks warns, naming the field and the rule's position; a display name shared by two fields is an error naming both ids. With `jira.branchType` set, Jira asks for `expand=names` and the rules replace the built-in mapping; without it the request and the result are unchanged. Docs: the rules section of Project Configuration, and the Jira guide, branch and configuration pages say the built-in mapping applies only without rules. Gate 1: pnpm check, pnpm typecheck, pnpm --filter docs typecheck, pnpm build, pnpm test (1566 passed), pnpm docs:test (69 passed), test markers clean, all exit 0. Gate 2: reviewer subagent, FAIL then PASS WITH NOTES after one loopback (the new section had cut a subsection off from its parent heading).
…chName template (#181) Phase 4 of #181. GitHub (Type, Labels), Linear (Labels, with ids) and ClickUp (Tags) produce normalised fields and use the rules engine when the project file sets their `branchType`; without rules each provider's request and result are unchanged, and GitHub's built-in fallback stays no prefix (#118: `otherwise` opts in). A `branchName` template ({prefix}, {id}, {slug}; default `{prefix}{id}-{slug}`, today's name) applies to every issue-derived name, Jira's included, never to a positional one, and a name it renders still goes through isValidBranchName. Docs: the three provider guides, the template on Project Configuration, and the branch page, README and skill no longer state the name shape as fixed. Gate 1: pnpm check, pnpm typecheck, pnpm --filter docs typecheck, pnpm build, pnpm test (1619 passed), pnpm docs:test (69 passed), test markers clean, all exit 0. Gate 2: reviewer subagent, FAIL then PASS WITH NOTES after one loopback (the branch page and README stated the name shape as fixed; the Linear guide omitted label ids).
…te rules (#181) Phase 5 of #181. `branch --dry-run` with an issue flag fetches the issue, evaluates the project file's rules and prints the branch name, the rule that matched (its position and text, the built-in mapping, or none), and every non-empty field with its id, name and values, plus a paste-ready `when:` line per value. It creates, assigns, writes and asks nothing; under --json it prints one dry-run document in place of the branch document. Jira asks expand=names and Linear asks label ids on every dry run. Without an issue flag it is a usage error. Docs: the flag on the branch page and how to write rules with it on Project Configuration. Gate 1: pnpm check, pnpm typecheck, pnpm --filter docs typecheck, pnpm build, pnpm test (1651 passed), pnpm docs:test (69 passed), test markers clean, all exit 0. Gate 2: reviewer subagent, PASS WITH NOTES.
…181) Phase 6 of #181. The `copy` patterns in .worktree.yml join the env-file pathspecs as `:(glob)` entries under the same untracked-and-ignored selection, keeping the node_modules exclusion, so a gitignored file such as config/master.key is copied and a tracked one never is. `branch` and `checkout` both apply them, and copied files are listed in envFilesCopied. `checkout` now reads the project file, so a bad one stops it with exit 2 before a worktree exists. With no file or no `copy` key the ls-files call is unchanged. Docs: the env-files guide, Project Configuration, the branch page, README and the skill. Gate 1: pnpm check, pnpm typecheck, pnpm --filter docs typecheck, pnpm build, pnpm test (1667 passed), pnpm docs:test (69 passed), test markers clean, all exit 0. Gate 2: reviewer subagent, FAIL, FAIL, then PASS WITH NOTES after two loopbacks (the real-git copy test read the machine's global git config, then git's default excludes file).
…proval (#181) Phase 7 of #181. The project file's postCreate steps run in order in the new worktree, by argv with no shell, each bounded by the install timeout, stopping at the first failure. Before anything is created, branch checks a per-person approval: a SHA-256 over the trimmed steps as a JSON array, kept in local git config under an internal key outside CONFIG_NAMES. An unapproved or changed list is shown and asked about in a terminal (no exits 1 with nothing created) and fails closed non-interactively with exit 2 naming `worktree config --approve` and `--no-install`; the hash is never printed. A personal postCreate, --no-install and --dry-run need no approval. A step may not hold control or formatting characters, and file text quoted in loader warnings and errors is shown with visible escapes, so neither can redraw or hide the steps being approved. `installed` gains `steps` and `from`. Docs: approval on Project Configuration, the branch page's install section and JSON table, the config and configuration pages, README, the skill and context7.json. Gate 1: pnpm check, pnpm typecheck, pnpm --filter docs typecheck, pnpm build, pnpm test (1717 passed), pnpm docs:test (69 passed), test markers clean, all exit 0. Gate 2: reviewer subagent, FAIL, FAIL, then PASS WITH NOTES after two loopbacks (control characters in a step, then an unknown key's name in a warning, could make the approval prompt show something other than what runs; the branch page said a missing file-step program is skipped).
…and offer migration (#181) Phase 8 of #181. `worktree config` now knows the project file: the wizard asks nothing the file answers (including through branch's delegated --missing run) and says once which keys it sets; `config <key> <value>` for a file-set key shows both values and asks, or sets and warns non-interactively; `--list` marks each value `(.worktree.yml)` or `(overrides .worktree.yml: …)` in plain text and summarises the file-only keys. `worktree config --project` writes a commented .worktree.yml with the $schema line from the team keys in git config, never a personal or secret key, uncommitted, and refuses to overwrite. Interactive branch (after open) and config runs offer `--project` once per repository when there is no file, and offer to remove local keys the file covers with the same value once per file content; neither runs under --json, --yes, CI or without a TTY. Docs: config, configuration, project-configuration and getting-started pages, README and the skill. Gate 1: pnpm check, pnpm typecheck, pnpm --filter docs typecheck, pnpm build, pnpm test (1777 passed), pnpm docs:test (69 passed), test markers clean, all exit 0. Gate 2: reviewer subagent, FAIL then PASS WITH NOTES after one loopback (the wizard branch delegates to still asked for keys the file sets).
craig-o-curtis
left a comment
There was a problem hiding this comment.
Approving. This is a big change but it holds together well. I read the source diff closely: the loader, precedence, the rules engine, postCreate approval, copy, the branch/config/checkout changes and all four integrations. CI is green, so I didn't rerun the gates.
What I checked and found sound:
postCreatecan't run without approval.runInstallis called only frombranch, afterensurePostCreateApproved, and both read the same cached load.--no-installskips both. A personalpostCreateoverrides the file in both places, and a whitespace-only one can't slip through, becausegitGetConfigValuetrims. A non-interactive run fails closed, and the hash never shows up in a message.copypatterns can't reach outside the repo. Leading/, leading:and..are refused, every pattern gets:(glob), and the existing ignored-only, untracked-only selection still applies.- Text from the file is escaped before it's printed (
showVisibly), and steps with control or formatting characters are refused by the schema. - The published schema has a drift test against
docs/public/schema/v1.json, and--dry-runis well covered, including that it never asks for approval. - A broken file stays contained.
verifyConfigand the team-key reads exist only inbranch, so a bad file can't breaklist,removeorcleanup, andconfigdowngrades it to a warning so people can still work around it.
Non-blocking notes. Any of these can be a follow-up:
- An ambiguous rule field exits with the wrong code, and nothing tests it.
AmbiguousFieldError(src/lib/branch-type.ts:39) extends plainError, so a rule naming a field that two fields share (common with Jira custom fields such as two "Team" fields) exits1/failed. Every other bad-file case exits2/invalid_value. It also stops--dry-runbefore it lists fields, though the message does name the ids. Suggest extendingInvalidValueError/ProjectConfigErrorand adding a test, since no test or doc covers this path today. jira.hostsends credentials with no prompt. I see this is a deliberate choice (D17), and the docs section on it is good. Still, the threat model is uneven:postCreateneeds per-person approval, while a mergedjira.hostchange sends every teammate's Jira email and token to a new host with no prompt. Worth considering approving it once per value, the way steps are approved, or at least warning when the file's host differs from the last one used.- Approval is asked before the reopen check.
ensurePostCreateApprovedruns beforereopenOrRefuseTakenBranch(branch.ts:915vs:924). A non-interactiveworktree branch <name-that-already-has-a-worktree>in a repo with unapproved steps exits on missing approval, not with the reopen/refuse guidance, and an interactive run asks for approval it then doesn't use. - The
approvalRequiredcomment overstates what it protects. Its rationale ("a script that could read it from an error could store it") overclaims: the hash is SHA-256 over the public steps, so anything that can write local git config can compute it. The real gate is the TTY requirement. Fine to keep the hash out of messages; the comment could just say why. - FYI: every
branchrun now prunes. It now always runsgit fetch --pruneup front. Before, it ran a plaingit fetchinsidegitCreateWorktree, and only--sourcepruned. Probably harmless, maybe even better, but it's a behaviour change that the changeset doesn't mention.
The changesets look right to me: minor for the new CLI surface, patch for the deps and the skill.
… the reopen check (#181) Review of #186: AmbiguousFieldError now extends ProjectConfigError, so a rule naming a shared display name exits 2 with invalid_value like any bad file. The postCreate approval is asked after the taken-branch check, so a taken name gets the reopen guidance. Corrects the approval-hash comment and adds a note for the --prune every branch run now does.
|
Thanks for the close read. Taking the five notes in order. Points 1, 3, 4 and 5 are fixed in 882264d, and point 2 is filed as #187.
Gate 1 ( |
Closes #181
A committed
.worktree.yml, read fromorigin/HEAD, lets a project state its shared conventions once instead of in every clone. The plan and its finished ledger are in #181. It also absorbs #128 and #118.What landed
.worktree.ymlschema, loader and the published JSON Schema (docs/public/schema/v1.json)branchNametemplateworktree branch --dry-runto preview a branch name and the rules that produced itcopypatterns for new worktreespostCreatesteps, run only after per-person approvalworktree configshows where each value comes from, writes.worktree.ymlwith--project, and offers migrationEach phase passed both gates; the ledger in #181 carries every commit sha.
Release notes
Three notes in
.changeset/(CLI minor, skill patch, runtime dependencies patch). This is not released: it waits on the release pull request. The docs site deploys on merge becausedocs/**changed.Owner checks left for a person
None of these is a known defect.