@erclx/canon 5.50.0 → 5.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +4037 -0
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/auto-ship/references/fan-in.md +20 -6
  4. package/claude/skills/canon-cli/SKILL.md +9 -7
  5. package/claude/skills/ci-workflow/REQUIREMENT.md +3 -3
  6. package/claude/skills/ci-workflow/SKILL.md +26 -1
  7. package/claude/skills/ci-workflow/references/workflows.md +28 -0
  8. package/claude/skills/design-record/references/folder.md +1 -1
  9. package/claude/skills/design-taste/SKILL.md +2 -1
  10. package/claude/skills/git-followup/SKILL.md +1 -0
  11. package/claude/skills/git-pr/references/pr.md +2 -2
  12. package/claude/skills/plan-feature/references/batches.md +1 -1
  13. package/claude/skills/review-address/SKILL.md +1 -1
  14. package/claude/skills/review-address/references/post-review-findings.md +1 -1
  15. package/claude/skills/review-pr/references/post.md +2 -2
  16. package/claude/skills/target-check/SKILL.md +2 -0
  17. package/claude/skills/target-check/references/domains.md +8 -6
  18. package/claude/skills/target-setup/SKILL.md +1 -1
  19. package/claude/skills/target-update/REQUIREMENT.md +33 -0
  20. package/claude/skills/target-update/SKILL.md +136 -0
  21. package/docs/agents/index.md +1 -1
  22. package/docs/agents/migrate.md +3 -0
  23. package/docs/agents/targets.md +8 -1
  24. package/docs/target/migrations.md +3 -1
  25. package/docs/target/projects.md +1 -1
  26. package/docs/target/sync.md +1 -1
  27. package/docs/workflow/parallel-features.md +1 -1
  28. package/docs/workflow/skill-map.md +1 -0
  29. package/governance/detect.toml +1 -1
  30. package/governance/ledger/tooling/ci-workflow.md +6 -0
  31. package/governance/ledger/ui/design-reference.md +8 -13
  32. package/governance/rules/tooling/ci-workflow.md +4 -0
  33. package/governance/rules/ui/design-reference.md +3 -6
  34. package/package.json +2 -1
  35. package/scripts/ci/check-browsers.sh +13 -0
  36. package/scripts/ci/delete-previews.sh +34 -0
  37. package/scripts/ci/publish-check.sh +29 -0
  38. package/scripts/ci/refresh-frames-pr.sh +63 -0
  39. package/scripts/ci/report-baseline.sh +23 -0
  40. package/scripts/ci/stack-issues.sh +24 -0
  41. package/scripts/ci/verify-npm-token.sh +14 -0
  42. package/src/claude/cases/setup.ts +7 -0
  43. package/src/cli/help.ts +1 -0
  44. package/src/cli.ts +2 -0
  45. package/src/commands/changelog.ts +146 -0
  46. package/src/commands/hooks.ts +7 -5
  47. package/src/commands/labels.ts +9 -7
  48. package/src/commands/migrate.ts +77 -0
  49. package/src/commands/worktrees.ts +273 -0
  50. package/src/gov/detect.ts +28 -10
  51. package/src/hooks/post-merge.ts +45 -6
  52. package/src/migrate/status.ts +132 -0
  53. package/src/version/changelog.ts +156 -0
  54. package/src/worktrees/members.ts +540 -0
  55. package/tooling/base/configs/.husky/post-merge +7 -6
  56. package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +1 -1
  57. package/tooling/cloudflare/configs/.github/workflows/deploy.yml +10 -28
  58. package/tooling/cloudflare/configs/scripts/delete-previews.sh +36 -0
  59. package/tooling/cloudflare/reference.md +7 -4
  60. package/tooling/web/reference.md +5 -0
  61. package/tooling/web/seeds/canon/design/index.md +23 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
4
- "version": "5.50.0",
4
+ "version": "5.52.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -48,12 +48,26 @@ Continue Step 2 with the integrator plan's own `**Files to touch:**`, then the r
48
48
 
49
49
  ### What the pull request carries
50
50
 
51
- Say in the pull request body that each member is one merge commit and name the members in merge order. Once the pull request merges, delete each member's branch on origin, its worktree, and its local branch:
51
+ Say in the pull request body that each member is one merge commit, and name the members on one line in `## Summary`, backticked, in merge order:
52
52
 
