@erclx/canon 5.32.0 → 5.33.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 +1 -1
- package/claude/skills/auto-ship/references/worktree-name.md +1 -1
- package/claude/skills/backlog-triage/REQUIREMENT.md +2 -2
- package/claude/skills/backlog-triage/SKILL.md +1 -1
- package/claude/skills/canon-cli/REQUIREMENT.md +16 -13
- package/claude/skills/canon-cli/SKILL.md +75 -32
- package/claude/skills/canon-feedback/SKILL.md +1 -1
- package/claude/skills/{canvas → canvas-board}/REQUIREMENT.md +2 -2
- package/claude/skills/{canvas → canvas-board}/SKILL.md +2 -2
- package/{standards → claude/skills/canvas-board/references}/design.md +11 -2
- package/claude/skills/context-fold/REQUIREMENT.md +25 -33
- package/claude/skills/context-fold/SKILL.md +25 -70
- package/claude/skills/context-fold/references/classify.md +3 -3
- package/claude/skills/context-fold/references/completion-report.md +4 -4
- package/claude/skills/context-fold/references/context-refresh.md +5 -5
- package/{standards → claude/skills/context-fold/references}/context.md +16 -5
- package/claude/skills/context-fold/references/unusable-baseline.md +3 -5
- package/claude/skills/create-rule/SKILL.md +2 -2
- package/{standards → claude/skills/create-rule/references}/rule.md +12 -5
- package/claude/skills/create-skill/SKILL.md +2 -2
- package/{standards → claude/skills/create-skill/references}/skill.md +17 -14
- package/claude/skills/design-taste/REQUIREMENT.md +3 -3
- package/claude/skills/design-taste/SKILL.md +1 -1
- package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
- package/claude/skills/docs-sync/SKILL.md +3 -3
- package/{standards → claude/skills/docs-sync/references}/docs.md +11 -0
- package/{standards → claude/skills/docs-sync/references}/readme.md +9 -2
- package/claude/skills/draft-and-pick/SKILL.md +5 -5
- package/claude/skills/draft-doc/SKILL.md +6 -6
- package/claude/skills/draft-figure/REQUIREMENT.md +2 -2
- package/claude/skills/draft-figure/SKILL.md +2 -2
- package/claude/skills/draft-figure/references/hand-drawn.md +1 -1
- package/claude/skills/draft-figure/references/mermaid-path.md +1 -1
- package/claude/skills/draft-identity/REQUIREMENT.md +2 -2
- package/claude/skills/draft-identity/SKILL.md +2 -2
- package/claude/skills/draft-ready/SKILL.md +1 -1
- package/claude/skills/git-branch/SKILL.md +1 -1
- package/{standards → claude/skills/git-branch/references}/branch.md +1 -1
- package/claude/skills/git-commit/SKILL.md +2 -2
- package/claude/skills/git-followup/REQUIREMENT.md +1 -1
- package/claude/skills/git-followup/SKILL.md +1 -1
- package/claude/skills/git-issue/SKILL.md +1 -1
- package/claude/skills/git-pr/SKILL.md +13 -13
- package/claude/skills/git-pr/references/evidence.md +2 -2
- package/claude/skills/git-pr/references/final-command.md +38 -14
- package/claude/skills/git-pr/references/labels.md +5 -1
- package/{standards → claude/skills/git-pr/references}/pr.md +19 -9
- package/{standards → claude/skills/git-pr/references}/publish.md +4 -2
- package/claude/skills/git-pr/references/task-number.md +1 -1
- package/claude/skills/git-ship/SKILL.md +15 -14
- package/claude/skills/git-split/SKILL.md +3 -3
- package/claude/skills/git-stage/SKILL.md +1 -1
- package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/SKILL.md +1 -1
- package/claude/skills/review-address/references/post-review-findings.md +1 -1
- package/claude/skills/review-pr/REQUIREMENT.md +1 -1
- package/claude/skills/review-pr/SKILL.md +3 -3
- package/claude/skills/review-pr/references/body-variants.md +2 -2
- package/claude/skills/review-pr/references/key-changes.md +3 -3
- package/claude/skills/review-pr/references/post.md +4 -2
- package/claude/skills/review-ui/REQUIREMENT.md +1 -1
- package/claude/skills/review-ui/SKILL.md +1 -1
- package/claude/skills/review-ui/references/post.md +1 -1
- package/claude/skills/role-orchestrator/REQUIREMENT.md +3 -0
- package/claude/skills/role-orchestrator/SKILL.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +8 -5
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +23 -1
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-resume.md +3 -0
- package/claude/skills/role-planner/REQUIREMENT.md +2 -1
- package/claude/skills/role-planner/SKILL.md +13 -2
- package/claude/skills/role-reviewer/SKILL.md +1 -1
- package/claude/skills/role-worker/REQUIREMENT.md +1 -0
- package/claude/skills/role-worker/SKILL.md +9 -1
- package/claude/skills/session-map/SKILL.md +1 -1
- package/claude/skills/session-resume/REQUIREMENT.md +2 -2
- package/claude/skills/session-worktree/SKILL.md +5 -5
- package/claude/skills/target-setup/references/detect.md +1 -1
- package/claude/skills/task-board/REQUIREMENT.md +7 -12
- package/claude/skills/task-board/SKILL.md +5 -17
- package/claude/skills/task-board/references/tasks.md +2 -2
- package/claude/skills/task-complete/REQUIREMENT.md +47 -0
- package/claude/skills/task-complete/SKILL.md +86 -0
- package/claude/skills/{task-board → task-complete}/references/archive.md +4 -4
- package/claude/skills/{context-fold → task-complete}/references/receipt-sweep.md +5 -5
- package/claude/skills/task-complete/references/unusable-baseline.md +19 -0
- package/claude/skills/teach-workspace/REQUIREMENT.md +6 -2
- package/claude/skills/teach-workspace/SKILL.md +8 -4
- package/claude/skills/teach-workspace/references/lesson-craft.md +1 -1
- package/claude/skills/{context-fold/references/promoted-pages.md → teach-workspace/references/promotion-fold.md} +3 -3
- package/claude/skills/teach-workspace/references/promotion.md +3 -3
- package/claude/skills/ui-checklist/SKILL.md +1 -1
- package/claude/skills/ux-audit/SKILL.md +1 -1
- package/claude/skills/ux-measure/SKILL.md +1 -1
- package/docs/agents/canvas.md +1 -1
- package/docs/agents/commands.md +3 -2
- package/docs/agents/context-audit-checks.md +4 -4
- package/docs/agents/index.md +3 -3
- package/docs/agents/indexes.md +1 -1
- package/docs/agents/install-and-sync.md +6 -11
- package/docs/agents/key-changes.md +5 -5
- package/docs/agents/label-coverage.md +11 -0
- package/docs/agents/migrate.md +1 -1
- package/docs/agents/records-push.md +2 -2
- package/docs/agents/records-stale.md +12 -47
- package/docs/agents/records-validate.md +3 -7
- package/docs/agents/records.md +5 -27
- package/docs/agents/rule-citations.md +1 -1
- package/docs/agents/sandbox.md +4 -4
- package/docs/agents/skills-audit.md +1 -1
- package/docs/agents/tasks-plans.md +2 -2
- package/docs/agents/tasks.md +2 -2
- package/docs/target/migrations.md +10 -2
- package/docs/target/projects.md +1 -1
- package/docs/target/sync.md +1 -1
- package/docs/workflow/operating-model.md +2 -2
- package/docs/workflow/parallel-features.md +7 -7
- package/docs/workflow/skill-map-on-request.md +9 -9
- package/docs/workflow/skill-map.md +12 -11
- package/docs/workflow/visual-design-workflow.md +2 -2
- package/governance/renames.toml +0 -11
- package/governance/rules/canon/context.md +13 -0
- package/governance/rules/claude/skill.md +17 -0
- package/governance/stacks/base.toml +2 -4
- package/package.json +4 -4
- package/scripts/core/install-check.sh +3 -3
- package/scripts/lib/ui.sh +0 -159
- package/src/audits/catalog.ts +0 -1
- package/src/claude/cases/misc.ts +1 -1
- package/src/claude/cases/setup.ts +4 -0
- package/src/claude/cases/workflow.ts +6 -1
- package/src/claude/seeds.ts +1 -1
- package/src/claude/skills-audit.ts +2 -2
- package/src/cli/help.ts +3 -1
- package/src/cli/ui.ts +2 -3
- package/src/commands/claude.ts +1 -1
- package/src/commands/gov.ts +2 -15
- package/src/commands/labels.ts +326 -7
- package/src/commands/migrate.ts +6 -1
- package/src/commands/pr.ts +1 -1
- package/src/commands/records.ts +13 -399
- package/src/commands/sandbox.ts +3 -6
- package/src/commands/tasks.ts +3 -3
- package/src/context/audit.ts +5 -5
- package/src/context/folders.ts +1 -1
- package/src/design/budget.ts +1 -1
- package/src/design/components.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/regen.ts +1 -1
- package/src/design/render.ts +1 -1
- package/src/gate/measures.ts +1 -1
- package/src/gate/sequencer.ts +56 -1
- package/src/gate/stages.ts +1 -7
- package/src/gov/install.ts +2 -3
- package/src/gov/stacks.ts +1 -1
- package/src/hooks/post-merge.ts +1 -1
- package/src/labels/apply.ts +39 -0
- package/src/labels/format.ts +3 -3
- package/src/labels/phase.ts +3 -3
- package/src/labels/sections.ts +43 -0
- package/src/migrate/skill-names.ts +20 -1
- package/src/pr/paths.ts +4 -4
- package/src/records/backup.ts +16 -6
- package/src/records/stale-canonical.ts +31 -1
- package/src/records/validate.ts +2 -188
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/coverage.ts +2 -6
- package/src/sandbox/equivalence.ts +5 -44
- package/src/sandbox/expect.ts +4 -4
- package/src/sandbox/provision.ts +13 -36
- package/src/sandbox/scenario.ts +26 -48
- package/src/sandbox/tree.ts +2 -6
- package/src/standards/read.ts +14 -1
- package/src/sync/git.ts +1 -1
- package/src/tasks/archive.ts +5 -5
- package/src/tasks/branch.ts +3 -3
- package/src/tasks/label.ts +1 -1
- package/src/tasks/record.ts +2 -2
- package/src/tasks/validate.ts +1 -1
- package/standards/index.md +0 -13
- package/standards/standard.md +1 -7
- package/tooling/base/reference.md +1 -1
- package/tooling/base/seeds/.github/pull_request_template.md +2 -2
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +9 -1
- package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +171 -0
- package/tooling/claude/seeds/.claude/settings.json +9 -0
- package/governance/rules/standards/context.md +0 -14
- package/governance/rules/standards/design.md +0 -12
- package/governance/rules/standards/readme.md +0 -12
- package/governance/rules/standards/rule.md +0 -13
- package/governance/rules/standards/skill.md +0 -21
- package/governance/rules/standards/standard.md +0 -13
- package/scripts/config.sh +0 -11
- package/scripts/lib/gov.sh +0 -14
- package/scripts/lib/sandbox-fixtures.sh +0 -191
- package/scripts/lib/sandbox-git.sh +0 -125
- package/scripts/lib/sandbox-path.sh +0 -50
- package/scripts/sandbox-hook.sh +0 -99
- package/src/gov/standard-rules.ts +0 -179
- package/src/records/migrate.ts +0 -78
- package/src/records/stale.ts +0 -243
- package/src/sandbox/hooks.ts +0 -128
- /package/claude/skills/{canvas → canvas-board}/references/canvas.md +0 -0
- /package/{standards → claude/skills/draft-figure/references}/figures.md +0 -0
- /package/{standards → claude/skills/git-branch/references}/slug.md +0 -0
- /package/{standards → claude/skills/git-commit/references}/commit.md +0 -0
- /package/{standards → claude/skills/git-commit/references}/versioning.md +0 -0
package/src/commands/labels.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
|
+
import { $ } from 'bun'
|
|
2
3
|
import type { Command } from 'commander'
|
|
4
|
+
import { execa } from 'execa'
|
|
5
|
+
import { gitEnv } from '@/git/env'
|
|
6
|
+
import { diffLabels } from '@/labels/apply'
|
|
3
7
|
import { type LabelAuditRefusal, auditLabels } from '@/labels/audit'
|
|
4
8
|
import { resolveScanInput } from '@/labels/event'
|
|
5
9
|
import { checkTitleFormat, type TitleFormatIssue } from '@/labels/format'
|
|
6
10
|
import { MAP_REL } from '@/labels/map'
|
|
7
|
-
import { scanPhaseLabels } from '@/labels/phase'
|
|
11
|
+
import { isReleasePullRequest, scanPhaseLabels } from '@/labels/phase'
|
|
12
|
+
import { checkSections } from '@/labels/sections'
|
|
8
13
|
import { scanTitleSpelling } from '@/labels/spelling'
|
|
14
|
+
import { BAN_SETS } from '@/markdown/bans'
|
|
15
|
+
import { bodyLines, scanBans } from '@/markdown/scan'
|
|
9
16
|
import { intro, logInfo, logStep, logWarn, outro, plural } from '@/cli/ui'
|
|
10
17
|
|
|
11
18
|
interface AuditOptions {
|
|
@@ -14,6 +21,14 @@ interface AuditOptions {
|
|
|
14
21
|
readonly json?: boolean
|
|
15
22
|
}
|
|
16
23
|
|
|
24
|
+
interface ApplyOptions extends AuditOptions {
|
|
25
|
+
readonly check?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ApplyRefusal = 'bad-number' | 'no-branch' | 'gh-failed' | 'head-mismatch'
|
|
29
|
+
|
|
30
|
+
const GH_TIMEOUT_MS = 30_000
|
|
31
|
+
|
|
17
32
|
interface ScanOptions {
|
|
18
33
|
readonly event?: string
|
|
19
34
|
readonly title?: string
|
|
@@ -47,6 +62,16 @@ const REFUSALS: Record<LabelAuditRefusal, string> = {
|
|
|
47
62
|
'git could not list what this branch changed, so the set is unknown.',
|
|
48
63
|
}
|
|
49
64
|
|
|
65
|
+
const APPLY_REFUSALS: Record<ApplyRefusal, string> = {
|
|
66
|
+
'bad-number': 'The pull request number must be a positive whole number.',
|
|
67
|
+
'no-branch':
|
|
68
|
+
'This checkout is on no branch, so there is no head to bind the pull request to.',
|
|
69
|
+
'gh-failed':
|
|
70
|
+
'gh could not read the pull request or the labels, so nothing was written.',
|
|
71
|
+
'head-mismatch':
|
|
72
|
+
'That pull request is not open on the current branch. Nothing was written to it.',
|
|
73
|
+
}
|
|
74
|
+
|
|
50
75
|
export function register(program: Command): void {
|
|
51
76
|
const labels = program
|
|
52
77
|
.command('labels')
|
|
@@ -99,6 +124,48 @@ export function register(program: Command): void {
|
|
|
99
124
|
process.exitCode = await runAudit(paths, opts)
|
|
100
125
|
})
|
|
101
126
|
|
|
127
|
+
labels
|
|
128
|
+
.command('apply')
|
|
129
|
+
.description(
|
|
130
|
+
'Add the labels this branch earns to its open pull request, adding what is missing and never removing',
|
|
131
|
+
)
|
|
132
|
+
.argument('<number>', "This branch's open pull request")
|
|
133
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
134
|
+
.option('--base <ref>', 'Far side of the range, defaulting to the trunk')
|
|
135
|
+
.option('--root <path>', 'Repository to read, defaulting to the cwd')
|
|
136
|
+
.option('--check', 'Report the labels owed and write nothing')
|
|
137
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
138
|
+
.addHelpText(
|
|
139
|
+
'after',
|
|
140
|
+
[
|
|
141
|
+
'',
|
|
142
|
+
'Resolves the earned set the way `canon labels audit` does, binds to the',
|
|
143
|
+
'pull request only when its head is the current branch and it is open,',
|
|
144
|
+
'and adds each earned label the pull request lacks over REST.',
|
|
145
|
+
'',
|
|
146
|
+
'It adds and never removes, so a label a person applied by hand stays.',
|
|
147
|
+
'A label the remote does not carry is reported under missingOnRemote and',
|
|
148
|
+
'skipped, never created. A project declaring no map earns nothing and',
|
|
149
|
+
'exits 0.',
|
|
150
|
+
'',
|
|
151
|
+
'Record: number, earned, present, added, owed, missingOnRemote, uncovered',
|
|
152
|
+
'',
|
|
153
|
+
'Exit codes:',
|
|
154
|
+
' 0 nothing is owed after the run',
|
|
155
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
156
|
+
' 2 labels are still owed, being any under --check, or a label the',
|
|
157
|
+
' remote lacks or a failed add otherwise',
|
|
158
|
+
'',
|
|
159
|
+
'Examples:',
|
|
160
|
+
' canon labels apply 123 --json',
|
|
161
|
+
' canon labels apply 123 --check',
|
|
162
|
+
'',
|
|
163
|
+
].join('\n'),
|
|
164
|
+
)
|
|
165
|
+
.action(async (number: string, opts: ApplyOptions) => {
|
|
166
|
+
process.exitCode = await runApply(number, opts)
|
|
167
|
+
})
|
|
168
|
+
|
|
102
169
|
labels
|
|
103
170
|
.command('scan')
|
|
104
171
|
.description(
|
|
@@ -152,19 +219,27 @@ export function register(program: Command): void {
|
|
|
152
219
|
'a target project carries none, rather than reaching the network or',
|
|
153
220
|
'forcing a new dependency.',
|
|
154
221
|
'',
|
|
155
|
-
"It also grades the title alone against
|
|
222
|
+
"It also grades the title alone against claude/skills/git-pr/references/pr.md's ## Title",
|
|
156
223
|
'section: the `<type>(<scope>)[!]: <subject>` structure, lowercase casing',
|
|
157
224
|
'for the type, the scope, and the first subject word, and a 72-',
|
|
158
225
|
'character length cap. A scan given no title, whether from a review',
|
|
159
226
|
'or from a bare --body or --body-file invocation, skips this check',
|
|
160
227
|
'rather than grading an empty string.',
|
|
161
228
|
'',
|
|
229
|
+
'It grades the body of an ordinary pull request against the closed',
|
|
230
|
+
'section list: a level-two heading outside it, or a missing',
|
|
231
|
+
'## Summary or ## Key changes, fails. A review, a release-please body,',
|
|
232
|
+
'and a scan carrying no title skip this check. It also fails a banned',
|
|
233
|
+
'character, an em dash or a semicolon, in the title or body of a pull',
|
|
234
|
+
'request or a review, outside a code span or a link.',
|
|
235
|
+
'',
|
|
162
236
|
'Exit codes:',
|
|
163
|
-
' 0 none of the
|
|
237
|
+
' 0 none of the seven found',
|
|
164
238
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
165
239
|
' 2 the title or body carries a phase label, a board identifier, a',
|
|
166
|
-
' session link, a
|
|
167
|
-
|
|
240
|
+
' session link, a banned character, a heading outside the closed',
|
|
241
|
+
' list or a required one missing, a title word no dictionary holds, or a title',
|
|
242
|
+
" breaking claude/skills/git-pr/references/pr.md's format, casing, or length",
|
|
168
243
|
'',
|
|
169
244
|
'Examples:',
|
|
170
245
|
' canon labels scan --event "$GITHUB_EVENT_PATH"',
|
|
@@ -265,6 +340,177 @@ async function runAudit(paths: string[], opts: AuditOptions): Promise<number> {
|
|
|
265
340
|
return coverage.uncovered.length === 0 ? 0 : 2
|
|
266
341
|
}
|
|
267
342
|
|
|
343
|
+
/** Runs one `gh` call under the stripped environment, null when it failed. */
|
|
344
|
+
async function gh(
|
|
345
|
+
cwd: string,
|
|
346
|
+
args: readonly string[],
|
|
347
|
+
): Promise<string | null> {
|
|
348
|
+
try {
|
|
349
|
+
const result = await execa('gh', [...args], {
|
|
350
|
+
cwd,
|
|
351
|
+
timeout: GH_TIMEOUT_MS,
|
|
352
|
+
env: gitEnv(),
|
|
353
|
+
extendEnv: false,
|
|
354
|
+
})
|
|
355
|
+
return result.stdout
|
|
356
|
+
} catch {
|
|
357
|
+
return null
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function nonEmptyLines(text: string): string[] {
|
|
362
|
+
return text.split('\n').filter((line) => line !== '')
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function runApply(number: string, opts: ApplyOptions): Promise<number> {
|
|
366
|
+
const root = resolve(opts.root ?? process.cwd())
|
|
367
|
+
const emitJson = opts.json ?? false
|
|
368
|
+
const isCheck = opts.check ?? false
|
|
369
|
+
|
|
370
|
+
intro('canon labels apply')
|
|
371
|
+
|
|
372
|
+
const refuse = (reason: ApplyRefusal | LabelAuditRefusal): number => {
|
|
373
|
+
const message =
|
|
374
|
+
reason in APPLY_REFUSALS
|
|
375
|
+
? APPLY_REFUSALS[reason as ApplyRefusal]
|
|
376
|
+
: REFUSALS[reason as LabelAuditRefusal]
|
|
377
|
+
logStep('Refused')
|
|
378
|
+
logWarn(message)
|
|
379
|
+
outro()
|
|
380
|
+
if (emitJson) {
|
|
381
|
+
process.stdout.write(`${JSON.stringify({ root, reason, message })}\n`)
|
|
382
|
+
}
|
|
383
|
+
return 1
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!/^[1-9][0-9]*$/.test(number)) return refuse('bad-number')
|
|
387
|
+
|
|
388
|
+
const branch = await $`git -C ${root} symbolic-ref --short -q HEAD`
|
|
389
|
+
.env(gitEnv())
|
|
390
|
+
.quiet()
|
|
391
|
+
.nothrow()
|
|
392
|
+
const head = branch.exitCode === 0 ? branch.text().trim() : ''
|
|
393
|
+
if (head === '') return refuse('no-branch')
|
|
394
|
+
|
|
395
|
+
// Binding runs ahead of the audit, every label read, and every write, under
|
|
396
|
+
// --check and a project with no map as well, so a number this branch does
|
|
397
|
+
// not own costs the run and nothing else.
|
|
398
|
+
const target = await gh(root, [
|
|
399
|
+
'api',
|
|
400
|
+
`repos/{owner}/{repo}/pulls/${number}`,
|
|
401
|
+
'--jq',
|
|
402
|
+
'"\\(.head.ref) \\(.state)"',
|
|
403
|
+
])
|
|
404
|
+
if (target === null) return refuse('gh-failed')
|
|
405
|
+
if (target.trim() !== `${head} open`) return refuse('head-mismatch')
|
|
406
|
+
|
|
407
|
+
const report = await auditLabels(root, { base: opts.base })
|
|
408
|
+
|
|
409
|
+
// A project declaring no map earns nothing and is labelled silently, which
|
|
410
|
+
// is an answer and exits clean rather than reading as a break.
|
|
411
|
+
if (report.kind === 'refused' && report.reason === 'no-map') {
|
|
412
|
+
logStep('Labels')
|
|
413
|
+
logInfo(REFUSALS['no-map'])
|
|
414
|
+
outro()
|
|
415
|
+
if (emitJson) {
|
|
416
|
+
process.stdout.write(
|
|
417
|
+
`${JSON.stringify({
|
|
418
|
+
number: Number(number),
|
|
419
|
+
reason: 'no-map',
|
|
420
|
+
earned: [],
|
|
421
|
+
present: [],
|
|
422
|
+
added: [],
|
|
423
|
+
owed: [],
|
|
424
|
+
missingOnRemote: [],
|
|
425
|
+
uncovered: [],
|
|
426
|
+
})}\n`,
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
return 0
|
|
430
|
+
}
|
|
431
|
+
if (report.kind === 'refused') return refuse(report.reason)
|
|
432
|
+
|
|
433
|
+
const presentRaw = await gh(root, [
|
|
434
|
+
'api',
|
|
435
|
+
'--paginate',
|
|
436
|
+
`repos/{owner}/{repo}/issues/${number}/labels`,
|
|
437
|
+
'--jq',
|
|
438
|
+
'.[].name',
|
|
439
|
+
])
|
|
440
|
+
const remoteRaw = await gh(root, [
|
|
441
|
+
'api',
|
|
442
|
+
'--paginate',
|
|
443
|
+
'repos/{owner}/{repo}/labels',
|
|
444
|
+
'--jq',
|
|
445
|
+
'.[].name',
|
|
446
|
+
])
|
|
447
|
+
if (presentRaw === null || remoteRaw === null) return refuse('gh-failed')
|
|
448
|
+
|
|
449
|
+
const earned = report.coverage.labels
|
|
450
|
+
const present = nonEmptyLines(presentRaw)
|
|
451
|
+
const diff = diffLabels(earned, present, nonEmptyLines(remoteRaw))
|
|
452
|
+
|
|
453
|
+
let added: string[] = []
|
|
454
|
+
let failed: string[] = []
|
|
455
|
+
|
|
456
|
+
if (!isCheck && diff.toAdd.length > 0) {
|
|
457
|
+
const wrote = await gh(root, [
|
|
458
|
+
'api',
|
|
459
|
+
'-X',
|
|
460
|
+
'POST',
|
|
461
|
+
`repos/{owner}/{repo}/issues/${number}/labels`,
|
|
462
|
+
...diff.toAdd.flatMap((label) => ['-f', `labels[]=${label}`]),
|
|
463
|
+
'--silent',
|
|
464
|
+
])
|
|
465
|
+
if (wrote === null) failed = [...diff.toAdd]
|
|
466
|
+
else added = [...diff.toAdd]
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
logStep('Labels')
|
|
470
|
+
logInfo(earned.length === 0 ? 'this set earns no label' : earned.join(', '))
|
|
471
|
+
|
|
472
|
+
if (isCheck) {
|
|
473
|
+
logStep(diff.owed.length === 0 ? 'Nothing owed' : 'Owed')
|
|
474
|
+
for (const label of diff.owed) logWarn(label)
|
|
475
|
+
} else {
|
|
476
|
+
logStep('Added')
|
|
477
|
+
logInfo(added.length === 0 ? 'no label was added' : added.join(', '))
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
for (const label of diff.missingOnRemote) {
|
|
481
|
+
logWarn(
|
|
482
|
+
`${label} is not on the remote. Create it with: gh label create ${label}`,
|
|
483
|
+
)
|
|
484
|
+
}
|
|
485
|
+
for (const label of failed) logWarn(`Label apply failed: ${label}`)
|
|
486
|
+
|
|
487
|
+
if (report.coverage.uncovered.length > 0) {
|
|
488
|
+
logWarn(
|
|
489
|
+
`${plural(report.coverage.uncovered.length, 'path')} reached by no row of ${MAP_REL}.`,
|
|
490
|
+
)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
outro()
|
|
494
|
+
|
|
495
|
+
const owed = diff.owed.filter((label) => !added.includes(label))
|
|
496
|
+
|
|
497
|
+
if (emitJson) {
|
|
498
|
+
process.stdout.write(
|
|
499
|
+
`${JSON.stringify({
|
|
500
|
+
number: Number(number),
|
|
501
|
+
earned,
|
|
502
|
+
present,
|
|
503
|
+
added,
|
|
504
|
+
owed,
|
|
505
|
+
missingOnRemote: diff.missingOnRemote,
|
|
506
|
+
uncovered: report.coverage.uncovered,
|
|
507
|
+
})}\n`,
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return owed.length === 0 ? 0 : 2
|
|
512
|
+
}
|
|
513
|
+
|
|
268
514
|
async function runScan(opts: ScanOptions): Promise<number> {
|
|
269
515
|
const emitJson = opts.json ?? false
|
|
270
516
|
|
|
@@ -363,6 +609,74 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
363
609
|
|
|
364
610
|
const titleFormatIssues = titleFormat?.issues ?? []
|
|
365
611
|
|
|
612
|
+
// A review carries its own headings, a release-please body opens on a
|
|
613
|
+
// version link, and a scan with no title is a reply or a review body rather
|
|
614
|
+
// than a pull request description, so none of the three has the closed list
|
|
615
|
+
// to grade against.
|
|
616
|
+
const sectionsChecked =
|
|
617
|
+
resolved.source === 'pull-request' &&
|
|
618
|
+
resolved.title !== '' &&
|
|
619
|
+
!isReleasePullRequest(resolved)
|
|
620
|
+
const sections = sectionsChecked
|
|
621
|
+
? checkSections(resolved.body)
|
|
622
|
+
: { outside: [], missing: [] }
|
|
623
|
+
|
|
624
|
+
logStep(
|
|
625
|
+
!sectionsChecked
|
|
626
|
+
? 'Sections not checked'
|
|
627
|
+
: sections.outside.length === 0 && sections.missing.length === 0
|
|
628
|
+
? 'Clean'
|
|
629
|
+
: 'Section issue found',
|
|
630
|
+
)
|
|
631
|
+
if (!sectionsChecked) {
|
|
632
|
+
logInfo(
|
|
633
|
+
'only an ordinary pull request body is graded against the closed section list',
|
|
634
|
+
)
|
|
635
|
+
} else if (sections.outside.length === 0 && sections.missing.length === 0) {
|
|
636
|
+
logInfo(
|
|
637
|
+
'every heading is in the closed list and the required pair is there',
|
|
638
|
+
)
|
|
639
|
+
} else {
|
|
640
|
+
if (sections.outside.length > 0) {
|
|
641
|
+
logWarn(
|
|
642
|
+
`${plural(sections.outside.length, 'heading')} outside the closed list in the pull request standard. Rename or drop each.`,
|
|
643
|
+
)
|
|
644
|
+
for (const heading of sections.outside) logWarn(heading)
|
|
645
|
+
}
|
|
646
|
+
if (sections.missing.length > 0) {
|
|
647
|
+
logWarn(
|
|
648
|
+
`${plural(sections.missing.length, 'required heading')} missing from the body.`,
|
|
649
|
+
)
|
|
650
|
+
for (const heading of sections.missing) logWarn(heading)
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const banFindings = [
|
|
655
|
+
...scanBans(
|
|
656
|
+
[{ number: 1, text: resolved.title, fenced: false }],
|
|
657
|
+
BAN_SETS,
|
|
658
|
+
).map((finding) => ({ ...finding, where: 'title' })),
|
|
659
|
+
...scanBans(bodyLines(resolved.body), BAN_SETS).map((finding) => ({
|
|
660
|
+
...finding,
|
|
661
|
+
where: 'body',
|
|
662
|
+
})),
|
|
663
|
+
]
|
|
664
|
+
const bannedCharacters = [
|
|
665
|
+
...new Set(banFindings.map((finding) => finding.term)),
|
|
666
|
+
]
|
|
667
|
+
|
|
668
|
+
logStep(banFindings.length === 0 ? 'Clean' : 'Banned character found')
|
|
669
|
+
if (banFindings.length === 0) {
|
|
670
|
+
logInfo('no banned character outside a code span or a link')
|
|
671
|
+
} else {
|
|
672
|
+
logWarn(
|
|
673
|
+
`${plural(banFindings.length, 'banned character')} in the title or body. Rewrite the sentence rather than swapping the character.`,
|
|
674
|
+
)
|
|
675
|
+
for (const finding of banFindings) {
|
|
676
|
+
logWarn(`${finding.where} line ${finding.line}: ${finding.term}`)
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
366
680
|
logStep(
|
|
367
681
|
titleFormat === undefined
|
|
368
682
|
? 'Title format not checked'
|
|
@@ -378,7 +692,7 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
378
692
|
)
|
|
379
693
|
} else {
|
|
380
694
|
logWarn(
|
|
381
|
-
`${plural(titleFormatIssues.length, 'title format issue')} against
|
|
695
|
+
`${plural(titleFormatIssues.length, 'title format issue')} against claude/skills/git-pr/references/pr.md's ## Title section.`,
|
|
382
696
|
)
|
|
383
697
|
for (const issue of titleFormatIssues) logWarn(TITLE_FORMAT_MESSAGES[issue])
|
|
384
698
|
}
|
|
@@ -396,6 +710,8 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
396
710
|
unspelledWords,
|
|
397
711
|
spellingChecked,
|
|
398
712
|
titleFormatIssues,
|
|
713
|
+
sections: { checked: sectionsChecked, ...sections },
|
|
714
|
+
bannedCharacters,
|
|
399
715
|
})}\n`,
|
|
400
716
|
)
|
|
401
717
|
}
|
|
@@ -404,7 +720,10 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
404
720
|
result.boardReferences.length === 0 &&
|
|
405
721
|
result.sessionLinks.length === 0 &&
|
|
406
722
|
unspelledWords.length === 0 &&
|
|
407
|
-
titleFormatIssues.length === 0
|
|
723
|
+
titleFormatIssues.length === 0 &&
|
|
724
|
+
sections.outside.length === 0 &&
|
|
725
|
+
sections.missing.length === 0 &&
|
|
726
|
+
banFindings.length === 0
|
|
408
727
|
? 0
|
|
409
728
|
: 2
|
|
410
729
|
}
|
package/src/commands/migrate.ts
CHANGED
|
@@ -25,7 +25,11 @@ import {
|
|
|
25
25
|
type RuleLayoutPlan,
|
|
26
26
|
walkFlatRules,
|
|
27
27
|
} from '@/migrate/rule-layout'
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
SKILL_CITATION_MAP,
|
|
30
|
+
SKILL_NAME_MAP,
|
|
31
|
+
SKILL_NAME_RULES,
|
|
32
|
+
} from '@/migrate/skill-names'
|
|
29
33
|
import {
|
|
30
34
|
planSurfaceRootsMove,
|
|
31
35
|
type SurfaceRootsPlan,
|
|
@@ -1250,6 +1254,7 @@ export function register(program: Command): void {
|
|
|
1250
1254
|
[
|
|
1251
1255
|
'',
|
|
1252
1256
|
`Rewrites ${Object.keys(SKILL_NAME_MAP).length} skill names and moves the folders that carry them.`,
|
|
1257
|
+
`It also carries ${Object.keys(SKILL_CITATION_MAP).length} namespaced-only rows for a skill renamed from a single word.`,
|
|
1253
1258
|
'It takes no scope. The skill folders it moves are authored in the',
|
|
1254
1259
|
'toolkit and no target holds a copy of that catalog, so in a target it',
|
|
1255
1260
|
'rewrites citations of a renamed skill and moves nothing.',
|
package/src/commands/pr.ts
CHANGED
|
@@ -389,7 +389,7 @@ export function register(program: Command): void {
|
|
|
389
389
|
' unresolved a path written partially, or one past its first comma,',
|
|
390
390
|
' which can credit a changed file and never accuse one',
|
|
391
391
|
'',
|
|
392
|
-
`Only ## ${KEY_CHANGES} is read. ## Technical
|
|
392
|
+
`Only ## ${KEY_CHANGES} is read. ## Technical context legitimately names`,
|
|
393
393
|
'files a branch never touched, so widening the read manufactures findings.',
|
|
394
394
|
'',
|
|
395
395
|
'One class survives the reader: a bullet citing where something is defined',
|