Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .docket.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@ reclaim:
auto: false


# ═══ leak_check — keeping docket out of a private repository's shared surfaces ═══════════

leak_check:
# match_word — true (default): in a private repository, a push or pull request whose commits,
# added lines, added paths, or PR text contain the bare word "docket" is refused. false: let
# the bare word through, for a codebase where it is an ordinary word (court or shipping
# dockets); markers, trailers, `.docket` paths, `dckt`, and change ids are still refused.
# A shared repository never runs the check.
# scope: any layer
match_word: true


# ═══ learnings — the learnings ledger ═════════════════════════════════════════════════════

learnings:
Expand Down
2 changes: 2 additions & 0 deletions agents/docket-integration-repair.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ You make the test suite pass after `docket-finalize-change` has rebased a featur

Charter: own every red-test outcome regardless of cause — genuine base drift, or a bad conflict resolution you can see in the Git state. Apply systematic-debugging discipline: find the root cause, write a MINIMAL fix, never game or weaken the tests, then commit the fix on the feature branch. You are bounded to the repair-attempt budget your dispatch payload names (`repair_max_attempts`, resolved from `finalize.repair_max_attempts`; treat an unstated budget as 6, the built-in default). The initial attempt counts as attempt 1; stop as soon as the suite is green. You do **not** re-run the gate for record, publish, merge, or transition any metadata — the controller re-gates your repaired head through the gate driver (`--owner finalize`), records the exact-head evidence through the `evidence.record` operation, and drives publish and merge. Never run the `finalize.merge`/`publish`/`closeout` operations, `gh pr merge`, or any metadata write yourself.

Your dispatch payload names the repository's visibility (`Visibility: <shared|private>`). In a private repository, every commit message, code line, comment, and test name you write follows the convention's private-repository writing rule — never a change id or docket wording; when the fix cannot be made without breaking it, return `disposition: stuck` naming the conflict, never commit the violation.

Return your work as a structured repair report — an authored hint the controller re-verifies against the real branch delta before acting — naming:

- the **claimed commits** you added on the feature branch (their SHAs);
Expand Down
4 changes: 3 additions & 1 deletion agents/docket-plan-writer.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ name: docket-plan-writer
description: Internal plan-writing agent for docket-implement-next Step 4 — invokes `superpowers:writing-plans` in a pinned context, writes the plan artifact with its backlink on the metadata branch through change.attach-plan, and returns only the plan's repo-relative path. Not invoked directly by a human.
worktree-scope: feature
---
You are docket's plan writer. You are dispatched by `docket-implement-next` (Step 4) with everything already resolved — you parse no configuration and perform no discovery. Your dispatch payload names: the change id, title, exact record revision, and synchronized change-file path; the synchronized spec path; the feature-worktree path and its pre-dispatch HEAD; and whether learnings are enabled and, when enabled, the learnings index path.
You are docket's plan writer. You are dispatched by `docket-implement-next` (Step 4) with everything already resolved — you parse no configuration and perform no discovery. Your dispatch payload names: the change id, title, exact record revision, and synchronized change-file path; the synchronized spec path; the feature-worktree path and its pre-dispatch HEAD; the repository's visibility (`Visibility: <shared|private>`); and whether learnings are enabled and, when enabled, the learnings index path.

In a private repository, the commit messages, code, comments, and test names the plan prescribes follow the private-repository writing rule; the plan itself is metadata and may name the change.

You own exactly one durable artifact: the plan file on the metadata branch, written through exactly one docket operation, `change.attach-plan`. You write nothing in either worktree; you perform no other Docket metadata mutation, board update, or status transition.

Expand Down
2 changes: 2 additions & 0 deletions agents/docket-rebase-resolver.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ You resolve the conflicts of an owned rebase of a feature branch onto its integr

Charter: for each conflicted hunk in the returned feature workspace, reconcile it with merge-intent judgment — work out what the base changed and what the PR intends, then keep one side or synthesize both. Edit ONLY the conflicted regions of the reported paths. You do **not** drive the rebase and you do **not** run the suite: the `finalize.rebase-continue`/`rebase-abort` operations own every Git rebase mechanic — staging, `--continue`, `--abort` — and the integration-repair agent owns making the suite pass after the rebase lands. Never run `git rebase`, `git add`, `git commit`, `git checkout`, `git reset`, or the test command yourself. Paths under `internal/assets/embedded/` (the generated bundle: `manifest.json` and everything under `tree/`) are controller-owned derived outputs — never hand-merge them and never list them in `conflicted_paths`; resolve and report the authored inputs only, and the finalize controller regenerates the bundle itself.

Your dispatch payload names the repository's visibility (`Visibility: <shared|private>`). In a private repository, every line you write into a resolved hunk follows the convention's private-repository writing rule — never a change id or docket wording; when a hunk cannot be reconciled without breaking it, return `disposition: stuck` naming it.

