@erclx/canon 5.45.0 → 5.46.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/api-design/REQUIREMENT.md +15 -31
- package/claude/skills/auto-ship/REQUIREMENT.md +14 -25
- package/claude/skills/backlog-triage/REQUIREMENT.md +12 -23
- package/claude/skills/build-in-slices/REQUIREMENT.md +15 -25
- package/claude/skills/canon-cli/REQUIREMENT.md +15 -27
- package/claude/skills/canon-feedback/REQUIREMENT.md +10 -18
- package/claude/skills/canon-feedback-triage/REQUIREMENT.md +10 -15
- package/claude/skills/canvas-board/REQUIREMENT.md +14 -28
- package/claude/skills/ci-workflow/REQUIREMENT.md +12 -21
- package/claude/skills/code-craft/REQUIREMENT.md +14 -28
- package/claude/skills/codebase-layout/REQUIREMENT.md +14 -31
- package/claude/skills/context-fold/REQUIREMENT.md +15 -39
- package/claude/skills/context-fold/references/context.md +3 -3
- package/claude/skills/create-rule/REQUIREMENT.md +10 -24
- package/claude/skills/create-rule/SKILL.md +3 -0
- package/claude/skills/create-skill/REQUIREMENT.md +10 -21
- package/claude/skills/decision-escalate/REQUIREMENT.md +12 -24
- package/claude/skills/deploy-app/REQUIREMENT.md +12 -22
- package/claude/skills/deprecation-migration/REQUIREMENT.md +14 -42
- package/claude/skills/design-identity/REQUIREMENT.md +14 -20
- package/claude/skills/design-taste/REQUIREMENT.md +16 -57
- package/claude/skills/docs-sync/REQUIREMENT.md +14 -23
- package/claude/skills/document-health/REQUIREMENT.md +15 -29
- package/claude/skills/document-health/SKILL.md +2 -2
- package/claude/skills/document-repair/REQUIREMENT.md +32 -0
- package/claude/skills/document-repair/SKILL.md +76 -0
- package/claude/skills/document-repair/references/batches.md +44 -0
- package/claude/skills/draft-and-pick/REQUIREMENT.md +4 -3
- package/claude/skills/draft-doc/REQUIREMENT.md +4 -3
- package/claude/skills/draft-figure/REQUIREMENT.md +4 -3
- package/claude/skills/draft-identity/REQUIREMENT.md +4 -3
- package/claude/skills/draft-ready/REQUIREMENT.md +4 -3
- package/claude/skills/draft-screencast/REQUIREMENT.md +4 -3
- package/claude/skills/draft-slides/REQUIREMENT.md +4 -3
- package/claude/skills/draw-figure/REQUIREMENT.md +15 -39
- package/claude/skills/git-branch/REQUIREMENT.md +12 -26
- package/claude/skills/git-commit/REQUIREMENT.md +11 -15
- package/claude/skills/git-followup/REQUIREMENT.md +11 -22
- package/claude/skills/git-issue/REQUIREMENT.md +9 -15
- package/claude/skills/git-pr/REQUIREMENT.md +13 -32
- package/claude/skills/git-pr/SKILL.md +10 -2
- package/claude/skills/git-pr/references/closing-issues.md +47 -0
- package/claude/skills/git-pr/references/pr.md +4 -0
- package/claude/skills/git-ship/REQUIREMENT.md +13 -26
- package/claude/skills/git-split/REQUIREMENT.md +10 -17
- package/claude/skills/git-stage/REQUIREMENT.md +12 -18
- package/claude/skills/git-worktree/REQUIREMENT.md +15 -19
- package/claude/skills/make-deck/REQUIREMENT.md +13 -21
- package/claude/skills/markdown-craft/REQUIREMENT.md +9 -3
- package/claude/skills/plan-feature/REQUIREMENT.md +15 -24
- package/claude/skills/plan-groundwork/REQUIREMENT.md +12 -28
- package/claude/skills/plan-groundwork/SKILL.md +5 -2
- package/claude/skills/plan-groundwork/references/output.md +47 -1
- package/claude/skills/plan-intake/REQUIREMENT.md +11 -26
- package/claude/skills/plan-intake/references/intake.md +2 -2
- package/claude/skills/plan-intake-answer/REQUIREMENT.md +14 -29
- package/claude/skills/plan-intake-answer/SKILL.md +7 -1
- package/claude/skills/plan-ready/REQUIREMENT.md +17 -36
- package/claude/skills/pr-evidence/REQUIREMENT.md +11 -17
- package/claude/skills/read-frames/REQUIREMENT.md +3 -8
- package/claude/skills/record-screencast/REQUIREMENT.md +14 -17
- package/claude/skills/review-address/REQUIREMENT.md +14 -38
- package/claude/skills/review-craft/REQUIREMENT.md +14 -32
- package/claude/skills/review-pr/REQUIREMENT.md +15 -34
- package/claude/skills/review-ui/REQUIREMENT.md +14 -39
- package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +5 -0
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +9 -0
- package/claude/skills/role-reviewer/REQUIREMENT.md +15 -65
- package/claude/skills/role-reviewer/SKILL.md +5 -1
- package/claude/skills/role-worker/REQUIREMENT.md +1 -1
- package/claude/skills/role-worker/SKILL.md +2 -0
- package/claude/skills/script-screencast/REQUIREMENT.md +10 -16
- package/claude/skills/search-craft/REQUIREMENT.md +12 -36
- package/claude/skills/search-craft/SKILL.md +1 -1
- package/claude/skills/search-craft/references/adopted.md +2 -2
- package/claude/skills/session-compact/REQUIREMENT.md +13 -23
- package/claude/skills/session-map/REQUIREMENT.md +13 -29
- package/claude/skills/session-relay/REQUIREMENT.md +14 -58
- package/claude/skills/session-relay/SKILL.md +6 -0
- package/claude/skills/session-resume/REQUIREMENT.md +13 -27
- package/claude/skills/session-worktree/REQUIREMENT.md +14 -43
- package/claude/skills/systematic-debugging/REQUIREMENT.md +13 -34
- package/claude/skills/target-check/REQUIREMENT.md +16 -23
- package/claude/skills/target-setup/REQUIREMENT.md +11 -34
- package/claude/skills/task-board/REQUIREMENT.md +13 -22
- package/claude/skills/task-board/SKILL.md +7 -0
- package/claude/skills/task-board/references/tasks.md +1 -1
- package/claude/skills/task-complete/REQUIREMENT.md +11 -24
- package/claude/skills/task-complete/references/archive.md +1 -0
- package/claude/skills/teach-workspace/REQUIREMENT.md +14 -45
- package/claude/skills/test-craft/REQUIREMENT.md +15 -46
- package/claude/skills/test-first/REQUIREMENT.md +14 -20
- package/claude/skills/ui-checklist/REQUIREMENT.md +14 -29
- package/claude/skills/ux-audit/REQUIREMENT.md +3 -7
- package/claude/skills/ux-measure/REQUIREMENT.md +14 -28
- package/claude/skills/video-craft/REQUIREMENT.md +14 -27
- package/claude/skills/write-doc/REQUIREMENT.md +14 -24
- package/claude/skills/write-human/REQUIREMENT.md +15 -27
- package/claude/skills/youtube-transcripts/REQUIREMENT.md +9 -15
- package/docs/agents/context-audit.md +19 -12
- package/docs/agents/markdown-audit.md +15 -10
- package/docs/target/projects.md +2 -0
- package/docs/workflow/skill-map.md +6 -5
- package/governance/renames.toml +0 -2
- package/governance/rules/claude/output.md +5 -1
- package/governance/rules/code/routing.md +5 -1
- package/governance/stacks/base.toml +0 -1
- package/package.json +1 -1
- package/src/claude/cases/workflow.ts +12 -0
- package/src/commands/context.ts +57 -3
- package/src/commands/labels.ts +83 -3
- package/src/commands/markdown.ts +188 -21
- package/src/commands/sessions.ts +36 -3
- package/src/commands/tasks.ts +180 -0
- package/src/commands/worktrees.ts +2 -0
- package/src/context/coverage.ts +210 -0
- package/src/context/gate.ts +12 -3
- package/src/gate/stages.ts +1 -1
- package/src/init/steps.ts +45 -0
- package/src/intake/items.ts +14 -0
- package/src/labels/closing.ts +46 -0
- package/src/markdown/gated.ts +250 -0
- package/src/markdown/scope.ts +115 -2
- package/src/migrate/rename.ts +6 -0
- package/src/sessions/claim.ts +7 -0
- package/src/sessions/live.ts +53 -6
- package/src/sessions/registry.ts +11 -0
- package/src/sessions/resolve.ts +32 -5
- package/src/tasks/closes.ts +136 -0
- package/src/tasks/record.ts +1 -1
- package/src/worktrees/reclaim.ts +16 -1
- package/tooling/base/configs/scripts/verify.ts +1 -1
- package/tooling/claude/seeds/.claude/hooks/board-validate.sh +1 -1
- package/tooling/claude/seeds/CLAUDE.md +2 -2
- package/governance/rules/canon/task-scope.md +0 -12
|
@@ -22,14 +22,14 @@ An argument is a file, a directory, or a glob, taken against the corpus git list
|
|
|
22
22
|
|
|
23
23
|
The ban set and checkpoints ship in `src/markdown/bans.ts` and `src/markdown/structure.ts`. `markdown.md` states each rule and the `write-human` skill states the cadence numbers, so move a number in both places in one change.
|
|
24
24
|
|
|
25
|
-
| Finding | Reports | Gates
|
|
26
|
-
| --------------- | ------------------------------------------------------------------------------------ |
|
|
27
|
-
| Bans | The two characters `markdown.md` bans, outside frontmatter, fences, spans, and links | Always
|
|
28
|
-
| Links | A relative link resolving to nothing on disk | Always
|
|
29
|
-
| Density | The six limits below, under `density` | On a
|
|
30
|
-
| Reference shape | A `kind: reference` page breaking its shape, under `referenceShape` | On a
|
|
31
|
-
| Colons | A colon followed by more text in a prose line, under `colons` | Never
|
|
32
|
-
| Heading case | A capital after the first word of a heading or `title`, under `headingCase` | Never
|
|
25
|
+
| Finding | Reports | Gates |
|
|
26
|
+
| --------------- | ------------------------------------------------------------------------------------ | ----------------- |
|
|
27
|
+
| Bans | The two characters `markdown.md` bans, outside frontmatter, fences, spans, and links | Always |
|
|
28
|
+
| Links | A relative link resolving to nothing on disk | Always |
|
|
29
|
+
| Density | The six limits below, under `density` | On a gated prefix |
|
|
30
|
+
| Reference shape | A `kind: reference` page breaking its shape, under `referenceShape` | On a gated prefix |
|
|
31
|
+
| Colons | A colon followed by more text in a prose line, under `colons` | Never |
|
|
32
|
+
| Heading case | A capital after the first word of a heading or `title`, under `headingCase` | Never |
|
|
33
33
|
|
|
34
34
|
A page marked `kind: reference` holds its opening, then `Output`, `Refusals`, and `See also` as H2s, each at most once and in that order. It takes no H3 and no table headed `Flag` or `Option`. An unmarked page is never measured.
|
|
35
35
|
|
|
@@ -46,9 +46,14 @@ A page marked `kind: reference` holds its opening, then `Output`, `Refusals`, an
|
|
|
46
46
|
- A table, fence, list, heading, or bold section marker ends a run of paragraphs
|
|
47
47
|
- A run whose every item opens with a bold label or a link is exempt, and numbered items never count
|
|
48
48
|
- Only context entries, docs, skill bodies, references, and requirements, tooling seeds and stack references, the wiki, `README.md`, and `CONTRIBUTING.md` take a finding
|
|
49
|
-
- A
|
|
49
|
+
- A requirement gates only once the gated list below names its own file, never through its skill's prefix and never on a bullet run alone
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
| Root | A density or reference shape finding gates |
|
|
52
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| Canon's checkout | Under a `GATED_PATHS` prefix in `src/markdown/scope.ts`, and a bullet run on every density path above |
|
|
54
|
+
| Any other project | Under a prefix `canon/config/markdown.toml` lists, a bullet run included. With no such file nothing gates. |
|
|
55
|
+
|
|
56
|
+
A project's list grows through `canon markdown gated add <prefix>`, which appends a folder only once it carries no finding, and `canon init` starts a new project with every root that passes already listed. A config that does not parse refuses the audit with its path named.
|
|
52
57
|
|
|
53
58
|
Five measures report and never set the exit code on their own.
|
|
54
59
|
|
package/docs/target/projects.md
CHANGED
|
@@ -72,6 +72,8 @@ The default is `base`. Escalate only for a project writing code, and `target-set
|
|
|
72
72
|
|
|
73
73
|
`canon init` installs base tooling, the Claude workflow, and governance. Pass `--stack <name>` for a language stack.
|
|
74
74
|
|
|
75
|
+
Init then lists each markdown root that already passes the density limits in `canon/config/markdown.toml`, so those folders fail a push from the start. A root carrying a finding is named and left out, and `canon:document-repair` brings it to zero later. An existing config is kept as it is.
|
|
76
|
+
|
|
75
77
|
- `--skip governance` leaves `.claude/rules/` empty. The run prints the `canon gov install <stack> <path>` command that restores the rules it dropped.
|
|
76
78
|
|
|
77
79
|
## Add a domain later
|
|
@@ -99,8 +99,9 @@ Groups run in the order a project meets them. Every name `canon claude skills li
|
|
|
99
99
|
|
|
100
100
|
## Keep the project current with the toolkit
|
|
101
101
|
|
|
102
|
-
| Skill | When to use
|
|
103
|
-
| ----------------------------- |
|
|
104
|
-
| `canon:target-check` | In a target, to compare each domain with the toolkit
|
|
105
|
-
| `canon:
|
|
106
|
-
| `canon:canon-feedback
|
|
102
|
+
| Skill | When to use |
|
|
103
|
+
| ----------------------------- | ------------------------------------------------------- |
|
|
104
|
+
| `canon:target-check` | In a target, to compare each domain with the toolkit |
|
|
105
|
+
| `canon:document-repair` | Rewrite a failing docs folder to the limits and gate it |
|
|
106
|
+
| `canon:canon-feedback` | The toolkit is broken, missing, or off |
|
|
107
|
+
| `canon:canon-feedback-triage` | In the toolkit repo, to file feedback issues |
|
package/governance/renames.toml
CHANGED
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
# file.
|
|
16
16
|
[renamed]
|
|
17
17
|
"015-output" = "566-output"
|
|
18
|
-
"035-tasks" = "602-tasks"
|
|
19
18
|
"055-scratch" = "604-scratch"
|
|
20
19
|
"085-worktrees" = "605-worktrees"
|
|
21
20
|
"087-git" = "606-git"
|
|
@@ -32,7 +31,6 @@
|
|
|
32
31
|
"655-tasks" = "tasks"
|
|
33
32
|
"570-skill" = "skill"
|
|
34
33
|
"801-markdown" = "markdown"
|
|
35
|
-
"602-tasks" = "task-scope"
|
|
36
34
|
"604-scratch" = "scratch"
|
|
37
35
|
"605-worktrees" = "worktrees"
|
|
38
36
|
"606-git" = "git"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Report file paths correctly for the reading surface
|
|
2
|
+
description: Report file paths correctly for the reading surface, group them when the response covers many, and name sessions to a person
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Output standards
|
|
@@ -17,3 +17,7 @@ description: Report file paths correctly for the reading surface and group them
|
|
|
17
17
|
|
|
18
18
|
- Group paths under `**Created:**`, `**Modified:**`, and `**Deleted:**` headers when the response covers multiple files, with every path under them in that form.
|
|
19
19
|
- Put a single-file change's path on its own line, with no header.
|
|
20
|
+
|
|
21
|
+
## Naming sessions
|
|
22
|
+
|
|
23
|
+
- Name a session to a person by its roster name from `canon sessions list`, and give its id only beside the name, never alone.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Routes code placement and shape decisions to the skills that carry them
|
|
2
|
+
description: Routes code placement and shape decisions to the skills that carry them, and library calls to the official docs
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Code standards
|
|
@@ -11,3 +11,7 @@ description: Routes code placement and shape decisions to the skills that carry
|
|
|
11
11
|
## Shape
|
|
12
12
|
|
|
13
13
|
- Load the `canon:code-craft` skill before deciding the shape of a new function, class, or module, and report it rather than proceeding silently when it does not resolve.
|
|
14
|
+
|
|
15
|
+
## Libraries
|
|
16
|
+
|
|
17
|
+
- Before calling a library API the codebase does not already use, fetch the official docs for the installed version and match the call to them, since recall of an API goes stale.
|
package/package.json
CHANGED
|
@@ -146,6 +146,18 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
|
|
|
146
146
|
'Are our requirements and architecture docs still true, and when were they last reviewed?',
|
|
147
147
|
expect: 'document-health',
|
|
148
148
|
},
|
|
149
|
+
// A repair rather than a report, and folder by folder until each gates,
|
|
150
|
+
// which is what separates it from `document-health` above.
|
|
151
|
+
{
|
|
152
|
+
prompt:
|
|
153
|
+
'Our docs folder fails the paragraph and bullet limits everywhere, so rewrite it until it passes and lock it in.',
|
|
154
|
+
expect: 'document-repair',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
prompt:
|
|
158
|
+
'Bring the context entries under the density limits one folder at a time and gate each once it is clean.',
|
|
159
|
+
expect: 'document-repair',
|
|
160
|
+
},
|
|
149
161
|
{
|
|
150
162
|
prompt: 'Open a new entry on the task board for this piece of work.',
|
|
151
163
|
expect: 'task-board',
|
package/src/commands/context.ts
CHANGED
|
@@ -56,6 +56,11 @@ import {
|
|
|
56
56
|
presentNames,
|
|
57
57
|
resolveFolders,
|
|
58
58
|
} from '@/context/folders'
|
|
59
|
+
import {
|
|
60
|
+
type CoverageReport,
|
|
61
|
+
measureCoverage,
|
|
62
|
+
readRoots,
|
|
63
|
+
} from '@/context/coverage'
|
|
59
64
|
import { isGating } from '@/context/gate'
|
|
60
65
|
import { isShapeGated, SHAPE_GATED_FOLDERS } from '@/context/shape-gated'
|
|
61
66
|
import { currentWorktreeRoot } from '@/git/worktree'
|
|
@@ -101,7 +106,7 @@ export function register(program: Command): void {
|
|
|
101
106
|
context
|
|
102
107
|
.command('audit')
|
|
103
108
|
.description(
|
|
104
|
-
'Report required sections, entry length, citations, reference form, catalog tables, provenance, index drift, and the requirements record against its own word cap',
|
|
109
|
+
'Report required sections, entry length, citations, reference form, catalog tables, provenance, index drift, code folders no entry names, and the requirements record against its own word cap',
|
|
105
110
|
)
|
|
106
111
|
.argument('[path]', 'Project root, defaulting to the current directory')
|
|
107
112
|
.helpOption('-h, --help', 'Show this help message')
|
|
@@ -113,7 +118,7 @@ export function register(program: Command): void {
|
|
|
113
118
|
.option('--citations-only', 'Run the gating citation check alone')
|
|
114
119
|
.option(
|
|
115
120
|
'--gate',
|
|
116
|
-
'Also fail on a missing required section, an H2 outside the set, a shape break in a gated folder,
|
|
121
|
+
'Also fail on a missing required section, an H2 outside the set, a shape break in a gated folder, index drift, or a code folder no entry names, the findings that are facts',
|
|
117
122
|
)
|
|
118
123
|
.addHelpText(
|
|
119
124
|
'after',
|
|
@@ -129,7 +134,9 @@ export function register(program: Command): void {
|
|
|
129
134
|
'--citations-only, which never measures it. A record stating no',
|
|
130
135
|
'cap is reported and never gated. --gate widens the gate to the',
|
|
131
136
|
'findings that are facts rather than judgments: a missing',
|
|
132
|
-
'required section, an H2 outside the closed set,
|
|
137
|
+
'required section, an H2 outside the closed set, index drift, and',
|
|
138
|
+
'a code folder no Layout names or two entries claim. The roots come',
|
|
139
|
+
'from canon/config/context.toml and default to src when present.',
|
|
133
140
|
'A context entry requires Overview and Layout and allows Gotchas.',
|
|
134
141
|
'A shape break, such as a Decisions section or a sixth gotcha, is',
|
|
135
142
|
'reported everywhere and gated only under a shape-gated folder.',
|
|
@@ -667,6 +674,9 @@ async function runAudit(
|
|
|
667
674
|
isShapeGated(relative(top, join(root, rel))),
|
|
668
675
|
)
|
|
669
676
|
const length = gateOnly ? undefined : lengthFindings(entries)
|
|
677
|
+
const coverage: CoverageReport | undefined = gateOnly
|
|
678
|
+
? undefined
|
|
679
|
+
: measureCoverage(root, readRoots(root), folders)
|
|
670
680
|
// Absent under `--citations-only` and null when the project carries no
|
|
671
681
|
// record. A run that never looked and a project with nothing to look at are
|
|
672
682
|
// different answers, and one value for both reports the second as the first.
|
|
@@ -685,6 +695,7 @@ async function runAudit(
|
|
|
685
695
|
reportTables(entries)
|
|
686
696
|
reportProvenance(entries, folders)
|
|
687
697
|
reportDrift(drift)
|
|
698
|
+
reportCoverage(coverage ?? { kind: 'no-root' })
|
|
688
699
|
reportRequirements(requirements, root)
|
|
689
700
|
outro()
|
|
690
701
|
}
|
|
@@ -721,6 +732,8 @@ async function runAudit(
|
|
|
721
732
|
extraSections: extras,
|
|
722
733
|
shapeFindings: shape,
|
|
723
734
|
indexDrift: drift,
|
|
735
|
+
// Absent under `--citations-only`, which never measures it.
|
|
736
|
+
coverage,
|
|
724
737
|
// Null says the run opened the project and found no record, which a
|
|
725
738
|
// target that never wrote one is entitled to. Absent says the run
|
|
726
739
|
// never looked, which is `--citations-only`.
|
|
@@ -753,6 +766,7 @@ async function runAudit(
|
|
|
753
766
|
extraSections: extras,
|
|
754
767
|
shape,
|
|
755
768
|
drift,
|
|
769
|
+
coverage: coverage ?? { kind: 'no-root' },
|
|
756
770
|
widened,
|
|
757
771
|
})
|
|
758
772
|
|
|
@@ -1210,6 +1224,46 @@ function reportProvenance(
|
|
|
1210
1224
|
)
|
|
1211
1225
|
}
|
|
1212
1226
|
|
|
1227
|
+
function reportCoverage(report: CoverageReport): void {
|
|
1228
|
+
logStep('Coverage')
|
|
1229
|
+
|
|
1230
|
+
if (report.kind === 'no-root') {
|
|
1231
|
+
logInfo(
|
|
1232
|
+
'No code root found, since no canon/config/context.toml declares one and there is no src/, so nothing was measured.',
|
|
1233
|
+
)
|
|
1234
|
+
return
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
if (report.kind === 'invalid') {
|
|
1238
|
+
logWarn(report.reason)
|
|
1239
|
+
return
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (report.kind === 'opted-out') {
|
|
1243
|
+
logInfo(
|
|
1244
|
+
'canon/config/context.toml declares roots = [], so coverage was not measured.',
|
|
1245
|
+
)
|
|
1246
|
+
return
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
const { unnamed, doubled, missingRoots } = report.findings
|
|
1250
|
+
const lines = [
|
|
1251
|
+
...missingRoots.map((root) => `${root} matches no folder`),
|
|
1252
|
+
...unnamed.map((folder) => `${folder} named in no Layout`),
|
|
1253
|
+
...doubled.map(
|
|
1254
|
+
({ folder, rels }) => `${folder} Layout H3 in ${rels.join(', ')}`,
|
|
1255
|
+
),
|
|
1256
|
+
]
|
|
1257
|
+
|
|
1258
|
+
if (lines.length === 0) {
|
|
1259
|
+
logInfo(`Every folder under ${report.roots.join(', ')} is named once.`)
|
|
1260
|
+
return
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
logWarn(plural(lines.length, 'coverage finding'))
|
|
1264
|
+
pipeOutput(lines.join('\n'))
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1213
1267
|
/** Reports the requirements record against the word cap it states, if any. */
|
|
1214
1268
|
function reportRequirements(
|
|
1215
1269
|
report: RequirementsReport | undefined,
|
package/src/commands/labels.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { execa } from 'execa'
|
|
|
5
5
|
import { gitEnv } from '@/git/env'
|
|
6
6
|
import { diffLabels } from '@/labels/apply'
|
|
7
7
|
import { type LabelAuditRefusal, auditLabels } from '@/labels/audit'
|
|
8
|
+
import { checkClosing } from '@/labels/closing'
|
|
8
9
|
import { resolveScanInput } from '@/labels/event'
|
|
9
10
|
import { checkTitleFormat, type TitleFormatIssue } from '@/labels/format'
|
|
10
11
|
import { MAP_REL } from '@/labels/map'
|
|
@@ -34,6 +35,7 @@ interface ScanOptions {
|
|
|
34
35
|
readonly body?: string
|
|
35
36
|
readonly bodyFile?: string
|
|
36
37
|
readonly head?: string
|
|
38
|
+
readonly closes?: string
|
|
37
39
|
readonly json?: boolean
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -196,6 +198,10 @@ export function register(program: Command): void {
|
|
|
196
198
|
'Body to scan, read from a file, overriding the event payload',
|
|
197
199
|
)
|
|
198
200
|
.option('--head <ref>', 'Head branch, overriding the event payload')
|
|
201
|
+
.option(
|
|
202
|
+
'--closes <numbers>',
|
|
203
|
+
'Issues the body must close, a comma list or `none`, graded when given',
|
|
204
|
+
)
|
|
199
205
|
.option('--json', 'Add a machine-readable record on stdout')
|
|
200
206
|
.addHelpText(
|
|
201
207
|
'after',
|
|
@@ -239,12 +245,20 @@ export function register(program: Command): void {
|
|
|
239
245
|
'character, an em dash or a semicolon, in the title or body of a pull',
|
|
240
246
|
'request or a review, outside a code span or a link.',
|
|
241
247
|
'',
|
|
248
|
+
'With --closes, it also grades the closing lines. Each issue named needs',
|
|
249
|
+
'its own `Closes #NNN` keyword in the body, and a closing keyword for an',
|
|
250
|
+
'issue the list leaves out fails. `none` expects no closing line.',
|
|
251
|
+
'Without the flag the check does not run, so the pull request job and a',
|
|
252
|
+
'followup keep their behavior. A keyword in a code span or a fence is',
|
|
253
|
+
'not read, as GitHub does not read it.',
|
|
254
|
+
'',
|
|
242
255
|
'Exit codes:',
|
|
243
|
-
' 0 none of the
|
|
256
|
+
' 0 none of the eight found',
|
|
244
257
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
245
258
|
' 2 the title or body carries a phase label, a board identifier, a',
|
|
246
259
|
' session link, a banned character, a heading outside the closed',
|
|
247
|
-
' list or a required one missing, or
|
|
260
|
+
' list or a required one missing, a missing or stray closing line',
|
|
261
|
+
' under --closes, or a title',
|
|
248
262
|
" breaking claude/skills/git-pr/references/pr.md's format, casing, or length",
|
|
249
263
|
'',
|
|
250
264
|
'Examples:',
|
|
@@ -536,11 +550,42 @@ async function runApply(number: string, opts: ApplyOptions): Promise<number> {
|
|
|
536
550
|
return owed.length === 0 ? 0 : 2
|
|
537
551
|
}
|
|
538
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Reads `--closes` as the issues the body must close. Undefined when the flag
|
|
555
|
+
* is absent, which skips the check, and a message when the value is neither
|
|
556
|
+
* `none` nor a comma list of numbers, since a guessed list would grade the
|
|
557
|
+
* body against the wrong issues.
|
|
558
|
+
*/
|
|
559
|
+
function parseCloses(value: string | undefined): number[] | string | undefined {
|
|
560
|
+
if (value === undefined) return undefined
|
|
561
|
+
if (value === 'none') return []
|
|
562
|
+
if (!/^\d+(,\d+)*$/.test(value)) {
|
|
563
|
+
return `--closes takes a comma list of issue numbers or none, not ${value}`
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return [...new Set(value.split(',').map(Number))]
|
|
567
|
+
}
|
|
568
|
+
|
|
539
569
|
async function runScan(opts: ScanOptions): Promise<number> {
|
|
540
570
|
const emitJson = opts.json ?? false
|
|
541
571
|
|
|
542
572
|
intro('canon labels scan')
|
|
543
573
|
|
|
574
|
+
const expected = parseCloses(opts.closes)
|
|
575
|
+
|
|
576
|
+
if (typeof expected === 'string') {
|
|
577
|
+
logStep('Refused')
|
|
578
|
+
logWarn(expected)
|
|
579
|
+
outro()
|
|
580
|
+
|
|
581
|
+
if (emitJson) {
|
|
582
|
+
process.stdout.write(
|
|
583
|
+
`${JSON.stringify({ reason: 'bad-input', message: expected })}\n`,
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
return 1
|
|
587
|
+
}
|
|
588
|
+
|
|
544
589
|
const resolved = resolveScanInput(opts)
|
|
545
590
|
|
|
546
591
|
if (resolved.kind === 'refused') {
|
|
@@ -693,6 +738,37 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
693
738
|
for (const issue of titleFormatIssues) logWarn(TITLE_FORMAT_MESSAGES[issue])
|
|
694
739
|
}
|
|
695
740
|
|
|
741
|
+
const closing =
|
|
742
|
+
expected === undefined
|
|
743
|
+
? { missing: [], stray: [] }
|
|
744
|
+
: checkClosing(resolved.body, expected)
|
|
745
|
+
|
|
746
|
+
logStep(
|
|
747
|
+
expected === undefined
|
|
748
|
+
? 'Closing lines not checked'
|
|
749
|
+
: closing.missing.length === 0 && closing.stray.length === 0
|
|
750
|
+
? 'Clean'
|
|
751
|
+
: 'Closing line issue found',
|
|
752
|
+
)
|
|
753
|
+
if (expected === undefined) {
|
|
754
|
+
logInfo('no --closes list, so the closing lines are not graded')
|
|
755
|
+
} else if (closing.missing.length === 0 && closing.stray.length === 0) {
|
|
756
|
+
logInfo('the closing lines match the issues the task closes')
|
|
757
|
+
} else {
|
|
758
|
+
if (closing.missing.length > 0) {
|
|
759
|
+
logWarn(
|
|
760
|
+
`${plural(closing.missing.length, 'issue')} with no closing line. Add one \`Closes #NNN\` per issue.`,
|
|
761
|
+
)
|
|
762
|
+
for (const number of closing.missing) logWarn(`#${number}`)
|
|
763
|
+
}
|
|
764
|
+
if (closing.stray.length > 0) {
|
|
765
|
+
logWarn(
|
|
766
|
+
`${plural(closing.stray.length, 'closing line')} for an issue this pull request does not close. Delete each.`,
|
|
767
|
+
)
|
|
768
|
+
for (const number of closing.stray) logWarn(`#${number}`)
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
696
772
|
outro()
|
|
697
773
|
|
|
698
774
|
if (emitJson) {
|
|
@@ -706,6 +782,8 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
706
782
|
titleFormatIssues,
|
|
707
783
|
sections: { checked: sectionsChecked, ...sections },
|
|
708
784
|
bannedCharacters,
|
|
785
|
+
missingClosures: closing.missing,
|
|
786
|
+
strayClosures: closing.stray,
|
|
709
787
|
})}\n`,
|
|
710
788
|
)
|
|
711
789
|
}
|
|
@@ -716,7 +794,9 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
716
794
|
titleFormatIssues.length === 0 &&
|
|
717
795
|
sections.outside.length === 0 &&
|
|
718
796
|
sections.missing.length === 0 &&
|
|
719
|
-
banFindings.length === 0
|
|
797
|
+
banFindings.length === 0 &&
|
|
798
|
+
closing.missing.length === 0 &&
|
|
799
|
+
closing.stray.length === 0
|
|
720
800
|
? 0
|
|
721
801
|
: 2
|
|
722
802
|
}
|