@erclx/canon 4.120.0 → 4.121.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 +29 -115
- package/claude/skills/auto-ship/references/draft-mark.md +30 -0
- package/claude/skills/auto-ship/references/failure-recovery.md +27 -0
- package/claude/skills/auto-ship/references/tier-failures.md +26 -0
- package/claude/skills/auto-ship/references/verb-absent.md +37 -0
- package/claude/skills/auto-ship/references/worktree-name.md +22 -0
- package/claude/skills/bash-script/SKILL.md +1 -1
- package/claude/skills/bash-script/references/patterns.md +0 -99
- package/claude/skills/bash-script/references/prompts.md +100 -0
- package/claude/skills/create-skill/SKILL.md +1 -1
- package/claude/skills/create-snippet/SKILL.md +1 -1
- package/claude/skills/create-standard/SKILL.md +1 -1
- package/claude/skills/design-extract/SKILL.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +2 -2
- package/claude/skills/docs-sync/SKILL.md +1 -1
- package/claude/skills/draft-context/SKILL.md +1 -1
- package/claude/skills/draft-docs/SKILL.md +1 -1
- package/claude/skills/draft-figure/SKILL.md +1 -1
- package/claude/skills/draft-readme/SKILL.md +1 -1
- package/claude/skills/draft-wiki/SKILL.md +1 -1
- package/claude/skills/draft-wireframes/SKILL.md +1 -1
- package/claude/skills/git-issue/SKILL.md +1 -1
- package/claude/skills/git-issue/references/issue.md +1 -1
- package/claude/skills/git-pr/SKILL.md +1 -1
- package/claude/skills/git-stage/SKILL.md +1 -1
- package/claude/skills/memory-capture/SKILL.md +1 -1
- package/claude/skills/restate-plainly/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/SKILL.md +4 -3
- package/claude/skills/review-branch/REQUIREMENT.md +1 -0
- package/claude/skills/review-branch/SKILL.md +5 -6
- package/claude/skills/review-craft/REQUIREMENT.md +43 -0
- package/claude/skills/review-craft/SKILL.md +50 -0
- package/claude/skills/review-craft/references/security.md +21 -0
- package/claude/skills/review-craft/references/ui.md +72 -0
- package/claude/skills/review-pr/REQUIREMENT.md +1 -0
- package/claude/skills/review-pr/SKILL.md +1 -1
- package/claude/skills/review-pr/references/post.md +1 -1
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/standards-audit/SKILL.md +1 -1
- package/claude/skills/write-human/REQUIREMENT.md +7 -4
- package/claude/skills/write-human/SKILL.md +4 -2
- package/claude/skills/write-human/references/machine-tells.md +1 -1
- package/claude/skills/write-human/references/source-material.md +5 -2
- package/docs/agents/audits.md +1 -1
- package/docs/agents/commands.md +1 -1
- package/docs/agents/markdown-audit.md +13 -19
- package/docs/agents/pr-local.md +6 -3
- package/docs/target-projects.md +1 -1
- package/docs/workflow/ai-workflow.md +1 -0
- package/governance/rules/claude/500-prose.md +1 -1
- package/governance/rules/claude/501-markdown.md +3 -3
- package/package.json +1 -1
- package/src/autoship/paths.ts +3 -2
- package/src/claude/cases/workflow.ts +5 -0
- package/src/commands/audits.ts +2 -2
- package/src/commands/markdown.ts +7 -9
- package/src/commands/pr.ts +11 -4
- package/src/gate/measures.ts +4 -4
- package/src/gate/stages.ts +0 -1
- package/src/markdown/bans.ts +11 -68
- package/src/markdown/gate.ts +1 -1
- package/src/markdown/scan.ts +7 -88
- package/standards/docs.md +1 -1
- package/standards/index.md +1 -1
- package/standards/markdown.md +5 -7
- package/standards/pr.md +1 -1
- package/standards/publish.md +1 -1
- package/standards/readme.md +2 -2
- package/tooling/base/configs/scripts/verify.sh +2 -2
- package/tooling/base/seeds/canon/context/development.md +1 -1
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +5 -6
|
@@ -7,7 +7,7 @@ description: Scope boundary between the writing rules a scan enforces and the on
|
|
|
7
7
|
|
|
8
8
|
## Gap
|
|
9
9
|
|
|
10
|
-
Without this skill, a session is graded on the words it avoids and never on how the prose reads. The banned
|
|
10
|
+
Without this skill, a session is graded on the words it avoids and never on how the prose reads. The banned characters ship as data the CLI and the installed hook both read, so that half is measured on every run. The voice half sat under a heading in a standard nothing opened, so a session wrote the whole passage without it and the audit reported a clean exit over prose that read machine-written.
|
|
11
11
|
|
|
12
12
|
The output of that arrangement is specific rather than general. Paragraphs come back with every sentence landing within a few words of the last, with consecutive sentences opening on the same word and the same grammatical shape, and with the compression rule applied until the verb is gone, so the passage trades uniform sentences for uniform fragments. A session told to be terse and told nothing about cadence produces exactly that, since cutting is the only instruction it holds.
|
|
13
13
|
|
|
@@ -21,13 +21,15 @@ Importing that catalog wholesale fails on a different measurement. External mate
|
|
|
21
21
|
- State cadence as something to measure, giving the sentence-length spread and the repeated-opening count a session can check, since a rule to vary sentence length is one a session satisfies by assertion
|
|
22
22
|
- Ban the fragment trade by name, since compression is the instruction a terse register already carries and the verb is what it drops first
|
|
23
23
|
- Name the machine tells a scan cannot reach, and hold the catalog behind a stated trigger so a run that needs none pays no read for it
|
|
24
|
-
-
|
|
24
|
+
- Carry the marketing words and vague qualifiers as word-choice guidance, since no command reads them and a word ban catches the token and misses the habit behind it
|
|
25
|
+
- Point at the banned characters rather than restating them, since they ship as package data a command reads and a second copy drifts with nothing comparing the two
|
|
25
26
|
- Record which external patterns were adopted, which were declined, and the measurement behind each, so a later session extends the position instead of re-deriving it
|
|
26
27
|
- Yield voice to a surface that states its own, and hold rhythm, density, and the tells on every surface
|
|
27
28
|
|
|
28
29
|
## Must not
|
|
29
30
|
|
|
30
|
-
- Restate the banned
|
|
31
|
+
- Restate the banned characters, at any length or as an example set
|
|
32
|
+
- Present the word guidance as a gate, or restate it on a second surface, since this body is its one copy
|
|
31
33
|
- Claim the reported failure is closed. Cadence is a property of a passage and nothing measures the output against these rules yet, so this skill states the rule and the measurement is owed separately.
|
|
32
34
|
- Rewrite a term the corpus already uses because an external source bans it, without the count and a recorded position
|
|
33
35
|
- Grow a second voice register inside this body. One baseline covers every surface, and a register a real surface needs arrives as an overlay that yields on voice alone.
|
|
@@ -39,7 +41,8 @@ Importing that catalog wholesale fails on a different measurement. External mate
|
|
|
39
41
|
|
|
40
42
|
## Out of scope
|
|
41
43
|
|
|
42
|
-
- The banned
|
|
44
|
+
- The banned characters, which `markdown.md` states for a reader and `canon markdown audit` gates from package data
|
|
45
|
+
- Spelling, which a project's spell checker enforces
|
|
43
46
|
- Markdown mechanics, being headings, lists, code spans, punctuation, and file references
|
|
44
47
|
- Restating text that already exists in plainer words, which `restate-plainly` owns on a different trigger, since a restatement is asked for by name and this arrives on a glob match. The rules here still govern how that restatement reads, and that body cites this one rather than carrying a copy.
|
|
45
48
|
- What voice a given surface declares, which belongs to the standard governing that surface
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: write-human
|
|
3
|
-
description: Carries the voice, rhythm, sentence construction, and information density rules a banned-
|
|
3
|
+
description: Carries the voice, word choice, rhythm, sentence construction, and information density rules a banned-character scan cannot express, plus the catalog of machine tells that survive a clean ban scan. Use when writing or revising prose a person will read, when a passage reads flat or uniform, or when asked to "make this read like a person", "fix the cadence", "this reads like AI wrote it", "vary the sentences", or "tighten this without gutting it". Do NOT use for banned characters, which `markdown.md` states and `canon markdown audit` gates, and do NOT use to restate text that already exists in plainer words, which is `restate-plainly`, whose body loads these rules from here.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Write human
|
|
@@ -9,7 +9,7 @@ Write prose that reads as though a person wrote it. A ban list subtracts words a
|
|
|
9
9
|
|
|
10
10
|
Load this before drafting a passage rather than after. A revision pass recovers the words and never recovers the structure the draft already settled.
|
|
11
11
|
|
|
12
|
-
The banned
|
|
12
|
+
The banned characters sit in `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`. They stay in force here. This skill adds to them and never restates them, since `canon markdown audit` reads them from the package and a second copy would drift. The word guidance under `## Voice` is the one copy of that list, and no command reads it.
|
|
13
13
|
|
|
14
14
|
## What yields and what does not
|
|
15
15
|
|
|
@@ -23,6 +23,8 @@ The banned words, characters, and spellings sit in `${CLAUDE_SKILL_DIR}/../../st
|
|
|
23
23
|
- Use active voice, and present tense unless past or future is factually correct.
|
|
24
24
|
- Name the actor in a sentence that has one. Passive voice with an unnamed actor hides who acts and reads as evasion.
|
|
25
25
|
- Prefer direct verbs and plain words. Write `use` over `utilize`, `help` over `facilitate`, `is` over `serves as`.
|
|
26
|
+
- Drop marketing words: `seamless`, `robust`, `powerful`, `revolutionary`, `enhanced`, `allows`, and `leverage`. Each claims a quality without showing it, so state what the thing does and let the reader judge it.
|
|
27
|
+
- Drop vague qualifiers: `simply`, `just`, `easily`, `quickly`, `very`, and `really`. Cut the word where the sentence stands without it, and give the measurement where the degree matters.
|
|
26
28
|
- Be direct on an established fact. Hedge on a genuinely uncertain claim and say which it is.
|
|
27
29
|
- Assume developer-level knowledge. Skip the explanation the reader already carries.
|
|
28
30
|
- Front-load the key information in a paragraph, and give every sentence something the reader did not have.
|
|
@@ -5,7 +5,7 @@ description: Patterns that survive a clean ban scan, why each is invisible to a
|
|
|
5
5
|
|
|
6
6
|
# Machine tells
|
|
7
7
|
|
|
8
|
-
Each pattern below is a shape rather than a token, so no closed word set matches it and `canon markdown audit` reports a clean exit over a passage carrying every one. Read this when revising text a model drafted, or when a passage passes the scan and still reads wrong.
|
|
8
|
+
Each pattern below is a shape rather than a token, so no closed word set matches it and the character scan `canon markdown audit` runs reports a clean exit over a passage carrying every one. Read this when revising text a model drafted, or when a passage passes the scan and still reads wrong.
|
|
9
9
|
|
|
10
10
|
Work top down. The first three account for most of what a reader calls machine-written.
|
|
11
11
|
|
|
@@ -21,10 +21,13 @@ The ambiguity rules travel with that layer for the same reason. Compression is w
|
|
|
21
21
|
|
|
22
22
|
- **The ban on abstract metaphor nouns.** The nouns it names are load-bearing terms in this corpus rather than decoration, and the count below is what decides it.
|
|
23
23
|
- **Document mode as a selector.** The source has the writer pick a mode before drafting. In this corpus a document's shape is already fixed by the standard governing its type, so a mode selector adds a second authority over one question and the two will disagree.
|
|
24
|
-
- **Any restatement of a banned word set.** The bans ship as package data that `canon markdown audit` reads, and a copy here would be a second source for one list with a machine reading the other.
|
|
25
24
|
- **A voice register per surface.** One baseline covers every surface. A surface needing a warmer register states its own voice and claims the yield, which lifts voice alone and leaves the bans, the rhythm rules, and the tells in force.
|
|
26
25
|
|
|
27
|
-
The first of those
|
|
26
|
+
The first of those three rests on a count. The source names `surface` among the nouns it bans, and tracked markdown in the authoring repository used `surface` 593 times, `harness` 95, `scaffolding` 17, and `primitive` 4. Adopting the item either rewrites established vocabulary across the corpus or is ignored in silence, and a reader arriving later cannot tell which happened.
|
|
27
|
+
|
|
28
|
+
## What moved in from the markdown standard
|
|
29
|
+
|
|
30
|
+
The marketing words and vague qualifiers under `## Voice` were a gated word set in the markdown standard until the audit narrowed to characters. A word ban catches the token and misses the habit behind it, so a writer swaps `robust` for a synonym and the claim stays unproven. The list now lives here as guidance a reader applies, and this body is its one copy, since no command reads it and a second copy would drift with nothing comparing the two.
|
|
28
31
|
|
|
29
32
|
## Where this diverges
|
|
30
33
|
|
package/docs/agents/audits.md
CHANGED
|
@@ -36,7 +36,7 @@ Each is invoked as the CLI the caller is running rather than as a global `canon`
|
|
|
36
36
|
|
|
37
37
|
## What gates and what reports
|
|
38
38
|
|
|
39
|
-
Five findings fail the run: an unresolved context citation, a banned character,
|
|
39
|
+
Five findings fail the run: an unresolved context citation, a banned character, a relative link resolving to nothing on disk, a skill folder carrying no `REQUIREMENT.md`, and a credential-shaped value in the tree the package ships. Each is a fact with no false-positive class behind it.
|
|
40
40
|
|
|
41
41
|
Four of the five are the ones `canon gate run` already fails a push on. The secret scan is the one entry gating without a stage behind it, added on that same fact-or-judgment test rather than as a side effect of registering a measure, and the toolkit already ranks content leaving the repository above content that stays.
|
|
42
42
|
|
package/docs/agents/commands.md
CHANGED
|
@@ -75,7 +75,7 @@ Full help: `canon <command> --help`. Bare `canon --help` lists every top-level c
|
|
|
75
75
|
| `canon context classify sweep` | Classify every section of the five canonical doc types, split at H3, as keep, rewrite, or move (`--doc-types`, `--json`) |
|
|
76
76
|
| `canon context classifier show` | Report the resolved classifier backend and model and which source decided them (`--json`) |
|
|
77
77
|
| `canon context classifier set` | Write the project classifier setting to `canon/config/classifier.toml` (`--backend`, `--model`, `--json`) |
|
|
78
|
-
| `canon markdown audit` | Fail any markdown path on a banned character
|
|
78
|
+
| `canon markdown audit` | Fail any markdown path on a banned character or a dead relative link, and report the structural checkpoints |
|
|
79
79
|
| `canon claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
|
|
80
80
|
| `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`) |
|
|
81
81
|
| `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`) |
|
|
@@ -27,7 +27,7 @@ A bare run measures every markdown file git lists, tracked plus untracked-and-no
|
|
|
27
27
|
|
|
28
28
|
## Where the rules come from
|
|
29
29
|
|
|
30
|
-
The
|
|
30
|
+
The ban set and all ten checkpoints ship with the `canon` package as data, in `src/markdown/bans.ts` and `src/markdown/structure.ts`. Every project is measured against the same set whether or not it installed any standards, and no file has to resolve for a run to mean something.
|
|
31
31
|
|
|
32
32
|
Seven of the ten are stated in `markdown.md` and the three cadence numbers are stated in the `write-human` skill. That split is the content boundary rather than an accident: `markdown.md` carries the enforced rules a scan can decide, and the skill carries the rhythm rules a ban list cannot express. A cadence number moved in the skill and left in the code drifts the same way, so move both.
|
|
33
33
|
|
|
@@ -35,11 +35,9 @@ Reading them out of the standards per run was the original design. It put a pars
|
|
|
35
35
|
|
|
36
36
|
`markdown.md` still states every ban and every checkpoint, and a reader follows it rather than the code. Nothing compares the two, so a number moved in one place and left in the other drifts silently. Move both in the same change.
|
|
37
37
|
|
|
38
|
-
The
|
|
38
|
+
The set is closed rather than extensible, so a project cannot add a term by editing a file. It holds the two characters alone, since a character is the one class a literal match settles. The set once carried 13 words and 6 British spellings as well. The words moved to the `write-human` skill as guidance, because a word ban catches the token and misses the habit, and `just`, `allows`, and `very` carry honest uses no literal match separates. Spelling went to a project's spell checker, which already reads every word.
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
A set shipped empty is reported rather than passed. It finds nothing and would exit clean, which reports a corpus nobody checked as a corpus carrying no violation, so the run names the empty set and exits `1`. The sets ship with the package, so a defect in the build is the only cause left.
|
|
40
|
+
A set shipped empty is reported rather than passed. It finds nothing and would exit clean, which reports a corpus nobody checked as a corpus carrying no violation, so the run names the empty set and exits `3`. The set ships with the package, so a defect in the build is the only cause left.
|
|
43
41
|
|
|
44
42
|
`canon standards <name>` still resolves a standard at the authoring root, then the package corpus, and prints it, so the human catalog reads without a project copy on disk.
|
|
45
43
|
|
|
@@ -47,17 +45,11 @@ A set shipped empty is reported rather than passed. It finds nothing and would e
|
|
|
47
45
|
|
|
48
46
|
### Bans
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Deriving the spellings rather than pattern-matching a suffix is what keeps `exercises`, `promises`, and `revised` out of the report. A suffix pattern over the same corpus produced 46 false positives from words of that shape, and a closed set of whole words reaches none of them.
|
|
48
|
+
One closed set reports a hit: the characters `markdown.md` bans under `## Punctuation`.
|
|
53
49
|
|
|
54
50
|
Frontmatter, fenced blocks, inline code spans, and link destinations are excluded. Without the code-span exclusion each standard would report its own backticked examples, and without the link exclusion a semicolon in a query string would report as prose no rewrite can fix.
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
A banned spelling keeps the plain word boundary, hyphens included. The two bans target different things: a word ban targets the word, so reading a compound as one word is correct, while a spelling ban targets the orthography inside it, which sits in `behaviour-driven` as plainly as it sits alone.
|
|
59
|
-
|
|
60
|
-
Two ban shapes stay unmeasured and the report says so on every run. A multi-word ban escapes the harvest by width, whether it carries a placeholder standing in for the rest of the sentence or spells the phrase out in full, and every rule under `## Voice` is a judgment. The bans `## Language` states over what a sentence may claim sit in the first group by construction, since a literal match over a pattern reports the compliant text and reaches none of the violations. A report listing hits without naming those would read as a verdict on the whole standard.
|
|
52
|
+
Word choice stays unmeasured and the report says so on every run. Single words travel in the `write-human` skill as guidance a reader applies. A multi-word ban escapes the harvest by width, whether it carries a placeholder standing in for the rest of the sentence or spells the phrase out in full, and every rule under `## Voice` is a judgment. The bans `## Language` states over what a sentence may claim are patterns of the multi-word kind by construction, since a literal match over a pattern reports the compliant text and reaches none of the violations. A report listing hits without naming those would read as a verdict on the whole standard.
|
|
61
53
|
|
|
62
54
|
### Links
|
|
63
55
|
|
|
@@ -108,7 +100,7 @@ A bullet, a heading, a table row, a blockquote, a blank line, and a fence each e
|
|
|
108
100
|
|
|
109
101
|
### Cadence
|
|
110
102
|
|
|
111
|
-
Uniform cadence is the failure a ban list cannot express. A ban set states negatives, and fragments, verbless clauses, and sentences that all run one length are each the absence of something, so no
|
|
103
|
+
Uniform cadence is the failure a ban list cannot express. A ban set states negatives, and fragments, verbless clauses, and sentences that all run one length are each the absence of something, so no word added to a ban list reaches any of them. The shape layer already measured a bullet, a paragraph, and a run, and stopped one level above where that failure lives.
|
|
112
104
|
|
|
113
105
|
Cadence measures a paragraph on two numbers. The spread is the words between its longest and shortest sentence, and the opener count is the times one word opens a sentence in it. A spread of five words or under reads as one cadence, and a word opening more than two sentences is a pattern rather than a coincidence. Both come from `## Rhythm` in the `write-human` skill, which states them about prose a person reads, and this measures against that statement rather than setting a threshold of its own.
|
|
114
106
|
|
|
@@ -134,7 +126,7 @@ The Length step sums rendered lines over the whole source, frontmatter and fence
|
|
|
134
126
|
|
|
135
127
|
## Exit codes
|
|
136
128
|
|
|
137
|
-
Exit codes are `0` for a completed run with no gating finding, `1` for a refusal, `2` for a ban hit or a dead relative link, and `3` for a shipped ban set that arrived empty. A banned character
|
|
129
|
+
Exit codes are `0` for a completed run with no gating finding, `1` for a refusal, `2` for a ban hit or a dead relative link, and `3` for a shipped ban set that arrived empty. A banned character and a relative link resolving to nothing on disk each fail the run, both facts a scan settles rather than a reader judging. Bullet, paragraph, and depth weight are judgments a reader settles, and cadence is a distribution whose healthy range moves with the surface, so all four report under every code. Length reports the same way, since the audit also runs on a plan kept out of version control, and the Document ceiling stage in `canon gate run` owns that verdict instead.
|
|
138
130
|
|
|
139
131
|
`3` is separate from `1` because the two want different responses from a caller. A refusal means no corpus was built, and the `Markdown bans` stage in `canon gate run` is right to report it as unmeasured rather than as a pass. An empty set means the corpus was walked and nothing was looked for, so that stage fails the push on `3` rather than skipping.
|
|
140
132
|
|
|
@@ -154,11 +146,11 @@ Rewrite the sentence rather than swapping the banned token for a near-synonym. T
|
|
|
154
146
|
|
|
155
147
|
A code span clears the report too, since the ban scan walks around one, and it is the answer only where the token is genuinely an identifier under discussion. `## Code and identifiers` in `markdown.md` reserves the span for commands, API names, file paths, and identifiers, so backticking a quoted utterance spends one rule to satisfy another and leaves the corpus no cleaner.
|
|
156
148
|
|
|
157
|
-
A hit the closed set
|
|
149
|
+
A hit the closed set could not separate from correct prose was the case with no third option while the set still carried words. The temporal `just` reported as the vague qualifier it happened to spell, and rewriting the sentence is what the toolkit settled on over building an exemption path, for the reasons below. That class is part of why the words left the gate.
|
|
158
150
|
|
|
159
151
|
### Where the rules are enforced
|
|
160
152
|
|
|
161
|
-
Five surfaces apply the ban
|
|
153
|
+
Five surfaces apply the ban set and four of them go through this verb. `.claude/hooks/standards-audit.sh` runs it against a single file after each markdown edit, the seed copy a project installs does the same, the `Markdown bans` stage in `canon gate run` runs it across the whole corpus before this repository's own push, and the same-named stage in `tooling/base/configs/scripts/verify.sh` runs it across a scaffolded project's tracked markdown before its own push. Each hook parsed its own copy of the word bans in awk before that, which left a British spelling passing at edit time and failing the push with nothing in between explaining the difference.
|
|
162
154
|
|
|
163
155
|
The seed copy moved onto the verb when the sets became data, since its awk had nothing left to parse. It resolves one runner where the toolkit copy resolves two, looking for no checkout source, and a machine carrying no `canon` gets a report naming the binary to install rather than a silent pass. `scripts/core/check-seed-independence.sh` scopes its walk to markdown and leaves the seed hooks outside it, which its own comment records as deliberate.
|
|
164
156
|
|
|
@@ -170,7 +162,7 @@ The fifth surface reads the standards directly and is not a consolidation left h
|
|
|
170
162
|
|
|
171
163
|
The hook prefers a checkout's own `src/cli.ts` over a globally installed binary, so it and the push stage read one build. A published binary lags a branch by whatever has not been released, which would put a ban kind added on the branch into the push and not into the edit. It reads its findings out of the `--json` record rather than off the exit code, so an older binary still reports where the fallback applies. It reads `bans.emptySets` out of the same record, so a set the verb shipped empty reaches the author as a check narrowed to what it could measure rather than as a clean pass.
|
|
172
164
|
|
|
173
|
-
That field replaced `bans.missingStandards`, which answered a standard resolving under none of three roots. The
|
|
165
|
+
That field replaced `bans.missingStandards`, which answered a standard resolving under none of three roots. The set ships with the package now, so the state it named cannot occur and the narrowed check has one cause left, a defect in the build. The hook keeps reading a field either way, since a reader cannot tell a narrowed check from a clean one without it.
|
|
174
166
|
|
|
175
167
|
Both hooks answer an absent record as well. A completed run always writes the record and a refusal writes none, so an empty one means the verb declined to measure rather than measured and found nothing. The verb needs a git repository to build its corpus and refuses without one, which is a project the seeded hook can be installed into, and reading the findings alone reported that as a clean file.
|
|
176
168
|
|
|
@@ -188,9 +180,11 @@ Masking took 7 of the weight-only paragraphs the checkpoint reported at 400 and
|
|
|
188
180
|
|
|
189
181
|
### How the ban count reached zero
|
|
190
182
|
|
|
183
|
+
The set still carried words and spellings when the gate turned on, so this section and the next are history rather than a description of the current scan.
|
|
184
|
+
|
|
191
185
|
Eight word hits stood between the baseline and a gate, and only three carried the sense the standard bans. `leverage` sat in the requirements worldview, `allows` in the claude stack reference, and one `just` was the vague qualifier in a skill body. Those three lost the qualifier rather than the word.
|
|
192
186
|
|
|
193
|
-
The other five were correct prose the closed set cannot separate from a violation. Four were the temporal `just`, meaning a moment ago, in phrases like the implementation `just` completed and the field the user `just` edited. The fifth quoted an anti-pattern a skill exists to forbid. `markdown.md`
|
|
187
|
+
The other five were correct prose the closed set cannot separate from a violation. Four were the temporal `just`, meaning a moment ago, in phrases like the implementation `just` completed and the field the user `just` edited. The fifth quoted an anti-pattern a skill exists to forbid. `markdown.md` banned vague qualifiers and listed the tokens those qualifiers happen to spell, so the rule as written reached none of the five while the scan reached all of them.
|
|
194
188
|
|
|
195
189
|
### Why they were rewritten rather than exempted
|
|
196
190
|
|
package/docs/agents/pr-local.md
CHANGED
|
@@ -29,9 +29,12 @@ The HTML test is there because a working tree often holds more than one
|
|
|
29
29
|
listener. A test runner's own server, or a dev server's reload socket, answers
|
|
30
30
|
HTTP too, and a reviewer opening either gets a bare 404 rather than the branch.
|
|
31
31
|
|
|
32
|
-
Sockets are read
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
Sockets are read from `/proc/net/tcp` and `/proc/net/tcp6` wherever they exist,
|
|
33
|
+
matched against each process's `fd` table, and through `lsof` on a machine
|
|
34
|
+
without them. Linux reads `/proc` even with `lsof` installed, since `lsof` 4.95
|
|
35
|
+
drops a process whose truncated name holds an unmatched `(`, which is what
|
|
36
|
+
Next's `next-server (vX.Y.Z)` title is cut down to. A machine with neither
|
|
37
|
+
refuses as `no-listener-reader`.
|
|
35
38
|
|
|
36
39
|
The port is never derived from the worktree. A port helper needs a per-stack
|
|
37
40
|
base port the toolkit cannot know, and a server started by hand or by an
|
package/docs/target-projects.md
CHANGED
|
@@ -102,7 +102,7 @@ Run `canon tooling list --json` and `canon gov list --json` to see the current c
|
|
|
102
102
|
|
|
103
103
|
`canon init` installs base tooling, Claude workflow, and governance, and scaffolds `.claude/wiki/`. Governance defaults to the `base` stack, so a bare init carries the rules that route a project. Pass `--stack <name>` to install a framework stack instead.
|
|
104
104
|
|
|
105
|
-
No standard is written into the project. Each governance rule's authority line names `canon standards <name>`, which answers from the corpus inside the CLI's own package, and every toolkit skill names the copy in its own plugin root. `canon markdown audit` needs no standard at all, its ban
|
|
105
|
+
No standard is written into the project. Each governance rule's authority line names `canon standards <name>`, which answers from the corpus inside the CLI's own package, and every toolkit skill names the copy in its own plugin root. `canon markdown audit` needs no standard at all, its ban set and checkpoints shipping with the package as data.
|
|
106
106
|
|
|
107
107
|
No snippet is written into the project either. `claude/snippets` in the plugin cache symlinks to the toolkit's own `snippets/`, so a session reaches one at its `@` reference with no install step, the same live resolution `claude/standards` gives a rule's authority line.
|
|
108
108
|
|
|
@@ -231,6 +231,7 @@ This section is the corpus the coverage claim is measured against: every name `c
|
|
|
231
231
|
| `canon:project-commands` | When the project's own command needs running |
|
|
232
232
|
| `canon:test-first` | Before implementing a planned change, to run its test red, green, then refactor |
|
|
233
233
|
| `canon:test-craft` | When writing or changing any test, to pick its layer and filter what it asserts |
|
|
234
|
+
| `canon:review-craft` | When reviewing a change, for what to look for and how much evidence a finding needs |
|
|
234
235
|
| `canon:systematic-debugging` | When a test fails or a bug surfaces, to force root cause first |
|
|
235
236
|
| `canon:ui-checklist` | After a UI change, to write what to look at and name what ships untested |
|
|
236
237
|
|
|
@@ -12,4 +12,4 @@ paths:
|
|
|
12
12
|
- Load it before drafting a passage, not after revising one.
|
|
13
13
|
- Report it rather than proceeding silently when the skill does not resolve.
|
|
14
14
|
- Do not work these rules from memory.
|
|
15
|
-
-
|
|
15
|
+
- Punctuation, formatting, and file references are a separate topic. `501-markdown` routes them.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Route markdown edits to the markdown standard for headings, lists, punctuation,
|
|
2
|
+
description: Route markdown edits to the markdown standard for headings, lists, punctuation, and frontmatter wording
|
|
3
3
|
paths:
|
|
4
4
|
- '**/*.md'
|
|
5
5
|
---
|
|
@@ -8,7 +8,7 @@ paths:
|
|
|
8
8
|
|
|
9
9
|
## Authority
|
|
10
10
|
|
|
11
|
-
- Follow the markdown standard for headings, paragraph and list structure, code spans, punctuation, emphasis, file references,
|
|
12
|
-
- Read it before a substantial markdown edit. Do not work the banned
|
|
11
|
+
- Follow the markdown standard for headings, paragraph and list structure, code spans, punctuation, emphasis, file references, and the wording of a `title` or `description`. It is the single source. Read it with `canon standards markdown`.
|
|
12
|
+
- Read it before a substantial markdown edit. Do not work the banned characters from memory.
|
|
13
13
|
- Run `canon markdown audit <path>` after the edit, and rewrite the sentence carrying a hit rather than swapping the token for a near-synonym.
|
|
14
14
|
- Voice, rhythm, and sentence construction are a separate topic. `500-prose` routes them.
|
package/package.json
CHANGED
package/src/autoship/paths.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* The surfaces where markdown states what an agent does, and the extensions
|
|
3
3
|
* that read as prose, held as data one command parses.
|
|
4
4
|
*
|
|
5
|
-
* Lifted verbatim in content from the list `auto-ship
|
|
6
|
-
*
|
|
5
|
+
* Lifted verbatim in content from the list `auto-ship` Step 6 carried, whose
|
|
6
|
+
* written fallback copy now sits in `auto-ship/references/verb-absent.md`,
|
|
7
|
+
* which a session was asked to apply by hand. It failed that
|
|
7
8
|
* application three times, so the set moved here and the body now calls a verb
|
|
8
9
|
* that reads it. Being machine-parsed makes it permanently exempt from any
|
|
9
10
|
* later design that folds a standard back into the surface citing it, per the
|
|
@@ -99,6 +99,11 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
|
|
|
99
99
|
'Look over everything that changed on this branch for bugs and edge cases.',
|
|
100
100
|
expect: 'review-branch',
|
|
101
101
|
},
|
|
102
|
+
{
|
|
103
|
+
prompt:
|
|
104
|
+
'What should a review of a change look for beyond bugs, and how much evidence does a finding need?',
|
|
105
|
+
expect: 'review-craft',
|
|
106
|
+
},
|
|
102
107
|
{
|
|
103
108
|
prompt: 'Draft me a script with beats for a screencast recording.',
|
|
104
109
|
expect: 'draft-screencast',
|
package/src/commands/audits.ts
CHANGED
|
@@ -76,8 +76,8 @@ export function register(program: Command): void {
|
|
|
76
76
|
' 3 an audit did not report, so the run measured less than the set',
|
|
77
77
|
'',
|
|
78
78
|
'It gates on exactly what already gates a push and on nothing new:',
|
|
79
|
-
'an unresolved context citation, a banned character,
|
|
80
|
-
'
|
|
79
|
+
'an unresolved context citation, a banned character, and a skill folder',
|
|
80
|
+
'carrying no REQUIREMENT.md. Every other measure is a',
|
|
81
81
|
'judgment a reader settles, and failing a push on one teaches',
|
|
82
82
|
'contributors to route around the stage.',
|
|
83
83
|
'',
|
package/src/commands/markdown.ts
CHANGED
|
@@ -67,7 +67,7 @@ export function register(program: Command): void {
|
|
|
67
67
|
markdown
|
|
68
68
|
.command('audit')
|
|
69
69
|
.description(
|
|
70
|
-
'Fail on a banned character,
|
|
70
|
+
'Fail on a banned character, and report bullet, paragraph, cadence, depth, and length',
|
|
71
71
|
)
|
|
72
72
|
.argument(
|
|
73
73
|
'[path...]',
|
|
@@ -82,8 +82,8 @@ export function register(program: Command): void {
|
|
|
82
82
|
'Exit codes:',
|
|
83
83
|
' 0 the audit completed with no gating finding',
|
|
84
84
|
' 1 refused, with the reason on stderr',
|
|
85
|
-
' 2 a banned character
|
|
86
|
-
'
|
|
85
|
+
' 2 a banned character is present, or a relative link resolves',
|
|
86
|
+
' to nothing on disk',
|
|
87
87
|
' 3 a shipped ban set is empty, so the run measured nothing',
|
|
88
88
|
'',
|
|
89
89
|
'A ban hit and a dead link are each a fact and gate unconditionally.',
|
|
@@ -192,8 +192,6 @@ async function runAudit(
|
|
|
192
192
|
unmatchedPaths: scope.unmatched,
|
|
193
193
|
bans: {
|
|
194
194
|
characters: bans.characters,
|
|
195
|
-
words: bans.words,
|
|
196
|
-
spellings: bans.spellings,
|
|
197
195
|
emptySets: empty,
|
|
198
196
|
},
|
|
199
197
|
checkpoints: {
|
|
@@ -284,19 +282,19 @@ function reportBans(
|
|
|
284
282
|
|
|
285
283
|
if (empty.length > 0) {
|
|
286
284
|
logWarn(
|
|
287
|
-
`Not measured. The shipped set is empty for: ${empty.join(', ')}. The
|
|
285
|
+
`Not measured. The shipped set is empty for: ${empty.join(', ')}. The set ships with the canon package, so an empty set is a defect in the build rather than a missing install.`,
|
|
288
286
|
)
|
|
289
287
|
return
|
|
290
288
|
}
|
|
291
289
|
|
|
292
290
|
logInfo(
|
|
293
|
-
`${plural(bans.characters.length, 'character')}
|
|
291
|
+
`${plural(bans.characters.length, 'character')} shipped with the canon package`,
|
|
294
292
|
)
|
|
295
293
|
logInfo(
|
|
296
294
|
'Frontmatter, fenced blocks, code spans, and link destinations are excluded.',
|
|
297
295
|
)
|
|
298
296
|
logInfo(
|
|
299
|
-
'
|
|
297
|
+
'Word bans, phrase bans, and every voice rule are guidance for the reader',
|
|
300
298
|
)
|
|
301
299
|
|
|
302
300
|
const carrying = reports
|
|
@@ -304,7 +302,7 @@ function reportBans(
|
|
|
304
302
|
.sort((a, b) => b.bans.length - a.bans.length)
|
|
305
303
|
|
|
306
304
|
if (carrying.length === 0) {
|
|
307
|
-
logInfo('No banned character
|
|
305
|
+
logInfo('No banned character.')
|
|
308
306
|
return
|
|
309
307
|
}
|
|
310
308
|
|
package/src/commands/pr.ts
CHANGED
|
@@ -558,7 +558,8 @@ export function register(program: Command): void {
|
|
|
558
558
|
'A process in a sibling worktree, in the main checkout seen from a',
|
|
559
559
|
'linked worktree, or in a linked worktree seen from the main checkout',
|
|
560
560
|
'is never reported, since its server shows another branch. Sockets are',
|
|
561
|
-
'read through
|
|
561
|
+
'read through /proc where it exists, or through lsof on a machine',
|
|
562
|
+
'without it.',
|
|
562
563
|
'',
|
|
563
564
|
'--remove reads the pull request comment carrying the pr-evidence',
|
|
564
565
|
'marker and replaces its **Local preview:** line with --note, editing',
|
|
@@ -1759,13 +1760,20 @@ async function readProcListeners(): Promise<Listener[] | undefined> {
|
|
|
1759
1760
|
}
|
|
1760
1761
|
|
|
1761
1762
|
/**
|
|
1762
|
-
* The machine half of a detection:
|
|
1763
|
-
*
|
|
1763
|
+
* The machine half of a detection: `/proc` wherever it exists, lsof on a
|
|
1764
|
+
* machine without it, and no reader anywhere else.
|
|
1764
1765
|
*/
|
|
1765
1766
|
function machineLocalRunner(): LocalRunner {
|
|
1766
1767
|
const hasLsof = Bun.which('lsof') !== null
|
|
1767
1768
|
return {
|
|
1768
1769
|
async listListeners() {
|
|
1770
|
+
// lsof 4.95 drops every process whose kernel-truncated name holds an
|
|
1771
|
+
// unmatched `(`, which Next's `next-server (vX.Y.Z)` title cuts down to,
|
|
1772
|
+
// so `/proc` goes first despite costing several times what lsof does.
|
|
1773
|
+
if (existsSync('/proc/net/tcp')) {
|
|
1774
|
+
const listeners = await readProcListeners()
|
|
1775
|
+
if (listeners !== undefined) return listeners
|
|
1776
|
+
}
|
|
1769
1777
|
if (hasLsof) {
|
|
1770
1778
|
const result = await $`lsof -nP -iTCP -sTCP:LISTEN -Fpn`
|
|
1771
1779
|
.quiet()
|
|
@@ -1774,7 +1782,6 @@ function machineLocalRunner(): LocalRunner {
|
|
|
1774
1782
|
// read some process, and the rows it did print are real either way.
|
|
1775
1783
|
if (result.exitCode <= 1) return parseLsofListeners(result.text())
|
|
1776
1784
|
}
|
|
1777
|
-
if (existsSync('/proc/net/tcp')) return readProcListeners()
|
|
1778
1785
|
return undefined
|
|
1779
1786
|
},
|
|
1780
1787
|
async cwdOf(pid) {
|
package/src/gate/measures.ts
CHANGED
|
@@ -438,8 +438,8 @@ export const recordIdempotence: Measure = async (ctx) => {
|
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
/**
|
|
441
|
-
* A banned character
|
|
442
|
-
*
|
|
441
|
+
* A banned character is a fact rather than a threshold, so it fails the push
|
|
442
|
+
* while bullet, paragraph, and depth weight stay advisory.
|
|
443
443
|
*
|
|
444
444
|
* The whole corpus is measured rather than the changed files, because a
|
|
445
445
|
* `Do not use` bullet added to a standard bans a token retroactively and no
|
|
@@ -453,7 +453,7 @@ export const markdownBans: Measure = async (ctx) => {
|
|
|
453
453
|
const { exitCode } = await ctx.cli(['markdown', 'audit', '--json'])
|
|
454
454
|
|
|
455
455
|
if (exitCode === 0) {
|
|
456
|
-
return { emissions: [info('No banned character
|
|
456
|
+
return { emissions: [info('No banned character')] }
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
if (exitCode === 1) {
|
|
@@ -476,7 +476,7 @@ export const markdownBans: Measure = async (ctx) => {
|
|
|
476
476
|
return {
|
|
477
477
|
emissions: [output(frame.all)],
|
|
478
478
|
failure:
|
|
479
|
-
'Markdown prose carries a banned character,
|
|
479
|
+
'Markdown prose carries a banned character, or a relative link resolves to nothing on disk. Rewrite the sentence or fix the link, and reach for a code span only where the token is genuinely an identifier under discussion.',
|
|
480
480
|
}
|
|
481
481
|
}
|
|
482
482
|
|
package/src/gate/stages.ts
CHANGED
|
@@ -97,7 +97,6 @@ export const TEST_CORPORA_PATTERNS = [
|
|
|
97
97
|
'^standards/markdown\\.md$',
|
|
98
98
|
'^tooling/base/reference\\.md$',
|
|
99
99
|
'^tooling/web/configs/scripts/worktree-port\\.sh$',
|
|
100
|
-
'^\\.cspell/banned-spellings\\.txt$',
|
|
101
100
|
'^scripts/lib/worktree\\.sh$',
|
|
102
101
|
'^scripts/core/check-ignore-parity\\.sh$',
|
|
103
102
|
]
|
package/src/markdown/bans.ts
CHANGED
|
@@ -10,85 +10,28 @@ import type { BanSets } from '@/markdown/scan'
|
|
|
10
10
|
const CHARACTERS = ['—', ';'] as const
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* The one closed set the audit measures, owned here rather than harvested
|
|
14
|
+
* from the standard stating it.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
const WORDS = [
|
|
21
|
-
'seamless',
|
|
22
|
-
'robust',
|
|
23
|
-
'powerful',
|
|
24
|
-
'revolutionary',
|
|
25
|
-
'enhanced',
|
|
26
|
-
'allows',
|
|
27
|
-
'leverage',
|
|
28
|
-
'simply',
|
|
29
|
-
'just',
|
|
30
|
-
'easily',
|
|
31
|
-
'quickly',
|
|
32
|
-
'very',
|
|
33
|
-
'really',
|
|
34
|
-
] as const
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* British spellings of the American examples `markdown.md` lists.
|
|
38
|
-
*
|
|
39
|
-
* The set is carried whole rather than derived from a suffix rule, because a
|
|
40
|
-
* suffix pattern run over prose produced 46 of the 58 false positives measured
|
|
41
|
-
* during intake: `exercises`, `promises`, and `revised` all end in `-ise` and
|
|
42
|
-
* none is a British spelling. Matching whole words reaches none of them.
|
|
43
|
-
*
|
|
44
|
-
* `analyse` is absent because the standard's example is `analyze`, which ends
|
|
45
|
-
* in `-yze` rather than the `-ize` its rule states. The set records what the
|
|
46
|
-
* standard reaches rather than what a reader would extend it to, so adding the
|
|
47
|
-
* spelling here would widen the check past the prose it answers to.
|
|
48
|
-
*/
|
|
49
|
-
const SPELLINGS = [
|
|
50
|
-
'organise',
|
|
51
|
-
'summarise',
|
|
52
|
-
'recognise',
|
|
53
|
-
'behaviour',
|
|
54
|
-
'colour',
|
|
55
|
-
'centre',
|
|
56
|
-
] as const
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* The three closed sets the audit measures, owned here rather than harvested
|
|
60
|
-
* from the standards stating them.
|
|
61
|
-
*
|
|
62
|
-
* Parsing the prose was the alternative and it put a parser contract on two
|
|
63
|
-
* documents authored for people, which the prose standard had to carry a
|
|
64
|
-
* paragraph of its own to protect. The sets are a prior an author already
|
|
65
|
-
* knows rather than a filter: measured at `60fc97bf` on 2026-08-19, these 21
|
|
66
|
-
* terms ran across 483 markdown files for a clean exit, and every one of the
|
|
67
|
-
* 70 occurrences of a banned word sat inside the ban list itself or inside an
|
|
68
|
-
* example demonstrating the ban.
|
|
69
|
-
*
|
|
70
|
-
* The set is closed rather than extensible. Enumeration cannot close the gap
|
|
71
|
-
* it aims at, and each addition costs a false-positive class, since `just`,
|
|
72
|
-
* `allows`, and `very` have honest uses no literal match separates. A project
|
|
73
|
-
* wanting its own vocabulary is asking for a different feature than this one.
|
|
16
|
+
* Parsing the prose was the alternative and it put a parser contract on a
|
|
17
|
+
* document authored for people. The set is closed rather than extensible,
|
|
18
|
+
* since a character is the one class a literal match settles. A word is left
|
|
19
|
+
* to `write-human` as guidance and a spelling to cspell, because a word ban
|
|
20
|
+
* catches the token and misses the habit behind it.
|
|
74
21
|
*/
|
|
75
22
|
export const BAN_SETS: BanSets = {
|
|
76
23
|
characters: CHARACTERS,
|
|
77
|
-
words: WORDS,
|
|
78
|
-
spellings: SPELLINGS,
|
|
79
24
|
}
|
|
80
25
|
|
|
81
26
|
/**
|
|
82
|
-
* Names
|
|
27
|
+
* Names the set when it arrived empty, so a run measuring nothing says so.
|
|
83
28
|
*
|
|
84
29
|
* Empty is not the same state as finding no hit. A scan with no terms reports
|
|
85
30
|
* a clean file having looked for nothing, which is the silence the audit
|
|
86
|
-
* refused to ship back when a standard could go missing. The
|
|
87
|
-
* the package now, so the only way
|
|
31
|
+
* refused to ship back when a standard could go missing. The set ships with
|
|
32
|
+
* the package now, so the only way it empties is an edit to this file, and
|
|
88
33
|
* the guard is what keeps that edit loud rather than quiet.
|
|
89
34
|
*/
|
|
90
35
|
export function emptyBanSets(sets: BanSets = BAN_SETS): string[] {
|
|
91
|
-
return (['characters'
|
|
92
|
-
(name) => sets[name].length === 0,
|
|
93
|
-
)
|
|
36
|
+
return (['characters'] as const).filter((name) => sets[name].length === 0)
|
|
94
37
|
}
|
package/src/markdown/gate.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface GateInput {
|
|
|
21
21
|
/**
|
|
22
22
|
* Whether the audit found something that should fail the caller.
|
|
23
23
|
*
|
|
24
|
-
* A banned character
|
|
24
|
+
* A banned character and a relative link resolving to
|
|
25
25
|
* nothing on disk are both facts rather than a threshold, so either gates
|
|
26
26
|
* unconditionally and there is no widened mode to reach for. The standards
|
|
27
27
|
* decide what counts as banned, which keeps this answering how many rather
|