53
- ```bash
54
- git push origin --delete <member branch>
55
- git worktree remove <member worktree>
56
- git branch -D <member branch>
53
+ ```markdown
54
+ Fan-in members: `feat/a`, `feat/b`, `feat/c`
57
55
  ```
58
56
 
59
- Read each member's worktree path off `git worktree list --porcelain` by its branch. The integrator holds the member list, so the deletion is its step rather than a sweep's. No member opens a pull request, so `canon worktrees reclaim` never reads a member's branch as merged and reports a leftover worktree as unmerged work on every run.
57
+ The line names branches rather than plan stems, since the branch is what gets deleted. Nothing outside it is read, so a branch named in prose elsewhere in the body is never a candidate.
58
+
59
+ ### After the merge
60
+
61
+ The post-merge hook runs `canon worktrees retire-members` for each merged pull request number, and the verb deletes each member's branch on origin, its worktree, and its local branch. No session runs a step.
62
+
63
+ No member opens a pull request, so neither head-branch deletion nor `canon worktrees reclaim` ever reads one as merged. The member line is what reaches them.
64
+
65
+ - A member half goes only when its tip is an ancestor of the integrator's merged head, so a commit added after the merge keeps that half
66
+ - A worktree a live session holds, or one with uncommitted work, stays with its local branch
67
+ - A line naming the trunk, the integrator's own branch, or a branch with an open pull request deletes nothing
68
+
69
+ Run the verb by hand for a merge the hook missed, reading the dry run first:
70
+
71
+ ```bash
72
+ canon worktrees retire-members --pull-request <number> --dry-run
73
+ ```
@@ -18,13 +18,15 @@ Consult to pick the `canon` verb for a job, and before running a verb that write
18
18
 
19
19
  ## Project setup
20
20
 
21
- | Job | Verb |
22
- | -------------------------------------------- | --------------- |
23
- | Bootstrap a project with toolkit domains | `canon init` |
24
- | Bring every installed domain current | `canon sync` |
25
- | Reinstall the CLI itself | `canon upgrade` |
26
- | Move a project off a retired name or layout | `canon migrate` |
27
- | List the projects the toolkit installed into | `canon targets` |
21
+ | Job | Verb |
22
+ | -------------------------------------------- | ---------------------- |
23
+ | Bootstrap a project with toolkit domains | `canon init` |
24
+ | Bring every installed domain current | `canon sync` |
25
+ | Reinstall the CLI itself | `canon upgrade` |
26
+ | Move a project off a retired name or layout | `canon migrate` |
27
+ | Read what a project still needs to migrate | `canon migrate status` |
28
+ | Read the releases since a target's install | `canon changelog` |
29
+ | List the projects the toolkit installed into | `canon targets` |
28
30
 
29
31
  ## Domains
30
32
 
@@ -9,15 +9,15 @@ description: Scope boundary for pipeline structure against job contents, and the
9
9
 
10
10
  Without this skill, a session chains jobs with `needs` to express reading order rather than a data dependency, so the pipeline costs the sum of its jobs. The same session splits a one-minute gate into parallel jobs, each repaying checkout, install, and toolchain setup, so the gate gets slower while reporting no earlier.
11
11
 
12
- The rest surface as flakes. An action pinned to a moving ref changes under the project, a cache keyed on a static string serves a stale toolchain after a bump, artifacts upload on every run and never expire, a broad default token grants writes nobody needed, and a workflow with no manual trigger reproduces only by pushing a commit.
12
+ The rest surface as flakes. A moving action ref changes under the project, a static cache key serves a stale toolchain, artifacts never expire, a broad default token grants unneeded writes, and a missing manual trigger leaves a run reproducible only by pushing a commit. A Linux-only pin also flags a supported macOS job as a departure.
13
13
 
14
14
  ## Must
15
15
 
16
16
  - A job is gated only on a data dependency or a cost that justifies the wait, and checks sharing setup stay in one job until a run log puts the gate past roughly two minutes
17
17
  - Every action is pinned to a tag that cannot cross a major version, and every cache is keyed on the version of what it caches
18
18
  - Every workflow carries a manual trigger beside its primary one, except one that commits back
19
- - The token defaults to read-only, with a write scope granted only on the job that needs it
20
- - Artifact upload is bounded to failures and expires
19
+ - The token defaults to read-only, with a write scope granted only on the job that needs it, and artifact upload is bounded to failures and expires
20
+ - A job runs on Linux unless the project states a platform for a layer that differs by platform
21
21
 
22
22
  ## Must not
23
23
 
@@ -20,7 +20,17 @@ Generate GitHub Actions workflow files for CI pipelines. Enforce parallel job ex
20
20
  - Include `workflow_dispatch` alongside the primary trigger, except on a commit-back workflow, where a manual run has no branch to commit onto and would pass while doing nothing.
21
21
  - Set `permissions: contents: read` at the top level, and grant a write scope only on the job that needs it.
22
22
  - Pin all actions to major version tags (`@v4`, never `@latest` or `@main`).
23
- - Use `runs-on: ubuntu-latest` for all jobs.
23
+ - Use `runs-on: ubuntu-latest` for every job, except one the runner platform rules below move to another platform.
24
+
25
+ ## Runner platform
26
+
27
+ Linux stays the default. A job leaves it only for a layer whose result differs by platform, and only on a platform the project states.
28
+
29
+ - Read the stated platforms from the project's requirements record or README. A platform stated in neither keeps every job on Linux.
30
+ - Keep lint, build, and browser jobs on Linux, since their result does not differ by platform. Name the layers that may leave it and never the steps they run.
31
+ - Name another runner by its `-latest` label, such as `macos-latest`, matching the `ubuntu-latest` convention above.
32
+ - Resolve a macOS `$TMPDIR` or `mktemp` path with `realpath` before comparing it with a tool's output, since the `/var` to `/private/var` symlink hides the target. Treat `$RUNNER_TEMP` alike.
33
+ - Expect a macOS job to cost more per minute than a Linux one. GitHub bills the two differently, so read the rate from its billing page rather than quoting a multiplier.
24
34
 
25
35
  ## Job naming
26
36
 
@@ -59,6 +69,14 @@ A workflow that pushes a regenerated file onto the pull request branch takes the
59
69
  - Set `HUSKY: 0` on the commit step when the runner lacks a tool a hook shells out to.
60
70
  - Fail a template's first step while a path list still holds its placeholder, so an unfinished install fails loudly rather than never firing.
61
71
 
72
+ ## Steps
73
+
74
+ - Keep an inline `run:` step to five lines and one guard that exits or writes an output. Move other branching, a loop, trap, or function to a script, which runs on a laptop and under shellcheck.
75
+ - Put the script at `scripts/<name>.sh`, or a `.ts` file for a Bun project, and call it after `actions/checkout`, since the runner holds no copy before that.
76
+ - Pass inputs as arguments or `env`, never spliced into the script text, so a branch name with a quote or `$(...)` in it cannot run.
77
+ - Keep `permissions` and secrets on the workflow step, with the script reading its token from `env`.
78
+ - Open the script with `set -e` at least, since the runner gives a `run:` step `bash -e` and a script called directly gets no flags.
79
+
62
80
  ## Template
63
81
 
64
82
  Load `${CLAUDE_SKILL_DIR}/references/workflows.md` for the base workflow template. Adapt it to the project's stack, test commands, and build output. Add or remove jobs as needed. The template already carries the folded shape, and the split beside it applies once the granularity rule above calls for it.
@@ -79,5 +97,12 @@ Before responding, also verify:
79
97
 
80
98
  - Artifacts upload on `if: failure()` only with `retention-days: 7`.
81
99
  - Job names use emoji + title format.
100
+ - Every job runs on `ubuntu-latest`, except one that targets a platform the project states and runs a layer that differs by platform.
82
101
  - Deploy, publish, and release jobs carry a placeholder step and a named handoff, never a guessed deploy command.
83
102
  - Bun projects use `oven-sh/setup-bun@v2` with `bun install --frozen-lockfile`.
103
+
104
+ ### Steps
105
+
106
+ Before responding, also verify:
107
+
108
+ - No `run:` step passes five lines or carries more than one exit-or-output guard, a loop, a trap, or a function. A step that does calls a script, with its inputs as arguments or `env`.
@@ -1,5 +1,13 @@
1
1
  # CI workflow template
2
2
 
3
+ ## Contents
4
+
5
+ - [Base template](#base-template)
6
+ - [Splitting the check job](#splitting-the-check-job)
7
+ - [Targeting another platform](#targeting-another-platform)
8
+
9
+ ## Base template
10
+
3
11
  Copy this base template and adapt it to the project's stack, test commands, and build output. It carries the shape a new pipeline starts in, with the checks that share setup in one job, per the granularity rule in the skill body. `e2e` and anything gated on it stay separate jobs at either size.
4
12
 
5
13
  ```yaml
