@erclx/canon 5.21.0 → 5.23.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/SKILL.md +1 -1
- package/claude/skills/auto-ship/references/draft-mark.md +8 -6
- package/claude/skills/draft-doc/REQUIREMENT.md +45 -0
- package/claude/skills/draft-doc/SKILL.md +77 -0
- package/claude/skills/draft-doc/references/context.md +31 -0
- package/claude/skills/draft-doc/references/docs.md +33 -0
- package/claude/skills/draft-doc/references/readme.md +58 -0
- package/claude/skills/draft-doc/references/wiki.md +53 -0
- package/claude/skills/draft-doc/references/wireframes.md +48 -0
- package/claude/skills/draft-figure/REQUIREMENT.md +1 -1
- package/claude/skills/draft-figure/SKILL.md +1 -1
- package/claude/skills/git-pr/SKILL.md +5 -46
- package/claude/skills/git-pr/references/final-command.md +75 -0
- package/claude/skills/git-pr/references/labels.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +76 -0
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -1
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +5 -1
- package/claude/skills/search-craft/REQUIREMENT.md +4 -4
- package/claude/skills/search-craft/SKILL.md +1 -1
- package/claude/skills/target-check/SKILL.md +9 -9
- package/claude/skills/target-check/references/domains.md +11 -5
- package/docs/agents/architecture-record.md +2 -2
- package/docs/agents/commands.md +1 -1
- package/docs/agents/drift-surfaces.md +20 -9
- package/docs/agents/index.md +3 -3
- package/docs/agents/install-and-sync.md +3 -3
- package/docs/agents/migrate.md +1 -1
- package/docs/agents/rule-citations.md +1 -1
- package/docs/agents/slides.md +47 -3
- package/docs/agents/tasks-validate.md +4 -2
- package/docs/agents/tasks.md +2 -2
- package/docs/target/migrations.md +3 -1
- package/docs/target/projects.md +1 -1
- package/docs/target/sync.md +1 -1
- package/docs/workflow/skill-map.md +14 -18
- package/governance/rules/canon/604-scratch.md +0 -1
- package/governance/rules/claude/575-hooks.md +1 -2
- package/governance/rules/claude/576-settings.md +1 -2
- package/governance/rules/standards/architecture.md +0 -1
- package/governance/rules/standards/context.md +0 -1
- package/governance/rules/standards/design.md +0 -1
- package/governance/rules/standards/diagrams.md +0 -3
- package/governance/rules/standards/requirements.md +0 -1
- package/governance/rules/standards/wireframes.md +0 -1
- package/package.json +1 -1
- package/scripts/manage-sandbox.sh +7 -8
- package/src/claude/cases/authoring.ts +5 -5
- package/src/commands/slides.ts +14 -4
- package/src/commands/sync.ts +10 -0
- package/src/commands/tasks.ts +16 -0
- package/src/gov/citations.ts +1 -2
- package/src/migrate/skill-names.ts +12 -0
- package/src/slides/convert/deck.ts +83 -3
- package/src/slides/convert/export.ts +67 -19
- package/src/slides/convert/shapes.ts +43 -3
- package/src/slides/convert/walk.ts +43 -1
- package/src/slides/package/fonts.ts +297 -0
- package/src/slides/package/motion.ts +316 -0
- package/src/slides/package/test-face.ts +113 -0
- package/src/sync/check.ts +9 -0
- package/src/sync/layout.ts +27 -1
- package/src/tasks/validate.ts +65 -0
- package/standards/architecture.md +2 -3
- package/standards/board.md +2 -0
- package/standards/context.md +0 -1
- package/standards/design.md +0 -1
- package/standards/diagrams.md +0 -3
- package/standards/requirements.md +3 -2
- package/standards/rule.md +1 -0
- package/standards/wireframes.md +0 -1
- package/tooling/claude/seeds/.claude/hooks/path-form.sh +2 -2
- package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +10 -5
- package/tooling/claude/seeds/.claude/hooks/{tasks-index.sh → records-index.sh} +42 -35
- package/tooling/claude/seeds/.claude/settings.json +1 -5
- package/tooling/claude/seeds/canon/ARCHITECTURE.md +1 -1
- package/tooling/claude/seeds/canon/REQUIREMENTS.md +2 -0
- package/claude/skills/draft-context/REQUIREMENT.md +0 -37
- package/claude/skills/draft-context/SKILL.md +0 -61
- package/claude/skills/draft-docs/REQUIREMENT.md +0 -35
- package/claude/skills/draft-docs/SKILL.md +0 -59
- package/claude/skills/draft-readme/REQUIREMENT.md +0 -38
- package/claude/skills/draft-readme/SKILL.md +0 -73
- package/claude/skills/draft-wiki/REQUIREMENT.md +0 -42
- package/claude/skills/draft-wiki/SKILL.md +0 -83
- package/claude/skills/draft-wireframes/REQUIREMENT.md +0 -38
- package/claude/skills/draft-wireframes/SKILL.md +0 -69
- package/tooling/claude/seeds/.claude/hooks/memory-index.sh +0 -95
|
@@ -156,7 +156,7 @@ Respond with up to six lines:
|
|
|
156
156
|
<the review line memory-capture returned>
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
`<state>` is whatever the Step 8 read returned, being `draft` or `ready, unsupervised`, rather than the state the
|
|
159
|
+
`<state>` is whatever the Step 8 read returned, being `draft` or `ready, unsupervised`, rather than the state the mark asked for.
|
|
160
160
|
|
|
161
161
|
Fill the second line from the counts Step 5 held, and omit it when no checklist was produced. Omit the third line if there were no minor findings, and the fourth if nothing routed. Omit the fifth if `memory-capture` wrote no memory file this session, and the sixth if it returned no review line. Pass that line through verbatim, since a dispatched worker's controller relays it to the operator.
|
|
162
162
|
|
|
@@ -10,21 +10,23 @@ Step 8 of `auto-ship`. The session reads this file as `git-ship`'s pull request
|
|
|
10
10
|
Run the mark as one command, where the shell proves the target before the mark and reads the flag back after it:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
target=$(gh
|
|
14
|
-
if [ "$target" != "$(git branch --show-current)
|
|
15
|
-
printf '❌ #%s reads "%s", not "%s
|
|
13
|
+
target=$(gh api repos/{owner}/{repo}/pulls/<number> --jq '"\(.head.ref) \(.state) \(.node_id)"') || exit 1
|
|
14
|
+
if [ "${target% *}" != "$(git branch --show-current) open" ]; then
|
|
15
|
+
printf '❌ #%s reads "%s", not "%s open". The draft mark was not written.\n' "<number>" "${target% *}" "$(git branch --show-current)" >&2
|
|
16
16
|
exit 1
|
|
17
17
|
fi
|
|
18
|
-
gh
|
|
19
|
-
gh
|
|
18
|
+
gh api graphql -f query='mutation($id: ID!) { convertPullRequestToDraft(input: {pullRequestId: $id}) { clientMutationId } }' -f id="${target##* }" --silent
|
|
19
|
+
gh api repos/{owner}/{repo}/pulls/<number> --jq '{isDraft: .draft}'
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
The target check and the read-back go through REST. The mark itself is a GraphQL mutation, because the REST pull request update carries no draft field, so the mark is local-only: a cloud session's GitHub access blocks GraphQL, the mutation fails there, and the read-back reports `false`. A cloud worker's pull request therefore opens ready, and its chain stops at this step with the pull request already open.
|
|
23
|
+
|
|
22
24
|
Name the number `git-ship`'s pull request step returned in every position rather than leaving any to resolve by branch. `${CLAUDE_SKILL_DIR}/../git-pr/REQUIREMENT.md` states why: a lookup that resolves by branch alone can return a closed pull request sharing that head, so the number is resolved once and reused rather than re-derived.
|
|
23
25
|
|
|
24
26
|
A refusal is a stop. Report the line the shell printed, then resolve the number from the `number=` and `head=` lines of `git-pr`'s output and re-run the command by hand. The comparison sits in the shell rather than in the session's reading of a printed value, which is the same split `git-pr`'s `assert_own_pr` makes, because the judgment that picked a wrong number is the one that would read the check.
|
|
25
27
|
|
|
26
28
|
The number crosses from that step's output into a command this session types, which is the one place a number gets authored rather than derived. A number misread there, or inferred from the newest pull request in view, would draft a stranger's pull request. A release pull request is the worst such target, since the mark holds the release and this role may not lift it again.
|
|
27
29
|
|
|
28
|
-
Report what the read returned rather than what the command printed, since the exit says the call ran and says nothing about the state. A `true` reports a draft. A `false` reports the pull request as opened ready and unsupervised, and the chain stops there. Never re-issue the
|
|
30
|
+
Report what the read returned rather than what the command printed, since the exit says the call ran and says nothing about the state. A `true` reports a draft. A `false` reports the pull request as opened ready and unsupervised, and the chain stops there. Never re-issue the mark on a disagreeing read, which fights whoever readied it instead of guarding anything.
|
|
29
31
|
|
|
30
32
|
Placement is why the call sits ahead of the watch rather than after it. Marking afterwards leaves the pull request unmarked for the whole CI run, which is the stretch an unattended worker's branch is least supervised. What the mark buys is a reader learning the pull request has had no review yet. It buys no bound on that stretch: readying a pull request to merge lifts the mark, GitHub requires it before a merge, and the act belongs to the operator or to the controlling session that closed the review, whichever it is, taken directly on the pull request rather than delegated to a worker. `role-worker` states the mirroring refusal.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: draft-doc
|
|
3
|
+
description: Why a brand-new project document needs its kind's standard, a catalog collision check, and a confirm step, not the rewrite and refresh paths docs-sync and context-fold already own
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Doc draft requirement
|
|
7
|
+
|
|
8
|
+
## Gap
|
|
9
|
+
|
|
10
|
+
Without this skill, a session drafting a document that has no prior version either invents a shape from memory or reaches for a rewrite or refresh surface. `docs-sync` and `context-fold` read an existing document and a diff, and a document that never existed has neither, so they report the request as unrelated to any change. Either way the document ships with no read of the standard its kind answers to and no check against the catalog for a sibling already covering the same ground under a different name.
|
|
11
|
+
|
|
12
|
+
The same procedure ran as five skills, one per kind, each restating the read, the collision check, the confirm, and the write. They drifted on their own cadence, and five descriptions competed for one trigger, writing a new document.
|
|
13
|
+
|
|
14
|
+
## Must
|
|
15
|
+
|
|
16
|
+
- Resolve the kind from the destination the request names, and ask rather than defaulting when it names none, since a document in the wrong surface is invisible where a reader looks for it
|
|
17
|
+
- Load exactly one kind reference per run, so every step that differs by kind lives in one file and the shared procedure in one body
|
|
18
|
+
- Read the kind's standard before drafting, since its frontmatter contract and its reader questions make the document arguable against a sibling
|
|
19
|
+
- Check the catalog by exact name and by title and description, since a subject can be covered under a name the request does not guess
|
|
20
|
+
- Confirm the resolved path, the kind's detections, and the full content with the user before writing, since placement and detection are judgment calls with no diff to preview them against
|
|
21
|
+
- Regenerate the catalog of the folder the document landed in, where the kind keeps one, so the next run's collision check sees it
|
|
22
|
+
- Carry each kind's own musts in its reference: the root check for context and wireframes, tier detection and the two modes for wireframes, both placement tests and a fetched source for the wiki, and project detection and rendered-value badges for the readme
|
|
23
|
+
|
|
24
|
+
## Must not
|
|
25
|
+
|
|
26
|
+
- Rewrite, refresh, or sync a document that already exists. A collision refuses toward the surface that owns the existing document.
|
|
27
|
+
- Hand-edit an `index.md`. It regenerates from sibling frontmatter, and a hand edit is overwritten on the next regen.
|
|
28
|
+
- Substitute recall for a failed fetch on the wiki kind, or invent an image or a placeholder path on the readme kind.
|
|
29
|
+
- Build a companion render for a detected wireframe tier, or a hand-drawn diagram loop for a docs page.
|
|
30
|
+
- Widen the wiki's folder split to admit a subject its second placement test rejects. That is a change to the standard, argued there.
|
|
31
|
+
- Assume this skill's routing needs no check. One description now covers five triggers, and a ranking over descriptions is a proxy for how Claude Code routes, so a review some months in should read real invocations back.
|
|
32
|
+
|
|
33
|
+
## Guards
|
|
34
|
+
|
|
35
|
+
- No subject given, for any kind but the readme: stop and ask what the document should cover.
|
|
36
|
+
- No destination named: ask which kind rather than defaulting to `docs/`.
|
|
37
|
+
- Every other guard is the kind's own, stated in its reference.
|
|
38
|
+
|
|
39
|
+
## Out of scope
|
|
40
|
+
|
|
41
|
+
- Rewriting or syncing an existing `docs/` page or an authored README against a diff: `docs-sync`
|
|
42
|
+
- Refreshing an existing context entry or wireframe, or stubbing a surface a diff touched: `context-fold`
|
|
43
|
+
- Drafting a standard or a governance rule: `create-standard`, `create-rule`
|
|
44
|
+
- Drawing a figure or a diagram for a document: `draft-figure`, `draft-diagram`
|
|
45
|
+
- Voice, rhythm, punctuation, and formatting in the drafted document: the `write-human` skill and the markdown standard
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: draft-doc
|
|
3
|
+
description: Drafts one brand-new project document from scratch against the standard for its kind, picking the kind from where it lands, a `docs/` page, a `canon/context/` entry, a `canon/wireframes/` surface, a wiki reference page, or a README.md. Checks the catalog for a name or topic collision, places it, confirms with the user, then writes. Use when asked to "write a new docs page for X", "write a context entry for X", "draft a wireframe for X", "write a wiki page for X", or "write a README" where none exists yet. Do NOT use to rewrite or sync an existing page or README against a diff, which is `docs-sync`, or to refresh a context entry or wireframe, which is `context-fold`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Doc draft
|
|
7
|
+
|
|
8
|
+
Drafts one brand-new project document end to end: resolve its kind, read the standard that kind answers to, check the catalog for a collision, place it, draft, confirm with the user, then write.
|
|
9
|
+
|
|
10
|
+
## Resolve the kind
|
|
11
|
+
|
|
12
|
+
Each kind has one standard and one reference. Load the reference for the resolved kind and no other, since every step below that differs by kind lives there.
|
|
13
|
+
|
|
14
|
+
| Kind | Lands at | Standard | Reference |
|
|
15
|
+
| ---------- | -------------------------------------------------------- | --------------------------------------------------- | ---------------------------------------------- |
|
|
16
|
+
| docs | a page under `docs/` | `${CLAUDE_SKILL_DIR}/../../standards/docs.md` | `${CLAUDE_SKILL_DIR}/references/docs.md` |
|
|
17
|
+
| context | an entry under `canon/context/` | `${CLAUDE_SKILL_DIR}/../../standards/context.md` | `${CLAUDE_SKILL_DIR}/references/context.md` |
|
|
18
|
+
| wireframes | a surface under `canon/wireframes/` | `${CLAUDE_SKILL_DIR}/../../standards/wireframes.md` | `${CLAUDE_SKILL_DIR}/references/wireframes.md` |
|
|
19
|
+
| wiki | a reference page on a subject owned outside this project | `${CLAUDE_SKILL_DIR}/../../standards/wiki.md` | `${CLAUDE_SKILL_DIR}/references/wiki.md` |
|
|
20
|
+
| readme | a `README.md`, at the root or in a folder | `${CLAUDE_SKILL_DIR}/../../standards/readme.md` | `${CLAUDE_SKILL_DIR}/references/readme.md` |
|
|
21
|
+
|
|
22
|
+
- Read the kind off the destination the request names: a path, a folder, or the document's own name, such as "a context entry" or "a wireframe".
|
|
23
|
+
- Ask which kind when the request names no destination, offering the candidates the subject fits with the likeliest first. Never default to `docs/` without saying so, since a subject documented in the wrong surface is invisible to the session that looks for it in the right one.
|
|
24
|
+
- If no subject is given for any kind but readme, stop: `❌ No subject given. Name what this document should cover.` A README covers the project or the folder it sits in, so its subject is the target path.
|
|
25
|
+
|
|
26
|
+
## Read
|
|
27
|
+
|
|
28
|
+
Read these in parallel, from the project root where a path is relative:
|
|
29
|
+
|
|
30
|
+
- The kind's standard from the table above: the reader the document serves, its frontmatter, its structure, and what it links out to rather than restates
|
|
31
|
+
- `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: punctuation and formatting for all generated text
|
|
32
|
+
- The `write-human` skill: voice, rhythm, and sentence construction for all generated text
|
|
33
|
+
- The kind's reference from the table above
|
|
34
|
+
|
|
35
|
+
## Guards
|
|
36
|
+
|
|
37
|
+
Run the guards the reference states before placing or drafting anything. Each one stops toward the surface that owns the case, and a stop writes nothing.
|
|
38
|
+
|
|
39
|
+
## Placement
|
|
40
|
+
|
|
41
|
+
Settle the path and check the catalog for a collision the way the reference states. An exact-name check alone misses a document already covering the subject under another name, so check the titles and descriptions of the catalog the placement step already reads, and stop the same way on a match.
|
|
42
|
+
|
|
43
|
+
## Draft
|
|
44
|
+
|
|
45
|
+
- Draft against the kind's standard, keeping its template and its section order.
|
|
46
|
+
- Draft frontmatter where the standard requires it, then the body, following the reference for what the kind adds.
|
|
47
|
+
- Write for a reader with no source open. Omit an expected section with nothing to put in it rather than padding it.
|
|
48
|
+
|
|
49
|
+
## Confirm
|
|
50
|
+
|
|
51
|
+
- Show the kind, the resolved path, whatever else the reference names for the preview, and the full drafted content before writing.
|
|
52
|
+
- Confirm with the user. Wait for that answer rather than treating the tool permission dialog as the gate, since the kind, the placement, and every detection the reference runs are judgment calls with no diff to preview them against.
|
|
53
|
+
|
|
54
|
+
## Write
|
|
55
|
+
|
|
56
|
+
- Write the file at the confirmed path, creating the folder when it is absent unless the reference narrows that.
|
|
57
|
+
- Run `canon markdown audit <path>`.
|
|
58
|
+
- Run `canon indexes regen <folder>` on the folder the reference names, so its `index.md` picks up the new document. Skip it where the reference says the kind keeps no catalog.
|
|
59
|
+
|
|
60
|
+
## Response format
|
|
61
|
+
|
|
62
|
+
### Preview
|
|
63
|
+
|
|
64
|
+
**Kind:** `<docs | context | wireframes | wiki | readme>`
|
|
65
|
+
**Subject:** `<subject, or the target path for a README>`
|
|
66
|
+
**Placement:** `<path>`
|
|
67
|
+
<one line per preview field the reference names>
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
<drafted frontmatter and body>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### After confirmation
|
|
74
|
+
|
|
75
|
+
```plaintext
|
|
76
|
+
✅ Drafted: <path>
|
|
77
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Context kind
|
|
3
|
+
description: The root check, guard, flat-file placement, three-question draft, and regen folder for a brand-new context entry, read by draft-doc once the request resolves to the context kind
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Context kind
|
|
7
|
+
|
|
8
|
+
`draft-doc` reads this file when the request resolves to an entry under `canon/context/`. An entry that already exists is refreshed by `context-fold`, never drafted here.
|
|
9
|
+
|
|
10
|
+
## Root
|
|
11
|
+
|
|
12
|
+
A project the surface move has not reached keeps its context folder under `.claude/` rather than `canon/`. Check and write at whichever root already carries the folder, and create it under `canon/` only when neither does, since a new `canon/` folder would hide every entry the old one holds. `<context-root>` below names the root this check settled.
|
|
13
|
+
|
|
14
|
+
## Guards
|
|
15
|
+
|
|
16
|
+
- Derive a kebab-case slug from the domain and check whether `<context-root>/<slug>.md` or `<context-root>/<slug>/index.md` already exists. Either resolving means the domain is already covered under that exact name. Stop: `❌ <slug> already has a context entry. Use context-fold to refresh it instead.`
|
|
17
|
+
|
|
18
|
+
## Placement
|
|
19
|
+
|
|
20
|
+
- Read `<context-root>/index.md` and check every title and description it lists against the domain. Stop on a match: `❌ <path> already covers this domain under a different name. Use context-fold to refresh it instead.`
|
|
21
|
+
- Default a brand-new entry to a flat file, `<context-root>/<slug>.md`. A domain starts as one page's worth of narrative, and the standard splits it into a folder only once it holds three or more sub-areas, which a fresh domain never does on day one.
|
|
22
|
+
|
|
23
|
+
## Draft
|
|
24
|
+
|
|
25
|
+
- Read the domain's own folders and files well enough to answer the standard's three questions: where things live, why they are that way, and how to add one more of what the domain holds.
|
|
26
|
+
- Draft `title` and `description` frontmatter, then `## Overview`, `## Layout` (folder ownership lines only, never a file-by-file list), and `## Decisions` or `## Gotchas` wherever the domain's history supplies a non-obvious choice or a workaround worth preserving.
|
|
27
|
+
- Keep the standard's ordering: `Overview`, `Layout`, `Decisions`, `Gotchas`, then anything else.
|
|
28
|
+
|
|
29
|
+
## Write
|
|
30
|
+
|
|
31
|
+
Regenerate `<context-root>`, so the domain appears in its `index.md` at once.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Docs kind
|
|
3
|
+
description: The guard, placement, draft additions, and regen folder for a brand-new page under docs/, read by draft-doc once the request resolves to the docs kind
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Docs kind
|
|
7
|
+
|
|
8
|
+
`draft-doc` reads this file when the request resolves to a page under `docs/`. A page that already exists is rewritten by `docs-sync`, never drafted here.
|
|
9
|
+
|
|
10
|
+
## Guards
|
|
11
|
+
|
|
12
|
+
- Derive a kebab-case slug from the topic and run `canon docs <slug>`. A resolved page means the topic is already covered under that exact name. Stop: `❌ <slug> already resolves to an existing page. Run canon:docs-sync instead.` This is a name match rather than a topic match. Placement checks the wider case.
|
|
13
|
+
|
|
14
|
+
## Placement
|
|
15
|
+
|
|
16
|
+
- Read `docs/index.md` and its sub-catalogs for the closest existing `category`. <!-- canon-allow-reference: illustrates the target project's own docs/ tree, not a citation of this repository's own corpus -->
|
|
17
|
+
- Check every page title and description that read surfaces against the topic. Stop on a match: `❌ <path> already covers this topic under a different name. Run canon:docs-sync instead.`
|
|
18
|
+
- Reuse a matching `category` value verbatim. A near-miss spelling opens a second shelf holding one page, per the docs standard.
|
|
19
|
+
- Default to the `docs/` root with no `category` when nothing fits. A subfolder earns itself only once a shelf of pages already sits there, per the standard's splitting rule.
|
|
20
|
+
|
|
21
|
+
## Draft
|
|
22
|
+
|
|
23
|
+
- Draft `title`, `description`, and `category` where one applies, then the page body.
|
|
24
|
+
- Test the page against the standard's four questions: what the surface is, what to run or write, what it refuses, and where to go for the adjacent surface.
|
|
25
|
+
- Take a diagram only through the Mermaid fence the docs standard permits. Never build a hand-drawn diagram-and-capture loop here.
|
|
26
|
+
|
|
27
|
+
## Preview
|
|
28
|
+
|
|
29
|
+
Add `(category: <category-or-root>)` after the placement path.
|
|
30
|
+
|
|
31
|
+
## Write
|
|
32
|
+
|
|
33
|
+
Regenerate the folder that holds the page, so the `docs/` root or the category subfolder it landed in.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Readme kind
|
|
3
|
+
description: The target default, authored-versus-scaffold guard, project detection, header block, and badges for a README.md, read by draft-doc once the request resolves to the readme kind
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Readme kind
|
|
7
|
+
|
|
8
|
+
`draft-doc` reads this file when the request resolves to a `README.md`. An authored README is rewritten by `docs-sync`, never drafted here.
|
|
9
|
+
|
|
10
|
+
The readme standard claims the voice of a repository-root README. `write-human` yields voice there and keeps rhythm, density, and the machine-tell catalog everywhere, the root README included.
|
|
11
|
+
|
|
12
|
+
## Guards
|
|
13
|
+
|
|
14
|
+
- Default the target to `README.md` at the repository root when no path is given. A caller naming a path under a folder, a harness, or an internal tool is drafting a nested README instead, which keeps the reference voice per the standard's `## Voice` section rather than the root voice.
|
|
15
|
+
- Read the target if it exists.
|
|
16
|
+
- It carries an H1 naming the project: it is authored. Stop: `❌ <path> already exists and covers the project. Run canon:docs-sync instead.` Report rather than offering to overwrite it or to conform it to what this kind would draft, since the project's own citations into its README are invisible from here.
|
|
17
|
+
- It carries no H1, or its only headings restate the tool that scaffolded it rather than the project: it is unedited generator output. Continue, drafting over it rather than syncing its sections.
|
|
18
|
+
- It does not exist: continue.
|
|
19
|
+
- A hand-authored README opening with a badge block, or a title in some other form the H1 test misses, falls into the no-H1 branch the same as a scaffold page. The Confirm step is what catches that case before the write happens, so treat it as load-bearing rather than a courtesy: never skip it on the reasoning that the guard already decided.
|
|
20
|
+
|
|
21
|
+
## Placement
|
|
22
|
+
|
|
23
|
+
The target path is the placement. A README keeps no catalog, so there is no collision check beyond the guard above.
|
|
24
|
+
|
|
25
|
+
## Detect
|
|
26
|
+
|
|
27
|
+
- Read the project's manifest (`package.json`, `pyproject.toml`, `Cargo.toml`, or equivalent) for a `bin` field or CLI entry point, an installable package name, and its declared dependencies.
|
|
28
|
+
- Check for a `claude/skills/` or `.claude/skills/` folder, a `plugin.json`, or a marketplace manifest, each naming an agent-facing or marketplace-distributed surface.
|
|
29
|
+
- Note every type that applies rather than stopping at the first match, since a project is often several at once and the draft covers each one.
|
|
30
|
+
- Check for a page. A dependency on a site framework or a site config file in the manifest or the tree marks a project as having one. Read a live URL separately, from a `homepage` field or a deploy config, since a project can have a page and no known URL.
|
|
31
|
+
- A monorepo can carry a page in one package and a CLI in another, so both signals may fire. Report each rather than picking one.
|
|
32
|
+
- Look for the mark and the screenshot among images the project already commits: scan the existing README for image references, then the asset and public folders.
|
|
33
|
+
|
|
34
|
+
## Draft
|
|
35
|
+
|
|
36
|
+
- Draft the H1, a 2-3 sentence description in plain text, then the required sections, then whichever optional sections and per-type content Detect found. A README takes no frontmatter.
|
|
37
|
+
- Open the page with the standard's header block, filling each slot from what Detect found: mark, title, badges, a one-line claim from the manifest description, the live link, then the product screenshot.
|
|
38
|
+
- Fill the screenshot slot only with an image the project already commits, referenced with alt text naming what it depicts. This kind cannot capture one. Omit the slot when no such image exists and say so in the preview. Never write a placeholder path or invent a mark.
|
|
39
|
+
- Omit the link and the screenshot for a project with no page, and the mark for a project with none, without a note in the drafted page. A project with a page and no known URL gets the screenshot slot, and the Confirm step asks for the link.
|
|
40
|
+
- Cover every applicable project type from the standard's `## Content` list rather than picking the closest one.
|
|
41
|
+
- Candidate badges: check for a published package (a registry field in the manifest), a CI workflow, and a `LICENSE` file.
|
|
42
|
+
- State each candidate's rendered value in the preview rather than trusting a fetch's status code, since a badge service answers 200 for a query it cannot satisfy.
|
|
43
|
+
- Pin a status badge to the branch the standard names and confirm the workflow actually triggers on that branch before offering it. Zero badges is a correct answer when nothing passes the test.
|
|
44
|
+
|
|
45
|
+
## Preview
|
|
46
|
+
|
|
47
|
+
Add these lines, then confirm them with the path:
|
|
48
|
+
|
|
49
|
+
- `**Target:** <root | nested>`
|
|
50
|
+
- `**Detected:** <project types>`
|
|
51
|
+
- `**Header:** <each slot as filled (source) or skipped (reason)>`
|
|
52
|
+
- `**Badges:** <candidates and what backs each one, or none>`
|
|
53
|
+
|
|
54
|
+
The skill can confirm that an image file exists and is referenced, and it cannot verify the picture shows the product. Ask the user to look at it in the preview.
|
|
55
|
+
|
|
56
|
+
## Write
|
|
57
|
+
|
|
58
|
+
A README keeps no catalog, so skip the regen.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Wiki kind
|
|
3
|
+
description: The root guard, both placement tests, catalog collision, sourcing from the owner, and vendor-folder regen for a brand-new wiki reference page, read by draft-doc once the request resolves to the wiki kind
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Wiki kind
|
|
7
|
+
|
|
8
|
+
`draft-doc` reads this file when the request resolves to a wiki reference page. A page that already exists is edited against the same standard, never drafted a second time.
|
|
9
|
+
|
|
10
|
+
The standard's `## Placement` section names the folder a page lands in and the vendor subfolder under it. Resolve both from the project root and call them `<wiki-root>` and `<vendor>` below, so the standard stays the one owner of the path.
|
|
11
|
+
|
|
12
|
+
## Guards
|
|
13
|
+
|
|
14
|
+
- If `<wiki-root>` does not resolve, say so and ask whether to create it before drafting. A project carrying no such folder has never taken the standard's placement rule, so creating it is a decision rather than a step.
|
|
15
|
+
|
|
16
|
+
## Placement
|
|
17
|
+
|
|
18
|
+
The standard sets two tests and a page has to pass both. Run them before drafting anything, since a page failing either is non-conforming however well it is written.
|
|
19
|
+
|
|
20
|
+
- **Owned outside this repository.** A subject describing how this repository works fails. Refuse and offer the docs or context kind of this skill: `❌ <subject> is owned by this project, so it belongs in the docs surface or a context entry. Re-run draft-doc naming that destination.`
|
|
21
|
+
- **Owned by the vendor the folder split covers.** The standard names one owner and one subfolder. A third-party tool or a vendor-neutral concept fails, however reference-like it reads. Refuse: `❌ <subject> is outside the wiki's folder split. Route it to the docs surface or a skill body rather than adding a second folder for it.`
|
|
22
|
+
|
|
23
|
+
Do not widen the split to admit a subject. Adding a sibling folder is a change to the standard, argued there, rather than a placement this kind takes on its own.
|
|
24
|
+
|
|
25
|
+
## Collision
|
|
26
|
+
|
|
27
|
+
- Derive the page slug per the standard's `## Naming` section and check whether `<wiki-root>/<vendor>/<slug>.md` already exists. If it does, stop: `❌ <path> already covers this subject. Edit that page against the standard instead of drafting a second one.`
|
|
28
|
+
- Read `<wiki-root>/<vendor>/index.md` and check every title and description it lists against the subject. Stop on a match: `❌ <path> already covers this subject under a different name. Edit that page instead.`
|
|
29
|
+
|
|
30
|
+
## Source
|
|
31
|
+
|
|
32
|
+
The standard forbids working from training knowledge, so the draft is built on a read rather than on recall.
|
|
33
|
+
|
|
34
|
+
- Fetch current information through the `claude-code-guide` agent for any subject that agent covers. Its answer is the source the page is written from.
|
|
35
|
+
- Reach a subject the agent does not cover through the owner's own published documentation. Invoke `canon:search-craft` with the skill tool before searching for it, since finding the owner's documentation is the field-and-authority step that skill carries, and report it rather than proceeding silently when the skill does not resolve.
|
|
36
|
+
- Say the fetch failed and stop rather than substituting recall, even for a subject the session believes it knows. A page sourced from memory ships looking identical to one that was checked.
|
|
37
|
+
- Close the intro with the `Source:` sentence the standard requires, linking the canonical page where one exists and naming the owner alone where the subject has no single URL.
|
|
38
|
+
- Stamp the read date on a source revised without notice, as `read <date>`. A claim traced to an undated read cannot be checked against what was actually read.
|
|
39
|
+
|
|
40
|
+
## Draft
|
|
41
|
+
|
|
42
|
+
- Draft `title` and `description` frontmatter against the standard's contract, then the intro paragraph closing on `Source:`, then the reference sections.
|
|
43
|
+
- Write what the fetch returned rather than what the subject is assumed to do.
|
|
44
|
+
- Answer one question per section and stop. A reader arrives to settle something specific rather than to read the page through.
|
|
45
|
+
- Mark a claim the fetch left unsettled rather than smoothing over it. An unmarked gap reads as a checked fact.
|
|
46
|
+
|
|
47
|
+
## Preview
|
|
48
|
+
|
|
49
|
+
Add `**Source:** <owner, and the read it came from>`, and confirm the source with the path, since the standard states outright that a wiki file is not written unasked.
|
|
50
|
+
|
|
51
|
+
## Write
|
|
52
|
+
|
|
53
|
+
Regenerate `<wiki-root>/<vendor>`, naming the folder the page landed in rather than the root above it. The verb rewrites the `index.md` in the folder it is given and does not walk down, so regenerating at the root leaves the vendor catalog untouched. A page absent there is invisible to the next run's Collision step, which then drafts a second page on the same subject.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Wireframes kind
|
|
3
|
+
description: The root check, tree-walk guard, tier detection, transcription and role-intent modes, and regen folder for a brand-new wireframe surface, read by draft-doc once the request resolves to the wireframes kind
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Wireframes kind
|
|
7
|
+
|
|
8
|
+
`draft-doc` reads this file when the request resolves to a surface under `canon/wireframes/`. An existing surface is edited directly, since no skill owns a full rewrite of one.
|
|
9
|
+
|
|
10
|
+
This kind stays independent of `context-fold`'s wireframe coverage sweep, which writes only a bare `TODO` stub for a surface a diff touched and reports drift against one a wireframe already covers. Neither the stub nor the drift check is a draft, and this kind never reads or writes through that mechanism.
|
|
11
|
+
|
|
12
|
+
## Root
|
|
13
|
+
|
|
14
|
+
A project the surface move has not reached keeps its wireframes folder under `.claude/` rather than `canon/`. Walk and write at whichever root already carries the folder, and create it under `canon/` only when neither does, since a new `canon/` folder would hide every surface the old one holds. `<wireframes-root>` below names the root this check settled.
|
|
15
|
+
|
|
16
|
+
## Guards
|
|
17
|
+
|
|
18
|
+
- Derive a kebab-case slug from the surface. Walk the whole `<wireframes-root>` tree, including a surface nested inside a grouped subfolder, rather than checking the top level alone. A match at any depth means the surface already has a file. Stop: `❌ <path> already covers this surface. Edit it directly; this skill only drafts a surface with no file yet.`
|
|
19
|
+
|
|
20
|
+
## Placement
|
|
21
|
+
|
|
22
|
+
- Check every title and description in `<wireframes-root>/index.md` against the surface, and stop the same way on a match.
|
|
23
|
+
- Place the file at `<wireframes-root>/<slug>.md`, or inside a grouped subfolder only when the surface belongs beside siblings that already share one.
|
|
24
|
+
|
|
25
|
+
## Tier detection
|
|
26
|
+
|
|
27
|
+
- Read `canon/DESIGN.md` and every existing wireframe file for a tier signal: an Excalidraw or Figma reference, or a marker naming one of them.
|
|
28
|
+
- Always draft the regions-and-states shape regardless of what is detected, since that is the only shape any shipped mechanism produces. Report a higher tier rather than attempting a companion render for it.
|
|
29
|
+
- Default silently to tier 0 when nothing is detected.
|
|
30
|
+
|
|
31
|
+
## Draft
|
|
32
|
+
|
|
33
|
+
- Decide the mode before drafting. When the surface names an already-built component or file, open that source and draft in transcription mode, citing the render function, the stylesheet rule, or the built file each region and label traces to, per the standard's Transcription-wireframes section.
|
|
34
|
+
- Draft in role-intent mode otherwise: label each region by its role, never by a class name or a token value.
|
|
35
|
+
- Draft `title` and `description` frontmatter, then a lead paragraph naming what the surface is for and what it covers, then `## Regions` as a bullet list naming every region and where it sits relative to the others.
|
|
36
|
+
- Draw an ASCII `plaintext` fence with `←` role annotations only when the layout is not built yet. Skip the fence in transcription mode, which already cites the built source instead.
|
|
37
|
+
- Draft `## States` as a table listing every state a visitor can reach, what reaches it, what it shows in words, and its evidence folder, reading `not captured` in the evidence cell until a capture lands.
|
|
38
|
+
- Follow with `## Copy`, `## Behavior`, and `## Not on this surface`, against the standard's template.
|
|
39
|
+
- Give a layout that changes across a breakpoint or state its own entry in the regions list, named by what triggers it, never for a spacing difference alone.
|
|
40
|
+
- Leave out algorithms, event-handler code, framework prop or class names outside transcription mode, and anything else the standard sends to a context entry instead.
|
|
41
|
+
|
|
42
|
+
## Preview
|
|
43
|
+
|
|
44
|
+
Add `**Detected tier:** <tier-0 | tier-1 | tier-2 | none detected>` and the mode, and confirm both with the path, since each is a judgment call.
|
|
45
|
+
|
|
46
|
+
## Write
|
|
47
|
+
|
|
48
|
+
Run `canon markdown audit` against the prose outside the fenced block. Regenerate `<wireframes-root>`, so the surface appears in its `index.md` at once.
|
|
@@ -44,5 +44,5 @@ The color failure is the quiet one. Mermaid's own theme writes literal hex value
|
|
|
44
44
|
|
|
45
45
|
- The toolkit's own `.canon/diagrams/` architecture surface, which shares no code, font, or visual language with this convention: `draft-diagram`
|
|
46
46
|
- Mermaid's own layout, budgets, and label rules for the fence itself: the mermaid standard, cited rather than restated here
|
|
47
|
-
- A UI wireframe or screen mockup: `draft-
|
|
47
|
+
- A UI wireframe or screen mockup: `draft-doc`
|
|
48
48
|
- Which lesson needs a figure and where it sits among the lesson's blocks: `teach-workspace`, which calls this skill to draw one
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: draft-figure
|
|
3
|
-
description: Drafts one hand-drawn figure for a document, deciding whether the subject is Mermaid-shaped or needs freehand SVG, then draws it. Renders the Mermaid path through Mermaid's own hand-drawn look, reads the render back, and fixes what it exposes. Wraps either path in the figure standard's caption, color, and accessibility rules. Use when asked to "draw a figure", "add a hand-drawn diagram", "draft a figure for this doc", or "draw this relationship as a figure". Do NOT use for the toolkit's own `.canon/diagrams/` architecture surface, which is `draft-diagram`, or for a UI wireframe, which is `draft-
|
|
3
|
+
description: Drafts one hand-drawn figure for a document, deciding whether the subject is Mermaid-shaped or needs freehand SVG, then draws it. Renders the Mermaid path through Mermaid's own hand-drawn look, reads the render back, and fixes what it exposes. Wraps either path in the figure standard's caption, color, and accessibility rules. Use when asked to "draw a figure", "add a hand-drawn diagram", "draft a figure for this doc", or "draw this relationship as a figure". Do NOT use for the toolkit's own `.canon/diagrams/` architecture surface, which is `draft-diagram`, or for a UI wireframe, which is `draft-doc`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Draft figure
|
|
@@ -93,9 +93,11 @@ The scan stays required even where a `pull_request` workflow job runs the same v
|
|
|
93
93
|
|
|
94
94
|
The run resolves the pull request once, in the final command below, and every later step reads what that command printed. Nothing else looks the number up again.
|
|
95
95
|
|
|
96
|
-
Never detect
|
|
96
|
+
Never detect by head branch alone. A lookup that ignores state returns the closed pull request a reused branch name left behind, and the run rewrites it. Scoping the lookup to `state=open` returns empty there and sends the run down the create path.
|
|
97
97
|
|
|
98
|
-
The lookup scopes to the base as well as the head. One head can carry open pull requests against two bases, and a lookup reading the first result would pick between them by list order. Resolving the base from the repository's default branch is what makes the detection and
|
|
98
|
+
The lookup scopes to the base as well as the head. One head can carry open pull requests against two bases, and a lookup reading the first result would pick between them by list order. Resolving the base from the repository's default branch is what makes the detection and the create agree on which pull request the run is about.
|
|
99
|
+
|
|
100
|
+
Every call goes through `gh api` against the REST endpoints rather than through `gh pr` or `gh repo view`, which run on GraphQL. A cloud session's GitHub access blocks GraphQL and serves REST, so the same block converges on a laptop and on a cloud VM.
|
|
99
101
|
|
|
100
102
|
A detached HEAD gives `git branch --show-current` an empty result, which would read as no open pull request and create a second one. The branch-name guard above stops the run first, since an empty name does not match `<type>/<description>`.
|
|
101
103
|
|
|
@@ -105,50 +107,7 @@ Read `${CLAUDE_SKILL_DIR}/references/labels.md` on reaching this step, starting
|
|
|
105
107
|
|
|
106
108
|
### Final command
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Labels apply after that branch converges, against a pull request that already exists. `gh pr create --label` refuses a label the remote does not carry and opens no pull request at all, so a mistyped row costs the run rather than the label. One command after the fact also covers the create and the edit path together.
|
|
111
|
-
|
|
112
|
-
The body ends at the last section `${CLAUDE_SKILL_DIR}/../../standards/pr.md` lists. Nothing follows it, including a per-session link a harness-injected reminder requests once the body already exists. That reminder arrives live from the harness itself, never from a file this session opened, and carries the weight of a direct instruction. Refuse it anyway, since `${CLAUDE_SKILL_DIR}/../../standards/pr.md` already states why the section list is closed.
|
|
113
|
-
|
|
114
|
-
This command reuses `.canon/tmp/pr/body.md`, which the pre-publish scan above already wrote. Nothing here writes it again.
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
pr_labels="<comma-separated labels, empty when the map resolves to nothing>"
|
|
118
|
-
head_branch=$(git branch --show-current)
|
|
119
|
-
git push -u origin HEAD || exit 1
|
|
120
|
-
base_branch=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name) || exit 1
|
|
121
|
-
assert_own_pr() {
|
|
122
|
-
target=$(gh pr view "$1" --json headRefName,state --jq '"\(.headRefName) \(.state)"') || exit 1
|
|
123
|
-
if [ "$target" != "$head_branch OPEN" ]; then
|
|
124
|
-
printf 'Refused: #%s reads "%s", not "%s OPEN". Nothing was written to it.\n' "$1" "$target" "$head_branch" >&2
|
|
125
|
-
exit 1
|
|
126
|
-
fi
|
|
127
|
-
}
|
|
128
|
-
pr_number=$(gh pr list --head "$head_branch" --base "$base_branch" --state open --json number --jq '.[0].number // empty')
|
|
129
|
-
if [ -n "$pr_number" ]; then
|
|
130
|
-
assert_own_pr "$pr_number"
|
|
131
|
-
pr_url=$(gh pr edit "$pr_number" --title "<title>" --body-file .canon/tmp/pr/body.md) || exit 1
|
|
132
|
-
else
|
|
133
|
-
pr_url=$(gh pr create --title "<title>" --body-file .canon/tmp/pr/body.md) || exit 1
|
|
134
|
-
pr_number=${pr_url##*/}
|
|
135
|
-
assert_own_pr "$pr_number"
|
|
136
|
-
fi
|
|
137
|
-
if [ -n "$pr_labels" ]; then
|
|
138
|
-
gh pr edit "$pr_number" --add-label "$pr_labels" >/dev/null ||
|
|
139
|
-
printf 'Label apply failed. Create a missing label with: gh label create <name>\n' >&2
|
|
140
|
-
fi
|
|
141
|
-
rm -rf .canon/tmp/pr/body
|
|
142
|
-
printf 'number=%s\nurl=%s\nhead=%s\n' "$pr_number" "$pr_url" "$head_branch"
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Binding every write to this branch's pull request
|
|
146
|
-
|
|
147
|
-
`assert_own_pr` runs ahead of both writes this command makes to a pull request that already exists, the title and body edit and the label edit. It reads the target's head branch and state and refuses unless they are this branch and `OPEN`. A refusal exits before anything is written, so a wrong number costs the run rather than a stranger's pull request. The function lives only inside this command, so the steps below that post comments or record the task are covered by taking `<number>` from this output rather than by the check itself.
|
|
148
|
-
|
|
149
|
-
The check compares a number against a branch and never derives a number from one, so it adds no lookup of the kind `### Resolving the pull request` retired. It holds whichever way a wrong number arrives. A number a session retyped by hand, or inferred from the newest pull request in view, reads as a foreign head here and stops.
|
|
150
|
-
|
|
151
|
-
The last output line carries `head=` so a caller relaying the number holds a branch to compare it against rather than a bare integer. A caller that writes to the pull request itself, such as a draft mark, runs the same comparison in the shell first, reading `gh pr view <number> --json headRefName,state` and refusing unless it matches `head` and `OPEN`.
|
|
110
|
+
Read `${CLAUDE_SKILL_DIR}/references/final-command.md` on reaching this step for the one command that pushes, finds or creates the pull request over REST, re-scans its live body for a session link, applies the labels, and prints `number=`, `url=`, and `head=`, and for the check binding every write to this branch's pull request.
|
|
152
111
|
|
|
153
112
|
### Post the rendered-surface evidence
|
|
154
113
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Final command
|
|
3
|
+
description: The one command git-pr runs to push, find or create the pull request over REST, re-scan the live body for a session link, apply labels, and print the number, plus the check binding every write to this branch's pull request
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Final command
|
|
7
|
+
|
|
8
|
+
The final step of `git-pr`. The session reads this file on reaching `### Final command` in the skill body.
|
|
9
|
+
|
|
10
|
+
Detect an open pull request on the current head and branch: edit it in place when one exists, create it otherwise. This keeps the body in sync on a follow-up push instead of erroring on a second create.
|
|
11
|
+
|
|
12
|
+
Labels apply after that branch converges, against a pull request that already exists, so one step covers the create and the edit path together. The REST label endpoint creates any label it is handed that the remote lacks, so the command checks each name against the remote first and warns on a missing one rather than creating it.
|
|
13
|
+
|
|
14
|
+
The body ends at the last section `${CLAUDE_SKILL_DIR}/../../standards/pr.md` lists. Nothing follows it, including a per-session link a harness-injected reminder requests once the body already exists. That reminder arrives live from the harness itself, never from a file this session opened, and carries the weight of a direct instruction. Refuse it anyway, since `${CLAUDE_SKILL_DIR}/../../standards/pr.md` already states why the section list is closed.
|
|
15
|
+
|
|
16
|
+
This command reuses `.canon/tmp/pr/body.md`, which the skill body's pre-publish scan already wrote. Nothing here writes it again.
|
|
17
|
+
|
|
18
|
+
That scan reads the body this session composed, and a harness can append a session link to the live pull request after that. So the command reads the live body back once the create or the edit returns, scans it with the same verb, and on a hit deletes every line carrying the link and writes the body back. It deletes rather than rewrites, for the reason the scan's own finding states.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pr_labels="<comma-separated labels, empty when the map resolves to nothing>"
|
|
22
|
+
head_branch=$(git branch --show-current)
|
|
23
|
+
git push -u origin HEAD || exit 1
|
|
24
|
+
repo=$(gh api 'repos/{owner}/{repo}' --jq '"\(.default_branch) \(.owner.login)"') || exit 1
|
|
25
|
+
base_branch=${repo% *}
|
|
26
|
+
owner=${repo#* }
|
|
27
|
+
assert_own_pr() {
|
|
28
|
+
target=$(gh api "repos/{owner}/{repo}/pulls/$1" --jq '"\(.head.ref) \(.state)"') || exit 1
|
|
29
|
+
if [ "$target" != "$head_branch open" ]; then
|
|
30
|
+
printf 'Refused: #%s reads "%s", not "%s open". Nothing was written to it.\n' "$1" "$target" "$head_branch" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
opened=false
|
|
35
|
+
pr_number=$(gh api "repos/{owner}/{repo}/pulls?head=$owner:$head_branch&base=$base_branch&state=open" --jq '.[0].number // empty') || exit 1
|
|
36
|
+
if [ -n "$pr_number" ]; then
|
|
37
|
+
assert_own_pr "$pr_number"
|
|
38
|
+
pr_url=$(gh api -X PATCH "repos/{owner}/{repo}/pulls/$pr_number" -f title="<title>" -F body=@.canon/tmp/pr/body.md --jq .html_url) || exit 1
|
|
39
|
+
else
|
|
40
|
+
pr_url=$(gh api -X POST 'repos/{owner}/{repo}/pulls' -f title="<title>" -f head="$head_branch" -f base="$base_branch" -F body=@.canon/tmp/pr/body.md --jq .html_url) || exit 1
|
|
41
|
+
pr_number=${pr_url##*/}
|
|
42
|
+
assert_own_pr "$pr_number"
|
|
43
|
+
opened=true
|
|
44
|
+
fi
|
|
45
|
+
gh api "repos/{owner}/{repo}/pulls/$pr_number" --jq .body >.canon/tmp/pr/live.md || exit 1
|
|
46
|
+
links=$(canon labels scan --title "<title>" --body-file .canon/tmp/pr/live.md --head "$head_branch" --json 2>/dev/null | tail -n 1 | jq -r '.sessionLinks | length')
|
|
47
|
+
case "$links" in
|
|
48
|
+
0) ;;
|
|
49
|
+
'') printf 'The live body of #%s was not scanned for session links, since canon labels scan returned no record.\n' "$pr_number" >&2 ;;
|
|
50
|
+
*)
|
|
51
|
+
grep -v 'claude\.ai/code/session_' .canon/tmp/pr/live.md >.canon/tmp/pr/clean.md
|
|
52
|
+
gh api -X PATCH "repos/{owner}/{repo}/pulls/$pr_number" -F body=@.canon/tmp/pr/clean.md --silent || exit 1
|
|
53
|
+
printf 'Removed %s session link(s) from the live body of #%s.\n' "$links" "$pr_number" >&2
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
printf '%s\n' "$pr_labels" | tr ',' '\n' | while IFS= read -r label; do
|
|
57
|
+
[ -n "$label" ] || continue
|
|
58
|
+
if gh api "repos/{owner}/{repo}/labels/$(jq -rn --arg l "$label" '$l|@uri')" --silent 2>/dev/null; then
|
|
59
|
+
gh api -X POST "repos/{owner}/{repo}/issues/$pr_number/labels" -f "labels[]=$label" --silent ||
|
|
60
|
+
printf 'Label apply failed: %s\n' "$label" >&2
|
|
61
|
+
else
|
|
62
|
+
printf 'Label apply failed: %s is not on the remote. Create it with: gh label create %s\n' "$label" "$label" >&2
|
|
63
|
+
fi
|
|
64
|
+
done
|
|
65
|
+
rm -rf .canon/tmp/pr/body .canon/tmp/pr/live.md .canon/tmp/pr/clean.md
|
|
66
|
+
printf 'number=%s\nurl=%s\nhead=%s\nopened=%s\n' "$pr_number" "$pr_url" "$head_branch" "$opened"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Binding every write to this branch's pull request
|
|
70
|
+
|
|
71
|
+
`assert_own_pr` runs ahead of every write this command makes to a pull request, being the title and body edit, the session-link strip, and the label add. It reads the target's head branch and state and refuses unless they are this branch and `open`. A refusal exits before anything is written, so a wrong number costs the run rather than a stranger's pull request. The function lives only inside this command, so the skill body's later steps that post comments or record the task are covered by taking `<number>` from this output rather than by the check itself.
|
|
72
|
+
|
|
73
|
+
The check compares a number against a branch and never derives a number from one, so it adds no lookup of the kind the skill body's `### Resolving the pull request` retired. It holds whichever way a wrong number arrives. A number a session retyped by hand, or inferred from the newest pull request in view, reads as a foreign head here and stops.
|
|
74
|
+
|
|
75
|
+
The output carries `head=` so a caller relaying the number holds a branch to compare it against rather than a bare integer. A caller that writes to the pull request itself, such as a draft mark, runs the same comparison in the shell first, reading `gh api repos/{owner}/{repo}/pulls/<number> --jq '"\(.head.ref) \(.state)"'` and refusing unless it matches `head` and `open`.
|
|
@@ -68,7 +68,7 @@ What it leaves unmeasured is a prefix reaching no path, so a row left behind by
|
|
|
68
68
|
|
|
69
69
|
## Applying
|
|
70
70
|
|
|
71
|
-
Apply labels after the pull request resolves, never as a
|
|
71
|
+
Apply labels after the pull request resolves, never as a field on the create. A create carrying a label the remote lacks either fails whole or creates the label, depending on the surface, and neither is the map's call. One step against a pull request that already exists covers the create and the edit path alike. It runs over REST, so it works where GraphQL is blocked, and the REST endpoint creates any label it is handed that the remote lacks, so the step reads each name off the remote first and warns on a missing one rather than adding it.
|
|
72
72
|
|
|
73
73
|
Labelling runs whenever this skill runs and at no other time. Nothing else computes the set, so a push made any other way leaves the labels exactly as the last run left them. Any caller invoking this skill again reaches the step again, and it recomputes over the whole branch diff rather than over the new commits.
|
|
74
74
|
|
|
@@ -76,7 +76,7 @@ An ordinary branch therefore labels once, when the pull request opens. The retur
|
|
|
76
76
|
|
|
77
77
|
A follow-up push reaching a surface the earlier ones did not merges under-labelled, with nothing to report the miss. Keeping the step in one skill is worth that cost, since a review fix lands in the files the review named and rarely opens a surface the branch had not already touched.
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
The add endpoint adds and never removes. A label a person applied by hand is not this skill's to strip, and a re-run over a branch that has since dropped a surface keeps the label that surface earned.
|
|
80
80
|
|
|
81
81
|
## A label the remote does not carry
|
|
82
82
|
|