@erclx/canon 5.40.0 → 5.41.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.
- package/README.md +34 -17
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
- package/claude/skills/auto-ship/SKILL.md +6 -0
- package/claude/skills/auto-ship/references/fan-in.md +59 -0
- package/claude/skills/create-skill/references/skill.md +1 -1
- package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
- package/claude/skills/decision-escalate/SKILL.md +1 -2
- package/claude/skills/git-branch/REQUIREMENT.md +17 -6
- package/claude/skills/git-branch/SKILL.md +63 -7
- package/claude/skills/plan-feature/references/batches.md +48 -1
- package/claude/skills/plan-feature/references/plan.md +2 -0
- package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
- package/claude/skills/plan-intake-answer/SKILL.md +1 -2
- package/claude/skills/review-address/references/rebase-conflicts.md +7 -0
- package/claude/skills/review-ui/SKILL.md +2 -0
- package/claude/skills/review-ui/references/close-out.md +47 -0
- package/claude/skills/review-ui/references/post.md +3 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -0
- package/claude/skills/role-worker/REQUIREMENT.md +1 -0
- package/claude/skills/role-worker/SKILL.md +9 -0
- package/claude/skills/teach-workspace/REQUIREMENT.md +1 -1
- package/claude/skills/teach-workspace/SKILL.md +1 -2
- package/docs/workflow/parallel-features.md +1 -0
- package/docs/workflow/skill-map.md +1 -1
- package/package.json +1 -1
- package/src/claude/cases/git.ts +4 -0
- package/src/commands/pr.ts +32 -9
- package/src/commands/records.ts +11 -3
- package/src/context/shape-gated.ts +1 -0
- package/src/markdown/scope.ts +6 -2
- package/src/pr/frames.ts +53 -9
- package/src/records/validate.ts +1 -0
- package/src/tasks/answers.ts +79 -2
- package/src/tasks/fan-in.ts +176 -0
- package/src/tasks/stack.ts +1 -1
- package/tooling/astro/reference.md +80 -39
- package/tooling/base/reference.md +63 -32
- package/tooling/base/seeds/canon/context/ci.md +7 -15
- package/tooling/base/seeds/canon/context/development.md +14 -14
- package/tooling/claude/reference.md +40 -22
- package/tooling/cloudflare/reference.md +31 -12
- package/tooling/go/reference.md +47 -23
- package/tooling/nextjs/reference.md +52 -19
- package/tooling/php/reference.md +35 -16
- package/tooling/python/reference.md +47 -30
- package/tooling/shell/reference.md +6 -5
- package/tooling/vite-react/reference.md +49 -21
- package/tooling/web/reference.md +130 -70
package/README.md
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
|
|
14
14
|
canon is a CLI and Claude Code plugin that runs your agent's work from plan to merge, holds it to the practices that make code worth keeping, and installs the same setup in every repository.
|
|
15
|
-
|
|
16
15
|
**[See it run at canon.erclx.dev](https://canon.erclx.dev)**
|
|
17
16
|
|
|
18
17
|
</div>
|
|
@@ -21,9 +20,13 @@ That page is one real session the toolkit ran on itself, and every count on it i
|
|
|
21
20
|
|
|
22
21
|
## Why
|
|
23
22
|
|
|
24
|
-
If an agent writes code for you in more than one repository, this is for you. canon runs that work from plan to merge. A planner writes the plan, a worker builds it on its own branch, and a separate session reviews the pull request.
|
|
23
|
+
If an agent writes code for you in more than one repository, this is for you. canon runs that work from plan to merge. A planner writes the plan, a worker builds it on its own branch, and a separate session reviews the pull request.
|
|
24
|
+
|
|
25
|
+
It holds the work to a practice, with rules that load when a matching path is edited and checks that fail when a rule is ignored. Both run on one setup, installed the same way in every repository you keep.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
### The shared setup
|
|
28
|
+
|
|
29
|
+
That shared setup is what the rest of this page describes, and it drifts without a source. Every AI coding setup accumulates the same assets. Prompts to reuse, rules agents should follow, slash commands, skills, seed docs, sync scripts. Once you have enough projects, your copies drift and your agents stop getting consistent signals.
|
|
27
30
|
|
|
28
31
|
Three design choices shape the toolkit.
|
|
29
32
|
|
|
@@ -61,17 +64,21 @@ bun install --global @erclx/canon
|
|
|
61
64
|
|
|
62
65
|
Each domain has a canonical source in this repo and a thin install or sync CLI on your side. The domains split on one line: some are copied into your project and become yours to edit, and some are never copied at all.
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
67
|
+
### Domain catalog
|
|
68
|
+
|
|
69
|
+
| Domain | What it is | How it reaches you |
|
|
70
|
+
| -------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
71
|
+
| Plugin skills | Skills that plan a feature, review a diff, sync the planning docs, and run the ship chain | Loaded live from the plugin, never copied |
|
|
72
|
+
| Governance | Coding and authoring rules that load into a Claude session when a matching path is edited | Installed per project by `canon gov install`, refreshed by sync |
|
|
73
|
+
| Tooling stacks | Golden configs, seeds, and a reference per framework | Laid down by `canon init`, reconciled by `canon tooling sync` |
|
|
74
|
+
| Design system | Ships a `DESIGN.md` intent format pointing at your stylesheet for values, a skill that drafts one, and a render command | `canon design render` |
|
|
75
|
+
| Slides | A folder of HTML slides per deck, drawn in the project's tokens and rebuilt as editable shapes | `canon slides render` writes PowerPoint |
|
|
76
|
+
| Teach | Lessons, sources, and a glossary in one workspace, rendered to a small site | `canon teach` |
|
|
77
|
+
| Canvas | Pages of HTML frames on a local pan and zoom surface, each frame drawn with the project's tokens | `canon canvas serve` |
|
|
78
|
+
| Transcripts | A YouTube transcript with metadata frontmatter | `canon transcripts <url>` writes it into any repo |
|
|
79
|
+
| Sandbox | Scenario scaffolds that provision an isolated project state for verifying each domain flow | `canon sandbox` |
|
|
80
|
+
|
|
81
|
+
### How domains reach you
|
|
75
82
|
|
|
76
83
|
Those domains reach you in three shapes. A tooling stack lands as real files under version control, because a config is something your build reads and your project owns. A plugin skill and the references it carries are never copied, so there is no copy in your repo to drift from this one.
|
|
77
84
|
|
|
@@ -117,6 +124,8 @@ The first two update the skills, the third updates the CLI, and they move indepe
|
|
|
117
124
|
|
|
118
125
|
`canon upgrade` reads the package manager off its own install path and reinstalls with that one, so you don't have to remember which put it there. It names what it detected before it runs anything, and it refuses a source checkout rather than reinstalling over your clone.
|
|
119
126
|
|
|
127
|
+
### Checking for drift
|
|
128
|
+
|
|
120
129
|
You don't have to wait until something breaks to find out you're behind. `canon sync --check` and `canon claude skills drift` both report the installed version against the newest published one, and neither changes its exit code over it, so an offline machine reads unknown rather than red.
|
|
121
130
|
|
|
122
131
|
To stop doing this by hand, turn auto-update on once under `/plugin` in the Marketplaces tab. Confirm what you are running with `canon --version` and `claude plugin list`.
|
|
@@ -127,6 +136,8 @@ Working on the toolkit starts from a clone. Running the CLI doesn't, since it in
|
|
|
127
136
|
|
|
128
137
|
### Prerequisites
|
|
129
138
|
|
|
139
|
+
Bun, Git, and a shell are required, and the GitHub CLI is optional.
|
|
140
|
+
|
|
130
141
|
- [Bun](https://bun.sh) for the CLI runtime and scripts
|
|
131
142
|
- [Git](https://git-scm.com) with worktree support
|
|
132
143
|
- [GitHub CLI](https://cli.github.com) (optional) for ship flows
|
|
@@ -141,7 +152,10 @@ bun install
|
|
|
141
152
|
bun run bootstrap
|
|
142
153
|
```
|
|
143
154
|
|
|
144
|
-
The script is idempotent, so re-run it after pulling upstream changes without duplicating anything.
|
|
155
|
+
The script is idempotent, so re-run it after pulling upstream changes without duplicating anything. It confirms the install by resolving `canon --help` on the last step.
|
|
156
|
+
|
|
157
|
+
- It leaves an alias block you already have alone rather than refreshing it, so an alias added upstream needs the block deleted and the script re-run.
|
|
158
|
+
- [Zshrc aliases](docs/workflow/zshrc-aliases.md) covers what each alias does, how to pick up a new one, and how to opt out of the block.
|
|
145
159
|
|
|
146
160
|
With the CLI linked, scaffold a fresh project.
|
|
147
161
|
|
|
@@ -151,9 +165,12 @@ git init
|
|
|
151
165
|
canon init
|
|
152
166
|
```
|
|
153
167
|
|
|
154
|
-
`canon init` installs base tooling configs, Claude seeds, and governance rules in one pass, and scaffolds a `.claude/wiki/` stub for your project's own reference pages.
|
|
168
|
+
`canon init` installs base tooling configs, Claude seeds, and governance rules in one pass, and scaffolds a `.claude/wiki/` stub for your project's own reference pages.
|
|
155
169
|
|
|
156
|
-
|
|
170
|
+
- Governance defaults to the `base` stack, so a bare init lands the workflow and doc-authoring rules in `.claude/rules/`.
|
|
171
|
+
- Each rule names the skill it loads, so no corpus is copied into your project.
|
|
172
|
+
- Pass `--stack <name>` for a language stack such as `node` or `python`, or `--skip governance` to leave rules out.
|
|
173
|
+
- Run `canon tooling list --json` to see the catalog.
|
|
157
174
|
|
|
158
175
|
For the full journey from scaffold through adding a domain later to syncing upstream drift, see [target projects](docs/target/projects.md).
|
|
159
176
|
|
|
@@ -19,6 +19,7 @@ Without this skill, the run from an approved plan to an open pull request is a c
|
|
|
19
19
|
- Open the pull request as a draft before the continuous integration watch begins, since a pull request marked after it is mergeable for the length of the run
|
|
20
20
|
- Continue past a produced UI checklist rather than stopping on it, and report its unchecked boxes in the closing block, since the draft mark already holds the merge
|
|
21
21
|
- Prove the draft target's head is the current branch and its state is open before marking it, and stop with nothing marked on a mismatch, since the number is retyped between steps and a draft mark on a release pull request holds that release
|
|
22
|
+
- Stop a fan-in member after its verify and test order with a push and a done message rather than a pull request, and open an integrator's build by merging the member branches, since parallel batches sharing edge files put every open sibling into conflict on each merge
|
|
22
23
|
- Name the recovery for the stop it took, since the value of stopping is that the user knows where to resume
|
|
23
24
|
|
|
24
25
|
## Must not
|
|
@@ -73,6 +73,8 @@ When this run decides against a suggestion, rewrite its line as `overridden at e
|
|
|
73
73
|
|
|
74
74
|
Load `canon:build-in-slices`, reporting it when it does not resolve, and implement only what the plan describes. Do not expand scope, refactor neighbors, or touch files outside the plan's "Files to touch" list without reason.
|
|
75
75
|
|
|
76
|
+
When the plan's `**Constraints:**` carry a `Fans in` bullet, this run is a fan-in integrator, so open this step by merging the member branches, per `${CLAUDE_SKILL_DIR}/references/fan-in.md`.
|
|
77
|
+
|
|
76
78
|
## Step 3: verify
|
|
77
79
|
|
|
78
80
|
Run the verify commands defined in `CLAUDE.md` (lint, typecheck, tests). On failure:
|
|
@@ -97,6 +99,10 @@ The verb reads git history rather than the working tree, so it catches a violati
|
|
|
97
99
|
|
|
98
100
|
Never read a missing subcommand as clean. Read `${CLAUDE_SKILL_DIR}/references/verb-absent.md` on meeting one.
|
|
99
101
|
|
|
102
|
+
### A fan-in member stops here
|
|
103
|
+
|
|
104
|
+
When the plan's `**Constraints:**` carry a `Fan-in member of` bullet, push the branch, announce it done, and stop before Step 5. A member opens no pull request, since its integrator ships the whole set. Read `${CLAUDE_SKILL_DIR}/references/fan-in.md` for the push and the message.
|
|
105
|
+
|
|
100
106
|
## Step 5: UI checklist (conditional)
|
|
101
107
|
|
|
102
108
|
If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:ui-checklist`, then continue to Step 6 whatever it returns, in every run. The Step 6 draft mark holds the merge, so a stop here protects nothing.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Fan-in member exit and integrator merge
|
|
3
|
+
description: How auto-ship stops a fan-in member after its push and how an integrator merges the member branches before its own build, so a set of parallel batches ships as one pull request
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fan-in
|
|
7
|
+
|
|
8
|
+
The session reads this file at one of two points in `auto-ship`. A member reads `## Member exit` after Step 4, and an integrator reads `## Integrator merge` at the opening of Step 2. When to fan in and how a plan declares its side are in `${CLAUDE_SKILL_DIR}/../plan-feature/references/batches.md`.
|
|
9
|
+
|
|
10
|
+
## Member exit
|
|
11
|
+
|
|
12
|
+
Step 3's verify and Step 4's test order have already run, so the branch is green and committed. The member ends here rather than at a pull request.
|
|
13
|
+
|
|
14
|
+
1. Push the branch with `git push -u origin <branch>`. The pre-push hook runs the project check, and a member that fails it stops the way Step 3 does.
|
|
15
|
+
2. Read the head with `git rev-parse HEAD` once the push returns.
|
|
16
|
+
3. Announce the batch done to the controlling session through `canon:session-relay`, carrying the branch, the head commit, the integrator the plan names, and any row this build turned up.
|
|
17
|
+
4. Stop. Run no UI checklist, no `git-ship`, and no draft mark.
|
|
18
|
+
|
|
19
|
+
The pushed branch is the done signal the integrator's gate reads, so push once, at the end, and never mid-build. The announcement carries no reach, since members of one set share files by design and the integrator's announcement carries the reach for the whole set.
|
|
20
|
+
|
|
21
|
+
A fix the integrator needs after this point lands on the integrator's branch, since a member has no pull request to fix it on.
|
|
22
|
+
|
|
23
|
+
## Integrator merge
|
|
24
|
+
|
|
25
|
+
The members are pushed before this run starts, since `canon tasks plan-answers` holds an integrator until every member branch is on origin. Confirm that before merging anything:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git fetch origin --quiet
|
|
29
|
+
canon tasks plan-answers <plan> --json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Stop on `launchable: false`, reporting each `open` entry, since a `Fan-in pending` entry names a member that has not pushed and a `Fan-in set` entry names a set whose sides disagree. Then merge each member in the order the `Fans in` bullet lists:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git merge --no-ff --no-edit origin/<member branch>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Conflicts and the order
|
|
39
|
+
|
|
40
|
+
`--no-ff` keeps one merge commit per member, so `git log --first-parent` shows the reviewer one member at a time. Never squash or fast-forward a member in.
|
|
41
|
+
|
|
42
|
+
- Resolve each conflict by `## Resolving a conflict` in `${CLAUDE_SKILL_DIR}/../review-address/references/rebase-conflicts.md`
|
|
43
|
+
- Stop on a hunk needing a decision with `git merge --abort` in place of the rebase abort that file names
|
|
44
|
+
- Regenerate the indexes once, after the last merge, rather than resolving a generated file by hand at each one
|
|
45
|
+
- Run `git config rerere.enabled true` before the first merge, since a stale integrator rebases with `--rebase-merges` per the same file and that replays each merge without its recorded resolution
|
|
46
|
+
|
|
47
|
+
Continue Step 2 with the integrator plan's own `**Files to touch:**`, then the rest of the chain as an ordinary worker.
|
|
48
|
+
|
|
49
|
+
### What the pull request carries
|
|
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:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git push origin --delete <member branch>
|
|
55
|
+
git worktree remove <member worktree>
|
|
56
|
+
git branch -D <member branch>
|
|
57
|
+
```
|
|
58
|
+
|
|
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.
|
|
@@ -104,7 +104,7 @@ allowed-tools: <tools required>
|
|
|
104
104
|
|
|
105
105
|
- **`name`** (required): kebab-case, matches folder name, no spaces or capitals
|
|
106
106
|
- **`description`** (required): what it does + when to use it, under 1024 chars, no XML tags
|
|
107
|
-
- **`disable-model-invocation: true`**:
|
|
107
|
+
- **`disable-model-invocation: true`**: Claude will not auto-trigger, and the `Skill` tool also refuses the operator's own request made in words. Set it only on a skill run by slash command alone. A skill wanting just the first effect writes `Do NOT auto-trigger` in its description.
|
|
108
108
|
- **`allowed-tools`**: restrict tool access when the skill is active
|
|
109
109
|
- **`metadata`**: optional key-value pairs (`author`, `version`, `mcp-server`)
|
|
110
110
|
- **`metadata.family`** (required on a skill under `claude/skills/`): the skill map group the skill's row sits under, as one key from `families` in `canon claude skills list --json`. A skill under `.claude/skills/` takes none.
|
|
@@ -41,4 +41,4 @@ The opposite failure is the one that ships silently. A session that takes a pref
|
|
|
41
41
|
|
|
42
42
|
- Making the ordinary judgment call, which is a pick plus a one-sentence tradeoff and needs no surface
|
|
43
43
|
- Writing the decision into a plan, task, or context entry, which each owning standard governs and this skill only routes to
|
|
44
|
-
- Deciding when to fire. The
|
|
44
|
+
- Deciding when to fire. The description states `Do NOT auto-trigger`, so escalating is the operator's call rather than a description match.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: decision-escalate
|
|
3
|
-
description: Collects every open decision whose answer turns on the operator's preference, since only they know what they need, puts them as one batch of questions each carrying options and a recommended default, then waits. Use when asked to "escalate this", "ask me the open questions", "batch the open decisions", "what do you need from me", or "stop and ask before you pick". Do NOT use for a judgment call the session can settle, which is a pick with the tradeoff stated in one sentence.
|
|
4
|
-
disable-model-invocation: true
|
|
3
|
+
description: Collects every open decision whose answer turns on the operator's preference, since only they know what they need, puts them as one batch of questions each carrying options and a recommended default, then waits. Use when asked to "escalate this", "ask me the open questions", "batch the open decisions", "what do you need from me", or "stop and ask before you pick". Do NOT use for a judgment call the session can settle, which is a pick with the tradeoff stated in one sentence. Do NOT auto-trigger.
|
|
5
4
|
metadata:
|
|
6
5
|
family: decide
|
|
7
6
|
---
|
|
@@ -9,12 +9,17 @@ description: What branch naming is for, the gaps it closes, and where the family
|
|
|
9
9
|
|
|
10
10
|
Without this skill, a branch name is invented once per session. The type prefix drifts from what the history already carries, the name runs past the width a pull request list renders, and a rename lands on a branch that already exists on the remote, which detaches every open pull request pointing at it.
|
|
11
11
|
|
|
12
|
+
A session standing on `main` has no route either. The `git.md` rule sends every git operation through a `git-*` skill, and the only one for branches stopped on `Cannot rename a protected branch`, so the first act of any feature, creating its branch, had no skill to go through.
|
|
13
|
+
|
|
12
14
|
## Must
|
|
13
15
|
|
|
14
16
|
- Read the branch format reference before generating, so the type vocabulary and the length limit come from one source
|
|
15
|
-
-
|
|
17
|
+
- Create the branch on `main` or `master` and rename it anywhere else, so one skill covers both ends of a branch's start
|
|
18
|
+
- Derive the name from the commits ahead of main when a rename carries no description
|
|
19
|
+
- Take a created name from `canon tasks plan-branch` when the invocation names a plan, so the skill and the dispatch gate hold one derivation
|
|
16
20
|
- Resolve whether the branch exists on the remote before proposing a rename, since the hazard is invisible from local state
|
|
17
|
-
-
|
|
21
|
+
- Read the remote itself with `git ls-remote` before creating, since `origin/<name>` sees only the last fetch
|
|
22
|
+
- Show the name and its length against the limit before renaming or creating
|
|
18
23
|
- Execute immediately after the preview. The tool permission dialog is the confirmation gate.
|
|
19
24
|
|
|
20
25
|
## Must not
|
|
@@ -22,17 +27,23 @@ Without this skill, a branch name is invented once per session. The type prefix
|
|
|
22
27
|
- Rename `main` or `master`
|
|
23
28
|
- Rename when the current name already conforms. The skill is chained, so a conforming name has to pass through rather than churn the branch.
|
|
24
29
|
- Rename a branch that exists on the remote. Local rename plus force push is not the same operation and it breaks the pull request.
|
|
30
|
+
- Create a branch from anywhere but `main` or `master`, or create one whose name exists locally or on the remote
|
|
31
|
+
- Invent a name when the plan verb refuses, since a name the skill calls non-conforming strands the rename at ship
|
|
32
|
+
- Fetch or stash while creating
|
|
25
33
|
- Emit anything after the result line
|
|
26
34
|
|
|
27
35
|
## Guards
|
|
28
36
|
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
37
|
+
- Create with no plan and no name: stop and name both missing inputs
|
|
38
|
+
- Create with a name breaking the branch standard: stop and name the cap
|
|
39
|
+
- Create with a name existing locally or on the remote: stop and say which
|
|
40
|
+
- Rename name already conforms: stop with a success marker, not a failure, since a chained caller continues past it
|
|
41
|
+
- Rename with no commits and no description: stop and name both missing inputs
|
|
42
|
+
- Rename of a branch existing on the remote: stop and route to the surface that preserves the pull request link
|
|
33
43
|
|
|
34
44
|
## Out of scope
|
|
35
45
|
|
|
36
46
|
- The commit message, which `git-commit` owns
|
|
37
47
|
- Pushing and opening the pull request, which `git-pr` owns. That skill reads a conforming name as a precondition and refuses without one, so this skill runs first.
|
|
38
48
|
- Splitting a branch whose commits span concerns, which `git-split` owns
|
|
49
|
+
- Entering a worktree, which `session-worktree` owns and which names its own branch
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: git-branch
|
|
3
|
-
description: Generates and validates conventional branch names. Use for any branch naming or renaming, such as when asked to "name this branch".
|
|
3
|
+
description: Generates and validates conventional branch names, creating the branch from main or master and renaming it anywhere else. Use for any branch naming, creating, or renaming, such as when asked to "name this branch" or "create the branch for this feature".
|
|
4
4
|
metadata:
|
|
5
5
|
family: ship
|
|
6
6
|
---
|
|
@@ -13,7 +13,64 @@ Before generating a branch name, read:
|
|
|
13
13
|
|
|
14
14
|
Follow it exactly.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Run `git branch --show-current` to pick the mode. On `main` or `master` the skill creates a branch, and anywhere else it renames the current one.
|
|
17
|
+
|
|
18
|
+
## Create mode
|
|
19
|
+
|
|
20
|
+
### Name
|
|
21
|
+
|
|
22
|
+
Take the name from the invocation. An argument ending in `.md` or resolving under `.canon/plans/` is a plan, and anything else is a branch name, since a name always carries a `<type>/` segment.
|
|
23
|
+
|
|
24
|
+
- For a plan, run `canon tasks plan-branch <plan> --json` and take the record's `branch`. The verb fixes the type at `feat`, so a plan never yields another type.
|
|
25
|
+
- When the verb refuses or the record carries `conforms: false`, stop and relay its reason. Never invent a name in its place.
|
|
26
|
+
- For a name, check it against the whole Format section of `branch.md`, including the description word bound and the no-repeated-type rule, as the rename guard does.
|
|
27
|
+
|
|
28
|
+
### Context
|
|
29
|
+
|
|
30
|
+
Run these in parallel once the name is known:
|
|
31
|
+
|
|
32
|
+
- `git show-ref --verify --quiet "refs/heads/<name>" && echo "LOCAL_EXISTS" || echo "LOCAL_FREE"`
|
|
33
|
+
- `git ls-remote --exit-code --heads origin "<name>" 2>/dev/null; echo "REMOTE_EXIT=$?"`
|
|
34
|
+
|
|
35
|
+
Exit 0 on the remote read is a collision. Exit 2, or a repository with no `origin`, is no collision. The check reads the remote itself, never `origin/<name>`, which sees only the last fetch. The skill does not fetch.
|
|
36
|
+
|
|
37
|
+
### Guards
|
|
38
|
+
|
|
39
|
+
Each guard stops before any ref moves.
|
|
40
|
+
|
|
41
|
+
- If the invocation carries no plan and no name, stop: `❌ No plan or branch name to create from.`
|
|
42
|
+
- If the name breaks `branch.md`, stop and name the cap it broke: `❌ <name> breaks the branch standard: <cap>.`
|
|
43
|
+
- If the name exists locally, stop: `❌ Branch <name> already exists locally.`
|
|
44
|
+
- If the name exists on the remote, stop: `❌ Branch <name> already exists on origin.`
|
|
45
|
+
|
|
46
|
+
### Preview
|
|
47
|
+
|
|
48
|
+
Output the preview first.
|
|
49
|
+
|
|
50
|
+
- **Current:** <main_or_master>
|
|
51
|
+
- **Creating:** <name>
|
|
52
|
+
- **Length:** <count>/50
|
|
53
|
+
- **Source:** <plan | name>
|
|
54
|
+
|
|
55
|
+
After outputting the preview, execute the final command immediately. Claude Code's tool permission dialog is the confirmation gate. Do not wait for user input.
|
|
56
|
+
|
|
57
|
+
### Final command
|
|
58
|
+
|
|
59
|
+
The branch starts at the current tip, and uncommitted changes carry onto it. The skill stashes nothing.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git switch -c <name>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### After execution
|
|
66
|
+
|
|
67
|
+
Respond with exactly one line and add no other text:
|
|
68
|
+
|
|
69
|
+
`✅ Created: <name>`
|
|
70
|
+
|
|
71
|
+
## Rename mode
|
|
72
|
+
|
|
73
|
+
### Context
|
|
17
74
|
|
|
18
75
|
Run these commands in parallel to gather git context:
|
|
19
76
|
|
|
@@ -21,14 +78,13 @@ Run these commands in parallel to gather git context:
|
|
|
21
78
|
- `git rev-parse --verify "origin/$(git branch --show-current)" 2>/dev/null && echo "EXISTS" || echo "LOCAL_ONLY"`
|
|
22
79
|
- `git log main..HEAD --oneline 2>/dev/null || echo "NO_COMMITS"`
|
|
23
80
|
|
|
24
|
-
|
|
81
|
+
### Guards
|
|
82
|
+
|
|
83
|
+
A conforming name passes through, so a chained caller continues past it.
|
|
25
84
|
|
|
26
|
-
- If already on main or master, stop: `❌ Cannot rename a protected branch.`
|
|
27
85
|
- If branch name already follows conventions, stop: `✅ Branch name already follows conventions.`
|
|
28
86
|
- If no commits and no args provided, stop: `❌ No commits or description to derive a branch name from.`
|
|
29
87
|
|
|
30
|
-
## Response format
|
|
31
|
-
|
|
32
88
|
### Preview
|
|
33
89
|
|
|
34
90
|
Output the preview first.
|
|
@@ -55,7 +111,7 @@ Only output if LOCAL_ONLY:
|
|
|
55
111
|
git branch -m <current> <suggested>
|
|
56
112
|
```
|
|
57
113
|
|
|
58
|
-
|
|
114
|
+
### After execution
|
|
59
115
|
|
|
60
116
|
Respond with exactly one line and add no other text:
|
|
61
117
|
|
|
@@ -9,7 +9,7 @@ Applies to a feature plan that sequences its work into more than one batch. A de
|
|
|
9
9
|
|
|
10
10
|
## One batch, one file
|
|
11
11
|
|
|
12
|
-
One batch is one pull request, and one pull request is one plan file, named for its own concern rather than a numeric suffix. `canon tasks plan-branch` derives one branch from one plan filename.
|
|
12
|
+
One batch is one pull request, and one pull request is one plan file, named for its own concern rather than a numeric suffix. `canon tasks plan-branch` derives one branch from one plan filename. A fan-in member is the exception, keeping its own file and branch and opening no pull request, per `## Fan-in`.
|
|
13
13
|
|
|
14
14
|
- A `**Batch N**` sub-heading inside one file's `**Files to touch:**` shares that branch across every batch, so the first merge strands the rest
|
|
15
15
|
- Write a plan opening five batches as five files before the first line of any of them
|
|
@@ -44,3 +44,50 @@ Slice a batch by one user-visible path end to end rather than by layer, so each
|
|
|
44
44
|
- Sequence the riskiest batch first, so the batch most likely to change the plan is built while every later batch can still move
|
|
45
45
|
- Sequence a sweep last, even when it is the riskiest. A batch rewriting files earlier batches touched forces every batch behind it into one review anywhere else.
|
|
46
46
|
- Keep each batch's commits contiguous once the work starts, since a batch interrupted by another cannot be lifted onto its own branch afterward
|
|
47
|
+
|
|
48
|
+
## Fan-in
|
|
49
|
+
|
|
50
|
+
A fan-in builds batches in parallel and ships them as one pull request. Each member builds on its own branch and opens none, and one integrator merges every member branch, resolves the shared lines once, and opens the pull request. The trade is one large review, read one merge commit at a time, in place of a review and a rebase per batch.
|
|
51
|
+
|
|
52
|
+
### When to fan in
|
|
53
|
+
|
|
54
|
+
Fan in when the split was made for speed rather than meaning. Every one of these holds:
|
|
55
|
+
|
|
56
|
+
- One task with one outcome, so no batch is a feature on its own
|
|
57
|
+
- The batches share edge files, such as a gated-paths list or an index, so separate pull requests rebase each other on every merge
|
|
58
|
+
- No batch needs to ship, be reviewed, or be reverted alone
|
|
59
|
+
|
|
60
|
+
Keep separate pull requests when the pieces are independent features, when one needs its own review or revert, when one may stall and hold the rest, or when the combined diff is too large to review even one merge commit at a time. Batches that build on each other stack or fold, per `## Dependent batches`, since a member stacks on nothing.
|
|
61
|
+
|
|
62
|
+
### Declaring the set
|
|
63
|
+
|
|
64
|
+
Each side names the other in its `**Constraints:**`, and `canon records validate plans` reports `fan-in-unmatched` wherever the two disagree.
|
|
65
|
+
|
|
66
|
+
- A member writes ``Fan-in member of `feature-<integrator>` ``, naming one integrator
|
|
67
|
+
- The integrator writes ``Fans in `feature-<a>`, `feature-<b>`, and `feature-<c>` ``, in the order it merges them
|
|
68
|
+
- The task's `Plan:` line cites every member and the integrator, since the merge archives only the plans that line cites and a live integrator listing archived members reports `fan-in-unmatched`
|
|
69
|
+
- The integrator is its own plan, since its writes, being the shared lists, the citer repoints, and the index regeneration, need a `**Files to touch:**` of their own
|
|
70
|
+
|
|
71
|
+
`canon tasks plan-answers` holds a member whose integrator is missing or does not list it with a `Fan-in set` entry. It holds the integrator with a `Fan-in pending` entry until every member branch is on origin.
|
|
72
|
+
|
|
73
|
+
### Dispatching the set
|
|
74
|
+
|
|
75
|
+
The members of one set dispatch together by design, since they share edge files and the integrator resolves those lines once. A shared file between members is never a hold.
|
|
76
|
+
|
|
77
|
+
- Hold the integrator on a dependency until `plan-answers` reads it launchable
|
|
78
|
+
- Re-read it after a fetch on each member's done message, which carries the branch and head commit in place of a pull request
|
|
79
|
+
- Never read a member with no pull request as stalled, since a member opens none
|
|
80
|
+
|
|
81
|
+
### What a member writes
|
|
82
|
+
|
|
83
|
+
A member builds, verifies, pushes its branch, and announces it done. A pushed branch is the done signal, so a member pushes once, at the end.
|
|
84
|
+
|
|
85
|
+
A member may edit the shared lists and regenerate the indexes on its own branch, since the pre-push check fails a member that deletes a path a gated list still names. The integrator resolves those lines once at merge.
|
|
86
|
+
|
|
87
|
+
### What the integrator writes
|
|
88
|
+
|
|
89
|
+
The integrator merges each member branch with `git merge --no-ff` in the order its bullet lists, resolving conflicts by the rules a rebase follows. It then writes its own files, regenerates the indexes once, and opens the pull request.
|
|
90
|
+
|
|
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
|
+
- 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
|
|
@@ -105,6 +105,8 @@ A constraint measured against work in flight expires when that work merges, as a
|
|
|
105
105
|
- Re-test a stamped constraint before honoring it with `git fetch origin main --quiet && git log <commit>..origin/main --oneline -- <the paths the constraint names>`. Any commit it reports means the track landed and the constraint is dead. Fetch and scope the log to the paths in the same command, since a stale ref reads a dead constraint as live and a squashed subject names no track.
|
|
106
106
|
- Read an unstamped constraint as unverified rather than as live, and confirm it against the open pull request list for the named paths.
|
|
107
107
|
|
|
108
|
+
A batch in a fan-in set declares its side in a constraint bullet, ``Fan-in member of `feature-<integrator>` `` on a member and ``Fans in `feature-<a>`, `feature-<b>` `` on the integrator. `batches.md` holds when to fan in, and `canon records validate plans` reports `fan-in-unmatched` for a set whose two sides disagree.
|
|
109
|
+
|
|
108
110
|
A constraint naming a `.canon/ready/` folder is a third shape beside the hold and the shared path. It makes that folder's files the verbatim source for the paths this plan's `**Files to touch:**` lists, per `ready.md`, so the executing session copies those paths rather than authoring them.
|
|
109
111
|
|
|
110
112
|
### Verification
|
|
@@ -45,4 +45,4 @@ The batching fails a fifth way. A folder holding thirty unread items put as thir
|
|
|
45
45
|
- Promoting an answered item onto the board, which is `task-board` and runs after the answers land
|
|
46
46
|
- The item format, the answer contract, and retrieval, which the `plan-intake` skill's `references/intake.md` owns and this skill cites
|
|
47
47
|
- The comparable answer slots in groundwork and feature plans, which carry their own contracts and are a separate measurement
|
|
48
|
-
- Deciding when to fire. The
|
|
48
|
+
- Deciding when to fire. The description states `Do NOT auto-trigger`, so answering is the operator's call rather than a description match.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plan-intake-answer
|
|
3
|
-
description: Walks an intake folder's unread items and puts them as batched questions in chat, writing each selection back onto the item it answers. Use when asked to "answer the intake", "work through the open items", "answer my intake questions", "go through the dump", or "let me answer these from here". Do NOT use to file a dump or write new items, which is `plan-intake`.
|
|
4
|
-
disable-model-invocation: true
|
|
3
|
+
description: Walks an intake folder's unread items and puts them as batched questions in chat, writing each selection back onto the item it answers. Use when asked to "answer the intake", "work through the open items", "answer my intake questions", "go through the dump", or "let me answer these from here". Do NOT use to file a dump or write new items, which is `plan-intake`. Do NOT auto-trigger.
|
|
5
4
|
metadata:
|
|
6
5
|
family: decide
|
|
7
6
|
---
|
|
@@ -21,6 +21,13 @@ Pop only when the stash ran. On a clean tree, which is what a run whose findings
|
|
|
21
21
|
|
|
22
22
|
Never merge `main` into the branch. The repository squash-merges, so a merge commit here reads as noise on the pull request.
|
|
23
23
|
|
|
24
|
+
### A branch carrying merges
|
|
25
|
+
|
|
26
|
+
A branch carrying merge commits of its own, such as a fan-in integrator's, rebases with `git rebase --rebase-merges origin/main` instead. A plain rebase drops the merges and replays every member commit in one line, which loses the one merge commit per member a reviewer reads.
|
|
27
|
+
|
|
28
|
+
- `--rebase-merges` re-runs each merge without the resolution it recorded, so resolve each again by the rules below
|
|
29
|
+
- `git config rerere.enabled true`, set before the first merge, replays a recorded resolution in place of resolving it again
|
|
30
|
+
|
|
24
31
|
## Resolving a conflict
|
|
25
32
|
|
|
26
33
|
These rules apply to a hunk from the rebase and a hunk from the stash pop alike:
|
|
@@ -148,6 +148,8 @@ A console error or an overflow is a finding. A probe result is a reading to conf
|
|
|
148
148
|
|
|
149
149
|
Read `${CLAUDE_SKILL_DIR}/references/post.md` for the heading rule, the body shape, the marker every body ends on, and the post command. Each box lands in the section its verdict names, and the `link` its Step 5 push returned goes as given into that box's own entry.
|
|
150
150
|
|
|
151
|
+
That file sends a pass closing under a prior `## UI review closed` at an older head on to `${CLAUDE_SKILL_DIR}/references/close-out.md`, which rewrites the standing close in place.
|
|
152
|
+
|
|
151
153
|
## Step 8: tick the passed boxes
|
|
152
154
|
|
|
153
155
|
Read `${CLAUDE_SKILL_DIR}/references/tick.md` once the verdict has posted, for how every box that passed is ticked through `canon pr tick` at the head this pass drove, and what a refusal reports.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Rewrite a repeated UI close in place
|
|
3
|
+
description: The branch of review-ui Step 7 taken when the prior verdict closed the review and this pass owes nothing, which replaces the standing close with a PUT rather than posting a second one
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# A UI close that repeats the standing one
|
|
7
|
+
|
|
8
|
+
Part of Step 7 of `review-ui`, reached only on the branch `post.md` names. The body shape, the scan, and the marker still come from `post.md`.
|
|
9
|
+
|
|
10
|
+
When the newest UI verdict from Step 1 reads `## UI review closed` at an older head and this pass closes too, the thread already holds this verdict. Replace the standing close rather than posting a second one beside it.
|
|
11
|
+
|
|
12
|
+
Only a closed prior is replaced. A prior `## UI review` stays in the thread when this pass closes, so the close posts normally. A pass that owes anything posts under `## UI review` normally too, whatever the prior read, since a rewrite there would hide a failed box behind a heading that reads closed.
|
|
13
|
+
|
|
14
|
+
Resolve the standing close's numeric id off the REST listing, keeping the newest match across every page:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
gh api --paginate 'repos/{owner}/{repo}/pulls/<number>/reviews?per_page=100' --jq '.[] | select((.body // "") | split("\n")[0] | rtrimstr("\r") == "## UI review closed") | .id' | tail -n 1
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Write the replacement
|
|
21
|
+
|
|
22
|
+
Write the full body for this pass to the file `post.md` names, with the heading, the count line, and the Passed table regenerated from what this pass drove. Patching only the marker would leave a count line and an address naming the old head.
|
|
23
|
+
|
|
24
|
+
Add one line under the count line, reading the prior head off the standing verdict's marker and the commit count off the compare:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
gh api repos/{owner}/{repo}/compare/<prior-head>...<head> --jq .total_commits
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```plaintext
|
|
31
|
+
Re-checked at `<short-sha>`, <N> commits since the prior pass.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Omit the line when the standing verdict carried no marker, since there is no prior head to count from. Run the scan `post.md` states against the file. Then record the instant just before the call, in UTC, as `<posted-at>`, since Step 9 keeps only the review-event runs created after it and the `PUT` fires one on `edited`. Replace the comment:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
date -u +%Y-%m-%dT%H:%M:%SZ
|
|
38
|
+
gh api -X PUT repos/{owner}/{repo}/pulls/<number>/reviews/<review-id> -F body=@<body-file>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## The marker
|
|
42
|
+
|
|
43
|
+
The body ends on `<!-- review-ui: head=<head> -->` at the full head this pass drove, with no line below it. `PUT` leaves the review's `commit.oid` pinned at the head the close was first posted against, and the marker is what `resolveUiScope`, the lift gate, and the orchestrator poll read in its place. A body missing it reads as the pinned stamp, so the lift refuses `ui-behind` and the poll reports the close as behind.
|
|
44
|
+
|
|
45
|
+
`PUT` keeps the comment's timestamp and its place in the thread, so the poll's age test reads a rewritten close as old, the same reading the code review accepts. A submitted review cannot be deleted, which is why this rewrites the standing close rather than replacing it with a fresh one.
|
|
46
|
+
|
|
47
|
+
The frames the standing verdict linked sit under their own pass stamp on the frames branch. The new body links this pass's frames, and the old images stay reachable by path.
|
|
@@ -104,12 +104,14 @@ The count line and the sections beneath it hold the same totals, and Step 10's p
|
|
|
104
104
|
|
|
105
105
|
## The marker
|
|
106
106
|
|
|
107
|
-
End every body on `<!-- review-ui: head=<head> -->`, carrying the full sha Step 1 resolved. The orchestrator's poll reads it off the last non-empty line to decide whether the verdict covers the current head, and the submission stamp cannot answer that, since GitHub stamps it with whatever the head is at the moment of posting.
|
|
107
|
+
End every body on `<!-- review-ui: head=<head> -->`, carrying the full sha Step 1 resolved. A body that replaces a standing close by `PUT` carries it too, at the head this pass drove. The orchestrator's poll reads it off the last non-empty line to decide whether the verdict covers the current head, and the submission stamp cannot answer that, since GitHub stamps it with whatever the head is at the moment of posting.
|
|
108
108
|
|
|
109
109
|
## Posting
|
|
110
110
|
|
|
111
111
|
Run `canon labels scan --body-file .canon/tmp/pr/review-ui/body-<number>-<short-sha>.md` against the body first, which applies the rule in `${CLAUDE_SKILL_DIR}/../git-pr/references/publish.md`, since nothing gates a file under `.canon/tmp/` before the review-event run does. Fix the body and scan again on a finding. Then post it as a review rather than an issue comment, since the poll reads the UI family off reviews:
|
|
112
112
|
|
|
113
|
+
Do not run the post below when the newest verdict from Step 1 reads `## UI review closed` at an older head and this pass closes too. That pass replaces the standing close, so read `${CLAUDE_SKILL_DIR}/references/close-out.md` instead of posting. Posting first leaves two closes, both naming a head.
|
|
114
|
+
|
|
113
115
|
Record the instant just before the post, in UTC, as `<posted-at>`. Step 9 keeps only review-event runs created after it, since the code review posts its own review on the same head and fires a run of the same gate.
|
|
114
116
|
|
|
115
117
|
```bash
|
|
@@ -45,6 +45,7 @@ Run `canon tasks plan-answers <plan> --json` and read `launchable` off the recor
|
|
|
45
45
|
|
|
46
46
|
- The command refuses for any other reason, or the record carries no `launchable` key: treat the row as unverified rather than clear, name what could not be read, and fall back to the human. A gate that reads nothing and proceeds is the gate not running.
|
|
47
47
|
- The same read also refuses a plan still staging its batches in one file with `**Batch N**` sub-headings. That reports as an entry in `open` labeled `Batch staging`, so a staged plan reads as `launchable: false` the same way an unanswered operator call does, and the row waits on a split rather than on the operator.
|
|
48
|
+
- A `Fan-in` entry holds a fan-in set, per `batches.md` in `plan-feature`
|
|
48
49
|
- An entry labeled `Stacked sibling` means the plan stacks on a live sibling it mostly shares files with, so the row waits on a fold into that sibling or on a `Judged apart from` constraint line, and not on the operator.
|
|
49
50
|
|
|
50
51
|
Branch on `launchable` rather than on the exit code, which a shell function wrapping `canon` can flatten to zero and so read a held row as a clear one.
|
|
@@ -28,6 +28,7 @@ The draft mark fails from both ends. On 2026-08-31 four sessions re-drafted a pu
|
|
|
28
28
|
- State the shared board as read-only for a building session and name what it does instead when a row is owed, since a task file with no row is a dropped task and placing the row is the controlling session's call
|
|
29
29
|
- Owe an announcement the moment the pull request opens, carrying the number, the branch, and the task, since that transition is the one interval only the worker can observe
|
|
30
30
|
- Carry a row the build turned up in the pull request announcement, and send it alone only when the session ends before a pull request opens, since the worker files no task file of its own and the channel is the row's only route
|
|
31
|
+
- Carry a fan-in member's branch and head commit in its done message in place of a pull request number, and end its role at the push, since a member opens no pull request and its integrator fixes what the merge surfaces
|
|
31
32
|
- Owe a message before a block becomes an interactive prompt, since a session already waiting on input never reaches the tool round an inbound message drains at
|
|
32
33
|
- Defer the addressee resolution to `session-relay` and keep only the operator ask and the last-rung inference, which discriminates here because a worker holds a feature branch and a controller does not, and which falls back to the roster and says so, since an operator's own launch names nobody
|
|
33
34
|
- Keep refusing a dispatcher a first-class move carrying its evidence, since the halts measured so far were correct and the cost of each fell where it belonged
|