@@ -99,3 +107,23 @@ jobs:
99
107
  name: build-output
100
108
  path: dist/
101
109
  ```
110
+
111
+ ## Targeting another platform
112
+
113
+ Add this job beside the Linux ones only when the project states macOS as a supported platform, per the runner platform rules in the skill body. It runs the unit layer, since that layer differs by platform, and the rest stay on `ubuntu-latest`.
114
+
115
+ ```yaml
116
+ jobs:
117
+ unit-macos:
118
+ name: '🧪 Unit Tests (macOS)'
119
+ runs-on: macos-latest
120
+ steps:
121
+ - uses: actions/checkout@v7
122
+ - uses: oven-sh/setup-bun@v2
123
+ with:
124
+ bun-version: latest
125
+ - run: bun install --frozen-lockfile
126
+ - name: Resolve the temp folder
127
+ run: echo "TMPDIR=$(realpath "$RUNNER_TEMP")" >> "$GITHUB_ENV"
128
+ - run: bun run test
129
+ ```
@@ -66,4 +66,4 @@ Each group holds accepted frames a build matches, light and dark.
66
66
 
67
67
  ## Lifetime
68
68
 
69
- Keep a reference while its surface exists. Redraw it only on an intentional design change, and remove its folder or files and its index row in the change that removes the surface.
69
+ The design-reference governance rule states how long a reference stays, when it is redrawn, and when it goes with its surface.
@@ -154,9 +154,10 @@ Run `${CLAUDE_SKILL_DIR}/references/preflight.md` against the output. It is the
154
154
 
155
155
  ### Other skills
156
156
 
157
- Four neighbors own the rest.
157
+ Five neighbors own the rest.
158
158
 
159
159
  - The candidate loop, the arm set, the render, and the pick: `pick-by-eye`
160
160
  - The shape of the project's design document and where its values live: `${CLAUDE_SKILL_DIR}/../design-record/references/design.md`
161
+ - The folder holding the accepted designs and how a reference lands in it: `${CLAUDE_SKILL_DIR}/../design-record/references/folder.md`
161
162
  - Reading source for roughness: `ux-audit`. Measuring what a running interface costs to paint: `ux-measure`
162
163
  - Prose on the surface being designed: the `write-human` skill
@@ -59,6 +59,7 @@ A missing tracking ref is not a guard, since an open pull request proves the bra
59
59
  - A fix commit answering a review changes what shipped exactly as much as an ordinary followup does, so the sync cannot wait on the invocation or the comment count below.
60
60
  - A body a person edited by hand between rounds gets no special handling: judge it against the tree the same way regardless of who wrote it last, since a hand-edit the fix commit has made stale is the exact drift this sync exists to close.
61
61
  - Carry a `UI pass` line marked `<!-- pr-ui-pass -->` into any rewritten body unchanged, since `canon pr tick` owns it and a dropped marker defeats the reset on a new head.
62
+ - Carry a `Fan-in members:` line into any rewritten body unchanged, since the post-merge hook reads it to delete the members and a dropped line deletes nothing and says nothing.
62
63
  8. Route on the invocation and the comment count for the reply alone.
63
64
  - When invoked with `reply-owned`, skip this step: the caller posts its own reply.
64
65
  - Otherwise, if the count is above zero, the followup addresses review feedback: write a one-line summary of the fix to `.canon/tmp/pr/followup/summary-<number>.md` in its own call, run `canon labels scan --body-file .canon/tmp/pr/followup/summary-<number>.md` against it, which applies the rule in `${CLAUDE_SKILL_DIR}/../git-pr/references/publish.md`, then post it with `gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@.canon/tmp/pr/followup/summary-<number>.md --silent`. A hook on `Bash` runs the same scan on that body file and denies the post on a hit, and it refuses an inline `-f body=` value, so the body goes through a file. The `pull_request` check the git-pr surface carries triggers on a push or an open rather than a plain edit, so neither this comment nor the title and body step 7 synced reaches it.
@@ -66,7 +66,7 @@ Past 5 bullets, group Key changes under bold labels, 5 bullets a group at most.
66
66
 
67
67
  ### Closing lines
68
68
 
69
- The closing lines end `## Summary`, one `Closes #NNN` per issue and no trailing period. A task shipped in slices carries them on its last slice only. `closing-issues.md` states how the list is decided, and `canon labels scan --closes` fails a body that disagrees with it.
69
+ The closing lines end `## Summary`, one `Closes #NNN` per issue and no trailing period, and a sliced task carries them on its last slice. `closing-issues.md` decides the list. A fan-in member line sits above them, per `${CLAUDE_SKILL_DIR}/../auto-ship/references/fan-in.md`.
70
70
 
