@erclx/canon 4.30.0 → 4.30.1
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.
|
@@ -228,7 +228,7 @@ The `What is right` section is optional, capped at three bullets, and included o
|
|
|
228
228
|
|
|
229
229
|
Close the body with `🤖 Reviewed by Claude Code` on its own line so the review reads as an independent machine pass, not a human sign-off.
|
|
230
230
|
|
|
231
|
-
Before posting, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against the body. The hook skips `.canon/tmp/`, so this scan is the only gate
|
|
231
|
+
Before posting, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against the body. The hook skips `.canon/tmp/`, so this scan is the only gate ahead of the post. A finding phrased against an internal phase label is what the label half of the scan catches here. This repository reads the same text again once posted, on `phase-label-gate.yml`'s `pull_request_review` trigger, which is what closes the gap this scan leaves open for a review a person writes and posts by hand with no scripted step in front of it. That workflow is not seeded to any target this skill ships to, so a target's coverage stops at this pre-post scan, even though the `canon labels scan` verb behind it already reads a review payload once triggered.
|
|
232
232
|
|
|
233
233
|
Do not run the command below when `<prior-heading>` from Step 2 reads `## Review closed` and this pass carries nothing owed. That pass replaces the standing comment rather than adding one, under `### A close-out that repeats the standing one` at the end of this step. Posting first and reaching that section afterward leaves two close-outs both naming the new head, which is worse than the pair the guard exists against.
|
|
234
234
|
|
package/docs/agents/commands.md
CHANGED
|
@@ -9,81 +9,81 @@ 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 docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name)
|
|
24
|
-
| `canon design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS
|
|
25
|
-
| `canon design regen` | Rewrite this repository's `.claude/DESIGN.md` and `src/design/base.css` from `src/design/tokens.ts`
|
|
26
|
-
| `canon design css` | Emit the design tokens and components as CSS on stdout (`--no-components` for properties alone)
|
|
27
|
-
| `canon design install` | Install the base stylesheet into a project at `.claude/design/base.css`
|
|
28
|
-
| `canon design sync` | Update a base stylesheet already installed under `.claude/design/`
|
|
29
|
-
| `canon slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck, reporting any unrecognized layout name on stderr
|
|
30
|
-
| `canon slides list` | List the available slide layouts (`--json` for the catalog)
|
|
31
|
-
| `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
|
|
32
|
-
| `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`)
|
|
33
|
-
| `canon tasks archive` | Move a shipped task and its plan off the board, clear its ordering row, and regenerate the index
|
|
34
|
-
| `canon tasks pull-request` | Record a pull request number on the task a branch closes, by stem or `--plan` (`--json`)
|
|
35
|
-
| `canon tasks outcome` | Mark outcomes `[x]` on a task by position, repeating `--close` (`--json`)
|
|
36
|
-
| `canon tasks validate` | Report board rows whose shape, order, plan, task file, group, file set, or blocker does not hold (`--json`)
|
|
37
|
-
| `canon intake list` | Report intake folder counts, or one folder's items, keeping what is unread with `--unread` (`--json`)
|
|
38
|
-
| `canon intake answer` | Write selections into one cluster's answer slots, repeating `--set <item>=<answer>` (`--json`)
|
|
39
|
-
| `canon teach list` | Report learning workspaces and the ordinal a new one takes, or what one workspace holds (`--json`)
|
|
40
|
-
| `canon teach open` | Open a workspace at the next ordinal with its mission, resources, and glossary files (`--json`)
|
|
41
|
-
| `canon teach resource` | Record sources and leads in a workspace, repeating `--read` or `--lead` as `<title>=<url>` (`--json`)
|
|
42
|
-
| `canon teach glossary` | Add terms to a workspace glossary alphabetically, repeating `--term <term>=<definition>` (`--json`)
|
|
43
|
-
| `canon teach stylesheet` | Seed a workspace stylesheet from the design source, leaving an existing one alone without `--force` (`--json`)
|
|
44
|
-
| `canon records validate` | Report a session record or a standard against the standard governing it, per kind (`--json`)
|
|
45
|
-
| `canon records migrate` | Rewrite the records a validate finding names a recoverable transform for (`--write`, `--json`)
|
|
46
|
-
| `canon records size` | Report what each record folder holds and how much of it is recent, heaviest first (`--json`)
|
|
47
|
-
| `canon records push` | Commit the nine backed record folders and push them to a private records remote (`--json`)
|
|
48
|
-
| `canon records pull` | Fetch the records remote and write it back, refusing rather than discarding unpushed records (`--json`)
|
|
49
|
-
| `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`)
|
|
50
|
-
| `canon migrate records` | Move the gitignored session records to `.canon/` and repoint every tracked citation, reporting the plan without `--write` (`--root`, `--json`)
|
|
51
|
-
| `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`)
|
|
52
|
-
| `canon sessions list` | Resolve live sessions to the worktree and branch each holds, filtered by `--branch` (`--json`)
|
|
53
|
-
| `canon worktrees list` | Report which worktrees are reclaimable, keyed on the pull request having merged, with every refusal and the removal route named (`--json`)
|
|
54
|
-
| `canon worktrees reclaim` | Remove every reclaimable worktree and the branch behind it, reporting without acting under `--dry-run` (`--json`)
|
|
55
|
-
| `canon comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git
|
|
56
|
-
| `canon context audit` | Report required sections, length, cited paths, reference form, catalog tables, provenance, superseded-decision narration, and index drift
|
|
57
|
-
| `canon markdown audit` | Fail any markdown path on a banned character, word, or spelling, and report the structural checkpoints
|
|
58
|
-
| `canon claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md`
|
|
59
|
-
| `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`)
|
|
60
|
-
| `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`)
|
|
61
|
-
| `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
|
|
62
|
-
| `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>`)
|
|
63
|
-
| `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`)
|
|
64
|
-
| `canon gov test-order` | Report where an implementation reached history ahead of the test covering it (`--json`)
|
|
65
|
-
| `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`)
|
|
66
|
-
| `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`)
|
|
67
|
-
| `canon gov citations` | Resolve every path a rule cites and every internal frontmatter glob, failing on one reaching nothing (`--json`)
|
|
68
|
-
| `canon secrets scan` | Report credential-shaped values in the tree the package ships, keyed on issued values rather than on words (`--json`)
|
|
69
|
-
| `canon deps audit` | Report published advisories against the resolved dependency set, refusing rather than reporting clean when the index is unreachable (`--json`)
|
|
70
|
-
| `canon labels audit` | Report the labels a changed set earns from the pull request label map and the paths no row reaches (`--json`)
|
|
71
|
-
| `canon labels scan` | Fail a pull request whose title or
|
|
72
|
-
| `canon autoship classify` | Decide whether a changed set needs the review pass, naming the file and the test that decided it (`--json`)
|
|
73
|
-
| `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`)
|
|
74
|
-
| `canon pr head` | Compare the head a pull request object reports against the branch tip the remote carries, naming both shas (`--root`, `--json`)
|
|
75
|
-
| `canon pr checks` | Report the check runs belonging to the branch tip, reading pending for a tip carrying none rather than clean (`--root`, `--json`)
|
|
76
|
-
| `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`)
|
|
77
|
-
| `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`)
|
|
78
|
-
| `canon census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`)
|
|
79
|
-
| `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`)
|
|
80
|
-
| `canon audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`)
|
|
81
|
-
| `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`)
|
|
82
|
-
| `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`)
|
|
83
|
-
| `canon drive <url> <run>` | Walk a page through named interactions and measure each state it reaches, reporting findings rather than gating (`--json`)
|
|
84
|
-
| `canon capture [source]` | Render HTML sources to PNG and prove the font each one declares resolved (`--selector` required, `--out`)
|
|
85
|
-
| `canon serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--json`)
|
|
86
|
-
| `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 docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
|
|
24
|
+
| `canon design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS |
|
|
25
|
+
| `canon design regen` | Rewrite this repository's `.claude/DESIGN.md` and `src/design/base.css` from `src/design/tokens.ts` |
|
|
26
|
+
| `canon design css` | Emit the design tokens and components as CSS on stdout (`--no-components` for properties alone) |
|
|
27
|
+
| `canon design install` | Install the base stylesheet into a project at `.claude/design/base.css` |
|
|
28
|
+
| `canon design sync` | Update a base stylesheet already installed under `.claude/design/` |
|
|
29
|
+
| `canon slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck, reporting any unrecognized layout name on stderr |
|
|
30
|
+
| `canon slides list` | List the available slide layouts (`--json` for the catalog) |
|
|
31
|
+
| `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 |
|
|
32
|
+
| `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
|
|
33
|
+
| `canon tasks archive` | Move a shipped task and its plan off the board, clear its ordering row, and regenerate the index |
|
|
34
|
+
| `canon tasks pull-request` | Record a pull request number on the task a branch closes, by stem or `--plan` (`--json`) |
|
|
35
|
+
| `canon tasks outcome` | Mark outcomes `[x]` on a task by position, repeating `--close` (`--json`) |
|
|
36
|
+
| `canon tasks validate` | Report board rows whose shape, order, plan, task file, group, file set, or blocker does not hold (`--json`) |
|
|
37
|
+
| `canon intake list` | Report intake folder counts, or one folder's items, keeping what is unread with `--unread` (`--json`) |
|
|
38
|
+
| `canon intake answer` | Write selections into one cluster's answer slots, repeating `--set <item>=<answer>` (`--json`) |
|
|
39
|
+
| `canon teach list` | Report learning workspaces and the ordinal a new one takes, or what one workspace holds (`--json`) |
|
|
40
|
+
| `canon teach open` | Open a workspace at the next ordinal with its mission, resources, and glossary files (`--json`) |
|
|
41
|
+
| `canon teach resource` | Record sources and leads in a workspace, repeating `--read` or `--lead` as `<title>=<url>` (`--json`) |
|
|
42
|
+
| `canon teach glossary` | Add terms to a workspace glossary alphabetically, repeating `--term <term>=<definition>` (`--json`) |
|
|
43
|
+
| `canon teach stylesheet` | Seed a workspace stylesheet from the design source, leaving an existing one alone without `--force` (`--json`) |
|
|
44
|
+
| `canon records validate` | Report a session record or a standard against the standard governing it, per kind (`--json`) |
|
|
45
|
+
| `canon records migrate` | Rewrite the records a validate finding names a recoverable transform for (`--write`, `--json`) |
|
|
46
|
+
| `canon records size` | Report what each record folder holds and how much of it is recent, heaviest first (`--json`) |
|
|
47
|
+
| `canon records push` | Commit the nine backed record folders and push them to a private records remote (`--json`) |
|
|
48
|
+
| `canon records pull` | Fetch the records remote and write it back, refusing rather than discarding unpushed records (`--json`) |
|
|
49
|
+
| `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`) |
|
|
50
|
+
| `canon migrate records` | Move the gitignored session records to `.canon/` and repoint every tracked citation, reporting the plan without `--write` (`--root`, `--json`) |
|
|
51
|
+
| `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`) |
|
|
52
|
+
| `canon sessions list` | Resolve live sessions to the worktree and branch each holds, filtered by `--branch` (`--json`) |
|
|
53
|
+
| `canon worktrees list` | Report which worktrees are reclaimable, keyed on the pull request having merged, with every refusal and the removal route named (`--json`) |
|
|
54
|
+
| `canon worktrees reclaim` | Remove every reclaimable worktree and the branch behind it, reporting without acting under `--dry-run` (`--json`) |
|
|
55
|
+
| `canon comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
|
|
56
|
+
| `canon context audit` | Report required sections, length, cited paths, reference form, catalog tables, provenance, superseded-decision narration, and index drift |
|
|
57
|
+
| `canon markdown audit` | Fail any markdown path on a banned character, word, or spelling, and report the structural checkpoints |
|
|
58
|
+
| `canon claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
|
|
59
|
+
| `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`) |
|
|
60
|
+
| `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`) |
|
|
61
|
+
| `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 |
|
|
62
|
+
| `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>`) |
|
|
63
|
+
| `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`) |
|
|
64
|
+
| `canon gov test-order` | Report where an implementation reached history ahead of the test covering it (`--json`) |
|
|
65
|
+
| `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`) |
|
|
66
|
+
| `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`) |
|
|
67
|
+
| `canon gov citations` | Resolve every path a rule cites and every internal frontmatter glob, failing on one reaching nothing (`--json`) |
|
|
68
|
+
| `canon secrets scan` | Report credential-shaped values in the tree the package ships, keyed on issued values rather than on words (`--json`) |
|
|
69
|
+
| `canon deps audit` | Report published advisories against the resolved dependency set, refusing rather than reporting clean when the index is unreachable (`--json`) |
|
|
70
|
+
| `canon labels audit` | Report the labels a changed set earns from the pull request label map and the paths no row reaches (`--json`) |
|
|
71
|
+
| `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, or a title word no dictionary holds (`--event`, `--json`) |
|
|
72
|
+
| `canon autoship classify` | Decide whether a changed set needs the review pass, naming the file and the test that decided it (`--json`) |
|
|
73
|
+
| `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`) |
|
|
74
|
+
| `canon pr head` | Compare the head a pull request object reports against the branch tip the remote carries, naming both shas (`--root`, `--json`) |
|
|
75
|
+
| `canon pr checks` | Report the check runs belonging to the branch tip, reading pending for a tip carrying none rather than clean (`--root`, `--json`) |
|
|
76
|
+
| `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`) |
|
|
77
|
+
| `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`) |
|
|
78
|
+
| `canon census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`) |
|
|
79
|
+
| `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`) |
|
|
80
|
+
| `canon audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`) |
|
|
81
|
+
| `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`) |
|
|
82
|
+
| `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`) |
|
|
83
|
+
| `canon drive <url> <run>` | Walk a page through named interactions and measure each state it reaches, reporting findings rather than gating (`--json`) |
|
|
84
|
+
| `canon capture [source]` | Render HTML sources to PNG and prove the font each one declares resolved (`--selector` required, `--out`) |
|
|
85
|
+
| `canon serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--json`) |
|
|
86
|
+
| `canon upgrade` | Reinstall the CLI globally with the package manager the install path names (`--json`) |
|
|
87
87
|
|
|
88
88
|
`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 stylesheet and 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. 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.
|
|
89
89
|
|
package/package.json
CHANGED
package/src/commands/labels.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs'
|
|
2
1
|
import { resolve } from 'node:path'
|
|
3
2
|
import type { Command } from 'commander'
|
|
4
3
|
import { type LabelAuditRefusal, auditLabels } from '@/labels/audit'
|
|
4
|
+
import { resolveScanInput } from '@/labels/event'
|
|
5
5
|
import { MAP_REL } from '@/labels/map'
|
|
6
6
|
import { scanPhaseLabels } from '@/labels/phase'
|
|
7
7
|
import { scanTitleSpelling } from '@/labels/spelling'
|
|
@@ -91,12 +91,12 @@ export function register(program: Command): void {
|
|
|
91
91
|
labels
|
|
92
92
|
.command('scan')
|
|
93
93
|
.description(
|
|
94
|
-
'Fail a pull request whose title or
|
|
94
|
+
'Fail a pull request or a posted review whose title, body, or review comment carries a phase label, a board identifier, a session link, or a title with an unspelled word',
|
|
95
95
|
)
|
|
96
96
|
.helpOption('-h, --help', 'Show this help message')
|
|
97
97
|
.option(
|
|
98
98
|
'--event <path>',
|
|
99
|
-
'GitHub pull_request event payload to read, such as $GITHUB_EVENT_PATH',
|
|
99
|
+
'GitHub pull_request or pull_request_review event payload to read, such as $GITHUB_EVENT_PATH',
|
|
100
100
|
)
|
|
101
101
|
.option('--title <text>', 'Title to scan, overriding the event payload')
|
|
102
102
|
.option('--body <text>', 'Body to scan, overriding the event payload')
|
|
@@ -113,6 +113,11 @@ export function register(program: Command): void {
|
|
|
113
113
|
'other pull request may carry neither, so any token found there is a',
|
|
114
114
|
'leaked phase label.',
|
|
115
115
|
'',
|
|
116
|
+
'A posted review is read the same way, scanning `review.body` off a',
|
|
117
|
+
'`pull_request_review` payload rather than the title and body of the',
|
|
118
|
+
'pull request itself, which is text a person can otherwise publish',
|
|
119
|
+
'by writing straight into the review box instead of the description.',
|
|
120
|
+
'',
|
|
116
121
|
'It reports a board identifier beside that, being text naming the task',
|
|
117
122
|
'board rather than the change: a version token a code span quotes, and a',
|
|
118
123
|
'path under a record root, both of which a reader on the remote holds no',
|
|
@@ -236,97 +241,6 @@ async function runAudit(paths: string[], opts: AuditOptions): Promise<number> {
|
|
|
236
241
|
return coverage.uncovered.length === 0 ? 0 : 2
|
|
237
242
|
}
|
|
238
243
|
|
|
239
|
-
/** Why `runScan` had no title and body to hand `scanPhaseLabels`. */
|
|
240
|
-
type ScanInputRefusal = 'no-input' | 'unreadable-event' | 'not-a-pull-request'
|
|
241
|
-
|
|
242
|
-
type ResolvedScanInput =
|
|
243
|
-
| {
|
|
244
|
-
readonly kind: 'resolved'
|
|
245
|
-
readonly title: string
|
|
246
|
-
readonly body: string
|
|
247
|
-
readonly headRefName: string
|
|
248
|
-
}
|
|
249
|
-
| {
|
|
250
|
-
readonly kind: 'refused'
|
|
251
|
-
readonly reason: ScanInputRefusal
|
|
252
|
-
readonly message: string
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Reads a title, a body, and a head branch from explicit flags first and the
|
|
257
|
-
* named event payload second, so a caller testing the wiring by hand never
|
|
258
|
-
* needs a real GitHub event file on disk.
|
|
259
|
-
*/
|
|
260
|
-
function resolveScanInput(opts: ScanOptions): ResolvedScanInput {
|
|
261
|
-
let title = opts.title
|
|
262
|
-
let body = opts.body
|
|
263
|
-
let headRefName = opts.head
|
|
264
|
-
|
|
265
|
-
if (opts.event !== undefined) {
|
|
266
|
-
let raw: string
|
|
267
|
-
try {
|
|
268
|
-
raw = readFileSync(opts.event, 'utf8')
|
|
269
|
-
} catch {
|
|
270
|
-
return {
|
|
271
|
-
kind: 'refused',
|
|
272
|
-
reason: 'unreadable-event',
|
|
273
|
-
message: `${opts.event} could not be read, so no payload was there to scan.`,
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
let payload: unknown
|
|
278
|
-
try {
|
|
279
|
-
payload = JSON.parse(raw)
|
|
280
|
-
} catch {
|
|
281
|
-
return {
|
|
282
|
-
kind: 'refused',
|
|
283
|
-
reason: 'unreadable-event',
|
|
284
|
-
message: `${opts.event} is not valid JSON, so no payload was there to scan.`,
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const pullRequest =
|
|
289
|
-
typeof payload === 'object' && payload !== null
|
|
290
|
-
? (payload as Record<string, unknown>).pull_request
|
|
291
|
-
: undefined
|
|
292
|
-
|
|
293
|
-
if (typeof pullRequest !== 'object' || pullRequest === null) {
|
|
294
|
-
return {
|
|
295
|
-
kind: 'refused',
|
|
296
|
-
reason: 'not-a-pull-request',
|
|
297
|
-
message: `${opts.event} carries no pull_request, so no title or body exists to scan.`,
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const record = pullRequest as Record<string, unknown>
|
|
302
|
-
const head = record.head
|
|
303
|
-
title ??= typeof record.title === 'string' ? record.title : undefined
|
|
304
|
-
body ??= typeof record.body === 'string' ? record.body : undefined
|
|
305
|
-
headRefName ??=
|
|
306
|
-
typeof head === 'object' &&
|
|
307
|
-
head !== null &&
|
|
308
|
-
typeof (head as Record<string, unknown>).ref === 'string'
|
|
309
|
-
? ((head as Record<string, unknown>).ref as string)
|
|
310
|
-
: undefined
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (title === undefined) {
|
|
314
|
-
return {
|
|
315
|
-
kind: 'refused',
|
|
316
|
-
reason: 'no-input',
|
|
317
|
-
message:
|
|
318
|
-
'No --event, --title, or --body given, so there is nothing to scan.',
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
kind: 'resolved',
|
|
324
|
-
title,
|
|
325
|
-
body: body ?? '',
|
|
326
|
-
headRefName: headRefName ?? '',
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
244
|
async function runScan(opts: ScanOptions): Promise<number> {
|
|
331
245
|
const emitJson = opts.json ?? false
|
|
332
246
|
|
|
@@ -350,11 +264,13 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
350
264
|
const result = scanPhaseLabels(resolved)
|
|
351
265
|
const spelling = await scanTitleSpelling(resolved.title, process.cwd())
|
|
352
266
|
|
|
353
|
-
logStep('Pull request')
|
|
267
|
+
logStep(resolved.source === 'review' ? 'Review comment' : 'Pull request')
|
|
354
268
|
logInfo(
|
|
355
|
-
|
|
356
|
-
? 'reads as a
|
|
357
|
-
:
|
|
269
|
+
resolved.source === 'review'
|
|
270
|
+
? 'reads as a posted review comment'
|
|
271
|
+
: result.cutsRelease
|
|
272
|
+
? 'reads as a release-please pull request'
|
|
273
|
+
: 'reads as an ordinary pull request',
|
|
358
274
|
)
|
|
359
275
|
|
|
360
276
|
logStep(result.phaseLabels.length === 0 ? 'Clean' : 'Phase label found')
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
/** Why `resolveScanInput` had no title and body to hand `scanPhaseLabels`. */
|
|
4
|
+
export type ScanInputRefusal =
|
|
5
|
+
| 'no-input'
|
|
6
|
+
| 'unreadable-event'
|
|
7
|
+
| 'not-a-pull-request'
|
|
8
|
+
| 'unreadable-review'
|
|
9
|
+
|
|
10
|
+
export type ResolvedScanInput =
|
|
11
|
+
| {
|
|
12
|
+
readonly kind: 'resolved'
|
|
13
|
+
readonly title: string
|
|
14
|
+
readonly body: string
|
|
15
|
+
readonly headRefName: string
|
|
16
|
+
/** Which text was scanned, for the caller's own reporting. */
|
|
17
|
+
readonly source: 'pull-request' | 'review'
|
|
18
|
+
}
|
|
19
|
+
| {
|
|
20
|
+
readonly kind: 'refused'
|
|
21
|
+
readonly reason: ScanInputRefusal
|
|
22
|
+
readonly message: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ScanInputOptions {
|
|
26
|
+
readonly event?: string
|
|
27
|
+
readonly title?: string
|
|
28
|
+
readonly body?: string
|
|
29
|
+
readonly head?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reads a title, a body, and a head branch from explicit flags first and the
|
|
34
|
+
* named event payload second, so a caller testing the wiring by hand never
|
|
35
|
+
* needs a real GitHub event file on disk.
|
|
36
|
+
*
|
|
37
|
+
* A `pull_request_review` payload carries no title of its own, and the text a
|
|
38
|
+
* reviewer wrote lives at `review.body` rather than at `pull_request.body`.
|
|
39
|
+
* `payload.review` is read ahead of `payload.pull_request` so a payload
|
|
40
|
+
* carrying both, which a real review event does, is never read off the
|
|
41
|
+
* pull request's own title and body once a review resolved.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveScanInput(opts: ScanInputOptions): ResolvedScanInput {
|
|
44
|
+
let title = opts.title
|
|
45
|
+
let body = opts.body
|
|
46
|
+
let headRefName = opts.head
|
|
47
|
+
let source: 'pull-request' | 'review' = 'pull-request'
|
|
48
|
+
|
|
49
|
+
if (opts.event !== undefined) {
|
|
50
|
+
let raw: string
|
|
51
|
+
try {
|
|
52
|
+
raw = readFileSync(opts.event, 'utf8')
|
|
53
|
+
} catch {
|
|
54
|
+
return {
|
|
55
|
+
kind: 'refused',
|
|
56
|
+
reason: 'unreadable-event',
|
|
57
|
+
message: `${opts.event} could not be read, so no payload was there to scan.`,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let payload: unknown
|
|
62
|
+
try {
|
|
63
|
+
payload = JSON.parse(raw)
|
|
64
|
+
} catch {
|
|
65
|
+
return {
|
|
66
|
+
kind: 'refused',
|
|
67
|
+
reason: 'unreadable-event',
|
|
68
|
+
message: `${opts.event} is not valid JSON, so no payload was there to scan.`,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const envelope =
|
|
73
|
+
typeof payload === 'object' && payload !== null
|
|
74
|
+
? (payload as Record<string, unknown>)
|
|
75
|
+
: undefined
|
|
76
|
+
const review = envelope?.review
|
|
77
|
+
const pullRequest = envelope?.pull_request
|
|
78
|
+
|
|
79
|
+
if (typeof review === 'object' && review !== null) {
|
|
80
|
+
const reviewRecord = review as Record<string, unknown>
|
|
81
|
+
|
|
82
|
+
if (body === undefined) {
|
|
83
|
+
const rawBody = reviewRecord.body
|
|
84
|
+
if (rawBody === null || rawBody === undefined) {
|
|
85
|
+
body = ''
|
|
86
|
+
source = 'review'
|
|
87
|
+
} else if (typeof rawBody === 'string') {
|
|
88
|
+
body = rawBody
|
|
89
|
+
source = 'review'
|
|
90
|
+
} else {
|
|
91
|
+
return {
|
|
92
|
+
kind: 'refused',
|
|
93
|
+
reason: 'unreadable-review',
|
|
94
|
+
message: `${opts.event} carries a review.body that is neither a string nor null, so no text was there to scan.`,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
title ??= ''
|
|
100
|
+
|
|
101
|
+
// Defense in depth for `isReleasePullRequest`, which already reads
|
|
102
|
+
// `cutsRelease` as false off the empty title above. A review carries no
|
|
103
|
+
// head ref of its own, so this reaches for the pull request's.
|
|
104
|
+
if (typeof pullRequest === 'object' && pullRequest !== null) {
|
|
105
|
+
const head = (pullRequest as Record<string, unknown>).head
|
|
106
|
+
headRefName ??=
|
|
107
|
+
typeof head === 'object' &&
|
|
108
|
+
head !== null &&
|
|
109
|
+
typeof (head as Record<string, unknown>).ref === 'string'
|
|
110
|
+
? ((head as Record<string, unknown>).ref as string)
|
|
111
|
+
: undefined
|
|
112
|
+
}
|
|
113
|
+
} else if (typeof pullRequest === 'object' && pullRequest !== null) {
|
|
114
|
+
const record = pullRequest as Record<string, unknown>
|
|
115
|
+
const head = record.head
|
|
116
|
+
title ??= typeof record.title === 'string' ? record.title : undefined
|
|
117
|
+
body ??= typeof record.body === 'string' ? record.body : undefined
|
|
118
|
+
headRefName ??=
|
|
119
|
+
typeof head === 'object' &&
|
|
120
|
+
head !== null &&
|
|
121
|
+
typeof (head as Record<string, unknown>).ref === 'string'
|
|
122
|
+
? ((head as Record<string, unknown>).ref as string)
|
|
123
|
+
: undefined
|
|
124
|
+
} else {
|
|
125
|
+
return {
|
|
126
|
+
kind: 'refused',
|
|
127
|
+
reason: 'not-a-pull-request',
|
|
128
|
+
message: `${opts.event} carries no pull_request or review, so no title or body exists to scan.`,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Loosened from `title === undefined` alone so `--body` on its own, with no
|
|
134
|
+
// `--event` and no `--title`, exercises the same empty-title, review-style
|
|
135
|
+
// path a real review scans by hand.
|
|
136
|
+
if (title === undefined && body === undefined) {
|
|
137
|
+
return {
|
|
138
|
+
kind: 'refused',
|
|
139
|
+
reason: 'no-input',
|
|
140
|
+
message:
|
|
141
|
+
'No --event, --title, or --body given, so there is nothing to scan.',
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
kind: 'resolved',
|
|
147
|
+
title: title ?? '',
|
|
148
|
+
body: body ?? '',
|
|
149
|
+
headRefName: headRefName ?? '',
|
|
150
|
+
source,
|
|
151
|
+
}
|
|
152
|
+
}
|
package/standards/publish.md
CHANGED
|
@@ -44,7 +44,7 @@ Two shapes get past a reader scanning for a bare label. A code span quoting a la
|
|
|
44
44
|
|
|
45
45
|
Rewrite a hit to name what the reader can reach rather than deleting it. A row's subject stated plainly replaces its label, and what a record folder holds, said in a sentence, replaces its path.
|
|
46
46
|
|
|
47
|
-
`canon labels scan` runs this check and the phase-label one over a pull request title and body
|
|
47
|
+
`canon labels scan` runs this check and the phase-label one over a pull request title and body, and over a posted review's own text once one is submitted. Any other channel, a plain issue comment among them, is still the author's own scan.
|
|
48
48
|
|
|
49
49
|
## Session links
|
|
50
50
|
|