@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.
Files changed (72) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/SKILL.md +29 -115
  3. package/claude/skills/auto-ship/references/draft-mark.md +30 -0
  4. package/claude/skills/auto-ship/references/failure-recovery.md +27 -0
  5. package/claude/skills/auto-ship/references/tier-failures.md +26 -0
  6. package/claude/skills/auto-ship/references/verb-absent.md +37 -0
  7. package/claude/skills/auto-ship/references/worktree-name.md +22 -0
  8. package/claude/skills/bash-script/SKILL.md +1 -1
  9. package/claude/skills/bash-script/references/patterns.md +0 -99
  10. package/claude/skills/bash-script/references/prompts.md +100 -0
  11. package/claude/skills/create-skill/SKILL.md +1 -1
  12. package/claude/skills/create-snippet/SKILL.md +1 -1
  13. package/claude/skills/create-standard/SKILL.md +1 -1
  14. package/claude/skills/design-extract/SKILL.md +1 -1
  15. package/claude/skills/design-taste/REQUIREMENT.md +2 -2
  16. package/claude/skills/docs-sync/SKILL.md +1 -1
  17. package/claude/skills/draft-context/SKILL.md +1 -1
  18. package/claude/skills/draft-docs/SKILL.md +1 -1
  19. package/claude/skills/draft-figure/SKILL.md +1 -1
  20. package/claude/skills/draft-readme/SKILL.md +1 -1
  21. package/claude/skills/draft-wiki/SKILL.md +1 -1
  22. package/claude/skills/draft-wireframes/SKILL.md +1 -1
  23. package/claude/skills/git-issue/SKILL.md +1 -1
  24. package/claude/skills/git-issue/references/issue.md +1 -1
  25. package/claude/skills/git-pr/SKILL.md +1 -1
  26. package/claude/skills/git-stage/SKILL.md +1 -1
  27. package/claude/skills/memory-capture/SKILL.md +1 -1
  28. package/claude/skills/restate-plainly/REQUIREMENT.md +1 -1
  29. package/claude/skills/review-address/SKILL.md +4 -3
  30. package/claude/skills/review-branch/REQUIREMENT.md +1 -0
  31. package/claude/skills/review-branch/SKILL.md +5 -6
  32. package/claude/skills/review-craft/REQUIREMENT.md +43 -0
  33. package/claude/skills/review-craft/SKILL.md +50 -0
  34. package/claude/skills/review-craft/references/security.md +21 -0
  35. package/claude/skills/review-craft/references/ui.md +72 -0
  36. package/claude/skills/review-pr/REQUIREMENT.md +1 -0
  37. package/claude/skills/review-pr/SKILL.md +1 -1
  38. package/claude/skills/review-pr/references/post.md +1 -1
  39. package/claude/skills/role-planner/REQUIREMENT.md +1 -1
  40. package/claude/skills/standards-audit/SKILL.md +1 -1
  41. package/claude/skills/write-human/REQUIREMENT.md +7 -4
  42. package/claude/skills/write-human/SKILL.md +4 -2
  43. package/claude/skills/write-human/references/machine-tells.md +1 -1
  44. package/claude/skills/write-human/references/source-material.md +5 -2
  45. package/docs/agents/audits.md +1 -1
  46. package/docs/agents/commands.md +1 -1
  47. package/docs/agents/markdown-audit.md +13 -19
  48. package/docs/agents/pr-local.md +6 -3
  49. package/docs/target-projects.md +1 -1
  50. package/docs/workflow/ai-workflow.md +1 -0
  51. package/governance/rules/claude/500-prose.md +1 -1
  52. package/governance/rules/claude/501-markdown.md +3 -3
  53. package/package.json +1 -1
  54. package/src/autoship/paths.ts +3 -2
  55. package/src/claude/cases/workflow.ts +5 -0
  56. package/src/commands/audits.ts +2 -2
  57. package/src/commands/markdown.ts +7 -9
  58. package/src/commands/pr.ts +11 -4
  59. package/src/gate/measures.ts +4 -4
  60. package/src/gate/stages.ts +0 -1
  61. package/src/markdown/bans.ts +11 -68
  62. package/src/markdown/gate.ts +1 -1
  63. package/src/markdown/scan.ts +7 -88
  64. package/standards/docs.md +1 -1
  65. package/standards/index.md +1 -1
  66. package/standards/markdown.md +5 -7
  67. package/standards/pr.md +1 -1
  68. package/standards/publish.md +1 -1
  69. package/standards/readme.md +2 -2
  70. package/tooling/base/configs/scripts/verify.sh +2 -2
  71. package/tooling/base/seeds/canon/context/development.md +1 -1
  72. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +5 -6