71
71
  ### Context section
72
72
 
@@ -129,7 +129,7 @@ A live agent session is never the human a box waits on.
129
129
 
130
130
  ### Density
131
131
 
132
- `canon labels scan` reports each section's density under `density`, naming the section, the kind, and the line in the whole body. Its help lists every limit.
132
+ `canon labels scan` fails on a density finding, naming the section, the kind, and the line in the whole body under `density`. Its help lists every limit.
133
133
 
134
134
  - Every section but Testing holds bullets to 200 characters and 5 in a row, and paragraphs to 350 characters and 2 in a row
135
135
  - Any section reports a `subheading`, and Technical context reports a `mixed-form`
@@ -90,4 +90,4 @@ The integrator merges each member branch with `git merge --no-ff` in the order i
90
90
 
91
91
  - Say in the pull request body that each member is one merge commit, so a reviewer reads `git log --first-parent` one member at a time
92
92
  - Fix a semantic conflict a merge surfaces on the integrator's branch, since a member has no pull request to fix it on
93
- - Delete each member branch on origin with `git push origin --delete <branch>` once the pull request merges
93
+ - Name the members on the body's `Fan-in members:` line, which the post-merge hook reads to delete each member's branches and worktree, per `${CLAUDE_SKILL_DIR}/../auto-ship/references/fan-in.md`
@@ -124,7 +124,7 @@ canon labels scan --body-file .canon/tmp/pr/reply/reply-<number>.md --json
124
124
  The hook skips `.canon/tmp/`, and the `pull_request` check reads only a pull request's own title and body, so this scan is the only gate on the reply.
125
125
 
126
126
  - Fix any hit, and stop on a `density` entry too, shortening the line it names
127
- - Read the record rather than the exit, since density never sets the exit code
127
+ - Read the record rather than the exit, since a post's density never sets the exit code
128
128
 
129
129
  Then post and keep the `id` the POST answers with, since Step 7 edits this exact comment:
130
130
 
@@ -13,7 +13,7 @@ Write the body the way Step 6 writes a reply.
13
13
 
14
14
  - Load `write-human` for voice and word choice, and follow `${CLAUDE_SKILL_DIR}/../markdown-craft/references/markdown.md` for punctuation
15
15
  - Run the `${CLAUDE_SKILL_DIR}/../git-pr/references/publish.md` scan before posting, with `canon labels scan --body-file .canon/tmp/pr/reply/reply-<number>.md --json`
16
- - Stop on a `density` entry as on any hit, since density never sets the exit code
16
+ - Stop on a `density` entry as on any hit, since a post's density never sets the exit code
17
17
  - Open with `## Post-review findings` rather than `## Review response`, since nothing on the thread is being answered
18
18
  - Close the body with `🤖 Addressed by Claude Code` on its own line, matching the reply's footer
19
19
 
@@ -114,7 +114,7 @@ Run `canon labels scan --title "<title>" --body "<body>" --head <headRefName>` a
114
114
 
115
115
  - Name the token quoted from the title or body for a phase label, a board identifier, or a session link
116
116
  - Name the broken rule, being `structure`, a casing issue, or `length`, for a title-format hit
117
- - A heading in `sections.outside` or `sections.missing`, or a character in `bannedCharacters`, is a hit of the same weight, naming the heading or the character
117
+ - A heading in `sections.outside` or `sections.missing`, an entry in `density`, or a character in `bannedCharacters`, is a hit of the same weight, naming the heading, the line and kind, or the character
118
118
  - This reads the pull request under review, distinct from the comment this pass posts, which the scan later in this step covers
119
119
 
120
120
  ### Why the threshold ignores the grade
@@ -175,7 +175,7 @@ The marker is the read-time record those fields are not.
175
175
  Before posting, run `canon labels scan --body-file .canon/tmp/pr/review/body-<number>-<short-sha>.md`, which applies the rule in `${CLAUDE_SKILL_DIR}/../git-pr/references/publish.md`. Pass `--json` and fix the body and scan again on a finding.
176
176
 
177
177
  - A `density` entry is a finding here, naming a subject or nested bullet past 200 characters, a run past 5 plain bullets, or a paragraph past its limits, by its line in the whole body
178
- - Density never sets the exit code, so the hook below lets the post through on it and this step is the one that stops
178
+ - A post's density never sets the exit code, so the hook below lets the post through on it and this step is the one that stops
179
179
 
180
180
  - A finding phrased against an internal phase label is what the label half of the scan catches here