Return your work as a structured **ResolverReport** JSON document (the controller feeds it to the `finalize.rebase-continue` or `rebase-abort` operation via `--input`; it is an authored hint that Go re-verifies against the live unmerged set before staging anything). Emit exactly these fields:

- `change_id` (integer) — the change id from your dispatch.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Implement private visibility for PRs, commits, and shipped files: design

Change 0532 — Implement private visibility for PRs, commits, and shipped files

Change #532, groomed interactively on 2026-10-05. Third in the private-visibility series (#529–#533). Depends on #530, which moves build artifacts off the feature branch, and #531, which adds private mode itself.

## Summary

In a private repository, other people see what docket ships: feature branches, PRs, commits, the spec copy, and code. None of them use docket. After #530 and #531, the remaining traces are PR-description blocks and lines, finalize's PR comments, and model habits (change ids in commit subjects and PR titles, docket vocabulary in shipped text). Repository-level instruction files are #535's concern.

This change removes those traces and adds a **blocking** leak check as the backstop, in private repositories only. The goal is that nothing reaching `origin` through a private repository's feature branch or PR contains a docket or `dckt` fingerprint.

Two related pieces were split out of this change: #535 loads a private repository's agent instructions without repository files, and #534 installs the `dckt` alias it relies on.

## Evidence gathered at grooming

- **PR body.**
- `pr.publish` (`assemblePRBody`, `internal/app/pr_publish.go`) always inserts the backlink block rendered by `render.BacklinkContent`. #530 removes the evidence block.
- `skills/docket-implement-next/references/edge-paths.md` adds a `↩ Change <padded-id> — <title>` line and a best-effort `#<issue>` reference.
- The coordinator writes PR titles freely; an observed habit is a `(0507)` suffix.
- **PR comments.** `finalize.block` posts a comment carrying the docket attempt marker (`finalizeBlockedCommentMarker` in `internal/app/finalize_block.go`, through `githubcli.Client.EnsureComment`).
- **Commit text.**
- `skills/docket-build-task/SKILL.md` ("The commit") sets no message format.
- The fix-pass reference (`skills/docket-implement-next/references/fix-pass.md`) produces fix commits.
- Observed `docs(plan): change 0507 …` subjects are inherited habit, not a template, and change ids leak into commit subjects through plan text.
- **Spec copy.** #530 ships the spec as the feature branch's first commit. Specs habitually carry `Change #N, groomed …`, `.docket/` paths, ADR numbers, and backlog references.
- **Feature-branch pushes** go through `workspace.Service.PublishHead` (`internal/workspace/publish.go`). PR create and edit go through `pr.publish`. Finalize's force-push after a rebase goes through `finalize.publish`.

## Decisions (settled with the human)

1. In private repositories PR descriptions are plain authored prose: no backlink block, no evidence block, no change line, no `#issue` reference.
2. A finalize block in a private repository is recorded on the change record (`## Finalize blocked`) only. No PR comment is posted.
3. Writing rules apply to everything that ships: no docket or `dckt` vocabulary and no change ids.
4. The leak check **blocks**, in private repositories only. This is a deliberate exception to docket's report-only default for new checks: a pushed leak is irreversible and outward-facing, and the check cannot affect shared repositories.
5. The check also matches `dckt`. Matching the bare word "docket" can be switched off for codebases where it is an ordinary domain word (court or shipping dockets).
6. A `docket` or `dckt` branch appearing on `origin` in a private repository is reported, never blocked.
7. Sparse documentation: `.docket.example.yml`, command help, and skills only.
## Design

### 1. PR text and comments

- `pr.publish` in a private repository submits the authored body verbatim, inserting no docket-owned block. `finalize.publish` and `evidence.recertify` make no PR-body writes in private repositories; after #530 the evidence lives in the record anyway.
- The implement-next PR-authoring instructions omit the `↩ Change` line and the `#issue` reference in private repositories. The title carries no change id.
- `finalize.block` in a private repository records `## Finalize blocked` on the change and skips `EnsureComment`. Comment idempotency keyed on the marker is unaffected in shared repositories.
- Retargeting stacked children's PR bases carries no text, so it is unchanged.

### 2. Writing rules

One rule, stated once in docket-convention and referenced from each caller:

> In a private repository, nothing that ships through the feature branch or the PR may contain the words "docket" or "dckt", docket marker comments, `Docket-` trailers, `.docket`/`dckt` paths, change ids or change references ("change 0612", "#0612", "(0612)"), docket ADR numbers, or references to the backlog or board. The metadata branch's own files (plan, results, records) are exempt and must not be copied from.

The rule applies to:

- **Grooming in private repositories:** `docket-new-change`, `docket-groom-next`, `docket-auto-groom`. The spec ships, so the spec body follows the rule, and the `Change #N, groomed …` line is omitted. #530's spec-copy operation omits its `Change NNNN — title` line in private repositories.
- **Build-task and fix workers:** commit messages, code, comments, and test names.
- **The implement-next coordinator:** PR title and description.