@@ -55,20 +55,9 @@ export interface BodyLine {
55
55
  readonly text: string
56
56
  /** True on a fence delimiter and on every line between one pair. */
57
57
  readonly fenced: boolean
58
- /**
59
- * Which fenced block the line sits in, counted from one, and undefined
60
- * outside one. Two blocks written with nothing between them are one
61
- * contiguous run of fenced lines, so this is the only field telling them
62
- * apart. Counting from one keeps the outside answer from reading as a block.
63
- *
64
- * The name carries the fence because `block` alone already names a run of
65
- * `BodyLine` values in `src/markdown/structure.ts`, and one word meaning both
66
- * an aggregate of lines and an index on each line reads as a bug.
67
- */
68
- readonly fenceBlock: number | undefined
69
58
  }
70
59
 
71
- export type BanKind = 'character' | 'word' | 'spelling'
60
+ export type BanKind = 'character'
72
61
 
73
62
  export interface BanFinding {
74
63
  readonly line: number
@@ -80,30 +69,20 @@ export interface BanFinding {
80
69
 
81
70
  export interface BanSets {
82
71
  readonly characters: readonly string[]
83
- readonly words: readonly string[]
84
- readonly spellings: readonly string[]
85
72
  }
86
73
 
87
74
  /**
88
- * Marks which lines sit inside a fence without dropping them, and which block
89
- * each of those lines belongs to.
75
+ * Marks which lines sit inside a fence without dropping them.
90
76
  *
91
77
  * Each measure excludes a fence for its own reason and needs a different
92
78
  * response. The depth measure skips a fenced line so an example cannot break
93
79
  * the run around it, bullet folding treats one as a break so a bullet does not
94
80
  * absorb the block below it, and the ban scan ignores it outright. Returning
95
81
  * the mark rather than a filtered list is what lets one walk serve all three.
96
- *
97
- * The block index answers the consumer that has to decide per block rather
98
- * than per contiguous run, since two adjacent blocks are indistinguishable
99
- * through the mark alone. It is counted here because the walk already holds
100
- * the opening delimiter, and a consumer parsing its own would be the second
101
- * fence walker this repository consolidated away.
102
82
  */
103
83
  function markFences(texts: readonly string[], offset: number): BodyLine[] {
104
84
  const lines: BodyLine[] = []
105
85
  let fence: string | undefined
106
- let fenceBlock = 0
107
86
 
108
87
  for (const [index, text] of texts.entries()) {
109
88
  const match = FENCE.exec(text.trim())
@@ -117,15 +96,9 @@ function markFences(texts: readonly string[], offset: number): BodyLine[] {
117
96
  } else if (match) {
118
97
  fenced = true
119
98
  fence = match[1]
120
- fenceBlock += 1
121
99
  }
122
100
 
123
- lines.push({
124
- number: offset + index + 1,
125
- text,
126
- fenced,
127
- fenceBlock: fenced ? fenceBlock : undefined,
128
- })
101
+ lines.push({ number: offset + index + 1, text, fenced })
129
102
  }
130
103
 
131
104
  return lines
@@ -227,47 +200,12 @@ export function visibleText(text: string): string {
227
200
  return visible + drop(text.slice(read))
228
201
  }
229
202
 
230
- function escape(term: string): string {
231
- return term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
232
- }
233
-
234
- /**
235
- * Bounds a banned word on a word character or a hyphen either side.
236
- *
237
- * `\b` sits after a hyphen, so a banned word ending a hyphenated compound
238
- * reports from inside it: `allows` came back out of `auto-allows`. A compound
239
- * is one word to a reader, and the ban is on the word rather than on a morpheme
240
- * of it. Stripping hyphens before matching was the alternative and it joins the
241
- * compound into a token neither half reaches.
242
- *
243
- * A spelling ban takes `wordBoundary` instead. This is not that rule with a
244
- * wider fence, since the two bans target different things.
245
- */
246
- function bannedWord(term: string): RegExp {
247
- return new RegExp(`(?<![\\w-])${escape(term)}(?![\\w-])`, 'gi')
248
- }
249
-
250
203
  /**
251
- * Bounds a banned spelling on a word character alone, hyphens included.
204
+ * Finds every banned character outside a fence, a code span, and a link.
252
205
  *
253
- * A word ban targets the word, so a compound reading as one word is correct.
254
- * A spelling ban targets the orthography inside it, and a compound is exactly
255
- * where the orthography still sits: `behaviour-driven` carries the banned
256
- * spelling as plainly as `behaviour` does, and rejecting a hyphen here would
257
- * leave the usual spelling of that phrase unreported.
258
- */
259
- function bannedSpelling(term: string): RegExp {
260
- return new RegExp(`\\b${escape(term)}\\b`, 'gi')
261
- }
262
-
263
- /**
264
- * Finds every banned term outside a fence, a code span, and a link.
265
- *
266
- * A word ban matches in either casing, since the standard states each in
267
- * lowercase and bans the word rather than a spelling of it. A closed set of
268
- * whole words separates the check from the pattern that produced most of the
269
- * intake's false positives: `exercises` and `promises` end in the banned
270
- * suffix and are not the banned words, and a closed set never reaches them.
206
+ * Characters are the whole set because a character is the one class a literal
207
+ * match settles. A word carries honest uses no match separates, which is why
208
+ * word choice is guidance a reader applies rather than a finding this reports.
271
209
  */
272
210
  export function scanBans(
273
211
  lines: readonly BodyLine[],
@@ -275,19 +213,6 @@ export function scanBans(
275
213
  ): BanFinding[] {
276
214
  const found: BanFinding[] = []
277
215
 
278
- const patterns: { kind: BanKind; term: string; pattern: RegExp }[] = [
279
- ...bans.words.map((term) => ({
280
- kind: 'word' as const,
281
- term,
282
- pattern: bannedWord(term),
283
- })),
284
- ...bans.spellings.map((term) => ({
285
- kind: 'spelling' as const,
286
- term,
287
- pattern: bannedSpelling(term),
288
- })),
289
- ]
290
-
291
216
  for (const line of lines) {
292
217
  if (line.fenced) continue
293
218
  const text = maskDisplayed(line.text)
@@ -299,12 +224,6 @@ export function scanBans(
299
224
  column = text.indexOf(term, column + term.length)
300
225
  }
301
226
  }
302
-
303
- for (const { kind, term, pattern } of patterns) {
304
- for (const match of text.matchAll(pattern)) {
305
- found.push({ line: line.number, column: match.index, kind, term })
306
- }
307
- }
308
227
  }
309
228
 
310
229
  return found.sort((a, b) => a.line - b.line || a.column - b.column)
package/standards/docs.md CHANGED
@@ -19,7 +19,7 @@ Does not govern:
19
19
  - Per-domain internal narrative written for a reader with the source open: `context.md`
20
20
  - The drawing inside a Mermaid fence: `mermaid.md`
21
21
  - Voice, rhythm, and sentence construction: the `write-human` skill
22
- - Spelling, banned words, punctuation, and formatting: `markdown.md`
22
+ - Punctuation and formatting: `markdown.md`
23
23
 
24
24
  ## What a working docs page looks like
25
25
 
@@ -19,7 +19,7 @@ Reference docs for consistent authoring across the toolkit and target projects.
19
19
  - [Glossary reference](glossary.md): Frontmatter, entry shape, ordering, and the rules deciding which terms a glossary carries
20
20
  - [Groundwork reference](groundwork.md): Folder layout, ordinal naming, reserved numbering, frontmatter and dating, required file contents, and conventions for a measurement track
21
21
  - [Intake reference](intake.md): Folder layout, ordinal naming, reserved index number, frontmatter and dating, the item template, the answer contract, and retrieval
22
- - [Markdown reference](markdown.md): Headings, paragraph and list structure, code spans, the date form, punctuation, emphasis, file references, banned words, and frontmatter wording
22
+ - [Markdown reference](markdown.md): Headings, paragraph and list structure, code spans, the date form, punctuation, emphasis, file references, American spelling, and frontmatter wording
23
23
  - [Memory reference](memory.md): Filename and type prefix, frontmatter, the body shape per type, links between entries, and the lifecycle from write to retire
24
24
  - [Mermaid reference](mermaid.md): Direction and layout, node and edge budgets, accessibility fields, label punctuation, and render verification for a Mermaid diagram
25
25
  - [Plan reference](plan.md): Filename and slug, required sections, the suggested-and-answer contract, and the lifecycle from the live folder to the archive
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Markdown reference
3
- description: Headings, paragraph and list structure, code spans, the date form, punctuation, emphasis, file references, banned words, and frontmatter wording
3
+ description: Headings, paragraph and list structure, code spans, the date form, punctuation, emphasis, file references, American spelling, and frontmatter wording
4
4
  ---
5
5
 
6
6
  # Markdown reference
@@ -9,11 +9,11 @@ Applies to markdown reference docs, READMEs, and inline documentation in repos.
9
9
 
10
10
  ## Scope
11
11
 
12
- Governs what a check can decide about a markdown file: headings, paragraph and list structure, code spans and fences, the form a date takes, punctuation, emphasis, file references, the closed-set word and spelling bans, what prose may claim about its sources, and the wording of a catalog title and description. It is an attribute standard rather than a document-type one, so it applies over documents whose shape another standard sets, and it carries no template because these rules are written across every document and have no shape of their own.
12
+ Governs what a check can decide about a markdown file: headings, paragraph and list structure, code spans and fences, the form a date takes, punctuation, emphasis, file references, the spelling convention, what prose may claim about its sources, and the wording of a catalog title and description. It is an attribute standard rather than a document-type one, so it applies over documents whose shape another standard sets, and it carries no template because these rules are written across every document and have no shape of their own.
13
13
 
14
14
  The two frontmatter fields it reaches are `title` and `description`. They are named here rather than in the statement above, since a backticked token in a scope statement's first sentence is published as the standard's jurisdiction.
15
15
 
16
- The split with the voice guidance is what reads the rule rather than what the rule covers. Every ban below ships as data `canon markdown audit` and the installed audit hook both read, so a violation is measured on every run. Cadence, rhythm, and information density are judgments a reader settles, and those travel in the `canon:write-human` skill, which a markdown edit routes to.
16
+ The split with the voice guidance is what reads the rule rather than what the rule covers. The character bans ship as data `canon markdown audit` and the installed audit hook both read, so a violation is measured on every run. Word choice, cadence, rhythm, and information density are judgments a reader settles, and those travel in the `canon:write-human` skill, which a markdown edit routes to.
17
17
 
18
18
  Does not govern:
19
19
 
@@ -71,9 +71,7 @@ Does not govern:
71
71
 
72
72
  ## Language
73
73
 
74
- - Use American English spelling. Prefer `-ize` over `-ise`, `-or` over `-our`, `-er` over `-re` (`organize`, `analyze`, `summarize`, `recognize`, `behavior`, `color`, `center`)
75
- - Do not use marketing buzzwords (`seamless`, `robust`, `powerful`, `revolutionary`, `enhanced`, `allows`, `leverage`)
76
- - Do not use vague qualifiers (`simply`, `just`, `easily`, `quickly`, `very`, `really`)
74
+ - Use American English spelling
77
75
  - Open a sentence with its subject and action, not filler (`Note that`, `Basically`), a hollow connective (`That being said`, `It's worth noting`), or a gerund windup (`Leveraging the API...`). Substantive transitions that carry a real relationship are fine.
78
76
  - Do not use the negative parallelism pattern (`It's not X, it's Y`, `not because X, but because Y`)
79
77
  - Do not pad verb phrases or delay the action. Write the shortest form (`in order to` → `to`, `ensure that X is set` → `set X`, `By doing X, you can Y` → state Y directly).
@@ -84,7 +82,7 @@ Does not govern:
84
82
  - Do not attribute a claim to an unnamed authority (`experts say`, `studies show`, `it is widely believed`). Name the source or cut the claim.
85
83
  - Do not introduce a fact, name, date, or citation the source does not carry when rewriting existing text. A rewrite changes wording and never claims.
86
84
 
87
- The word bans and the character bans sit under one file because one command reads both. `canon markdown audit` ships them as package data, so a project that installed no standards is measured the same as one that did, and this section states them for a reader rather than for the parser.
85
+ One command reads the character bans under `## Punctuation`. `canon markdown audit` ships them as package data, so a project that installed no standards is measured the same as one that did, and this file states them for a reader rather than for the parser. The spelling line above states a convention and ships no set, so a project's spell checker is what enforces it.
88
86
 
89
87
  ## Frontmatter descriptions
90
88
 
package/standards/pr.md CHANGED
@@ -16,7 +16,7 @@ Does not govern:
16
16
  - Whether a phase label or a semver tag may appear in a title or body: `versioning.md`
17
17
  - Whether a quoted label, a gitignored record path, or a link to one Claude Code session may appear in a title or body, which `canon labels scan` fails on: `publish.md`
18
18
  - Voice, rhythm, and sentence construction in pull request prose: the `write-human` skill
19
- - Punctuation, formatting, and banned words in pull request prose: `markdown.md`
19
+ - Punctuation and formatting in pull request prose: `markdown.md`
20
20
 
21
21
  ## Title
22
22
 
@@ -26,7 +26,7 @@ Scope every check below by destination. Text published to a remote takes all of
26
26
 
27
27
  ## Banned characters
28
28
 
29
- `markdown.md` holds the character bans and the banned words alike. Read it at scan time rather than working the sets from memory, then scan the drafted text and rewrite each occurrence.
29
+ `markdown.md` holds the character bans. Read it at scan time rather than working the sets from memory, then scan the drafted text and rewrite each occurrence.
30
30
 
31
31
  Restructure the sentence rather than substituting the character. A semicolon swapped for a period leaves both clauses in the order the semicolon chose, which is the shape the ban exists to remove.
32
32
 
@@ -5,7 +5,7 @@ description: Readme voice, structure, and content conventions
5
5
 
6
6
  # Readme reference
7
7
 
8
- Applies to every `README.md`. The `## Voice` section states the voice for a repository's root README, so the `write-human` skill yields to it there. The yield covers voice alone. The rhythm and density rules that skill carries stay in force, as do the spelling rules, banned words, punctuation, and formatting in `markdown.md`, so the warmer register ships with the same hygiene: no em dashes, no semicolons, no buzzwords.
8
+ Applies to every `README.md`. The `## Voice` section states the voice for a repository's root README, so the `write-human` skill yields to it there. The yield covers voice alone. The rhythm and density rules that skill carries stay in force, as does the word-choice guidance that travels with them, and so do the punctuation and formatting in `markdown.md`, so the warmer register ships with the same hygiene: no em dashes, no semicolons, no marketing words.
9
9
 
10
10
  The reader is what changes. Reference prose serves someone who already committed to the project and is scanning for a fact. A root README meets someone deciding whether to commit at all, and it is often the only file they read.
11
11
 
@@ -16,7 +16,7 @@ Governs every `README.md`: voice, heading structure, required and optional secti
16
16
  Does not govern:
17
17
 
18
18
  - Rhythm and sentence construction in README prose: the `write-human` skill, which yields the voice and keeps the rest
19
- - Spelling, banned words, punctuation, and formatting in README prose: `markdown.md`, which yields nothing
19
+ - Punctuation and formatting in README prose: `markdown.md`, which yields nothing
20
20
  - Product scope and goals: `requirements.md`
21
21
  - The consumer-facing reference under `docs/`, whose reader has already committed and is operating the project: `docs.md`
22
22
 
@@ -48,14 +48,14 @@ check_markdown_bans() {
48
48
 
49
49
  case "$code" in
50
50
  0)
51
- log_info "No banned character, word, or spelling"
51
+ log_info "No banned character"
52
52
  ;;
53
53
  1)
54
54
  log_info "Skipped: canon markdown audit refused and measured nothing."
55
55
  ;;
56
56
  2)
57
57
  echo "$output" | pipe_output
58
- log_error "Markdown prose carries a banned character, word, or spelling, or a relative link resolves to nothing on disk"
58
+ log_error "Markdown prose carries a banned character, or a relative link resolves to nothing on disk"
59
59
  ;;
60
60
  3)
61
61
  echo "$output" | pipe_output
@@ -34,4 +34,4 @@ All `.sh` files live under `scripts/`. Do not place shell scripts outside `scrip
34
34
 
35
35
  - `pre-commit` runs `lint-staged` (prettier, cspell, shfmt, shellcheck on staged files).
36
36
  - `commit-msg` runs `commitlint` against the conventional commit format.
37
- - `pre-push` runs `bun run check`. When a markdown-bans audit tool is on PATH, it also gates on banned characters, words, and spellings across every tracked markdown file except `CHANGELOG.md`. After pushing, run `git status`. If files changed, commit the diff as `style(<scope>):` and push again.
37
+ - `pre-push` runs `bun run check`. When a markdown-bans audit tool is on PATH, it also gates on banned characters across every tracked markdown file except `CHANGELOG.md`. After pushing, run `git status`. If files changed, commit the diff as `style(<scope>):` and push again.
@@ -28,12 +28,11 @@ esac
28
28
 
29
29
  [ -f "$file" ] || exit 0
30
30
 
31
- # The audit verb owns the ban sets, so this hook carries no copy of them. The
32
- # awk this replaces parsed the word bans out of the project's own standards,
33
- # hardcoded the em-dash and semicolon, and reached none of the spellings, so a
34
- # British spelling passed at edit time and a corpus check caught it later with
35
- # nothing in between explaining the difference. A ban class added to the verb
36
- # now reaches this hook without an edit here.
31
+ # The audit verb owns the ban set, so this hook carries no copy of it. The awk
32
+ # this replaces parsed its bans out of the project's own standards and
33
+ # hardcoded the rest, so edit time and a later corpus check could disagree with
34
+ # nothing in between explaining the difference. A ban added to the verb now
35
+ # reaches this hook without an edit here.
37
36
  #
38
37
  # The verb resolves its own paths under the cwd, so the project root is named
39
38
  # rather than inherited. The payload carries an absolute file path, which is