@erclx/canon 4.56.0 → 4.57.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/claude-orchestrate/references/orchestrator-parked.md +2 -2
- package/claude/skills/claude-pr-review/SKILL.md +1 -1
- package/claude/skills/context-draft/SKILL.md +1 -1
- package/claude/skills/docs-draft/SKILL.md +1 -1
- package/claude/skills/git-pr/SKILL.md +22 -5
- package/claude/skills/wireframe-draft/SKILL.md +2 -2
- package/docs/agents/commands.md +79 -79
- package/package.json +1 -1
- package/src/commands/labels.ts +51 -3
- package/src/gate/measures.ts +4 -2
- package/src/labels/format.ts +58 -0
- package/src/shipped/references.ts +74 -7
- package/standards/wireframes.md +4 -2
- package/tooling/base/configs/commitlint.config.js +26 -0
- package/tooling/web/configs/scripts/screenshot.sh +2 -1
|
@@ -17,7 +17,7 @@ The sweep's own question stays distinct from this one. It asks which parked row
|
|
|
17
17
|
|
|
18
18
|
Every row under `## Up next` and `## Needs a plan` in `.canon/tasks/priority.md`. A `## Run now` row carries no blocker by definition, so the pass skips it. On the idle trigger, also every row in `.canon/tasks/backlog.md` when the file exists, per the trigger split above. Resolve the board, the backlog, and each task file at the main worktree root, the way `claude-worktree` does.
|
|
19
19
|
|
|
20
|
-
Take the board rows in board order and finish one before opening the next. Clearing a row changes what the next row collides with, so a pass that measures every row first and writes afterwards writes against a board it has already invalidated. On the idle trigger, walk the backlog after the board, in the file's own filename order, since
|
|
20
|
+
Take the board rows in board order and finish one before opening the next. Clearing a row changes what the next row collides with, so a pass that measures every row first and writes afterwards writes against a board it has already invalidated. On the idle trigger, walk the backlog after the board, in the file's own filename order, since `${CLAUDE_SKILL_DIR}/../../standards/tasks.md` fixes that file as unordered and nothing about a row's position there means anything to preserve mid-pass.
|
|
21
21
|
|
|
22
22
|
## Re-testing a row
|
|
23
23
|
|
|
@@ -35,7 +35,7 @@ Write the result into the row. A re-test reported in chat is lost at the next co
|
|
|
35
35
|
|
|
36
36
|
## Re-testing a backlog row
|
|
37
37
|
|
|
38
|
-
A backlog row carries no blocker cell, since
|
|
38
|
+
A backlog row carries no blocker cell, since `${CLAUDE_SKILL_DIR}/../../standards/tasks.md` fixes `backlog.md` as a flat list of links with no state of its own. The five kinds above test what a blocker names, and a backlog row names none, so this pass asks the board-or-backlog question the standard already states instead: has the work in front of the row landed, or has the world changed under it. Read the answer from the task file's own `## Findings` and its origin line (`Plan:`, `Groundwork:`, `Intake:`, or `Issue:`), which name what the row was filed against and whether the fact behind it still holds.
|
|
39
39
|
|
|
40
40
|
Clearing a row answers yes to that question. It is not a count of how long the row has waited: `## Two results that are not a re-test` below refuses age as evidence for a board row, and it refuses it here on the same ground, since a row untouched for weeks is not more clearable than one added yesterday.
|
|
41
41
|
|
|
@@ -168,7 +168,7 @@ X critical, Y should-fix, Z minor. Reviewed against project docs and the board.
|
|
|
168
168
|
|
|
169
169
|
A stale ticked box goes in a `**PR body**` block, in place of a `**`path/to/file.ext`**` block and ahead of every one of those, since it precedes the code the diff carries rather than sitting inside it.
|
|
170
170
|
|
|
171
|
-
Run `canon labels scan --title "<title>" --body "<body>" --head <headRefName>` against the PR under review, since Step 1 already holds all three and this pass is the last human-shaped gate before merge. A hit lands in the same `**PR body**` block, `should-fix`, naming each
|
|
171
|
+
Run `canon labels scan --title "<title>" --body "<body>" --head <headRefName>` against the PR under review, since Step 1 already holds all three and this pass is the last human-shaped gate before merge. A hit lands in the same `**PR body**` block, `should-fix`, naming each finding the scan returns: a token quoted from the title or body for a phase label, a board identifier, a session link, or an unspelled word, and the broken rule's name, `structure`, a casing issue, or `length`, for a title-format hit. This reads the pull request being reviewed, distinct from the comment this pass is about to post, which the scan later in this step still covers.
|
|
172
172
|
|
|
173
173
|
A later pass carrying findings keeps that shape and changes only the summary line:
|
|
174
174
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: context-draft
|
|
3
|
-
description: Drafts a brand-new `.claude/context/<domain>.md` entry against
|
|
3
|
+
description: Drafts a brand-new `.claude/context/<domain>.md` entry against the context standard, checks the catalog for a name-or-topic collision, decides flat-file placement, confirms with the user, then writes. Use when asked to "write a context entry for X", "document the X domain", "add a context entry for X", or "create a .claude/context page for X" where no existing entry covers the domain. Do NOT use to refresh an existing entry against a diff, which is `claude-docs`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Context draft
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: docs-draft
|
|
3
|
-
description: Drafts a brand-new `docs/*.md` page against
|
|
3
|
+
description: Drafts a brand-new `docs/*.md` page against the docs standard, decides its placement in the existing catalog, confirms with the user, then writes. Use when asked to "add a docs page for X", "write a new doc for X", "document X under docs/", or "create a docs page for X" where no existing page covers the topic. Do NOT use to rewrite or sync an existing `docs/*.md` section against a diff, which is `docs-sync`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Docs draft
|
|
@@ -72,7 +72,26 @@ Leave a box unchecked only for the human-only cases the reference defines, and n
|
|
|
72
72
|
|
|
73
73
|
Before running the final command, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against the PR title and body. The title and body go straight to the remote with nothing checking them on the way, so run the scan regardless of what backs it downstream. It covers the phase-label check as well as the characters, since both go to a reader who has no task board. It applies on top of the banned phrases in `${CLAUDE_SKILL_DIR}/../../standards/pr.md`.
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
Write the body here, ahead of the final command, since the scan below needs a file to read it from:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
mkdir -p .canon/tmp/pr
|
|
79
|
+
cat <<'BODY' > .canon/tmp/pr/body.md
|
|
80
|
+
<body content following pr.md template exactly>
|
|
81
|
+
BODY
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Then run:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
canon labels scan --title "<title>" --body-file .canon/tmp/pr/body.md --json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Branch on the JSON record rather than the exit code. An operator's shell can wrap `canon` in a function whose status comes from a trailing command, flattening a non-zero exit to 0, the same reason `### Labels` below branches on its own record rather than the exit.
|
|
91
|
+
|
|
92
|
+
Stop and fix the title or body on a non-empty `phaseLabels`, `boardReferences`, `sessionLinks`, `unspelledWords`, or `titleFormatIssues`. Do not proceed to `### Final command` until a re-run comes back clean on all five. `titleFormatIssues` names which `## Title` rule in `${CLAUDE_SKILL_DIR}/../../standards/pr.md` the title breaks, structure, casing, or length, so fix the named rule rather than guessing. Leave `cutsRelease` and `semverTags` alone, since a release-please pull request legitimately carries version references its own fixed shape explains.
|
|
93
|
+
|
|
94
|
+
A `pull_request` workflow job now backs the phase-label half for this repository, running `canon labels scan` against the opened title and body. A project holding an older `canon` carries no such job, and one predating this plan carries no `titleFormatIssues` key at all, so the scan above stays required rather than optional.
|
|
76
95
|
|
|
77
96
|
### Resolving the pull request
|
|
78
97
|
|
|
@@ -112,11 +131,9 @@ Labels apply after that branch converges, against a pull request that already ex
|
|
|
112
131
|
|
|
113
132
|
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.
|
|
114
133
|
|
|
134
|
+
This command reuses `.canon/tmp/pr/body.md`, which the pre-publish scan above already wrote. Nothing here writes it again.
|
|
135
|
+
|
|
115
136
|
```bash
|
|
116
|
-
mkdir -p .canon/tmp/pr
|
|
117
|
-
cat <<'BODY' > .canon/tmp/pr/body.md
|
|
118
|
-
<body content following pr.md template exactly>
|
|
119
|
-
BODY
|
|
120
137
|
pr_labels="<comma-separated labels, empty when the map resolves to nothing>"
|
|
121
138
|
git push -u origin HEAD || exit 1
|
|
122
139
|
base_branch=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name) || exit 1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wireframe-draft
|
|
3
|
-
description: Drafts a brand-new `.claude/wireframes/<surface>.md` file against
|
|
3
|
+
description: Drafts a brand-new `.claude/wireframes/<surface>.md` file against the wireframes standard, walks the tree for a name collision, detects an existing higher visual-design tier without building one, confirms with the user, then writes. Use when asked to "draft a wireframe for X", "write the wireframe for this surface", "add a .claude/wireframes entry for X", or "wireframe this screen" where no surface file covers it yet. Do NOT use to fix a stale TODO stub or report wireframe drift against a diff, which is `claude-docs`'s wireframe-sweep step.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Wireframe draft
|
|
@@ -13,7 +13,7 @@ Read these files in parallel:
|
|
|
13
13
|
- `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: banned words, punctuation, and formatting for the prose around the fences
|
|
14
14
|
- The `write-human` skill: voice, rhythm, and sentence construction for the prose around the fences
|
|
15
15
|
|
|
16
|
-
This skill stays fully independent of `claude
|
|
16
|
+
This skill stays fully independent of `claude-docs`'s wireframe coverage sweep, which only ever writes 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 skill never reads or writes through that mechanism.
|
|
17
17
|
|
|
18
18
|
## Guards
|
|
19
19
|
|
package/docs/agents/commands.md
CHANGED
|
@@ -9,85 +9,85 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
|
|
|
9
9
|
|
|
10
10
|
## Project-level
|
|
11
11
|
|
|
12
|
-
| Command | Purpose
|
|
13
|
-
| ----------------------------- |
|
|
14
|
-
| `canon init [path]` | Bootstrap a project with selected toolkit domains
|
|
15
|
-
| `canon sync [path]` | Sync all installed domains in a target project
|
|
16
|
-
| `canon sync --check` | Report toolkit drift and the installed version against the newest published (`--json`, `--exit-code`)
|
|
17
|
-
| `canon sandbox [cat:cmd]` | Run sandbox scenarios (interactive or routed), toolkit-only like the tree it reads
|
|
18
|
-
| `canon sandbox reset` | Reset sandbox to baseline
|
|
19
|
-
| `canon sandbox clean` | Wipe the sandbox
|
|
20
|
-
| `canon sandbox check` | Score a provisioned sandbox against a scenario expectation (`--json` for the verdict)
|
|
21
|
-
| `canon sandbox coverage` | Report which scenarios declare expectations (`--json`, `--strict`, `--skills`)
|
|
22
|
-
| `canon indexes regen` | Regenerate `index.md` files from sibling frontmatter
|
|
23
|
-
| `canon indexes list [path]` | Flatten every folder index under a path into one `{path, title, description}` catalog (`--json`)
|
|
24
|
-
| `canon docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name)
|
|
25
|
-
| `canon design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS
|
|
26
|
-
| `canon design regen` | Rewrite this repository's `.claude/DESIGN.md` and `src/design/base.css` from `src/design/tokens.ts`
|
|
27
|
-
| `canon design css` | Emit the design tokens and components as CSS on stdout (`--no-components` for properties alone)
|
|
28
|
-
| `canon design install` | Install the base stylesheet into a project at `.claude/design/base.css`
|
|
29
|
-
| `canon design sync` | Update a base stylesheet already installed under `.claude/design/`
|
|
30
|
-
| `canon slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck, reporting any unrecognized layout name on stderr
|
|
31
|
-
| `canon slides list` | List the available slide layouts (`--json` for the catalog)
|
|
32
|
-
| `canon feedback` | Write toolkit feedback from stdin to `.canon/review/feedback/`, or open a GitHub issue with `--github`, refusing either when a required field is absent
|
|
33
|
-
| `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`)
|
|
34
|
-
| `canon tasks archive` | Move a shipped task and its plan off the board, clear its ordering row, and regenerate the index
|
|
35
|
-
| `canon tasks pull-request` | Record a pull request number on the task a branch closes, by stem or `--plan` (`--json`)
|
|
36
|
-
| `canon tasks plan-link` | Write or correct a task's `Plan:` line to point at a plan, by stem and plan path or slug (`--json`)
|
|
37
|
-
| `canon tasks outcome` | Mark outcomes `[x]` on a task by position, repeating `--close` (`--json`)
|
|
38
|
-
| `canon tasks validate` | Report board rows whose shape, order, plan, task file, group, file set, or blocker does not hold (`--json`)
|
|
39
|
-
| `canon intake list` | Report intake folder counts, or one folder's items, keeping what is unread with `--unread` (`--json`)
|
|
40
|
-
| `canon intake answer` | Write selections into one cluster's answer slots, repeating `--set <item>=<answer>` (`--json`)
|
|
41
|
-
| `canon teach list` | Report learning workspaces and the ordinal a new one takes, or what one workspace holds and what its records schedule (`--json`)
|
|
42
|
-
| `canon teach open` | Open a workspace at the next ordinal with its mission, resources, and glossary files (`--json`)
|
|
43
|
-
| `canon teach resource` | Record sources and leads in a workspace, repeating `--read` or `--lead` as `<title>=<url>` (`--json`)
|
|
44
|
-
| `canon teach glossary` | Add terms to a workspace glossary alphabetically, repeating `--term <term>=<definition>` (`--json`)
|
|
45
|
-
| `canon teach stylesheet` | Seed a workspace stylesheet from the design source, leaving an existing one alone without `--force` (`--json`)
|
|
46
|
-
| `canon teach nav` | Rewrite the teach-root listing, a workspace's contents page, and each lesson's chrome from its four marker regions (`--json`)
|
|
47
|
-
| `canon records validate` | Report a session record or a standard against the standard governing it, per kind (`--json`)
|
|
48
|
-
| `canon records migrate` | Rewrite the records a validate finding names a recoverable transform for (`--write`, `--json`)
|
|
49
|
-
| `canon records size` | Report what each record folder holds and how much of it is recent, heaviest first (`--json`)
|
|
50
|
-
| `canon records push` | Commit the nine backed record folders and push them to a private records remote (`--json`)
|
|
51
|
-
| `canon records pull` | Fetch the records remote and write it back, refusing rather than discarding unpushed records (`--json`)
|
|
52
|
-
| `canon migrate rename` | Rewrite every unprotected `aitk` token to `canon` and move the paths that carry the name, reporting the plan without `--write` (`--scope`, `--json`)
|
|
53
|
-
| `canon migrate records` | Move the gitignored session records to `.canon/` and repoint every tracked citation, reporting the plan without `--write` (`--root`, `--json`)
|
|
54
|
-
| `canon migrate record-tree` | Repoint the old-root citations inside the records themselves, scoped to the live folders and reporting every line without `--write` (`--root`, `--json`)
|
|
55
|
-
| `canon migrate rule-layout` | Move a target's installed rules from the flat `.claude/rules/<subdir>/` layout to `.claude/rules/canon/<subdir>/`, reporting the plan without `--write` (`--root`, `--json`)
|
|
56
|
-
| `canon sessions list` | Resolve live sessions to the worktree and branch each holds, filtered by `--branch` (`--json`)
|
|
57
|
-
| `canon worktrees list` | Report which worktrees are reclaimable, keyed on the pull request having merged, with every refusal and the removal route named (`--json`)
|
|
58
|
-
| `canon worktrees reclaim` | Remove every reclaimable worktree and the branch behind it, reporting without acting under `--dry-run` (`--json`)
|
|
59
|
-
| `canon comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git
|
|
60
|
-
| `canon context audit` | Report required sections, length, cited paths, reference form, catalog tables, provenance, superseded-decision narration, and index drift
|
|
61
|
-
| `canon markdown audit` | Fail any markdown path on a banned character, word, or spelling, or a dead relative link, and report the structural checkpoints
|
|
62
|
-
| `canon claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md`
|
|
63
|
-
| `canon standards audit` | Report the corpus against the success-criterion gate `standards/standard.md` states, failing only on a standard new to the branch (`--json`, `--arrivals-only`)
|
|
64
|
-
| `canon claude skills drift` | Name the shipped skill bodies rewritten between a given ref and `HEAD`, and the installed version against the newest published (`--json`)
|
|
65
|
-
| `canon claude skills reach` | Report the bodies in either skill corpus citing a toolkit path no target project receives, exiting 2 on an unqualified one
|
|
66
|
-
| `canon claude skills rank` | Score either skill corpus's descriptions against a case corpus by TF-IDF cosine similarity, reporting rank-one and top-three (`--cases <path>`)
|
|
67
|
-
| `canon claude routing` | Report per `CLAUDE.md` section how many bullets name a path and how many of those a path-scoped rule already covers (`--json`)
|
|
68
|
-
| `canon gov test-order` | Report where an implementation reached history ahead of the test covering it (`--json`)
|
|
69
|
-
| `canon gov superseded` | Report where the tree still asserts a value a changed convention no longer produces, keyed on the value and on the family stem behind a templated citation (`--json`)
|
|
70
|
-
| `canon gov restated` | Report every instruction the always-loaded file or a rule shares with the seed, a shipped skill body, or another rule, classed and with its anchors named (`--json`)
|
|
71
|
-
| `canon gov citations` | Resolve every path a rule cites and every internal frontmatter glob, failing on one reaching nothing (`--json`)
|
|
72
|
-
| `canon secrets scan` | Report credential-shaped values in the tree the package ships, keyed on issued values rather than on words (`--json`)
|
|
73
|
-
| `canon deps audit` | Report published advisories against the resolved dependency set, refusing rather than reporting clean when the index is unreachable (`--json`)
|
|
74
|
-
| `canon labels audit` | Report the labels a changed set earns from the pull request label map and the paths no row reaches (`--json`)
|
|
75
|
-
| `canon labels scan` | Fail a pull request or a posted review whose title, body, or review comment carries a phase label, a label a code span quotes, a gitignored record path, a session link,
|
|
76
|
-
| `canon autoship classify` | Decide whether a changed set needs the review pass, naming the file and the test that decided it (`--json`)
|
|
77
|
-
| `canon pr key-changes` | Compare the files a pull request body's Key Changes names against its own diff, in both directions (`--body`, `--base`, `--json`)
|
|
78
|
-
| `canon pr head` | Compare the head a pull request object reports against the branch tip the remote carries, naming both shas (`--root`, `--json`)
|
|
79
|
-
| `canon pr checks` | Report the check runs belonging to the branch tip, reading pending for a tip carrying none rather than clean (`--root`, `--json`)
|
|
80
|
-
| `canon repo metadata propose` | Compare a description, homepage, and topic set computed from the README and `package.json` against what the remote carries, writing nothing (`--root`, `--json`)
|
|
81
|
-
| `canon repo metadata apply` | Write an explicitly supplied description, homepage, or topic set to the remote through `gh repo edit` (`--description`, `--homepage`, `--topics`, `--root`, `--json`)
|
|
82
|
-
| `canon census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`)
|
|
83
|
-
| `canon audits run` | Run every audit as one set, report per check under one verdict, and compare each count to the recorded baseline (`--json`, `--record`, `--corpus`)
|
|
84
|
-
| `canon audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`)
|
|
85
|
-
| `canon gate run` | Run every stage that guards a branch here, scoping shell, types, and tests to the changed set (`--all`, `--no-write`, `--nested`, `--json`)
|
|
86
|
-
| `canon inventory [subject]` | Walk every route a project declares and group its elements by the property each computes, as a listing rather than a gate (`--json`)
|
|
87
|
-
| `canon drive <url> <run>` | Walk a page through named interactions and measure each state it reaches, reporting findings rather than gating (`--json`)
|
|
88
|
-
| `canon capture [source]` | Render HTML sources to PNG and prove the font each one declares resolved (`--selector` required, `--out`)
|
|
89
|
-
| `canon serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--json`)
|
|
90
|
-
| `canon upgrade` | Reinstall the CLI globally with the package manager the install path names (`--json`)
|
|
12
|
+
| Command | Purpose |
|
|
13
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
14
|
+
| `canon init [path]` | Bootstrap a project with selected toolkit domains |
|
|
15
|
+
| `canon sync [path]` | Sync all installed domains in a target project |
|
|
16
|
+
| `canon sync --check` | Report toolkit drift and the installed version against the newest published (`--json`, `--exit-code`) |
|
|
17
|
+
| `canon sandbox [cat:cmd]` | Run sandbox scenarios (interactive or routed), toolkit-only like the tree it reads |
|
|
18
|
+
| `canon sandbox reset` | Reset sandbox to baseline |
|
|
19
|
+
| `canon sandbox clean` | Wipe the sandbox |
|
|
20
|
+
| `canon sandbox check` | Score a provisioned sandbox against a scenario expectation (`--json` for the verdict) |
|
|
21
|
+
| `canon sandbox coverage` | Report which scenarios declare expectations (`--json`, `--strict`, `--skills`) |
|
|
22
|
+
| `canon indexes regen` | Regenerate `index.md` files from sibling frontmatter |
|
|
23
|
+
| `canon indexes list [path]` | Flatten every folder index under a path into one `{path, title, description}` catalog (`--json`) |
|
|
24
|
+
| `canon docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
|
|
25
|
+
| `canon design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS |
|
|
26
|
+
| `canon design regen` | Rewrite this repository's `.claude/DESIGN.md` and `src/design/base.css` from `src/design/tokens.ts` |
|
|
27
|
+
| `canon design css` | Emit the design tokens and components as CSS on stdout (`--no-components` for properties alone) |
|
|
28
|
+
| `canon design install` | Install the base stylesheet into a project at `.claude/design/base.css` |
|
|
29
|
+
| `canon design sync` | Update a base stylesheet already installed under `.claude/design/` |
|
|
30
|
+
| `canon slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck, reporting any unrecognized layout name on stderr |
|
|
31
|
+
| `canon slides list` | List the available slide layouts (`--json` for the catalog) |
|
|
32
|
+
| `canon feedback` | Write toolkit feedback from stdin to `.canon/review/feedback/`, or open a GitHub issue with `--github`, refusing either when a required field is absent |
|
|
33
|
+
| `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
|
|
34
|
+
| `canon tasks archive` | Move a shipped task and its plan off the board, clear its ordering row, and regenerate the index |
|
|
35
|
+
| `canon tasks pull-request` | Record a pull request number on the task a branch closes, by stem or `--plan` (`--json`) |
|
|
36
|
+
| `canon tasks plan-link` | Write or correct a task's `Plan:` line to point at a plan, by stem and plan path or slug (`--json`) |
|
|
37
|
+
| `canon tasks outcome` | Mark outcomes `[x]` on a task by position, repeating `--close` (`--json`) |
|
|
38
|
+
| `canon tasks validate` | Report board rows whose shape, order, plan, task file, group, file set, or blocker does not hold (`--json`) |
|
|
39
|
+
| `canon intake list` | Report intake folder counts, or one folder's items, keeping what is unread with `--unread` (`--json`) |
|
|
40
|
+
| `canon intake answer` | Write selections into one cluster's answer slots, repeating `--set <item>=<answer>` (`--json`) |
|
|
41
|
+
| `canon teach list` | Report learning workspaces and the ordinal a new one takes, or what one workspace holds and what its records schedule (`--json`) |
|
|
42
|
+
| `canon teach open` | Open a workspace at the next ordinal with its mission, resources, and glossary files (`--json`) |
|
|
43
|
+
| `canon teach resource` | Record sources and leads in a workspace, repeating `--read` or `--lead` as `<title>=<url>` (`--json`) |
|
|
44
|
+
| `canon teach glossary` | Add terms to a workspace glossary alphabetically, repeating `--term <term>=<definition>` (`--json`) |
|
|
45
|
+
| `canon teach stylesheet` | Seed a workspace stylesheet from the design source, leaving an existing one alone without `--force` (`--json`) |
|
|
46
|
+
| `canon teach nav` | Rewrite the teach-root listing, a workspace's contents page, and each lesson's chrome from its four marker regions (`--json`) |
|
|
47
|
+
| `canon records validate` | Report a session record or a standard against the standard governing it, per kind (`--json`) |
|
|
48
|
+
| `canon records migrate` | Rewrite the records a validate finding names a recoverable transform for (`--write`, `--json`) |
|
|
49
|
+
| `canon records size` | Report what each record folder holds and how much of it is recent, heaviest first (`--json`) |
|
|
50
|
+
| `canon records push` | Commit the nine backed record folders and push them to a private records remote (`--json`) |
|
|
51
|
+
| `canon records pull` | Fetch the records remote and write it back, refusing rather than discarding unpushed records (`--json`) |
|
|
52
|
+
| `canon migrate rename` | Rewrite every unprotected `aitk` token to `canon` and move the paths that carry the name, reporting the plan without `--write` (`--scope`, `--json`) |
|
|
53
|
+
| `canon migrate records` | Move the gitignored session records to `.canon/` and repoint every tracked citation, reporting the plan without `--write` (`--root`, `--json`) |
|
|
54
|
+
| `canon migrate record-tree` | Repoint the old-root citations inside the records themselves, scoped to the live folders and reporting every line without `--write` (`--root`, `--json`) |
|
|
55
|
+
| `canon migrate rule-layout` | Move a target's installed rules from the flat `.claude/rules/<subdir>/` layout to `.claude/rules/canon/<subdir>/`, reporting the plan without `--write` (`--root`, `--json`) |
|
|
56
|
+
| `canon sessions list` | Resolve live sessions to the worktree and branch each holds, filtered by `--branch` (`--json`) |
|
|
57
|
+
| `canon worktrees list` | Report which worktrees are reclaimable, keyed on the pull request having merged, with every refusal and the removal route named (`--json`) |
|
|
58
|
+
| `canon worktrees reclaim` | Remove every reclaimable worktree and the branch behind it, reporting without acting under `--dry-run` (`--json`) |
|
|
59
|
+
| `canon comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
|
|
60
|
+
| `canon context audit` | Report required sections, length, cited paths, reference form, catalog tables, provenance, superseded-decision narration, and index drift |
|
|
61
|
+
| `canon markdown audit` | Fail any markdown path on a banned character, word, or spelling, or a dead relative link, and report the structural checkpoints |
|
|
62
|
+
| `canon claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
|
|
63
|
+
| `canon standards audit` | Report the corpus against the success-criterion gate `standards/standard.md` states, failing only on a standard new to the branch (`--json`, `--arrivals-only`) |
|
|
64
|
+
| `canon claude skills drift` | Name the shipped skill bodies rewritten between a given ref and `HEAD`, and the installed version against the newest published (`--json`) |
|
|
65
|
+
| `canon claude skills reach` | Report the bodies in either skill corpus citing a toolkit path no target project receives, exiting 2 on an unqualified one |
|
|
66
|
+
| `canon claude skills rank` | Score either skill corpus's descriptions against a case corpus by TF-IDF cosine similarity, reporting rank-one and top-three (`--cases <path>`) |
|
|
67
|
+
| `canon claude routing` | Report per `CLAUDE.md` section how many bullets name a path and how many of those a path-scoped rule already covers (`--json`) |
|
|
68
|
+
| `canon gov test-order` | Report where an implementation reached history ahead of the test covering it (`--json`) |
|
|
69
|
+
| `canon gov superseded` | Report where the tree still asserts a value a changed convention no longer produces, keyed on the value and on the family stem behind a templated citation (`--json`) |
|
|
70
|
+
| `canon gov restated` | Report every instruction the always-loaded file or a rule shares with the seed, a shipped skill body, or another rule, classed and with its anchors named (`--json`) |
|
|
71
|
+
| `canon gov citations` | Resolve every path a rule cites and every internal frontmatter glob, failing on one reaching nothing (`--json`) |
|
|
72
|
+
| `canon secrets scan` | Report credential-shaped values in the tree the package ships, keyed on issued values rather than on words (`--json`) |
|
|
73
|
+
| `canon deps audit` | Report published advisories against the resolved dependency set, refusing rather than reporting clean when the index is unreachable (`--json`) |
|
|
74
|
+
| `canon labels audit` | Report the labels a changed set earns from the pull request label map and the paths no row reaches (`--json`) |
|
|
75
|
+
| `canon labels scan` | Fail a pull request or a posted review whose title, body, or review comment carries a phase label, a label a code span quotes, a gitignored record path, a session link, a title word no dictionary holds, or a title breaking `standards/pr.md`'s format, casing, or length rule (`--event`, `--body-file`, `--json`) |
|
|
76
|
+
| `canon autoship classify` | Decide whether a changed set needs the review pass, naming the file and the test that decided it (`--json`) |
|
|
77
|
+
| `canon pr key-changes` | Compare the files a pull request body's Key Changes names against its own diff, in both directions (`--body`, `--base`, `--json`) |
|
|
78
|
+
| `canon pr head` | Compare the head a pull request object reports against the branch tip the remote carries, naming both shas (`--root`, `--json`) |
|
|
79
|
+
| `canon pr checks` | Report the check runs belonging to the branch tip, reading pending for a tip carrying none rather than clean (`--root`, `--json`) |
|
|
80
|
+
| `canon repo metadata propose` | Compare a description, homepage, and topic set computed from the README and `package.json` against what the remote carries, writing nothing (`--root`, `--json`) |
|
|
81
|
+
| `canon repo metadata apply` | Write an explicitly supplied description, homepage, or topic set to the remote through `gh repo edit` (`--description`, `--homepage`, `--topics`, `--root`, `--json`) |
|
|
82
|
+
| `canon census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`) |
|
|
83
|
+
| `canon audits run` | Run every audit as one set, report per check under one verdict, and compare each count to the recorded baseline (`--json`, `--record`, `--corpus`) |
|
|
84
|
+
| `canon audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`) |
|
|
85
|
+
| `canon gate run` | Run every stage that guards a branch here, scoping shell, types, and tests to the changed set (`--all`, `--no-write`, `--nested`, `--json`) |
|
|
86
|
+
| `canon inventory [subject]` | Walk every route a project declares and group its elements by the property each computes, as a listing rather than a gate (`--json`) |
|
|
87
|
+
| `canon drive <url> <run>` | Walk a page through named interactions and measure each state it reaches, reporting findings rather than gating (`--json`) |
|
|
88
|
+
| `canon capture [source]` | Render HTML sources to PNG and prove the font each one declares resolved (`--selector` required, `--out`) |
|
|
89
|
+
| `canon serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--json`) |
|
|
90
|
+
| `canon upgrade` | Reinstall the CLI globally with the package manager the install path names (`--json`) |
|
|
91
91
|
|
|
92
92
|
`canon serve` drives no browser, which is what separates it from the four that do. All four ship now that `capture` does, so the line between them is the engine rather than the package. A generated page loses its script to an editor preview and to a `file://` open, so the link is the delivery rather than a convenience, and every generated surface here reaches a reader through one. A teach lesson's stylesheet is embedded rather than linked, so only its script still needs a server. It binds `127.0.0.1` and never a wildcard, because what it is pointed at is routinely a gitignored record tree. It sends `cache-control: no-store`, since a preview exists to be edited and reloaded and a cached stylesheet reads as a fix that did not work.
|
|
93
93
|
|
package/package.json
CHANGED
package/src/commands/labels.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { resolve } from 'node:path'
|
|
|
2
2
|
import type { Command } from 'commander'
|
|
3
3
|
import { type LabelAuditRefusal, auditLabels } from '@/labels/audit'
|
|
4
4
|
import { resolveScanInput } from '@/labels/event'
|
|
5
|
+
import { checkTitleFormat, type TitleFormatIssue } from '@/labels/format'
|
|
5
6
|
import { MAP_REL } from '@/labels/map'
|
|
6
7
|
import { scanPhaseLabels } from '@/labels/phase'
|
|
7
8
|
import { scanTitleSpelling } from '@/labels/spelling'
|
|
@@ -22,6 +23,15 @@ interface ScanOptions {
|
|
|
22
23
|
readonly json?: boolean
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
/** What a reader does about each way `checkTitleFormat` graded a title as broken. */
|
|
27
|
+
const TITLE_FORMAT_MESSAGES: Record<TitleFormatIssue, string> = {
|
|
28
|
+
structure: 'does not match <type>(<scope>): <subject>',
|
|
29
|
+
'casing-type': 'the type is not lowercase',
|
|
30
|
+
'casing-scope': 'the scope is not lowercase',
|
|
31
|
+
'casing-subject': 'the first word of the subject is not lowercase',
|
|
32
|
+
length: 'is over 72 characters',
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
/** What a reader does about each way the audit produced no reading. */
|
|
26
36
|
const REFUSALS: Record<LabelAuditRefusal, string> = {
|
|
27
37
|
// An answer rather than a fault. A project declaring no map is labelled
|
|
@@ -142,11 +152,19 @@ export function register(program: Command): void {
|
|
|
142
152
|
'a target project carries none, rather than reaching the network or',
|
|
143
153
|
'forcing a new dependency.',
|
|
144
154
|
'',
|
|
155
|
+
"It also grades the title alone against standards/pr.md's ## Title",
|
|
156
|
+
'section: the `<type>(<scope>): <subject>` structure, lowercase casing',
|
|
157
|
+
'for the type, the scope, and the first subject word, and a 72-',
|
|
158
|
+
'character length cap. A review carries no title of its own, so this',
|
|
159
|
+
'check is skipped there rather than graded against the forced empty',
|
|
160
|
+
'string.',
|
|
161
|
+
'',
|
|
145
162
|
'Exit codes:',
|
|
146
|
-
' 0 none of the
|
|
163
|
+
' 0 none of the five found',
|
|
147
164
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
148
165
|
' 2 the title or body carries a phase label, a board identifier, a',
|
|
149
|
-
' session link,
|
|
166
|
+
' session link, a title word no dictionary holds, or a title',
|
|
167
|
+
" breaking standards/pr.md's format, casing, or length",
|
|
150
168
|
'',
|
|
151
169
|
'Examples:',
|
|
152
170
|
' canon labels scan --event "$GITHUB_EVENT_PATH"',
|
|
@@ -269,6 +287,12 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
269
287
|
|
|
270
288
|
const result = scanPhaseLabels(resolved)
|
|
271
289
|
const spelling = await scanTitleSpelling(resolved.title, process.cwd())
|
|
290
|
+
// A review carries no title of its own, so `resolved.title` is forced
|
|
291
|
+
// empty and grading it would fail as `structure` for the wrong reason.
|
|
292
|
+
const titleFormat =
|
|
293
|
+
resolved.source === 'pull-request'
|
|
294
|
+
? checkTitleFormat(resolved.title)
|
|
295
|
+
: undefined
|
|
272
296
|
|
|
273
297
|
logStep(resolved.source === 'review' ? 'Review comment' : 'Pull request')
|
|
274
298
|
logInfo(
|
|
@@ -339,6 +363,28 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
339
363
|
for (const word of unspelledWords) logWarn(word)
|
|
340
364
|
}
|
|
341
365
|
|
|
366
|
+
const titleFormatIssues = titleFormat?.issues ?? []
|
|
367
|
+
|
|
368
|
+
logStep(
|
|
369
|
+
titleFormat === undefined
|
|
370
|
+
? 'Title format not checked'
|
|
371
|
+
: titleFormat.conforms
|
|
372
|
+
? 'Clean'
|
|
373
|
+
: 'Title format issue found',
|
|
374
|
+
)
|
|
375
|
+
if (titleFormat === undefined) {
|
|
376
|
+
logInfo('a review comment carries no title, so there is no format to grade')
|
|
377
|
+
} else if (titleFormat.conforms) {
|
|
378
|
+
logInfo(
|
|
379
|
+
'the title matches <type>(<scope>): <subject> and its casing and length rules',
|
|
380
|
+
)
|
|
381
|
+
} else {
|
|
382
|
+
logWarn(
|
|
383
|
+
`${plural(titleFormatIssues.length, 'title format issue')} against standards/pr.md's ## Title section.`,
|
|
384
|
+
)
|
|
385
|
+
for (const issue of titleFormatIssues) logWarn(TITLE_FORMAT_MESSAGES[issue])
|
|
386
|
+
}
|
|
387
|
+
|
|
342
388
|
outro()
|
|
343
389
|
|
|
344
390
|
if (emitJson) {
|
|
@@ -351,6 +397,7 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
351
397
|
sessionLinks: result.sessionLinks,
|
|
352
398
|
unspelledWords,
|
|
353
399
|
spellingChecked,
|
|
400
|
+
titleFormatIssues,
|
|
354
401
|
})}\n`,
|
|
355
402
|
)
|
|
356
403
|
}
|
|
@@ -358,7 +405,8 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
358
405
|
return result.phaseLabels.length === 0 &&
|
|
359
406
|
result.boardReferences.length === 0 &&
|
|
360
407
|
result.sessionLinks.length === 0 &&
|
|
361
|
-
unspelledWords.length === 0
|
|
408
|
+
unspelledWords.length === 0 &&
|
|
409
|
+
titleFormatIssues.length === 0
|
|
362
410
|
? 0
|
|
363
411
|
: 2
|
|
364
412
|
}
|
package/src/gate/measures.ts
CHANGED
|
@@ -500,6 +500,8 @@ function describeShippedReference(reference: ShippedReference): string {
|
|
|
500
500
|
return 'a pull request number that resolves elsewhere for a reader in a target'
|
|
501
501
|
case 'docs-path':
|
|
502
502
|
return 'a path into this repository that a registry install never carries'
|
|
503
|
+
case 'standards-path':
|
|
504
|
+
return 'a bare standards/ path that has nothing to expand it in an installed plugin cache'
|
|
503
505
|
case 'phase-label':
|
|
504
506
|
return 'a phase label that names a board no target holds'
|
|
505
507
|
}
|
|
@@ -558,8 +560,8 @@ export const shippedReferences: Measure = async (ctx) => {
|
|
|
558
560
|
),
|
|
559
561
|
failure:
|
|
560
562
|
found.length === 1
|
|
561
|
-
? `One reference in the shipped corpora resolves wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning .claude/context/ entry, or mark the line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`
|
|
562
|
-
: `${found.length} references in the shipped corpora resolve wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning .claude/context/ entry, or mark each line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`,
|
|
563
|
+
? `One reference in the shipped corpora resolves wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, rewrite a bare standards/ path under claude/skills/ as \${CLAUDE_SKILL_DIR}/../../standards/<name>.md, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning .claude/context/ entry, or mark the line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`
|
|
564
|
+
: `${found.length} references in the shipped corpora resolve wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, rewrite a bare standards/ path under claude/skills/ as \${CLAUDE_SKILL_DIR}/../../standards/<name>.md, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning .claude/context/ entry, or mark each line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`,
|
|
563
565
|
}
|
|
564
566
|
}
|
|
565
567
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which rule in `standards/pr.md`'s `## Title` section a title breaks.
|
|
3
|
+
*
|
|
4
|
+
* `structure` stands alone rather than beside the other three, because a
|
|
5
|
+
* title failing the shape match has no parsed `<type>`, `<scope>`, or
|
|
6
|
+
* `<subject>` to grade for casing, and `length` is read off the raw string
|
|
7
|
+
* regardless of whether it parses.
|
|
8
|
+
*/
|
|
9
|
+
export type TitleFormatIssue =
|
|
10
|
+
| 'structure'
|
|
11
|
+
| 'casing-type'
|
|
12
|
+
| 'casing-scope'
|
|
13
|
+
| 'casing-subject'
|
|
14
|
+
| 'length'
|
|
15
|
+
|
|
16
|
+
export interface TitleFormatCheck {
|
|
17
|
+
readonly conforms: boolean
|
|
18
|
+
readonly issues: readonly TitleFormatIssue[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const TITLE_MAX_LENGTH = 72
|
|
22
|
+
|
|
23
|
+
// Casing-agnostic on purpose. Casing is graded separately once the shape
|
|
24
|
+
// matches, so `type` and `scope` accept either case here and `structure`
|
|
25
|
+
// reports only a title with no `<type>(<scope>): <subject>` shape at all.
|
|
26
|
+
const TITLE_SHAPE = /^([A-Za-z]+)\(([A-Za-z0-9][\w.-]*)\): (.+)$/
|
|
27
|
+
const LEADING_LETTERS = /^[A-Za-z]+/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Grades a pull request title, or a commit subject sharing the same form,
|
|
31
|
+
* against `standards/pr.md`'s structure, casing, and length rules.
|
|
32
|
+
*
|
|
33
|
+
* Does not check `<type>` against `standards/commit.md`'s fixed enum.
|
|
34
|
+
* `pr.md`'s own `## Title` section states format, casing, and length only,
|
|
35
|
+
* and names no type list of its own to check against.
|
|
36
|
+
*/
|
|
37
|
+
export function checkTitleFormat(title: string): TitleFormatCheck {
|
|
38
|
+
const match = TITLE_SHAPE.exec(title)
|
|
39
|
+
|
|
40
|
+
if (match === null) {
|
|
41
|
+
return { conforms: false, issues: ['structure'] }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const [, type, scope, subject] = match
|
|
45
|
+
const issues: TitleFormatIssue[] = []
|
|
46
|
+
|
|
47
|
+
if (type !== type.toLowerCase()) issues.push('casing-type')
|
|
48
|
+
if (scope !== scope.toLowerCase()) issues.push('casing-scope')
|
|
49
|
+
|
|
50
|
+
const leadingWord = LEADING_LETTERS.exec(subject)?.[0]
|
|
51
|
+
if (leadingWord !== undefined && leadingWord !== leadingWord.toLowerCase()) {
|
|
52
|
+
issues.push('casing-subject')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (title.length > TITLE_MAX_LENGTH) issues.push('length')
|
|
56
|
+
|
|
57
|
+
return { conforms: issues.length === 0, issues }
|
|
58
|
+
}
|
|
@@ -148,6 +148,37 @@ const SAME_REPOSITORY =
|
|
|
148
148
|
*/
|
|
149
149
|
const DOCS_PATH = /(?<![\w./-])docs\/[^\s`)\]]*\.md\b/g
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* A bare `standards/<name>.md` citation from a body under `claude/skills/`,
|
|
153
|
+
* which `598-authoring-layout.md` fixes as the broken form there:
|
|
154
|
+
* `${CLAUDE_SKILL_DIR}` is what resolves off the `claude/standards` symlink
|
|
155
|
+
* in every plugin cache, and a raw path has nothing to expand it.
|
|
156
|
+
*
|
|
157
|
+
* Scoped to that one corpus rather than every `isShippedCorpus` reads,
|
|
158
|
+
* because `docs/agents/` and `docs/workflow/` carry the identical
|
|
159
|
+
* `standards/<name>.md` shape correctly: a docs page resolves from this
|
|
160
|
+
* checkout's own root rather than through a skill's `${CLAUDE_SKILL_DIR}`,
|
|
161
|
+
* so the same string is the fix in one corpus and the defect in the other.
|
|
162
|
+
* Widening the pattern to every corpus turns it into a mass false-positive
|
|
163
|
+
* over the dozens of correct citations those two folders carry.
|
|
164
|
+
*
|
|
165
|
+
* The same leading-boundary discipline as `DOCS_PATH` excludes the already
|
|
166
|
+
* correct form: a `/` sits ahead of `standards` in
|
|
167
|
+
* `${CLAUDE_SKILL_DIR}/../../standards/<name>.md`, which the negative
|
|
168
|
+
* lookbehind rejects, so a citation already rewritten to the resolving form
|
|
169
|
+
* does not fail this check a second time.
|
|
170
|
+
*
|
|
171
|
+
* Reported only where `isStandardsPathResolvable` confirms the match
|
|
172
|
+
* resolves against this checkout, the same gate `DOCS_PATH` runs. A body
|
|
173
|
+
* illustrating the shape a project's own `standards/<name>.md` takes writes
|
|
174
|
+
* the identical placeholder token this pattern matches, such as
|
|
175
|
+
* `standards/<slug>.md` in `create-standard/SKILL.md` or `standards/<name>.md`
|
|
176
|
+
* in `migration-standards-drop/SKILL.md`, and neither resolves to a real
|
|
177
|
+
* file. Four such placeholders surfaced across three files the first time
|
|
178
|
+
* this pattern ran unresolved, measured 2026-09-06.
|
|
179
|
+
*/
|
|
180
|
+
const STANDARDS_PATH = /(?<![\w./-])standards\/[^\s`)\]]*\.md\b/g
|
|
181
|
+
|
|
151
182
|
/**
|
|
152
183
|
* A phase-label-shaped token: exactly two numeric groups, with a negative
|
|
153
184
|
* lookahead rejecting a third.
|
|
@@ -173,7 +204,12 @@ export interface ShippedReference {
|
|
|
173
204
|
readonly file: string
|
|
174
205
|
/** One-based, matching the `file:line` form a reader clicks. */
|
|
175
206
|
readonly line: number
|
|
176
|
-
readonly kind:
|
|
207
|
+
readonly kind:
|
|
208
|
+
| 'pull-request'
|
|
209
|
+
| 'commit'
|
|
210
|
+
| 'docs-path'
|
|
211
|
+
| 'standards-path'
|
|
212
|
+
| 'phase-label'
|
|
177
213
|
/** The reference as written, so a report names the token to qualify. */
|
|
178
214
|
readonly text: string
|
|
179
215
|
/**
|
|
@@ -202,18 +238,37 @@ function isDocsPathResolvable(
|
|
|
202
238
|
return existsSync(join(root, path))
|
|
203
239
|
}
|
|
204
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Whether `file` sits in the one corpus `STANDARDS_PATH` gates.
|
|
243
|
+
*
|
|
244
|
+
* `REQUIREMENT.md` is excluded for the reason `598-authoring-layout.md`
|
|
245
|
+
* leaves it alone: a maintainer or an audit command reads that file rather
|
|
246
|
+
* than a session loading it, so the resolver rule this pattern enforces
|
|
247
|
+
* never applies there.
|
|
248
|
+
*/
|
|
249
|
+
function isStandardsPathScope(file: string): boolean {
|
|
250
|
+
return file.startsWith('claude/skills/') && !file.endsWith('/REQUIREMENT.md')
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Whether a `STANDARDS_PATH` match names a real file in this checkout. */
|
|
254
|
+
function isStandardsPathResolvable(path: string, root: string): boolean {
|
|
255
|
+
return existsSync(join(root, path))
|
|
256
|
+
}
|
|
257
|
+
|
|
205
258
|
/**
|
|
206
259
|
* Every reference in one shipped file that no marker mutes.
|
|
207
260
|
*
|
|
208
261
|
* The corpus walk is deliberately absent, which lets most of the shape be
|
|
209
262
|
* tested against a string rather than against a fixture. That is the seam
|
|
210
263
|
* `headingCitationsIn` draws in `src/claude/skills-headings.ts` and `citationsIn`
|
|
211
|
-
* draws in `skills-reach.ts`. `DOCS_PATH`
|
|
212
|
-
* a filesystem, since resolving against this
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* caller
|
|
216
|
-
*
|
|
264
|
+
* draws in `skills-reach.ts`. `DOCS_PATH` and `STANDARDS_PATH` are the two
|
|
265
|
+
* patterns that still need a filesystem, since resolving against this
|
|
266
|
+
* checkout is the only thing that separates a real citation from an
|
|
267
|
+
* illustration for either, so `referencesIn` takes `root` as the one
|
|
268
|
+
* caller-supplied exception to that rule. `root` is required rather than
|
|
269
|
+
* defaulted, since a caller that dropped it silently would report zero
|
|
270
|
+
* findings for both rather than raising, which is the wrong failure
|
|
271
|
+
* direction for a gate.
|
|
217
272
|
*
|
|
218
273
|
* The unit is the match rather than the line, unlike those two, because one
|
|
219
274
|
* line here can carry three separate tokens each needing its own repair and a
|
|
@@ -272,6 +327,18 @@ export function referencesIn(
|
|
|
272
327
|
})
|
|
273
328
|
}
|
|
274
329
|
|
|
330
|
+
if (isStandardsPathScope(file)) {
|
|
331
|
+
for (const match of line.matchAll(STANDARDS_PATH)) {
|
|
332
|
+
if (!isStandardsPathResolvable(match[0], root)) continue
|
|
333
|
+
references.push({
|
|
334
|
+
file,
|
|
335
|
+
line: index + 1,
|
|
336
|
+
kind: 'standards-path',
|
|
337
|
+
text: match[0],
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
275
342
|
for (const match of line.matchAll(PHASE_LABEL)) {
|
|
276
343
|
references.push({
|
|
277
344
|
file,
|
package/standards/wireframes.md
CHANGED
|
@@ -26,7 +26,7 @@ A wireframe works when someone can rebuild the surface from it without opening t
|
|
|
26
26
|
|
|
27
27
|
- What is on screen, and where does it sit relative to everything else?
|
|
28
28
|
- Which states can a visitor reach, and what does each one look like?
|
|
29
|
-
- What does
|
|
29
|
+
- What does its structural copy say, word for word, and where does its long-form content come from?
|
|
30
30
|
|
|
31
31
|
A wireframe that fails these is non-conforming regardless of whether it satisfies every section rule below. The fences are the means. These three questions are the test.
|
|
32
32
|
|
|
@@ -60,7 +60,8 @@ Both fields feed `.claude/wireframes/index.md` when regenerated.
|
|
|
60
60
|
|
|
61
61
|
## Copy
|
|
62
62
|
|
|
63
|
-
- Carry
|
|
63
|
+
- Carry short structural text verbatim, in the ASCII block or a short list below it: labels, headings, empty-state strings, and nav or footer copy. The wireframe is the source of truth for this text.
|
|
64
|
+
- Cite the source file for long-form or article-body content instead of duplicating it. A lede sentence or a body paragraph pulled into an ASCII figure drifts the moment its source changes, and nothing checks a fenced block against prose elsewhere. A citation elsewhere in the file does not excuse the block itself from still carrying the duplicate. Trim the block once the citation exists.
|
|
64
65
|
- Mark copy that is dynamic or templated so a reader does not treat a placeholder as final text.
|
|
65
66
|
|
|
66
67
|
## Behavior
|
|
@@ -113,6 +114,7 @@ description: <when and where the surface appears>
|
|
|
113
114
|
|
|
114
115
|
- <on-screen text, word for word>
|
|
115
116
|
- <text the surface templates>: <marked so a reader does not read it as final>
|
|
117
|
+
- <long-form content>: cited at <its source file>, not duplicated here
|
|
116
118
|
|
|
117
119
|
## Behavior
|
|
118
120
|
|
|
@@ -1,7 +1,33 @@
|
|
|
1
|
+
const SUBJECT_STARTS_WITH_LETTER = /^[A-Za-z]/
|
|
2
|
+
const SUBJECT_LEADING_LETTERS = /^[A-Za-z]+/
|
|
3
|
+
|
|
4
|
+
// Checks the first word alone, unlike the built-in subject-case rule, which
|
|
5
|
+
// tests the whole subject and would reject a legitimate capitalized proper
|
|
6
|
+
// noun anywhere past the first word.
|
|
7
|
+
const subjectFirstWordCase = (parsed) => {
|
|
8
|
+
const { subject } = parsed
|
|
9
|
+
|
|
10
|
+
if (
|
|
11
|
+
typeof subject !== 'string' ||
|
|
12
|
+
!SUBJECT_STARTS_WITH_LETTER.test(subject)
|
|
13
|
+
) {
|
|
14
|
+
return [true]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const leadingWord = SUBJECT_LEADING_LETTERS.exec(subject)[0]
|
|
18
|
+
|
|
19
|
+
return [
|
|
20
|
+
leadingWord === leadingWord.toLowerCase(),
|
|
21
|
+
'subject must start with a lowercase word',
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
|
|
1
25
|
const config = {
|
|
2
26
|
extends: ['@commitlint/config-conventional'],
|
|
27
|
+
plugins: [{ rules: { 'subject-first-word-case': subjectFirstWordCase } }],
|
|
3
28
|
rules: {
|
|
4
29
|
'subject-case': [0],
|
|
30
|
+
'subject-first-word-case': [2, 'always'],
|
|
5
31
|
'header-max-length': [2, 'always', 72],
|
|
6
32
|
'scope-case': [2, 'always', 'lower-case'],
|
|
7
33
|
'subject-full-stop': [2, 'never', '.'],
|
|
@@ -11,7 +11,8 @@ fi
|
|
|
11
11
|
bun run build
|
|
12
12
|
bun run preview >/dev/null 2>&1 &
|
|
13
13
|
PREVIEW_PID=$!
|
|
14
|
-
|
|
14
|
+
# shellcheck disable=SC2154 # pid is bound by the for loop inside the single-quoted trap body, which shellcheck does not track there
|
|
15
|
+
trap 'kill "$PREVIEW_PID" 2>/dev/null || true; if command -v lsof >/dev/null 2>&1; then for pid in $(lsof -ti tcp:"$PREVIEW_PORT" 2>/dev/null); do kill "$pid" 2>/dev/null || true; done; else echo "lsof not found on PATH; cannot confirm the preview port is clear of a detached grandchild." >&2; fi; wait "$PREVIEW_PID" 2>/dev/null || true' EXIT
|
|
15
16
|
|
|
16
17
|
for _ in $(seq 1 40); do
|
|
17
18
|
if curl -sSf "http://localhost:$PREVIEW_PORT/" >/dev/null 2>&1; then
|