The repository's mode reaches each worker through the payload or context it already receives. Shared repositories are unaffected.

### 3. The leak check

- **Where:** before any feature-branch push (`workspace.publish` / `PublishHead`, and `finalize.publish`'s post-rebase push) and before any PR create or edit (`pr.publish`). Private repositories only.
- **What it scans:**
- every commit in `<effective base>..<head>`: subject, body, and trailers;
- every added line in that range's diff, and added file paths;
- the PR title and description.
- The spec copy is an added file, so it is covered.
- **Patterns** (case-insensitive; the plan finalizes exact expressions with tests for each):
- the word `dckt`;
- the word `docket`, unless `leak_check.match_word: false`;
- `docket:` and `dckt:` marker prefixes;
- `Docket-` trailer keys;
- `.docket` and `.git/dckt` path fragments;
- change references to ids that exist in the backlog ("change 0612", zero-padded `#0612` and `(0612)`). The zero-padded form avoids matching years.
- **Outcome.** A typed refusal (new finding code `leak-detected`) lists each hit as {commit, file, line, matched text, rule}. Nothing is pushed and no PR is created or edited. The coordinator ends the run `halted` through `change.halt`, carrying the hit list. Nothing retries automatically. A human, or a later fix run, rewords the commit or line and re-runs.
- **Config.** `leak_check.match_word` (bool, default `true`) is an ordinary key in all layers, consulted only in private repositories and documented in `.docket.example.yml`.
- **Shared repositories never invoke the scanner.** A test pins that a shared-mode publish performs no scan.

### 4. Metadata on the shared remote

In a private repository, `repository check` and `repository prepare` probe `origin` for a `docket` or `dckt` branch and report a warning finding (`metadata-on-shared-remote`). The remedy says to delete the branch, or, once #533 lands, to use `set-visibility private --delete-shared-branch`. The finding is report-only.

### 5. Documentation

- `leak_check.match_word` is documented in `.docket.example.yml` and its twin.
- Skills carry the writing rule and the leak-check halt handling.
- No `docs/` pages.

## Acceptance criteria

1. **Plain PR.** A private-repository run produces a PR whose description is exactly the authored prose, whose title has no change id, and whose commits contain no fingerprint.
2. **Each seeded leak is refused before anything reaches `origin`:**
- a commit subject with "(0612)";
- an added line containing `.docket/`;
- a spec copy containing "docket";
- a PR title with "change 0612";
- a commit containing `dckt`.

The run halts with the hit list.
3. **`leak_check.match_word: false`** lets the bare word "docket" through, and still blocks markers, trailers, paths, `dckt`, and change ids.
4. **Year and ADR safety.** A year such as "(2026)" and a host repository's own ADR references are not matched.
5. **Shared mode unchanged.** Shared-mode PR bodies and finalize comments are unchanged, and no scan runs (pinned).
6. **Finalize block.** In a private repository it posts no PR comment and records `## Finalize blocked`.
7. **Shared-remote finding.** A `docket` or `dckt` branch on `origin` of a private repository produces the warning finding, with no refusal.
8. **Mutation.** Removing the scanner call before a push turns a test red.
## ADRs expected

The leak check blocks: a deliberate, scoped exception to report-only checks, justified because a pushed leak is irreversible and outward-facing.

## Out of scope

- The metadata branch's content, which stays private and may use docket vocabulary freely.
- Rewriting already-pushed history.
- Shared-mode PR bodies and comments.
- Scanning for references to the host repository's own ADRs or tickets.
- Agent instructions for private repositories (#535) and the `dckt` alias (#534).
1 change: 1 addition & 0 deletions internal/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ func effectiveLines(eff *config.Effective) []effectiveLine {
leafLine("learnings.enabled", strconv.FormatBool(eff.Learnings.Enabled.Value), eff.Learnings.Enabled.Provenance),
leafLine("reclaim.lease_ttl", strconv.Itoa(eff.Reclaim.LeaseTTL.Value), eff.Reclaim.LeaseTTL.Provenance),
leafLine("reclaim.auto", strconv.FormatBool(eff.Reclaim.Auto.Value), eff.Reclaim.Auto.Provenance),
leafLine("leak_check.match_word", strconv.FormatBool(eff.LeakCheck.MatchWord.Value), eff.LeakCheck.MatchWord.Provenance),
leafLine("review.min_fix_severity", textValue(eff.Review.MinFixSeverity.Value), eff.Review.MinFixSeverity.Provenance),
leafLine("review.max_fix_tasks", strconv.Itoa(eff.Review.MaxFixTasks.Value), eff.Review.MaxFixTasks.Provenance),
leafLine("gate_observation_budget", strconv.Itoa(eff.GateObservation.Value), eff.GateObservation.Provenance),
Expand Down
Loading
Loading