181
181
  - The call carries no `--title`, so the scan reads the text as a review
@@ -98,3 +98,5 @@ No overall verdict. The harness row is unchecked rather than passing.
98
98
  ```
99
99
 
100
100
  Keep the harness row `unchecked` on every run until a verb reads it. A domain left out of the table reads as a domain that passed, which is the one shape the row exists to prevent.
101
+
102
+ Close on one line naming `/canon:target-update` as the skill that applies the catch-up, when a domain reads behind and `target-update` did not invoke this check.
@@ -21,7 +21,7 @@ This domain is first because it conditions the other five. A target a release be
21
21
 
22
22
  ## 2. The `canon/` folder
23
23
 
24
- Read four fields off the same report, then two verbs:
24
+ Read four fields off the same report, then three verbs:
25
25
 
26
26
  - `unmigrated`: a domain still at the root layout with nothing under `.claude/`. The most urgent finding, because such a domain reports no drift of its own while being entirely behind.
27
27
  - `superseded`: a file a newer seed folder replaced. No command moves it, since the content is the project's own.
@@ -34,19 +34,21 @@ The fourth field and the two verbs cover what the toolkit stopped shipping and w
34
34
  - `reverse`: `unclaimed` names folders at roots the toolkit stopped shipping, each with an `attribution` of `dropped`, `unattributed`, or `project`.
35
35
  - `canon context audit --json`: required sections, entry length, citations, and index drift across the context folder.
36
36
  - `canon records validate <kind> --json`, once per record folder the target carries.
37
+ - `canon migrate status --json`: every migration in run order, each with a `state` of `pending`, `clean`, or `refused`. It exits 2 while any is pending.
37
38
 
38
39
  ### Verdicts
39
40
 
40
- Each of the three states takes all six reads together.
41
+ Each of the three states takes all seven reads together.
41
42
 
42
- - Current: `unmigrated`, `superseded`, and `unmovedSurfaces` all empty, `reverse.unclaimed` carrying nothing outside `project` attribution, and both verbs finding nothing.
43
- - Behind: any of those carrying an entry. Name the entry and its attribution, and for `unmovedSurfaces` each `rel` with its `movesTo`.
44
- - Unread: `historyUnavailable` set on `reverse`, a `reverse` or `unmovedSurfaces` key the report does not carry, or either verb refusing
43
+ - Current: `unmigrated`, `superseded`, and `unmovedSurfaces` all empty, `reverse.unclaimed` carrying nothing outside `project` attribution, and all three verbs finding nothing.
44
+ - Behind: any of those carrying an entry, or a `pending` migration. Name the entry and its attribution, and for `unmovedSurfaces` each `rel` with its `movesTo`.
45
+ - Unread: `historyUnavailable` set on `reverse`, a `reverse` or `unmovedSurfaces` key the report does not carry, or any verb refusing, a `refused` migration included
45
46
  - A missing key is a binary predating the field rather than a target with nothing to report, so name the installed version beside it
46
47
 
47
48
  Routes by finding.
48
49
 
49
- - `unmovedSurfaces` routes to `canon migrate surface-roots`, which only reports its plan until `--write` is passed. Name the verb with each entry's `rel` and `movesTo`, and run neither form.
50
+ - A `pending` migration routes to its entry's `verb`, which only reports its plan until `--write` is passed. Name each in the order the status gives, and run neither form.
51
+ - `unmovedSurfaces` is the `surface-roots` migration. Name each entry's `rel` and `movesTo` beside its verb.
50
52
  - `unmigrated` routes to a relocation the user runs, since no command moves it.
51
53
  - `superseded` routes to a split the user makes, since no command moves the content and no skill proposes it.
52
54
  - An `unclaimed` folder carrying `project` attribution routes nowhere, since history proved the project owns it.
@@ -59,7 +59,7 @@ Three states reach this skill that the chain does not serve. Name the destinatio
59
59
 
60
60
  ### An existing project
61
61
 
62
- Stop and hand off to `target-check`, which reports what the target already carries before anything is repaired. This chain never reads that, so a per-domain install picked here is a guess against configs nobody read.
62
+ Stop and hand off to `target-check`, which reports what the target already carries before anything is repaired. This chain never reads that, so a per-domain install picked here is a guess against configs nobody read. Name `target-update` when the operator wants the catch-up applied.
63
63
 
64
64
  ### The `.claude/` folder alone
65
65
 
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: target-update
3
+ description: What catching a target up to the toolkit is for, the judgment it holds, and where it hands off to the check and the setup
4
+ ---
5
+
6
+ # Target update requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, a session catching a target up after a release improvises the order. It runs a sync before the migrations that move its paths, writes a record-tree rewrite with no backup, and lets a sync replace a rule the project customized without saying what was lost.
11
+
12
+ The verbs report every fact, so the gap is the judgment between them. A session reads a refused migration as a step to force, picks rules and seed merges with nobody asked, and ends with no way to tell a current target from one it stopped partway through.
13
+
14
+ ## Must
15
+
16
+ - Show the changelog summary, the pending migrations, and the drift before the first write to the target, and apply only on the operator's go-ahead
17
+ - Read every fact from a `canon` verb, so a later release changes what the skill reports with no edit to its body
18
+ - Stop and ask on a refused migration, a stranded file, an unattributed folder, and a rule the stack newly ships
19
+ - Keep a customized file and a drifted seed as the project wrote them, naming the toolkit change each one skipped
20
+ - End on a clean `target-check` or on named remainders, with nothing written when nothing is pending
21
+
22
+ ## Must not
23
+
24
+ - Detect pending migrations or parse the changelog inside the body, since `canon migrate status` and `canon changelog` own both
25
+ - Name a toolkit version in an update commit's subject
26
+ - Run from the toolkit's own checkout, or write into a target from another repository's session
27
+ - Rely on being typed by name. It is reached through its description and `target-check`'s close, and a quarter of releases with no migration and no drift is the signal to fold it in there.
28
+
29
+ ## Out of scope
30
+
31
+ - First-time scaffold, which `target-setup` owns
32
+ - Reporting drift without repairing it, which `target-check` owns
33
+ - Opening the target's pull request, which `git-ship` owns
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: target-update
3
+ description: Catches a target project up to the toolkit after a release. Upgrades the binary and plugin, previews the changelog summary, the pending migrations, and the drift, then on the operator's go-ahead migrates, syncs, regenerates the indexes, and ends on a clean `target-check` or named remainders. Use when asked to "update this project to the latest canon", "catch this target up", "apply the new release here", "run the pending migrations", or "bring this project current with the toolkit". Do NOT use on a project without the toolkit, which is `target-setup`, or to report drift without repairing it, which is `target-check`.
4
+ metadata:
5
+ family: upkeep
6
+ ---
7
+
8
+ # Target update
9
+
10
+ Takes a target from behind the toolkit to current. Every fact comes from a `canon` verb, and this body holds the order and the judgment calls the verbs leave open. It names no migration, rule, or stack, so a later release reaches it with no edit here.
11
+
12
+ Read `canon docs migrate` for what each migrate verb moves and refuses, and `canon docs sync` for the drift causes. This body cites them rather than restating them.
13
+
14
+ ## Guards
15
+
16
+ Stop with the line beside each state.
17
+
18
+ | State | Stop |
19
+ | ------------------------------------------- | ------------------------------------------------------------------------------------------------- |
20
+ | `canon` absent from the path | `❌ canon is not on PATH, so nothing can be read.` |
21
+ | No `.claude/` directory at the working root | `❌ No .claude/ here, so the toolkit is not installed. Run /canon:target-setup instead.` |
22
+ | A `claude/skills/` folder, no leading dot | `❌ This is the toolkit itself rather than a target. Run this from the target's own session.` |
23
+ | A dirty working tree | `❌ Uncommitted changes here. Commit or stash them first, since canon sync refuses a dirty tree.` |
24
+
25
+ Then hold two rules for every verb this body runs.
26
+
27
+ - Run it under `CANON_NON_INTERACTIVE=1` from the target root
28
+ - Branch on its record's fields rather than its exit, which a shell function wrapping `canon` can flatten
29
+
30
+ ## Step 1: bring the binary and plugin forward
31
+
32
+ Read `skew` off `canon sync --check . --json`. When `installed` trails `latest`, run `canon upgrade --json`, then `canon claude plugin-update --json` whatever the skew reads.
33
+
34
+ Both write to the machine rather than the target, and the preview needs the verbs and the changelog a newer binary carries, so they run before it. The permission dialog gates each. Report a refusal from either and continue on the installed binary, saying the preview reads against it.
35
+
36
+ ## Step 2: read the pending work
37
+
38
+ Run these in parallel from the target root:
39
+
40
+ ```bash
41
+ CANON_NON_INTERACTIVE=1 canon changelog --json
42
+ CANON_NON_INTERACTIVE=1 canon migrate status --json
43
+ CANON_NON_INTERACTIVE=1 canon sync --check . --json
44
+ ```
45
+
46
+ Then take `<stack>` from the first name in the report's `tooling.chain` and run `canon tooling diff <stack> . --json`. Never run it bare, since the stack prompt resolves to the catalog's first entry under the non-interactive variable. Report tooling unmeasured when `tooling.measured` is false or `chain` names nothing.
47
+
48
+ ### When a read refuses
49
+
50
+ A refusal is a fact for the preview, never a gap to fill by reading files.
51
+
52
+ - `changelog` refusing `no-stamp` or `unknown-version`: say the summary is unread and why. Offer `--since <version>` when the operator names one, and never guess a start.
53
+ - `migrate status` carrying no such subcommand: the binary predates it. Stop, since pending migrations cannot be listed without detecting them here, which this skill never does.
54
+ - A missing report key is an older binary rather than an empty answer. Name the installed version beside it.
55
+
56
+ ## Step 3: preview and wait
57
+
58
+ Write the preview from those records alone, then stop and wait for the operator. Nothing in the target is written before the go-ahead.
59
+
60
+ ```markdown
61
+ Target update: `<path>`, canon `<installed>` to `<running>`.
62
+
63
+ **What changed since `<since>`:** <three to six bullets from the changelog entries, each naming what this project will notice, never the version>
64
+
65
+ **Migrations, in run order:** <each pending or refused entry from migrate status, with its verb>
66
+
67
+ **Drift:** <each non-empty cause from sync --check, with its count and paths>
68
+
69
+ **Calls for you:** <each judgment call below this run meets, with the default this skill takes>
70
+
71
+ **Commands, in order:** <the exact commands Steps 4 to 6 will run>
72
+ ```
73
+
74
+ Summarize the changelog for this project, not for the toolkit. Drop an entry that touches nothing the target holds, and lead with anything a migration or a sync will change.
75
+
76
+ ### Nothing pending
77
+
78
+ When the changelog range is empty, `migrate status` lists nothing pending or refused, and the drift lines hold nothing a step here would take, write nothing. Invoke `canon:target-check` and report the target current with its table.
79
+
80
+ ## Step 4: migrate
81
+
82
+ Run each pending migration in the order `migrate status` gave, one at a time, as the entry's `verb` with `--write --json` appended. The verb carries the flags its dry run used, so a bare `canon migrate <name>` would apply a plan the preview never showed.
83
+
84
+ - Run `canon migrate status --json` again after each write, since an applied move can turn a clean entry pending
85
+ - Stop and ask on a `refused` entry, quoting its reason and the remedy it names. Run the remedy only on the operator's word, since a refusal guards a destination the project may own.
86
+ - Stage each write with `git add -A`, since no migrate verb stages and the tree was clean before it, then commit through `canon:git-commit`, one commit per migration
87
+ - Skip the commit when the write changed no tracked file, as `record-tree` does by touching only gitignored records
88
+
89
+ ### Markers
90
+
91
+ A planned rewrite can reach a line that names an old spelling on purpose, such as a dated note or a quoted history. Propose a marker for each such line in the preview, and add it before the write. `canon docs migrate` names the marker for each spelling.
92
+
93
+ ### The record-tree backup
94
+
95
+ The record tree is untracked, so a `record-tree` rewrite has no undo. Run `canon records push --json` before its write. When that reports no backup repository, copy `.canon/` to a dated folder outside the project and name the path in the report.
96
+
97
+ ## Step 5: sync
98
+
99
+ Run `canon sync .`, then `canon tooling sync <stack> . --write` when the tooling diff named files to change. Treat each drift cause the way the preview stated:
100
+
101
+ | Cause | This skill |
102
+ | --------------------------- | ------------------------------------------------------------------------------------------------ |
103
+ | `stale`, `retired`, renamed | Takes the sync's write |
104
+ | `customized` | Keeps the project's file. Shows `git diff -- <rel>` as the toolkit's change, then `git restore`s |
105
+ | `stranded` | Names where the file sits and where the toolkit now installs, and moves it only on the word |
106
+ | `seeds` drifted | Merges by hand against `canon claude seeds list --json`, keeping the project's edits |
107
+ | `seeds` stale or missing | Takes the toolkit's copy whole |
108
+ | `newRules` | Lists them, and installs only the ones the operator picks, with `canon gov install --add` |
109
+
110
+ A `customized` rule is the file `canon sync` replaces, so read it back after the sync rather than trusting the report from Step 2. Report each kept file with the toolkit's change summarized in a line, so the operator can take it later.
111
+
112
+ ### What the toolkit dropped
113
+
114
+ Read `reverse.unclaimed` by attribution. Delete a `dropped` folder, read an `unattributed` one and ask before deleting it, and leave a `project` one alone.
115
+
116
+ Read the report's top-level `superseded` and `unmigrated` lists apart from it. Each names a move only the operator can make, being a file a newer seed replaced or a domain still at the root layout, so name each and move nothing.
117
+
118
+ ## Step 6: regenerate and verify
119
+
120
+ Run `canon indexes regen`, then the project's own check script when it carries one. Invoke `canon:target-check` last.
121
+
122
+ Stage the sync, the merges, and the regenerated indexes with `git add -A`, since `canon sync` leaves its writes unstaged, then commit through `canon:git-stage`. Name what changed in the project in each subject, such as the rules refreshed or the seeds merged, and never the toolkit version.
123
+
124
+ ## Output
125
+
126
+ ```markdown
127
+ Target update: `<path>`, now on canon `<version>`.
128
+
129
+ <the target-check table>
130
+
131
+ **Kept for you:** <each customized or drifted file left as the project wrote it, with the toolkit change it skipped>
132
+
133
+ **Remaining:** <each refusal, stranded file, or unread domain, with the command or decision it waits on>
134
+ ```
135
+
136
+ Omit a section that holds nothing. End on the commits and the check, and leave the pull request to `canon:git-ship`.
@@ -18,4 +18,4 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
18
18
  - [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and how canon docs resolves a topic name
19
19
  - [Records](records.md): Backing the record folders to a private remote, restoring them on a new machine, reading folder size, and pruning stale scratch
20
20
  - [Sessions transfer](sessions-transfer.md): Moving a session to another machine with export and import, the manifest fields, the refusal reasons, and what a resume on the target depends on
21
- - [Targets](targets.md): The projects this toolkit installed into, where each answer comes from, and the cross-target pull request read
21
+ - [Targets](targets.md): The projects this toolkit installed into, where each answer comes from, the cross-target pull request read, and the releases since a target's install
@@ -10,6 +10,7 @@ kind: reference
10
10
 
11
11
  | Verb | Moves |
12
12
  | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
13
+ | `status` | Nothing. It lists every migration the project still needs, in run order. |
13
14
  | `rename` | The retired `aitk` name. `--scope target` rewrites toolkit-owned folders only. |
14
15
  | `skill-names` | Plugin skills off their `claude-` and `draft-` prefixes and other retired names, with every citation |
15
16
  | `records` | Session records from `.claude/` to `.canon/`, with every tracked citation |
@@ -33,6 +34,8 @@ kind: reference
33
34
  - `records` reports skipped record folders as a count on its own line
34
35
  - A `record-tree` rewrite has no undo in version control, so read the report before `--write`
35
36
 
37
+ `status` runs each verb as a dry run and exits 2 while any is pending, with a refusal outranking that. Each entry's `verb` carries the flags its dry run used, so append `--write` to it. A clean entry can turn pending once an earlier one applies, so run it again after each `--write`.
38
+
36
39
  Three markers keep an old spelling on a line. Each also covers the nearest non-blank line, below it for `canon-keep-record-root` and above it for `canon-keep-surface-root`.
37
40
 
38
41
  | Marker | Keeps |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Targets
3
- description: The projects this toolkit installed into, where each answer comes from, and the cross-target pull request read
3
+ description: The projects this toolkit installed into, where each answer comes from, the cross-target pull request read, and the releases since a target's install
4
4
  kind: reference
5
5
  ---
6
6
 
@@ -12,6 +12,13 @@ kind: reference
12
12
  - Each stamping sync records its target in `$XDG_STATE_HOME/canon/targets.json`, and no row is removed
13
13
  - A sweep finds installs the index never held
14
14
 
15
+ `canon changelog` prints the releases a target has not seen, from the `CHANGELOG.md` the installed package ships. It reads no registry and needs no `gh`.
16
+
17
+ - The start defaults to the oldest `version` across the target's stamp domains. `--since <version>` overrides it, and `--root <path>` names the target.
18
+ - `--json` carries `since`, `running`, and `entries`, each with its `version` and raw `markdown`, oldest first
19
+ - A start equal to the running version is the one empty range, since that target is current
20
+ - A refusal names its `reason` instead of printing an empty range, being `no-stamp`, `no-changelog`, or `unknown-version`. The last covers a pre-release and a start newer than the binary.
21
+
15
22
  ## Output
16
23
 
17
24
  - `bound` on a sweep names the roots walked, the depth, and what it skipped. Read it before trusting a count.
@@ -6,7 +6,9 @@ category: Agent surface
6
6
 
7
7
  # Target migrations
8
8
 
9
- Each section is a move a project runs once, when it was scaffolded before the change it names. Run `canon upgrade` first. The recurring sync that follows is in [target sync](sync.md).
9
+ Each section is a move a project runs once, when it was scaffolded before the change it names. Run `canon upgrade` first, then `canon migrate status --json` to list the moves a project still needs in the order to run them. The recurring sync that follows is in [target sync](sync.md).
10
+
11
+ `canon:target-update` is the guided path through both. It runs the moves in that order once you approve its preview.
10
12
 
11
13
  ## Move the records first, once
12
14
 
@@ -47,7 +47,7 @@ The chain runs these phases:
47
47
  - The `verify` phase runs the installed `package.json` scripts and reports pass or fail
48
48
  - The `indexes` phase bootstraps `index.md` over the project's documentation folders, confirming candidates with you
49
49
 
50
- An existing project goes to `canon:target-check` first. For the Claude layer alone, run `canon claude init` and then `canon:target-setup indexes`. Run `canon:target-setup verify deep` by hand for the heavier smoke and screenshot pass.
50
+ An existing project goes to `canon:target-check` first, then to `canon:target-update` to apply the catch-up. For the Claude layer alone, run `canon claude init` and then `canon:target-setup indexes`. Run `canon:target-setup verify deep` by hand for the heavier smoke and screenshot pass.
51
51
 
52
52
  Keep the `## Scripts` table in `canon/context/development/local-loop.md` current as scripts are added. A split entry keeps it in any entry directly under `development/`, grouping a long set into short tables under lead sentences. <!-- audit-ignore-citations: canon/context/development/local-loop.md -->
53
53
 
@@ -10,7 +10,7 @@ A target pulls toolkit changes per domain, through one catch-all and several tar
10
10
 
11
11
  ## Check first
12
12
 
13
- `canon sync --check <path>` reports drift without writing, split by cause. `canon:target-check` groups the same state by domain and repairs nothing.
13
+ `canon sync --check <path>` reports drift without writing, split by cause. `canon:target-check` groups the same state by domain and repairs nothing. `canon:target-update` is the guided path, previewing the drift and then applying it on your go-ahead.
14
14
 
15
15
  The report opens with the binary version against the newest published one, and a version behind means run `canon upgrade` first.
16
16
 
@@ -60,4 +60,4 @@ Merge order matters only when two branches touch the same file. Expect to rebase
60
60
 
61
61
  `canon:task-board` creates a task file, and `canon:task-complete` marks outcomes. The `post-merge` hook archives the task a merged pull request closed, with its plan, and a `post-rewrite` hook does the same for a rebase pull. Both archive a closed task whose merge carried a PR number, and a slice merged before then moves only its own plan.
62
62
 
63
- `CANON_SKIP_RECLAIM`, `CANON_SKIP_UPGRADE`, and `CANON_SKIP_PLUGIN_UPDATE` turn off the three hook steps that act on the machine.
63
+ The hook also deletes the member branches and worktrees a merged fan-in integrator names, and `canon worktrees retire-members --pull-request <n>` does it for a merge the hook missed. `CANON_SKIP_RECLAIM` turns off that step and the reclaim, and `CANON_SKIP_UPGRADE` and `CANON_SKIP_PLUGIN_UPDATE` turn off the other two.
@@ -103,6 +103,7 @@ Groups run in the order a project meets them. Every name `canon claude skills li
103
103
  | Skill | When to use |
104
104
  | ----------------------------- | ------------------------------------------------------- |
105
105
  | `canon:target-check` | In a target, to compare each domain with the toolkit |
106
+ | `canon:target-update` | After a release, to catch a target up to the toolkit |
106
107
  | `canon:document-repair` | Rewrite a failing docs folder to the limits and gate it |
107
108
  | `canon:canon-feedback` | The toolkit is broken, missing, or off |
108
109
  | `canon:canon-feedback-triage` | In the toolkit repo, to file feedback issues |
@@ -207,4 +207,4 @@ files = [".canon/canvas"]
207
207
 
208
208
  ["design-reference"]
209
209
  deps = []
210
- files = ["canon/design"]
210
+ files = ["canon/design/*/"]