Skip to content

feat(repository): switch a repository between shared and private visibility - #404

Merged
danielhanold merged 20 commits into
mainfrom
feat/switch-a-repository-between-shared-and-private-visibility
Oct 7, 2026
Merged

danielhanold merged 20 commits into
mainfrom
feat/switch-a-repository-between-shared-and-private-visibility

Conversation

@danielhanold

@danielhanold danielhanold commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

↩ Change 0533 — Switch a repository between shared and private visibility

Summary

Adds docket repository set-visibility <shared|private>, the one supported way to move an existing repository between shared and private visibility while keeping every record.

  • Preview by default; --yes applies, pinned to the previewed state. Re-running resumes from the first unfinished phase. Refuses while any run, gate drive, or worktree lock is live.
  • Publishes the identical metadata history under the other branch name (docket on origin, dckt on the local bare remote). No record is rewritten (ADR-0147).
  • Going private: creates or adopts the bare remote, folds config into .git/dckt/config.yml, renames the state folder, moves ignore entries to .git/info/exclude, moves the metadata worktree out of the clone. --delete-shared-branch deletes origin/docket only on an exact tip match; --remove-shared-files makes one local commit Remove docket configurations from repository.
  • Going shared: refuses unrelated or diverged docket history on origin, publishes, splits config, restores the shared layout, makes one local commit Add docket configurations to repository, keeps the bare remote as a backup.
  • Commit subjects never contain shared/public/private/visibility, carry no body or trailers, and are never pushed by the switch.
  • repository check reports, and repository repair re-stamps, absolute spec/plan/results backlinks as relative links. The switch preview warns about them (never refuses).

Review

Whole-branch review: deep tier. 12 findings, disposition below.

# Severity Finding State
1 blocker Going shared with cursor opted in wedged: the add commit tried to commit the ignored Cursor rule fixed b1dd937
2 important Going private left the generated Cursor rule behind as an untracked file fixed 0d28de1
3 important Re-running set-visibility private refused (or pulled teammate writes) once origin/docket moved on fixed d09a03e
4 important Switching to the current mode was not align-only fixed f86fde0
5 important New test shard budget of 150s; split into three sibling shards fixed 75c0855
6 important Expected ADR not recorded fixed (ADR-0147 on the metadata branch)
7 minor Identity-keys stop reported applied fixed 936f36b
8 minor Preview commit file list was not the real one fixed 936f36b
9 minor Dirty-file precondition over-refused fixed 936f36b
10 minor Commit-path set was a hand-written third list of surface paths fixed 936f36b
11 minor Unreadable private layout read as "no store" fixed 936f36b
12 minor Round trip re-encoded a hand-formatted .docket.yml fixed 936f36b

Test plan

  • Full suite green at 936f36b.
  • New integration shards: tests/test_go_integration_app_repovisibility{,_private,_shared}.sh.
  • Human checks (repair of existing absolute backlinks after merge; one round trip on a scratch repository) are in the results file.

…or reuse

The integration-test helper that computed the expected private layout is
renamed to expectedPrivateLayout so the production privateLayoutOf can take
its name; no assertion changed.
… runs, dirty metadata, and unset repositories
…r every phase, private result, commit messages
…en going shared

Review finding (blocker): going shared with cursor in agent_harnesses wedged on
every run. sharedCommitPhase journaled every planned surface, including
.cursor/rules/docket-dispatch.mdc, which the managed .gitignore block written by
the earlier ignore phase ignores; CommitOwnPaths' `git add --intent-to-add` then
refused the ignored path, and the surviving journal made every re-run fail the
same way.

The phase now journals only surfaces git status reports as changed and drops
every other surface (unchanged, or ignored by the shared layout) from the
journal, so an ignored path never reaches `git add` and a journal left by an
earlier failed run self-heals. A new going-shared integration case with claude
and cursor opted in reproduces the wedge and proves the add commit carries
CLAUDE.md and AGENTS.md but not the ignored Cursor rule.
Review finding 2: going private disowned every working-tree surface, so the
private install never retired the generated, ignored Cursor dispatch rule,
and --remove-shared-files then left it untracked once the .gitignore block
was stripped. Disown only the surfaces committed at the primary checkout's
HEAD; the private install retires the rest.
…, not origin's docket tip

Review finding 3: once the state folder has moved to the private layout
and the dckt remote holds the dckt branch, the publish is behind the
repository. A later set-visibility private run no longer refuses when a
teammate advanced origin's docket branch past the private history, and
no longer fast-forwards those writes into the dckt branch.
--delete-shared-branch still keeps origin's branch on a tip mismatch.
…nless a switch is under way

Review finding 4: a never-switched shared repository switched to shared
planned the ignore, commit, and other going-shared phases, so it refused
over the user's own edits to AGENTS.md or init's unstaged .gitignore, or
made an unrequested add commit. Those phases now settle unless a switch is
under way (not yet shared, a private trace left, or the switch journal),
and a going-shared run opens the add journal before it retires any of the
private layout, so a run killed after its last private trace is gone still
resumes its commit. An empty journal of the other subject no longer blocks
a new switch.
…s budget (review finding 5)

The single repovisibility shard carried a 150s budget, nearly twice the
largest existing row. Split its tests into three sibling shards by
non-overlapping name prefixes — TestIntegrationRepoVisibilityPrivate,
TestIntegrationRepoVisibilityShared, and TestIntegrationRepoVisibilitySwitch
(preview, refusals, commit machinery, direction-neutral acceptance) — and
split the interrupted-phases proof by direction so each half lands with its
direction's shard. Budgets reseeded from measured serial runs (42s, 67s,
61s -> 50, 75, 70).
- 7: the identity-keys stop now returns an invalid-state needs-review
  result carrying the push remedy and the commit row, never applied
  (visibilityRun.stop carries the refusal; the phase seam still fires).
- 8: the preview's commit rows come from the real targets: the removal
  targets of HEAD going private (deletions as delete), and going shared
  the config, the .gitignore block, and the planned non-ignored surfaces
  whose result differs from HEAD.
- 9: the dirty-path refusal checks only the paths the pending commit
  writes or commits (planVisibilityCommit's guard).
- 10: the surface path list derives from reposeed.SurfacePaths, guarded
  by a test that enumerates Plan; visibilityCommitPaths and the init
  clean-preflight set build on it.
- 11: an unlocatable private store (origin URL or data home unreadable)
  is an unknown probe, not "init first"; an origin URL that names no
  store still means no store.
- 12: going shared keeps an on-disk .docket.yml byte for byte when it
  declares the split's leaves (reposetup.SameConfigLeaves).
@danielhanold
danielhanold merged commit 1ace733 into main Oct 7, 2026
7 checks passed
@danielhanold
danielhanold deleted the feat/switch-a-repository-between-shared-and-private-visibility branch October 7, 2026 15:05
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.

1 participant