@erclx/canon 5.39.0 → 5.40.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/README.md +5 -5
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/canvas-board/SKILL.md +1 -1
- package/claude/skills/draft-slides/SKILL.md +2 -3
- package/claude/skills/draft-slides/references/deck.md +183 -0
- package/claude/skills/git-pr/references/evidence.md +2 -2
- package/claude/skills/git-pr/references/final-command.md +1 -1
- package/claude/skills/git-pr/references/labels.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- package/claude/skills/review-ui/SKILL.md +1 -0
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
- package/claude/skills/task-board/references/board.md +1 -1
- package/claude/skills/task-board/references/tasks.md +1 -1
- package/claude/skills/ux-measure/SKILL.md +1 -1
- package/docs/agents/audits.md +61 -60
- package/docs/agents/context-audit.md +40 -58
- package/docs/agents/index.md +5 -51
- package/docs/agents/install-and-sync.md +1 -0
- package/docs/agents/markdown-audit.md +53 -99
- package/docs/agents/records.md +37 -79
- package/docs/agents/sessions-transfer.md +26 -70
- package/docs/target/index.md +0 -1
- package/docs/target/migrations.md +57 -66
- package/docs/target/projects.md +36 -78
- package/docs/target/scenarios.md +9 -19
- package/docs/target/sync.md +27 -68
- package/docs/workflow/ai-workflow.md +22 -68
- package/docs/workflow/operating-model.md +32 -186
- package/docs/workflow/parallel-features.md +29 -66
- package/docs/workflow/skill-map-on-request.md +22 -24
- package/docs/workflow/skill-map.md +72 -76
- package/docs/workflow/visual-design-workflow.md +25 -118
- package/docs/workflow/zshrc-aliases.md +18 -37
- package/package.json +1 -1
- package/src/canvas/client/inspector/fields.css +4 -0
- package/src/canvas/index.html +6 -0
- package/src/claude/skills-reach.ts +2 -2
- package/src/cli/ui.ts +3 -1
- package/src/commands/canvas.ts +23 -0
- package/src/commands/capture.ts +14 -0
- package/src/commands/claude.ts +13 -0
- package/src/commands/demo.ts +22 -0
- package/src/commands/deps.ts +6 -0
- package/src/commands/driver.ts +28 -2
- package/src/commands/gov.ts +52 -18
- package/src/commands/intake.ts +17 -0
- package/src/commands/labels.ts +38 -5
- package/src/commands/pr.ts +160 -43
- package/src/commands/records.ts +8 -0
- package/src/commands/secrets.ts +6 -0
- package/src/commands/sessions.ts +8 -0
- package/src/commands/tasks.ts +52 -0
- package/src/commands/teach.ts +13 -0
- package/src/commands/upstream.ts +25 -1
- package/src/commands/worktrees.ts +5 -0
- package/src/context/audit.ts +22 -4
- package/src/context/folders.ts +1 -1
- package/src/context/shape-gated.ts +4 -2
- package/src/counts/scan.ts +1 -1
- package/src/design/components.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/tokens.ts +1 -1
- package/src/gate/boundaries.ts +4 -1
- package/src/gate/measures.ts +1 -1
- package/src/gate/stages.ts +7 -7
- package/src/git/env.ts +6 -0
- package/src/markdown/scope.ts +5 -17
- package/src/pr/lift.ts +153 -19
- package/src/pr/painted.ts +87 -0
- package/src/roots/record.ts +18 -1
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/headless/run.ts +3 -2
- package/src/sandbox/headless/watch.ts +3 -4
- package/src/slides/convert/master.ts +3 -0
- package/src/sync/format.ts +46 -0
- package/src/sync/stamp.ts +6 -0
- package/tooling/astro/reference.md +2 -2
- package/tooling/base/reference.md +1 -1
- package/tooling/go/reference.md +1 -1
- package/tooling/nextjs/reference.md +1 -1
- package/tooling/php/reference.md +1 -1
- package/tooling/python/reference.md +1 -1
- package/tooling/vite-react/reference.md +1 -1
- package/tooling/web/reference.md +1 -1
- package/docs/agents/canvas.md +0 -86
- package/docs/agents/capture.md +0 -65
- package/docs/agents/census.md +0 -23
- package/docs/agents/comments.md +0 -34
- package/docs/agents/context-audit-checks.md +0 -108
- package/docs/agents/context-classify.md +0 -101
- package/docs/agents/counts.md +0 -74
- package/docs/agents/demo.md +0 -140
- package/docs/agents/driver.md +0 -103
- package/docs/agents/gate.md +0 -90
- package/docs/agents/hooks.md +0 -78
- package/docs/agents/intake.md +0 -81
- package/docs/agents/key-changes.md +0 -116
- package/docs/agents/label-coverage.md +0 -86
- package/docs/agents/markdown-audit-density.md +0 -39
- package/docs/agents/markdown-audit-weight.md +0 -83
- package/docs/agents/pr-evidence-layout.md +0 -70
- package/docs/agents/pr-evidence.md +0 -273
- package/docs/agents/pr-frames.md +0 -131
- package/docs/agents/pr-lift.md +0 -132
- package/docs/agents/pr-local.md +0 -111
- package/docs/agents/pr-preview.md +0 -155
- package/docs/agents/pr-reads.md +0 -111
- package/docs/agents/pr-tick.md +0 -72
- package/docs/agents/records-push.md +0 -61
- package/docs/agents/records-stale.md +0 -40
- package/docs/agents/records-validate.md +0 -57
- package/docs/agents/restated.md +0 -83
- package/docs/agents/routing.md +0 -61
- package/docs/agents/rule-citations.md +0 -90
- package/docs/agents/sandbox.md +0 -126
- package/docs/agents/sessions.md +0 -128
- package/docs/agents/skills-audit.md +0 -120
- package/docs/agents/skills-reach.md +0 -64
- package/docs/agents/slides.md +0 -176
- package/docs/agents/state-scoped-risk.md +0 -105
- package/docs/agents/superseded.md +0 -85
- package/docs/agents/tasks-archive.md +0 -83
- package/docs/agents/tasks-plans.md +0 -152
- package/docs/agents/tasks-validate.md +0 -133
- package/docs/agents/tasks.md +0 -123
- package/docs/agents/teach-authoring.md +0 -118
- package/docs/agents/teach.md +0 -157
- package/docs/agents/test-order.md +0 -58
- package/docs/agents/upstream.md +0 -94
- package/docs/agents/worktrees.md +0 -118
- package/docs/target/standards-moved.md +0 -34
|
@@ -6,15 +6,13 @@ category: Workflow
|
|
|
6
6
|
|
|
7
7
|
# Zshrc aliases for Claude Code
|
|
8
8
|
|
|
9
|
-
Claude Code auto-discovers the toolkit plugin
|
|
9
|
+
Claude Code auto-discovers the toolkit plugin inside the toolkit repository. Outside it, the `clp` aliases pass `--plugin-dir` explicitly.
|
|
10
10
|
|
|
11
11
|
## The aliases
|
|
12
12
|
|
|
13
|
-
`bun run bootstrap`
|
|
13
|
+
`bun run bootstrap` appends this block to `~/.zshrc` between the two marker comments and sets `TOOLKIT` to the cloned path. A re-run skips a block it finds and never updates it. To pick up a newer alias, delete the marked block and re-run, or paste the new lines in.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
A block installed by hand before the managed one has no markers. Bootstrap detects that case through the `alias clp=` line instead, warns, and leaves the block untouched rather than appending a second copy. To switch over, delete the hand-rolled block and re-run the bootstrap.
|
|
15
|
+
A hand-written block without markers is detected through its `alias clp=` line, and bootstrap warns and leaves it. Delete it and re-run to switch.
|
|
18
16
|
|
|
19
17
|
```zsh
|
|
20
18
|
# >>> canon aliases >>>
|
|
@@ -34,46 +32,29 @@ alias clpac='clpa --cwd .'
|
|
|
34
32
|
# <<< canon aliases <<<
|
|
35
33
|
```
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
To opt out, delete the block between the two markers. Bootstrap re-adds it on the next run, so skip that step by running `bun install` and `bun link` yourself instead.
|
|
40
|
-
|
|
41
|
-
The block itself works unchanged in bash, and what differs is the file it goes in and who writes it. Paste it into `~/.bashrc` by hand, since `bun run bootstrap` appends to `~/.zshrc` alone and a run from a bash shell installs nothing that shell reads. Bash re-checks the first word of an alias expansion the way zsh does, so the `cl` and `clp` chains below still inherit their base, and the `bun install` and `bun link` step above is the opt-out either shell takes.
|
|
35
|
+
To opt out, delete the block and run `bun install` and `bun link` yourself. Bash takes the block unchanged in `~/.bashrc`, pasted by hand. Bootstrap writes `~/.zshrc` alone.
|
|
42
36
|
|
|
43
37
|
## What each one does
|
|
44
38
|
|
|
45
|
-
`cl`, `clw`, and `cls` carry no explicit plugin dir and start a fresh session. Use them inside the toolkit repository, where Claude Code auto-discovers the plugin from `claude/.claude-plugin/plugin.json`. Loading `--plugin-dir` on top of auto-discovery registers every skill twice and produces duplicate entries in the slash command list.
|
|
46
|
-
|
|
47
|
-
`clr` and `clc` resume a session rather than launching one, so auto-discovery never fires for them. A resume restores whatever plugin registration the session started with and recomputes nothing, which a bare `claude --resume` demonstrated by holding a stale registration through a full process restart until `--plugin-dir` was passed on the resume itself.
|
|
48
|
-
|
|
49
39
|
- `cl`: plain session in the current directory
|
|
50
|
-
- `clr`: opens the `/resume` picker
|
|
51
|
-
- `clc`:
|
|
52
|
-
- `clw`: creates a worktree under `.claude/worktrees/<name>/` on a fresh branch and starts a
|
|
53
|
-
- `cls`: pins the session to Sonnet
|
|
40
|
+
- `clr`: opens the `/resume` picker for the current directory. `clr auth` filters to sessions containing "auth".
|
|
41
|
+
- `clc`: resumes the most recent session for the current directory, no picker
|
|
42
|
+
- `clw`: creates a worktree under `.claude/worktrees/<name>/` on a fresh branch and starts a session there, as in `clw feat-auth`
|
|
43
|
+
- `cls`: pins the session to Sonnet
|
|
54
44
|
|
|
55
|
-
The `clp` family
|
|
45
|
+
The `clp` family loads the plugin explicitly.
|
|
56
46
|
|
|
57
|
-
- `clp`: session with the toolkit plugin loaded
|
|
58
|
-
- `clpc`:
|
|
47
|
+
- `clp`: session with the toolkit plugin loaded
|
|
48
|
+
- `clpc`: resumes the most recent session with the plugin loaded
|
|
59
49
|
- `clps`: `clp` pinned to Sonnet
|
|
60
|
-
- `clpa`: opens the agent view with the plugin loaded,
|
|
61
|
-
- `clpac`: the same view filtered to
|
|
50
|
+
- `clpa`: opens the agent view with the plugin loaded, across every directory
|
|
51
|
+
- `clpac`: the same view filtered to the current directory
|
|
62
52
|
|
|
63
53
|
## When to use which
|
|
64
54
|
|
|
65
|
-
Use `cl`
|
|
66
|
-
|
|
67
|
-
Use `clp` in any other repository where you want the toolkit skills available. Without the flag, those repos do not load the plugin.
|
|
68
|
-
|
|
69
|
-
Use `cls` or `clps` to save Opus usage on routine sessions. Switch mid-session with `/model` to avoid restarting.
|
|
70
|
-
|
|
71
|
-
Use `clw <name>` for features that will take more than one session. A worktree isolates the branch, the transcripts, and the `/resume` history. See [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for the worktree mechanism, and [Parallel features](parallel-features.md) for fan-out rules.
|
|
72
|
-
|
|
73
|
-
Use `clc` to resume the last session without a picker. Use `clr` when you have several sessions and need to pick by name or recency. Reach for `clpc` over `clc` wherever the resumed session needs the plugin, inside the toolkit repository as well as outside it, since neither resume re-runs discovery.
|
|
74
|
-
|
|
75
|
-
Use `clpa` to see what is running across every directory, and `clpac` when only the current project matters.
|
|
76
|
-
|
|
77
|
-
## Why not a function
|
|
55
|
+
Use `cl` inside the toolkit repository and `clp` in any other repository that needs the toolkit skills. A resume does not reload the plugin, so use `clpc` over `clc` wherever the resumed session needs it.
|
|
78
56
|
|
|
79
|
-
|
|
57
|
+
- Use `cls` or `clps` for routine sessions, and `/model` to switch mid-session
|
|
58
|
+
- Use `clw <name>` for features that take more than one session. See [Claude Code and git worktrees](../../wiki/claude/worktrees.md) and [Parallel features](parallel-features.md).
|
|
59
|
+
- Use `clc` to resume without a picker and `clr` to pick by name
|
|
60
|
+
- Use `clpa` for every directory and `clpac` for the current project
|
package/package.json
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* The inspector's own rules. Every value reads a chrome token, the same set
|
|
3
3
|
* shell.css reads. Each field's glyph sits inside its box, so two fields share
|
|
4
4
|
* a row without a label column eating the width.
|
|
5
|
+
*
|
|
6
|
+
* These rules share one class namespace with shell.css, where the Theme tab
|
|
7
|
+
* owns `.token`, so a new class here takes its own prefix, as `.token-option`
|
|
8
|
+
* does.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
.inspector-section {
|
package/src/canvas/index.html
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Canvas</title>
|
|
7
|
+
<!--
|
|
8
|
+
Bun's bundler resolves every href and src here at start. A root-relative
|
|
9
|
+
route the server answers, such as /api/icon.svg, fails with "Could not
|
|
10
|
+
resolve" and leaves / serving an empty 200, so main.tsx points the icon
|
|
11
|
+
at it on load.
|
|
12
|
+
-->
|
|
7
13
|
<link rel="icon" href="data:," />
|
|
8
14
|
<link rel="stylesheet" href="./client/shell.css" />
|
|
9
15
|
</head>
|
|
@@ -13,8 +13,8 @@ import { SURFACE_ROOTS } from '@/roots/surface'
|
|
|
13
13
|
* `src/`, `scripts/`, and bare `docs/` are deliberately absent. A body naming
|
|
14
14
|
* one of those is describing the reader's own tree, so listing them would
|
|
15
15
|
* report a correct citation on every run and bury the defect this measures.
|
|
16
|
-
* `docs/agents/` is the exception, being the
|
|
17
|
-
* here alone.
|
|
16
|
+
* `docs/agents/` is the exception, being the reference pages for verbs run by
|
|
17
|
+
* hand, which exist here alone.
|
|
18
18
|
*
|
|
19
19
|
* The context folder carries both surface-root spellings, since this list
|
|
20
20
|
* decides whether a shipped body names this repository's own tree and a body
|
package/src/cli/ui.ts
CHANGED
|
@@ -162,6 +162,8 @@ export async function select<Value>(opts: {
|
|
|
162
162
|
message: string
|
|
163
163
|
options: { value: Value; label: string; hint?: string }[]
|
|
164
164
|
nonInteractiveDefault?: boolean
|
|
165
|
+
/** Replaces the "Pass an argument" clause of the TTY refusal. */
|
|
166
|
+
ttyHint?: string
|
|
165
167
|
}): Promise<Value> {
|
|
166
168
|
const { message, options } = opts
|
|
167
169
|
const { GREEN, GREY, NC, RED, WHITE } = palette(process.stderr)
|
|
@@ -196,7 +198,7 @@ export async function select<Value>(opts: {
|
|
|
196
198
|
return new Promise<Value>((resolve) => {
|
|
197
199
|
if (!process.stdin.isTTY) {
|
|
198
200
|
process.stderr.write(
|
|
199
|
-
`${GREY}│${NC} ${RED}✗${NC} ${message} requires a TTY. Pass an argument or set CANON_NON_INTERACTIVE=1.\n${GREY}└${NC}\n`,
|
|
201
|
+
`${GREY}│${NC} ${RED}✗${NC} ${message} requires a TTY. ${opts.ttyHint ?? 'Pass an argument'} or set CANON_NON_INTERACTIVE=1.\n${GREY}└${NC}\n`,
|
|
200
202
|
)
|
|
201
203
|
process.exit(1)
|
|
202
204
|
}
|
package/src/commands/canvas.ts
CHANGED
|
@@ -77,6 +77,11 @@ const CLIENT_DEPS = ['preact', '@preact/signals'] as const
|
|
|
77
77
|
* the bundler then answers `/` with an empty page rather than an error. The
|
|
78
78
|
* caller passes the package root the binary runs from, never the served
|
|
79
79
|
* project's, so a global install serving a target finds its own copies.
|
|
80
|
+
*
|
|
81
|
+
* `Bun.resolveSync` and `createRequire(...).resolve` fall back to Bun's global
|
|
82
|
+
* install cache when no ancestor of the starting directory holds a
|
|
83
|
+
* `node_modules`, so a presence check from such a directory passes where the
|
|
84
|
+
* bundler fails. The package root always sits under one.
|
|
80
85
|
*/
|
|
81
86
|
export function missingClientDeps(from: string): Refused | undefined {
|
|
82
87
|
const missing = CLIENT_DEPS.filter((name) => {
|
|
@@ -161,6 +166,24 @@ export function register(program: Command): void {
|
|
|
161
166
|
'it, and layout.json beside them holds each frame box. Edit a frame by',
|
|
162
167
|
'editing its file, and an open canvas reloads that frame.',
|
|
163
168
|
'',
|
|
169
|
+
'Reasons, on stderr or in the JSON record:',
|
|
170
|
+
' no-root no .canon/canvas/ folder under the root',
|
|
171
|
+
' no-page, no-frame the target is not on disk',
|
|
172
|
+
' invalid-name a name or target breaks the page or frame form',
|
|
173
|
+
' invalid-size a width or height is not a whole number of pixels',
|
|
174
|
+
' invalid-position an x or y is not a whole number',
|
|
175
|
+
' exists the page or frame is already there',
|
|
176
|
+
' malformed-layout layout.json does not read as a frame map',
|
|
177
|
+
' busy another write holds the page',
|
|
178
|
+
' invalid-address an element address is not a path of positions',
|
|
179
|
+
' address-mismatch the file builds elements the address does not',
|
|
180
|
+
' stale-address the file changed since the address was served',
|
|
181
|
+
' invalid-edit an edit key or value is not one the writer takes',
|
|
182
|
+
' not-text-only text targets an element holding other elements',
|
|
183
|
+
' missing-client-deps preact or @preact/signals does not resolve, so run bun install',
|
|
184
|
+
' no-server no canvas server answers at the address',
|
|
185
|
+
' capture-failed a frame did not render, with the font or browser reason',
|
|
186
|
+
'',
|
|
164
187
|
'Examples:',
|
|
165
188
|
' canon canvas serve',
|
|
166
189
|
' canon canvas page add drafts',
|
package/src/commands/capture.ts
CHANGED
|
@@ -58,6 +58,20 @@ export function register(program: Command): void {
|
|
|
58
58
|
'--width <px>',
|
|
59
59
|
'Viewport width in pixels, so a media query resolves at that width',
|
|
60
60
|
)
|
|
61
|
+
.addHelpText(
|
|
62
|
+
'after',
|
|
63
|
+
[
|
|
64
|
+
'',
|
|
65
|
+
'The selector has no default, since the element to crop to belongs to the',
|
|
66
|
+
'page. Each element holding text inside it must resolve its first font',
|
|
67
|
+
'family, and a generic keyword such as monospace or system-ui fails,',
|
|
68
|
+
'since it resolves differently on each machine. The user agent sets',
|
|
69
|
+
'monospace on pre, code, kbd, and samp, so each needs its own',
|
|
70
|
+
'font-family. --width takes one whole number and sets the viewport, so',
|
|
71
|
+
'run it once per breakpoint with its own --out.',
|
|
72
|
+
'',
|
|
73
|
+
].join('\n'),
|
|
74
|
+
)
|
|
61
75
|
.action(
|
|
62
76
|
async (
|
|
63
77
|
source: string,
|
package/src/commands/claude.ts
CHANGED
|
@@ -319,6 +319,11 @@ export function register(program: Command): void {
|
|
|
319
319
|
' 0 the file was read',
|
|
320
320
|
' 1 refused, with the reason on stderr',
|
|
321
321
|
'',
|
|
322
|
+
'Reasons:',
|
|
323
|
+
' no-claude-md the tree has no always-loaded file',
|
|
324
|
+
' no-rules no path-scoped rule under .claude/rules/, so every path would',
|
|
325
|
+
' report as uncovered',
|
|
326
|
+
'',
|
|
322
327
|
'Reports rather than gates. Whether a bullet belongs in a rule is the',
|
|
323
328
|
'judgment canon/context/context-model.md states, and naming a path is evidence for it',
|
|
324
329
|
'rather than the answer.',
|
|
@@ -432,6 +437,8 @@ export function register(program: Command): void {
|
|
|
432
437
|
' 1 refused, with the reason on stderr',
|
|
433
438
|
' 2 a skill folder carries no REQUIREMENT.md',
|
|
434
439
|
'',
|
|
440
|
+
'Refuses when neither claude/skills/ nor .claude/skills/ resolves.',
|
|
441
|
+
'',
|
|
435
442
|
'Only a missing REQUIREMENT.md sets a failing exit code. Name, description,',
|
|
436
443
|
'folder, and requirement-section findings are advisory.',
|
|
437
444
|
'',
|
|
@@ -460,6 +467,10 @@ export function register(program: Command): void {
|
|
|
460
467
|
' 0 history was read, whether or not a body moved',
|
|
461
468
|
' 1 the question could not be answered, with the reason on stderr',
|
|
462
469
|
'',
|
|
470
|
+
'Refuses on an absent shipped corpus, an absent repository, and a ref',
|
|
471
|
+
'matching no commit, each with its own reason. A target loading the',
|
|
472
|
+
'plugin from a marketplace cache has no history, so it refuses there.',
|
|
473
|
+
'',
|
|
463
474
|
'A moved body means the file changed, not that a session holds a stale',
|
|
464
475
|
'copy. Passing a ref older than the oldest load over-reports, which is',
|
|
465
476
|
'the safe direction. Confirm a name by reading the body.',
|
|
@@ -502,6 +513,8 @@ export function register(program: Command): void {
|
|
|
502
513
|
' 1 refused, with the reason on stderr',
|
|
503
514
|
' 2 at least one citation is unqualified',
|
|
504
515
|
'',
|
|
516
|
+
'Refuses a tree carrying neither claude/skills/ nor .claude/skills/.',
|
|
517
|
+
'',
|
|
505
518
|
'Reports rather than gates. A toolkit-scoped instruction is sometimes',
|
|
506
519
|
'meant for a session in this repository, so the verdict is a reading',
|
|
507
520
|
'and the repair is to name the owner in the sentence.',
|
package/src/commands/demo.ts
CHANGED
|
@@ -76,6 +76,12 @@ export function register(program: Command): void {
|
|
|
76
76
|
'wait condition, and the timing a beat lacks, and the timing is a',
|
|
77
77
|
'starting point you tune, which is why a recompile refuses to overwrite.',
|
|
78
78
|
'',
|
|
79
|
+
'Reasons:',
|
|
80
|
+
' draft-missing no file at the draft path',
|
|
81
|
+
' draft-unreadable no beat sheet in the draft, or a sheet with no beats',
|
|
82
|
+
' plan-exists a plan is already there and --force was not passed',
|
|
83
|
+
' plan-unresolved a URL or a target is still empty, named field by field',
|
|
84
|
+
'',
|
|
79
85
|
'Exit codes:',
|
|
80
86
|
' 0 a plan was written',
|
|
81
87
|
' 1 refused, with the reason on stderr',
|
|
@@ -114,9 +120,20 @@ export function register(program: Command): void {
|
|
|
114
120
|
'Writes mp4 beside the webm when ffmpeg is on PATH, and skips it',
|
|
115
121
|
`otherwise without failing the run. Install it with: ${INSTALL_CONVERTER}`,
|
|
116
122
|
'',
|
|
123
|
+
'Beside the webm it writes <video>.timeline.json, an entries array with',
|
|
124
|
+
'one entry per step carrying index, kind, and startMs and endMs from the',
|
|
125
|
+
'first frame. A click, fill, or hover entry adds box, the target in',
|
|
126
|
+
'viewport pixels. The still is the hero beat, or the last beat.',
|
|
127
|
+
'',
|
|
117
128
|
'A gif is opt-in behind --gif, since it is far larger than the webm',
|
|
118
129
|
'and only a host that strips video needs one. GitHub is that host.',
|
|
119
130
|
'',
|
|
131
|
+
'Reasons:',
|
|
132
|
+
' no-output-requested --no-video with --no-still, which would write nothing',
|
|
133
|
+
' plan-unreadable the plan is not JSON, or a step names a kind nothing drives',
|
|
134
|
+
' browser-missing the browser binary is not installed',
|
|
135
|
+
' engine-missing the browser package did not resolve',
|
|
136
|
+
'',
|
|
120
137
|
'Exit codes:',
|
|
121
138
|
' 0 the recording and the still were written',
|
|
122
139
|
' 1 refused, with the reason on stderr',
|
|
@@ -149,6 +166,11 @@ export function register(program: Command): void {
|
|
|
149
166
|
'Needs ffmpeg on PATH, the same binary demo run already shells to for',
|
|
150
167
|
`mp4 and gif conversion. Install it with: ${INSTALL_CONVERTER}`,
|
|
151
168
|
'',
|
|
169
|
+
'Reasons:',
|
|
170
|
+
' video-missing no file at the video path',
|
|
171
|
+
' converter-missing ffmpeg is not on PATH, so nothing was extracted',
|
|
172
|
+
' extraction-failed ffmpeg exited non-zero, with its stderr in the message',
|
|
173
|
+
'',
|
|
152
174
|
'Exit codes:',
|
|
153
175
|
' 0 frames were written',
|
|
154
176
|
' 1 refused, with the reason on stderr',
|
package/src/commands/deps.ts
CHANGED
|
@@ -56,6 +56,12 @@ export function register(program: Command): void {
|
|
|
56
56
|
' 1 refused, with the reason on stderr',
|
|
57
57
|
' 2 at least one advisory was published',
|
|
58
58
|
'',
|
|
59
|
+
'Reasons:',
|
|
60
|
+
' no-manifest no package.json in the project',
|
|
61
|
+
' no-lockfile dependencies were never resolved, so install before auditing',
|
|
62
|
+
' no-record the lookup returned no record that parses, as an unreachable',
|
|
63
|
+
' index does',
|
|
64
|
+
'',
|
|
59
65
|
'Examples:',
|
|
60
66
|
' canon deps audit',
|
|
61
67
|
' canon deps audit --json',
|
package/src/commands/driver.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Command } from 'commander'
|
|
|
4
4
|
import { INSTALL_BROWSER, isEngineMissing } from '@/browser/engine'
|
|
5
5
|
import type { DriverRefusal } from '@/driver/drive'
|
|
6
6
|
import { describeViewport } from '@/driver/probes/viewport'
|
|
7
|
-
import { PROBE_NAMES, readDriverPlan } from '@/driver/steps'
|
|
7
|
+
import { PROBE_NAMES, readDriverPlan, type ProbeName } from '@/driver/steps'
|
|
8
8
|
import type { PlanRefusal } from '@/driver/steps'
|
|
9
9
|
import {
|
|
10
10
|
intro,
|
|
@@ -55,6 +55,15 @@ const DRIVE_REFUSALS: Record<DriverRefusal, string> = {
|
|
|
55
55
|
'drive-failed': 'The drive failed against a reachable page.',
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
const PROBE_NOTES: Record<ProbeName, string> = {
|
|
59
|
+
focus: 'keyboard focus that changes nothing visible, read after a Tab press',
|
|
60
|
+
details:
|
|
61
|
+
'a details panel off the viewport or a row under the tap minimum, read open',
|
|
62
|
+
'diagram-geometry':
|
|
63
|
+
'an SVG label outside its frame, covered by a fill, or on another label',
|
|
64
|
+
'diagram-strokes': 'an SVG label a stroke crosses, in either paint order',
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
export function register(program: Command): void {
|
|
59
68
|
program
|
|
60
69
|
.command('drive')
|
|
@@ -103,7 +112,24 @@ export function register(program: Command): void {
|
|
|
103
112
|
` ${INSTALL_BROWSER}`,
|
|
104
113
|
'',
|
|
105
114
|
'Probes:',
|
|
106
|
-
...PROBE_NAMES.map(
|
|
115
|
+
...PROBE_NAMES.map(
|
|
116
|
+
(name) => ` ${name.padEnd(18)}${PROBE_NOTES[name]}`,
|
|
117
|
+
),
|
|
118
|
+
'',
|
|
119
|
+
'Reasons:',
|
|
120
|
+
' no-run-file nothing readable at the run path',
|
|
121
|
+
' unreadable-plan the run file is not valid JSON',
|
|
122
|
+
' no-steps the run names no step',
|
|
123
|
+
' no-probes the run names an empty probe list',
|
|
124
|
+
' unknown-probe the run names a probe this build does not ship',
|
|
125
|
+
' bad-step a step is missing what the driver needs to perform it',
|
|
126
|
+
' no-viewport the run declares no viewport',
|
|
127
|
+
' no-width the viewport has no width to wrap at',
|
|
128
|
+
' no-heights the viewport names no height, which is never defaulted',
|
|
129
|
+
' engine-missing the browser engine is not installed in this project',
|
|
130
|
+
' browser-missing the engine is installed and its browser binary is not',
|
|
131
|
+
' server-unreachable nothing answered at the URL, so no state was reached',
|
|
132
|
+
' drive-failed the drive failed against a reachable page',
|
|
107
133
|
'',
|
|
108
134
|
'Step kinds:',
|
|
109
135
|
' click <target> press the first element the selector matches',
|
package/src/commands/gov.ts
CHANGED
|
@@ -84,6 +84,7 @@ const CANON_RULES_REL = join('.claude', 'rules', 'canon')
|
|
|
84
84
|
|
|
85
85
|
interface InstallOptions {
|
|
86
86
|
readonly add?: string
|
|
87
|
+
readonly yes?: boolean
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
interface RegenOptions {
|
|
@@ -147,6 +148,7 @@ export function register(program: Command): void {
|
|
|
147
148
|
.argument('[target]', 'Target directory', '.')
|
|
148
149
|
.helpOption('-h, --help', 'Show this help message')
|
|
149
150
|
.option('--add <rules>', 'Comma-separated rules to layer on the stack')
|
|
151
|
+
.option('--yes', 'Answer the install confirm without a prompt')
|
|
150
152
|
.addHelpText(
|
|
151
153
|
'after',
|
|
152
154
|
[
|
|
@@ -156,6 +158,7 @@ export function register(program: Command): void {
|
|
|
156
158
|
' canon gov install node ../my-app',
|
|
157
159
|
' canon gov install node --add astro,shadcn ../my-app',
|
|
158
160
|
' canon gov install --add tasks,plan ../my-app',
|
|
161
|
+
' canon gov install node --yes ../my-app',
|
|
159
162
|
'',
|
|
160
163
|
'With --add and no stack, the named rules install alone. A first',
|
|
161
164
|
'positional naming a stack is read as the stack, so pass . or a path',
|
|
@@ -164,6 +167,10 @@ export function register(program: Command): void {
|
|
|
164
167
|
'Copies each rule into .claude/rules/canon/, replacing a copy already',
|
|
165
168
|
'there. Rules under .claude/rules/project/ are never read.',
|
|
166
169
|
'',
|
|
170
|
+
'The install confirm needs a TTY. --yes answers it, so a headless call',
|
|
171
|
+
'installs without setting CANON_NON_INTERACTIVE=1. It does not pick a',
|
|
172
|
+
'stack, so a headless call still names one.',
|
|
173
|
+
'',
|
|
167
174
|
'Exit codes:',
|
|
168
175
|
' 0 installed, the stack held no rules, or the prompt was cancelled',
|
|
169
176
|
' 1 refused: no stack named headlessly, an unknown stack, no --add',
|
|
@@ -349,6 +356,10 @@ export function register(program: Command): void {
|
|
|
349
356
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
350
357
|
' 2 at least one stated figure disagrees with the tree',
|
|
351
358
|
'',
|
|
359
|
+
'Reasons:',
|
|
360
|
+
' no-git git could not list the tree',
|
|
361
|
+
' no-markdown the tree holds no markdown file',
|
|
362
|
+
'',
|
|
352
363
|
'It reports and never gates. The false-positive rate is read off the',
|
|
353
364
|
'first run rather than assumed, and a push failing on a stated figure',
|
|
354
365
|
'that reads correctly to a person would teach a contributor to route',
|
|
@@ -415,6 +426,9 @@ export function register(program: Command): void {
|
|
|
415
426
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
416
427
|
' 2 at least one declaration asserts either',
|
|
417
428
|
'',
|
|
429
|
+
'Refuses an empty superseded value, a superseded value equal to its',
|
|
430
|
+
'replacement, and a tree git cannot list. An empty replacement is allowed.',
|
|
431
|
+
'',
|
|
418
432
|
'Examples:',
|
|
419
433
|
' canon gov superseded feature-feat- feature-',
|
|
420
434
|
' canon gov superseded feature-feat- feature- --json',
|
|
@@ -496,6 +510,9 @@ export function register(program: Command): void {
|
|
|
496
510
|
' 2 at least one cited path resolves to nothing, or one glob matches',
|
|
497
511
|
' nothing in this tree',
|
|
498
512
|
'',
|
|
513
|
+
'Refuses a tree holding neither rule corpus, and a `git check-ignore`',
|
|
514
|
+
'read that fails.',
|
|
515
|
+
'',
|
|
499
516
|
'Examples:',
|
|
500
517
|
' canon gov citations',
|
|
501
518
|
' canon gov citations --json',
|
|
@@ -543,6 +560,10 @@ export function register(program: Command): void {
|
|
|
543
560
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
544
561
|
' 2 at least one instruction is restated outside a declared mirror',
|
|
545
562
|
'',
|
|
563
|
+
'Reasons:',
|
|
564
|
+
' no-instructions the tree holds no always-loaded file or seed to read',
|
|
565
|
+
' no-surfaces the tree holds no skills tree or rules tree to match against',
|
|
566
|
+
'',
|
|
546
567
|
'Examples:',
|
|
547
568
|
' canon gov restated',
|
|
548
569
|
' canon gov restated --json',
|
|
@@ -1268,8 +1289,9 @@ async function runInstall(
|
|
|
1268
1289
|
intro('canon gov install')
|
|
1269
1290
|
|
|
1270
1291
|
const add = opts.add ?? ''
|
|
1292
|
+
const yes = opts.yes === true
|
|
1271
1293
|
if (add !== '' && isAddWithoutStack(stack, target)) {
|
|
1272
|
-
return installExtras(add, stack ?? target)
|
|
1294
|
+
return installExtras(add, stack ?? target, yes)
|
|
1273
1295
|
}
|
|
1274
1296
|
|
|
1275
1297
|
const resolved = resolveTarget(target, PROJECT_ROOT)
|
|
@@ -1314,14 +1336,7 @@ async function runInstall(
|
|
|
1314
1336
|
for (const rule of missing) logWarn(`${rule} (source not found, skipping)`)
|
|
1315
1337
|
for (const source of found) logInfo(source.rule)
|
|
1316
1338
|
|
|
1317
|
-
const shouldInstall = await
|
|
1318
|
-
message: `Install ${found.length} rules to ${displayPath(target, CANON_RULES_REL)}?`,
|
|
1319
|
-
options: [
|
|
1320
|
-
{ value: true, label: 'Yes' },
|
|
1321
|
-
{ value: false, label: 'No' },
|
|
1322
|
-
],
|
|
1323
|
-
nonInteractiveDefault: true,
|
|
1324
|
-
})
|
|
1339
|
+
const shouldInstall = await confirmInstall(found.length, target, yes)
|
|
1325
1340
|
|
|
1326
1341
|
if (!shouldInstall) {
|
|
1327
1342
|
logWarn('Cancelled')
|
|
@@ -1347,6 +1362,28 @@ async function runInstall(
|
|
|
1347
1362
|
return 0
|
|
1348
1363
|
}
|
|
1349
1364
|
|
|
1365
|
+
/**
|
|
1366
|
+
* The install confirm. `--yes` answers it without a prompt, so the line it
|
|
1367
|
+
* would have printed is skipped rather than duplicated under
|
|
1368
|
+
* `CANON_NON_INTERACTIVE=1`.
|
|
1369
|
+
*/
|
|
1370
|
+
async function confirmInstall(
|
|
1371
|
+
count: number,
|
|
1372
|
+
target: string,
|
|
1373
|
+
yes: boolean,
|
|
1374
|
+
): Promise<boolean> {
|
|
1375
|
+
if (yes) return true
|
|
1376
|
+
return select({
|
|
1377
|
+
message: `Install ${count} rules to ${displayPath(target, CANON_RULES_REL)}?`,
|
|
1378
|
+
options: [
|
|
1379
|
+
{ value: true, label: 'Yes' },
|
|
1380
|
+
{ value: false, label: 'No' },
|
|
1381
|
+
],
|
|
1382
|
+
nonInteractiveDefault: true,
|
|
1383
|
+
ttyHint: 'Pass --yes',
|
|
1384
|
+
})
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1350
1387
|
/**
|
|
1351
1388
|
* Whether `--add` runs with no stack under it. Commander fills the first
|
|
1352
1389
|
* positional before the second, so `--add a,b ../my-app` arrives with the
|
|
@@ -1366,7 +1403,11 @@ function isAddWithoutStack(stack: string | undefined, target: string): boolean {
|
|
|
1366
1403
|
* extras are not a stack, and overwriting the chain would cost a target the
|
|
1367
1404
|
* missing-rule report its stack install recorded.
|
|
1368
1405
|
*/
|
|
1369
|
-
async function installExtras(
|
|
1406
|
+
async function installExtras(
|
|
1407
|
+
add: string,
|
|
1408
|
+
target: string,
|
|
1409
|
+
yes: boolean,
|
|
1410
|
+
): Promise<number> {
|
|
1370
1411
|
const resolved = resolveTarget(target, PROJECT_ROOT)
|
|
1371
1412
|
if (typeof resolved === 'number') return resolved
|
|
1372
1413
|
|
|
@@ -1382,14 +1423,7 @@ async function installExtras(add: string, target: string): Promise<number> {
|
|
|
1382
1423
|
return 1
|
|
1383
1424
|
}
|
|
1384
1425
|
|
|
1385
|
-
const shouldInstall = await
|
|
1386
|
-
message: `Install ${found.length} rules to ${displayPath(target, CANON_RULES_REL)}?`,
|
|
1387
|
-
options: [
|
|
1388
|
-
{ value: true, label: 'Yes' },
|
|
1389
|
-
{ value: false, label: 'No' },
|
|
1390
|
-
],
|
|
1391
|
-
nonInteractiveDefault: true,
|
|
1392
|
-
})
|
|
1426
|
+
const shouldInstall = await confirmInstall(found.length, target, yes)
|
|
1393
1427
|
|
|
1394
1428
|
if (!shouldInstall) {
|
|
1395
1429
|
logWarn('Cancelled')
|
package/src/commands/intake.ts
CHANGED
|
@@ -64,6 +64,12 @@ export function register(program: Command): void {
|
|
|
64
64
|
.addHelpText(
|
|
65
65
|
'after',
|
|
66
66
|
[
|
|
67
|
+
'',
|
|
68
|
+
'Refusals, read off `reason`:',
|
|
69
|
+
' no-intake the project holds no intake folder',
|
|
70
|
+
' no-folder no folder matches the slug',
|
|
71
|
+
' ambiguous-slug a bare slug matches more than one ordinal-prefixed',
|
|
72
|
+
' folder, all named in `detail`',
|
|
67
73
|
'',
|
|
68
74
|
'Exit codes:',
|
|
69
75
|
' 0 the folders or items were listed',
|
|
@@ -105,6 +111,17 @@ export function register(program: Command): void {
|
|
|
105
111
|
.addHelpText(
|
|
106
112
|
'after',
|
|
107
113
|
[
|
|
114
|
+
'',
|
|
115
|
+
'Refusals, read off `reason`:',
|
|
116
|
+
' no-intake the project holds no intake folder',
|
|
117
|
+
' no-folder no folder matches the slug',
|
|
118
|
+
' ambiguous-slug a bare slug matches more than one ordinal-prefixed',
|
|
119
|
+
' folder, all named in `detail`',
|
|
120
|
+
' no-cluster the cluster file named does not exist',
|
|
121
|
+
' no-item the item number named does not exist in the cluster',
|
|
122
|
+
' answered the item already carries an answer',
|
|
123
|
+
' bad-input a malformed command line, such as no selection, an empty',
|
|
124
|
+
' answer, or two answers for one item',
|
|
108
125
|
'',
|
|
109
126
|
'Exit codes:',
|
|
110
127
|
' 0 every named item now carries its answer',
|
package/src/commands/labels.ts
CHANGED
|
@@ -111,6 +111,10 @@ export function register(program: Command): void {
|
|
|
111
111
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
112
112
|
' 2 at least one changed path is reached by no row',
|
|
113
113
|
'',
|
|
114
|
+
'Reasons:',
|
|
115
|
+
' no-map the project declares no label map, so it labels nothing. An',
|
|
116
|
+
' expected absence for `canon audits run`, not a fault',
|
|
117
|
+
'',
|
|
114
118
|
'Examples:',
|
|
115
119
|
' canon labels audit',
|
|
116
120
|
' canon labels audit --json',
|
|
@@ -130,7 +134,10 @@ export function register(program: Command): void {
|
|
|
130
134
|
)
|
|
131
135
|
.argument('<number>', "This branch's open pull request")
|
|
132
136
|
.helpOption('-h, --help', 'Show this help message')
|
|
133
|
-
.option(
|
|
137
|
+
.option(
|
|
138
|
+
'--base <ref>',
|
|
139
|
+
"Read the git range back to this ref instead of the pull request's files",
|
|
140
|
+
)
|
|
134
141
|
.option('--root <path>', 'Repository to read, defaulting to the cwd')
|
|
135
142
|
.option('--check', 'Report the labels owed and write nothing')
|
|
136
143
|
.option('--json', 'Add a machine-readable record on stdout')
|
|
@@ -138,9 +145,10 @@ export function register(program: Command): void {
|
|
|
138
145
|
'after',
|
|
139
146
|
[
|
|
140
147
|
'',
|
|
141
|
-
|
|
142
|
-
'
|
|
143
|
-
'
|
|
148
|
+
"Resolves the earned set from the pull request's own files listing, or",
|
|
149
|
+
'from the git range when --base is passed, binds to the pull request only',
|
|
150
|
+
'when its head is the current branch and it is open, and adds each earned',
|
|
151
|
+
'label the pull request lacks over REST.',
|
|
144
152
|
'',
|
|
145
153
|
'It adds and never removes, so a label a person applied by hand stays.',
|
|
146
154
|
'A label the remote does not carry is reported under missingOnRemote and',
|
|
@@ -155,6 +163,12 @@ export function register(program: Command): void {
|
|
|
155
163
|
' 2 labels are still owed, being any under --check, or a label the',
|
|
156
164
|
' remote lacks or a failed add otherwise',
|
|
157
165
|
'',
|
|
166
|
+
'Reasons:',
|
|
167
|
+
' head-mismatch the pull request is not open on the current branch, under',
|
|
168
|
+
' --check as well',
|
|
169
|
+
' gh-failed the files listing or the labels could not be read, and the',
|
|
170
|
+
' run does not fall back to git',
|
|
171
|
+
'',
|
|
158
172
|
'Examples:',
|
|
159
173
|
' canon labels apply 123 --json',
|
|
160
174
|
' canon labels apply 123 --check',
|
|
@@ -396,7 +410,26 @@ async function runApply(number: string, opts: ApplyOptions): Promise<number> {
|
|
|
396
410
|
if (target === null) return refuse('gh-failed')
|
|
397
411
|
if (target.trim() !== `${head} open`) return refuse('head-mismatch')
|
|
398
412
|
|
|
399
|
-
|
|
413
|
+
// An explicit --base is an explicit request for the git route. Otherwise the
|
|
414
|
+
// pull request's own file listing is the diff the labels describe, and a
|
|
415
|
+
// failed read refuses rather than falling back, since binding needed gh too.
|
|
416
|
+
let paths: string[] | undefined
|
|
417
|
+
if (opts.base === undefined) {
|
|
418
|
+
const listing = await gh(root, [
|
|
419
|
+
'api',
|
|
420
|
+
'--paginate',
|
|
421
|
+
`repos/{owner}/{repo}/pulls/${number}/files`,
|
|
422
|
+
'--jq',
|
|
423
|
+
'.[].filename',
|
|
424
|
+
])
|
|
425
|
+
if (listing === null) return refuse('gh-failed')
|
|
426
|
+
paths = nonEmptyLines(listing)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const report = await auditLabels(root, {
|
|
430
|
+
base: opts.base,
|
|
431
|
+
...(paths !== undefined && { paths }),
|
|
432
|
+
})
|
|
400
433
|
|
|
401
434
|
// A project declaring no map earns nothing and is labelled silently, which
|
|
402
435
|
// is an answer and exits clean rather than reading as a break.
|