@erclx/canon 5.39.0 → 5.41.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 +39 -22
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
- package/claude/skills/auto-ship/SKILL.md +6 -0
- package/claude/skills/auto-ship/references/fan-in.md +59 -0
- package/claude/skills/canvas-board/SKILL.md +1 -1
- package/claude/skills/create-skill/references/skill.md +1 -1
- package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
- package/claude/skills/decision-escalate/SKILL.md +1 -2
- package/claude/skills/draft-slides/SKILL.md +2 -3
- package/claude/skills/draft-slides/references/deck.md +183 -0
- package/claude/skills/git-branch/REQUIREMENT.md +17 -6
- package/claude/skills/git-branch/SKILL.md +63 -7
- 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/plan-feature/references/batches.md +48 -1
- package/claude/skills/plan-feature/references/plan.md +2 -0
- package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
- package/claude/skills/plan-intake-answer/SKILL.md +1 -2
- package/claude/skills/review-address/references/rebase-conflicts.md +7 -0
- package/claude/skills/review-ui/SKILL.md +3 -0
- package/claude/skills/review-ui/references/close-out.md +47 -0
- package/claude/skills/review-ui/references/post.md +3 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -0
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
- package/claude/skills/role-worker/REQUIREMENT.md +1 -0
- package/claude/skills/role-worker/SKILL.md +9 -0
- package/claude/skills/task-board/references/board.md +1 -1
- package/claude/skills/task-board/references/tasks.md +1 -1
- package/claude/skills/teach-workspace/REQUIREMENT.md +1 -1
- package/claude/skills/teach-workspace/SKILL.md +1 -2
- 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 +30 -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/cases/git.ts +4 -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 +192 -52
- package/src/commands/records.ts +19 -3
- 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 +5 -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 +11 -19
- package/src/pr/frames.ts +53 -9
- package/src/pr/lift.ts +153 -19
- package/src/pr/painted.ts +87 -0
- package/src/records/validate.ts +1 -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/src/tasks/answers.ts +79 -2
- package/src/tasks/fan-in.ts +176 -0
- package/src/tasks/stack.ts +1 -1
- package/tooling/astro/reference.md +80 -39
- package/tooling/base/reference.md +63 -32
- package/tooling/base/seeds/canon/context/ci.md +7 -15
- package/tooling/base/seeds/canon/context/development.md +14 -14
- package/tooling/claude/reference.md +40 -22
- package/tooling/cloudflare/reference.md +31 -12
- package/tooling/go/reference.md +47 -23
- package/tooling/nextjs/reference.md +52 -19
- package/tooling/php/reference.md +35 -16
- package/tooling/python/reference.md +47 -30
- package/tooling/shell/reference.md +6 -5
- package/tooling/vite-react/reference.md +49 -21
- package/tooling/web/reference.md +131 -71
- 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
package/src/commands/pr.ts
CHANGED
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
pruneFramesWithLegacy,
|
|
58
58
|
pushFrame,
|
|
59
59
|
readFrame,
|
|
60
|
+
referenceSlug,
|
|
60
61
|
type TreeEntry,
|
|
61
62
|
} from '@/pr/frames'
|
|
62
63
|
import { type HeadRefusal, resolveHead, resolveTip } from '@/pr/head'
|
|
@@ -69,7 +70,9 @@ import {
|
|
|
69
70
|
type LiftRefusal,
|
|
70
71
|
type PullState,
|
|
71
72
|
readBaseStanding,
|
|
73
|
+
readChangedPaths,
|
|
72
74
|
} from '@/pr/lift'
|
|
75
|
+
import { loadPaintedPatterns } from '@/pr/painted'
|
|
73
76
|
import {
|
|
74
77
|
dropAddressLine,
|
|
75
78
|
findLocalServer,
|
|
@@ -182,6 +185,7 @@ const LOCAL_REFUSALS: Record<LocalRefusal | 'gh-failed', string> = {
|
|
|
182
185
|
interface FramesOptions extends ReadOptions {
|
|
183
186
|
readonly add?: string
|
|
184
187
|
readonly box?: string
|
|
188
|
+
readonly reference?: string
|
|
185
189
|
readonly head?: string
|
|
186
190
|
readonly pass?: string
|
|
187
191
|
readonly theme?: string
|
|
@@ -194,6 +198,7 @@ type FramesCommandRefusal =
|
|
|
194
198
|
| 'bad-mode'
|
|
195
199
|
| 'no-number'
|
|
196
200
|
| 'bad-box'
|
|
201
|
+
| 'bad-reference'
|
|
197
202
|
| 'bad-head'
|
|
198
203
|
| 'bad-pass'
|
|
199
204
|
| 'bad-theme'
|
|
@@ -210,13 +215,15 @@ const FRAMES_REFUSALS: Record<FramesCommandRefusal, string> = {
|
|
|
210
215
|
'no-number':
|
|
211
216
|
'--add and --drop write for one pull request, so name its number.',
|
|
212
217
|
'bad-box':
|
|
213
|
-
'--add takes --box <n>, a positive whole number naming the checklist box.',
|
|
218
|
+
'--add takes --box <n>, a positive whole number naming the checklist box, or --reference <page>/<frame>, never both.',
|
|
219
|
+
'bad-reference':
|
|
220
|
+
'--reference takes <page>/<frame>, each half opening on a letter or digit and holding only letters, digits, dots, underscores, and dashes, with no `--` in either, so the capture files under a name that reads back as the frame.',
|
|
214
221
|
'bad-head':
|
|
215
222
|
'--head takes a commit sha, 7 to 40 lowercase hex characters. A branch name or a path would file the frame under a folder that names no commit.',
|
|
216
223
|
'bad-pass':
|
|
217
224
|
'--add takes --pass <stamp>, the instant the pass started in compact UTC such as 20261002T154450Z, so a second pass never replaces an earlier one.',
|
|
218
225
|
'bad-theme':
|
|
219
|
-
'--theme takes dark or light and goes with --add, so a second shot of one box never replaces the first.',
|
|
226
|
+
'--theme takes dark or light and goes with --add --box, so a second shot of one box never replaces the first.',
|
|
220
227
|
'bad-days': '--prune takes a positive whole number of days.',
|
|
221
228
|
'unreadable-frame':
|
|
222
229
|
'The file named by --add is missing, empty, or not a PNG, so it would embed as a broken image. Nothing is pushed.',
|
|
@@ -421,6 +428,24 @@ export function register(program: Command): void {
|
|
|
421
428
|
'points the change at a locative the path does not name. Read the bullet on',
|
|
422
429
|
"the record's preview before filing an unmet path as a stale claim.",
|
|
423
430
|
'',
|
|
431
|
+
'Refusals, read off `reason`:',
|
|
432
|
+
' no-section the body carries no `## Key changes` heading, so it',
|
|
433
|
+
' claims nothing',
|
|
434
|
+
' no-claims the heading is there and no span resolved, which is',
|
|
435
|
+
' the extractor failing over prose and raises nothing',
|
|
436
|
+
' no-changes the pull request changed no files, so no claim has',
|
|
437
|
+
' anything to answer',
|
|
438
|
+
' unreadable-body the pull request body could not be read',
|
|
439
|
+
' unreadable-tree the file tree of the head could not be read',
|
|
440
|
+
' no-base GitHub reported no merge base for the pull request',
|
|
441
|
+
' bad-base `--base` names a ref sharing no history with HEAD',
|
|
442
|
+
' unreadable-changes the changed files could not be listed',
|
|
443
|
+
' ambiguous-pull two open pull requests share the branch against',
|
|
444
|
+
' different bases, so name a number',
|
|
445
|
+
' gh-missing `gh` is not on the path, so no pull request could be',
|
|
446
|
+
' resolved',
|
|
447
|
+
' gh-failed `gh` could not answer for this repository or branch',
|
|
448
|
+
'',
|
|
424
449
|
'Exit codes:',
|
|
425
450
|
' 0 every claimed path is in the diff',
|
|
426
451
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -455,6 +480,17 @@ export function register(program: Command): void {
|
|
|
455
480
|
'tip from the remote with `git ls-remote` and reports which commit each',
|
|
456
481
|
'source names.',
|
|
457
482
|
'',
|
|
483
|
+
'Refusals, read off `reason`:',
|
|
484
|
+
' ambiguous-pull two open pull requests share the branch against',
|
|
485
|
+
' different bases, so name a number',
|
|
486
|
+
' no-branch the pull request carries no head branch name',
|
|
487
|
+
' unresolvable-ref the remote read failed, which is not an absent branch',
|
|
488
|
+
' no-remote-branch the remote carries no branch by that name, so there',
|
|
489
|
+
' is no tip to compare against',
|
|
490
|
+
' gh-missing `gh` is not on the path, so no pull request resolved',
|
|
491
|
+
' gh-failed `gh` could not answer for this branch, so name a number',
|
|
492
|
+
' no-object-head the pull request object reported no head commit',
|
|
493
|
+
'',
|
|
458
494
|
'Read the verdict off the record rather than off the exit. A stale head',
|
|
459
495
|
'exits 0, because an operator shell profile can wrap this binary in a',
|
|
460
496
|
'function whose status comes from a trailing command.',
|
|
@@ -492,6 +528,19 @@ export function register(program: Command): void {
|
|
|
492
528
|
'an empty row list, and reading that as passing is the false green the',
|
|
493
529
|
'sha key alone does not close.',
|
|
494
530
|
'',
|
|
531
|
+
'Refusals, read off `reason`:',
|
|
532
|
+
' ambiguous-pull two open pull requests share the branch against',
|
|
533
|
+
' different bases, so name a number',
|
|
534
|
+
' no-branch the pull request carries no head branch name',
|
|
535
|
+
' unresolvable-ref the remote read failed, which is not an absent branch',
|
|
536
|
+
' no-remote-branch the remote carries no branch by that name, so there',
|
|
537
|
+
' is no tip to compare against',
|
|
538
|
+
' runs-unreadable the check runs for the tip could not be read, which is',
|
|
539
|
+
' unread rather than none',
|
|
540
|
+
' gh-missing `gh` is not on the path, so no pull request resolved',
|
|
541
|
+
' gh-failed `gh` could not answer for this branch, so name a number',
|
|
542
|
+
' no-object-head the pull request object reported no head commit',
|
|
543
|
+
'',
|
|
495
544
|
'A branch conflicting with its base gets no merge ref, so no run ever',
|
|
496
545
|
'starts for it and the state stays pending. The record carries',
|
|
497
546
|
'`conflicted: true` beside it when `mergeStateStatus` reads DIRTY and no',
|
|
@@ -538,6 +587,19 @@ export function register(program: Command): void {
|
|
|
538
587
|
' fallback a pass posted before the marker shipped, off GitHub stamps',
|
|
539
588
|
' none the thread carries no pass, so the next one is a first pass',
|
|
540
589
|
'',
|
|
590
|
+
'Refusals, read off `reason`:',
|
|
591
|
+
' ambiguous-pull two open pull requests share the branch against',
|
|
592
|
+
' different bases, so name a number',
|
|
593
|
+
' no-branch the pull request carries no head branch name',
|
|
594
|
+
' unresolvable-ref the remote read failed, which is not an absent branch',
|
|
595
|
+
' no-remote-branch the remote carries no branch by that name, so there',
|
|
596
|
+
' is no tip to compare against',
|
|
597
|
+
' reviews-unreadable the reviews could not be parsed, which is unread',
|
|
598
|
+
' rather than none',
|
|
599
|
+
' gh-missing `gh` is not on the path, so no pull request resolved',
|
|
600
|
+
' gh-failed `gh` could not answer for this branch, so name a number',
|
|
601
|
+
' no-object-head the pull request object reported no head commit',
|
|
602
|
+
'',
|
|
541
603
|
'Exit codes:',
|
|
542
604
|
' 0 the thread was read, whether it carries a pass or not',
|
|
543
605
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -656,6 +718,17 @@ export function register(program: Command): void {
|
|
|
656
718
|
'The record carries `owed` and the same marked fields. --check refuses',
|
|
657
719
|
'with --preview, --local, or --checklist as check-writes.',
|
|
658
720
|
'',
|
|
721
|
+
'Refusals, read off `reason`:',
|
|
722
|
+
' unreadable-checklist --checklist named a path that could not be read,',
|
|
723
|
+
' or one holding nothing',
|
|
724
|
+
' no-branch the pull request carries no head branch name',
|
|
725
|
+
' no-base GitHub reported no merge base for the pull request',
|
|
726
|
+
' unreadable-changes GitHub could not list what the pull request changed',
|
|
727
|
+
' gh-missing `gh` is not on the path',
|
|
728
|
+
' gh-failed `gh` could not read or edit the pull request or its',
|
|
729
|
+
' comments',
|
|
730
|
+
' no-object-head the pull request object reported no head commit',
|
|
731
|
+
'',
|
|
659
732
|
'Exit codes:',
|
|
660
733
|
' 0 read, whether it produced a body, reported no-evidence, or checked',
|
|
661
734
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -696,7 +769,11 @@ export function register(program: Command): void {
|
|
|
696
769
|
'they are read:',
|
|
697
770
|
' review the newest `## Review` is closed at the head, off its marker',
|
|
698
771
|
' ui the newest `## UI review` is closed at the head, bound only',
|
|
699
|
-
' where `canon pr evidence --check` carries a checklist',
|
|
772
|
+
' where `canon pr evidence --check` carries a checklist. A close',
|
|
773
|
+
' on an earlier commit still holds when it is an ancestor of the',
|
|
774
|
+
' head and no painted path changed since, and the record names',
|
|
775
|
+
' it as `carriedFrom`. A painted change refuses `ui-behind` and',
|
|
776
|
+
' lists the paths as `paintedPaths`',
|
|
700
777
|
' evidence the evidence check reads settled',
|
|
701
778
|
' standing the head is fresh against the base, not stale or conflicted',
|
|
702
779
|
' ci every check run on the tip passed, waited on up to --wait',
|
|
@@ -705,8 +782,21 @@ export function register(program: Command): void {
|
|
|
705
782
|
'wait, so a reviewer whose partner is still driving refuses at once. The',
|
|
706
783
|
'head and the reviews are read again before the mutation.',
|
|
707
784
|
'',
|
|
785
|
+
'Painted paths are what a browser renders (`.tsx`, `.jsx`, `.vue`,',
|
|
786
|
+
'`.svelte`, `.astro`, `.html`, `.css`, `.scss`, `.svg`, images, fonts)',
|
|
787
|
+
'minus test, spec, and story files. A project adds to or trims the list',
|
|
788
|
+
'in `canon/config/painted.json` as `{ "painted": [glob], "unpainted":',
|
|
789
|
+
'[glob] }`.',
|
|
790
|
+
'',
|
|
791
|
+
'A pull request that is already ready has the review, ui, and evidence',
|
|
792
|
+
'gates read too. One that reads not clear returns to draft with reason',
|
|
793
|
+
'`redrafted` and the failing reason as `failed`, exit 1. Standing and CI',
|
|
794
|
+
'are reported and never cause a re-draft. A ready pull request that',
|
|
795
|
+
'clears reports `already-ready`, exit 0. Under --check nothing is',
|
|
796
|
+
'written and the first failing reason is reported.',
|
|
797
|
+
'',
|
|
708
798
|
'Reasons:',
|
|
709
|
-
' lifted, already-ready, clear (--check)',
|
|
799
|
+
' lifted, already-ready, redrafted, clear (--check)',
|
|
710
800
|
' pull-closed, review-missing, review-open, review-unmarked,',
|
|
711
801
|
' review-behind, ui-missing, ui-open, ui-unmarked, ui-behind,',
|
|
712
802
|
' evidence-owed, standing-stale, standing-conflict, standing-unknown,',
|
|
@@ -784,6 +874,15 @@ export function register(program: Command): void {
|
|
|
784
874
|
' building a deploy of the tip is still running, so wait',
|
|
785
875
|
' no-build no deploy of this branch has succeeded',
|
|
786
876
|
'',
|
|
877
|
+
'Refusals before either read, read off `reason`:',
|
|
878
|
+
' bad-timeout --timeout was not a positive number',
|
|
879
|
+
' check-timeout --check was combined with --timeout',
|
|
880
|
+
' no-branch the pull request carries no head branch name',
|
|
881
|
+
' unreadable-changes the changed paths of the pull request could not be read',
|
|
882
|
+
' gh-missing `gh` is not on the path',
|
|
883
|
+
' gh-failed `gh` could not read the pull request, or list or',
|
|
884
|
+
' dispatch the workflow',
|
|
885
|
+
'',
|
|
787
886
|
'Exit codes:',
|
|
788
887
|
' 0 a preview was published, or --check read fresh',
|
|
789
888
|
' 1 refused, or --check read anything but fresh',
|
|
@@ -838,6 +937,10 @@ export function register(program: Command): void {
|
|
|
838
937
|
' no-comment --remove found no marked comment, a no-op',
|
|
839
938
|
' no-line --remove found no address line, a no-op',
|
|
840
939
|
'',
|
|
940
|
+
'Refusals, read off `reason`:',
|
|
941
|
+
' gh-missing `--remove` needs `gh` and it is not on the path',
|
|
942
|
+
' gh-failed `--remove` could not read or edit the comment',
|
|
943
|
+
'',
|
|
841
944
|
'Exit codes:',
|
|
842
945
|
' 0 a server was found, or --remove finished, including a no-op',
|
|
843
946
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -913,6 +1016,10 @@ export function register(program: Command): void {
|
|
|
913
1016
|
.option('--json', 'Add a machine-readable record on stdout')
|
|
914
1017
|
.option('--add <file>', 'Push this PNG as a frame')
|
|
915
1018
|
.option('--box <n>', 'The checklist box the frame shows, with --add')
|
|
1019
|
+
.option(
|
|
1020
|
+
'--reference <address>',
|
|
1021
|
+
'The design reference the capture shows, as <page>/<frame>, with --add in place of --box',
|
|
1022
|
+
)
|
|
916
1023
|
.option(
|
|
917
1024
|
'--head <sha>',
|
|
918
1025
|
"The head the frame was driven at, with --add, defaulting to the pull request's",
|
|
@@ -937,7 +1044,8 @@ export function register(program: Command): void {
|
|
|
937
1044
|
`An add writes only refs/heads/${FRAMES_BRANCH}, through the GitHub git data API,`,
|
|
938
1045
|
'so nothing is checked out and no other branch is touched. A frame lands',
|
|
939
1046
|
'at pr-<number>/<short-head>/<pass>/box-<n>.png, or box-<n>-<theme>.png',
|
|
940
|
-
'with --theme,
|
|
1047
|
+
'with --theme, or ref-<page>--<frame>.png for a design reference capture',
|
|
1048
|
+
'passed as --reference <page>/<frame> in place of --box, and `link` names that',
|
|
941
1049
|
`path on ${FRAMES_BRANCH}, so it survives a rewrite that drops other pull`,
|
|
942
1050
|
'requests and resolves until its own pull request is dropped. The pass',
|
|
943
1051
|
'segment keeps a second pass at one head and box from replacing an image',
|
|
@@ -958,10 +1066,11 @@ export function register(program: Command): void {
|
|
|
958
1066
|
' ok an add pushed, or a drop or prune finished',
|
|
959
1067
|
' bad-mode not exactly one of --add, --drop, --prune',
|
|
960
1068
|
' no-number --add or --drop named no pull request',
|
|
961
|
-
' bad-box --add
|
|
1069
|
+
' bad-box --add with both --box and --reference, or neither',
|
|
1070
|
+
' bad-reference --reference is not <page>/<frame> in the safe name form',
|
|
962
1071
|
' bad-head --head is not 7 to 40 lowercase hex characters',
|
|
963
1072
|
' bad-pass --add without a compact UTC --pass stamp',
|
|
964
|
-
' bad-theme --theme is not dark or light, or no --add',
|
|
1073
|
+
' bad-theme --theme is not dark or light, or no --add, or rides a --reference',
|
|
965
1074
|
' bad-days --prune without a positive number of days',
|
|
966
1075
|
' unreadable-frame the --add file is missing, empty, or not a PNG',
|
|
967
1076
|
' read-only GitHub refused the write, as on a fork token',
|
|
@@ -2986,10 +3095,18 @@ function framesArgumentRefusal(
|
|
|
2986
3095
|
if (number === undefined || !POSITIVE_WHOLE.test(number)) return 'no-number'
|
|
2987
3096
|
if (
|
|
2988
3097
|
opts.add !== undefined &&
|
|
2989
|
-
(opts.box === undefined
|
|
3098
|
+
(opts.box === undefined) === (opts.reference === undefined)
|
|
2990
3099
|
) {
|
|
2991
3100
|
return 'bad-box'
|
|
2992
3101
|
}
|
|
3102
|
+
if (opts.add !== undefined && opts.box !== undefined) {
|
|
3103
|
+
if (!POSITIVE_WHOLE.test(opts.box)) return 'bad-box'
|
|
3104
|
+
}
|
|
3105
|
+
if (opts.reference !== undefined) {
|
|
3106
|
+
if (opts.add === undefined || referenceSlug(opts.reference) === undefined) {
|
|
3107
|
+
return 'bad-reference'
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
2993
3110
|
if (opts.head !== undefined && !isFrameHead(opts.head)) return 'bad-head'
|
|
2994
3111
|
if (
|
|
2995
3112
|
opts.add !== undefined &&
|
|
@@ -2999,7 +3116,9 @@ function framesArgumentRefusal(
|
|
|
2999
3116
|
}
|
|
3000
3117
|
if (
|
|
3001
3118
|
opts.theme !== undefined &&
|
|
3002
|
-
(opts.add === undefined ||
|
|
3119
|
+
(opts.add === undefined ||
|
|
3120
|
+
opts.reference !== undefined ||
|
|
3121
|
+
!isFrameTheme(opts.theme))
|
|
3003
3122
|
) {
|
|
3004
3123
|
return 'bad-theme'
|
|
3005
3124
|
}
|
|
@@ -3101,7 +3220,8 @@ async function runFrames(
|
|
|
3101
3220
|
if (head === undefined || head === '') return refuseFrames('no-object-head')
|
|
3102
3221
|
if (!isFrameHead(head)) return refuseFrames('bad-head')
|
|
3103
3222
|
|
|
3104
|
-
const box = Number(opts.box)
|
|
3223
|
+
const box = opts.box === undefined ? undefined : Number(opts.box)
|
|
3224
|
+
const reference = opts.reference
|
|
3105
3225
|
const pass = opts.pass ?? ''
|
|
3106
3226
|
const theme = opts.theme as FrameTheme | undefined
|
|
3107
3227
|
const result = await pushFrame(api, {
|
|
@@ -3109,6 +3229,7 @@ async function runFrames(
|
|
|
3109
3229
|
head,
|
|
3110
3230
|
pass,
|
|
3111
3231
|
box,
|
|
3232
|
+
reference,
|
|
3112
3233
|
theme,
|
|
3113
3234
|
bytes: bytes ?? new Uint8Array(),
|
|
3114
3235
|
})
|
|
@@ -3117,7 +3238,7 @@ async function runFrames(
|
|
|
3117
3238
|
return finish(
|
|
3118
3239
|
{
|
|
3119
3240
|
number: pull,
|
|
3120
|
-
box,
|
|
3241
|
+
...(box === undefined ? { reference } : { box }),
|
|
3121
3242
|
...(theme === undefined ? {} : { theme }),
|
|
3122
3243
|
head,
|
|
3123
3244
|
pass,
|
|
@@ -3169,6 +3290,43 @@ const LIFT_REFUSALS: Record<LiftRefusal | 'bad-wait' | 'gh-missing', string> = {
|
|
|
3169
3290
|
const READY_MUTATION =
|
|
3170
3291
|
'mutation($id: ID!) { markPullRequestReadyForReview(input: {pullRequestId: $id}) { pullRequest { isDraft } } }'
|
|
3171
3292
|
|
|
3293
|
+
const CONVERT_DRAFT_MUTATION =
|
|
3294
|
+
'mutation($id: ID!) { convertPullRequestToDraft(input: {pullRequestId: $id}) { pullRequest { isDraft } } }'
|
|
3295
|
+
|
|
3296
|
+
/**
|
|
3297
|
+
* Runs one pull request mutation and answers whether the draft flag now reads
|
|
3298
|
+
* `isDraft`. The flag is read back rather than the exit trusted, since the
|
|
3299
|
+
* mutation can answer 200 with an errors array and no change.
|
|
3300
|
+
*/
|
|
3301
|
+
async function flipDraft(
|
|
3302
|
+
root: string,
|
|
3303
|
+
mutation: string,
|
|
3304
|
+
field: 'markPullRequestReadyForReview' | 'convertPullRequestToDraft',
|
|
3305
|
+
nodeId: string,
|
|
3306
|
+
isDraft: boolean,
|
|
3307
|
+
): Promise<boolean> {
|
|
3308
|
+
const result = await execa(
|
|
3309
|
+
'gh',
|
|
3310
|
+
['api', 'graphql', '-f', `query=${mutation}`, '-f', `id=${nodeId}`],
|
|
3311
|
+
{
|
|
3312
|
+
cwd: root,
|
|
3313
|
+
reject: false,
|
|
3314
|
+
timeout: GH_TIMEOUT_MS,
|
|
3315
|
+
env: gitEnv(),
|
|
3316
|
+
extendEnv: false,
|
|
3317
|
+
},
|
|
3318
|
+
)
|
|
3319
|
+
if (result.exitCode !== 0) return false
|
|
3320
|
+
try {
|
|
3321
|
+
const body = JSON.parse(String(result.stdout)) as {
|
|
3322
|
+
data?: Record<string, { pullRequest?: { isDraft?: boolean } } | undefined>
|
|
3323
|
+
}
|
|
3324
|
+
return body.data?.[field]?.pullRequest?.isDraft === isDraft
|
|
3325
|
+
} catch {
|
|
3326
|
+
return false
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3172
3330
|
function gitRunner(root: string): GitRunner {
|
|
3173
3331
|
return async (args) => {
|
|
3174
3332
|
const result = await execa('git', ['-C', root, ...args], {
|
|
@@ -3292,44 +3450,23 @@ function liftApi(root: string, number: string, baseRef: string): LiftApi {
|
|
|
3292
3450
|
return undefined
|
|
3293
3451
|
}
|
|
3294
3452
|
},
|
|
3295
|
-
markReady:
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
},
|
|
3313
|
-
)
|
|
3314
|
-
if (result.exitCode !== 0) return false
|
|
3315
|
-
// The flag is read back rather than the exit trusted, since the
|
|
3316
|
-
// mutation can answer 200 with an errors array and no change.
|
|
3317
|
-
try {
|
|
3318
|
-
const body = JSON.parse(String(result.stdout)) as {
|
|
3319
|
-
data?: {
|
|
3320
|
-
markPullRequestReadyForReview?: {
|
|
3321
|
-
pullRequest?: { isDraft?: boolean }
|
|
3322
|
-
}
|
|
3323
|
-
}
|
|
3324
|
-
}
|
|
3325
|
-
return (
|
|
3326
|
-
body.data?.markPullRequestReadyForReview?.pullRequest?.isDraft ===
|
|
3327
|
-
false
|
|
3328
|
-
)
|
|
3329
|
-
} catch {
|
|
3330
|
-
return false
|
|
3331
|
-
}
|
|
3332
|
-
},
|
|
3453
|
+
markReady: (nodeId) =>
|
|
3454
|
+
flipDraft(
|
|
3455
|
+
root,
|
|
3456
|
+
READY_MUTATION,
|
|
3457
|
+
'markPullRequestReadyForReview',
|
|
3458
|
+
nodeId,
|
|
3459
|
+
false,
|
|
3460
|
+
),
|
|
3461
|
+
markDraft: (nodeId) =>
|
|
3462
|
+
flipDraft(
|
|
3463
|
+
root,
|
|
3464
|
+
CONVERT_DRAFT_MUTATION,
|
|
3465
|
+
'convertPullRequestToDraft',
|
|
3466
|
+
nodeId,
|
|
3467
|
+
true,
|
|
3468
|
+
),
|
|
3469
|
+
readChanged: (from, to) => readChangedPaths(git, from, to),
|
|
3333
3470
|
sleep: (ms) => new Promise((done) => setTimeout(done, ms)),
|
|
3334
3471
|
now: () => Date.now(),
|
|
3335
3472
|
}
|
|
@@ -3385,6 +3522,7 @@ async function runLift(
|
|
|
3385
3522
|
const record = await lift(liftApi(root, resolved.number, `origin/${base}`), {
|
|
3386
3523
|
isCheck: opts.check ?? false,
|
|
3387
3524
|
waitSeconds,
|
|
3525
|
+
painted: loadPaintedPatterns(root),
|
|
3388
3526
|
})
|
|
3389
3527
|
|
|
3390
3528
|
logStep(record.reason)
|
|
@@ -3393,9 +3531,11 @@ async function runLift(
|
|
|
3393
3531
|
? `#${resolved.number} is ready for review at ${record.head?.slice(0, 8)}`
|
|
3394
3532
|
: record.reason === 'already-ready'
|
|
3395
3533
|
? `#${resolved.number} is already ready for review`
|
|
3396
|
-
: record.reason === '
|
|
3397
|
-
?
|
|
3398
|
-
:
|
|
3534
|
+
: record.reason === 'redrafted'
|
|
3535
|
+
? `#${resolved.number} is back to draft, since ${record.failed === undefined ? 'a gate' : LIFT_REFUSALS[record.failed]}`
|
|
3536
|
+
: record.reason === 'clear'
|
|
3537
|
+
? `every gate reads clear for #${resolved.number}`
|
|
3538
|
+
: LIFT_REFUSALS[record.reason],
|
|
3399
3539
|
)
|
|
3400
3540
|
for (const gate of record.gates) {
|
|
3401
3541
|
if (!gate.isOk) logWarn(`${gate.gate}: ${gate.reason ?? 'not clear'}`)
|
package/src/commands/records.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
plural,
|
|
45
45
|
} from '@/cli/ui'
|
|
46
46
|
import { currentWorktreeRoot, mainWorktreeRoot } from '@/git/worktree'
|
|
47
|
+
import { fanInFindings } from '@/tasks/fan-in'
|
|
47
48
|
import { readLivePlans, stackFindings } from '@/tasks/stack'
|
|
48
49
|
|
|
49
50
|
/** Returned when a record carries a finding, which is the gating result. */
|
|
@@ -100,6 +101,10 @@ export function register(program: Command): void {
|
|
|
100
101
|
' groundwork README and current-state files, numbering, dating, and a half-closed track',
|
|
101
102
|
' intake overview file, numbering, dating, and the four bullets every item carries',
|
|
102
103
|
'',
|
|
104
|
+
'Refusals, read off `reason`:',
|
|
105
|
+
" no-folder none of the kind's directories exist at either record root",
|
|
106
|
+
' unknown-kind the argument names no published kind',
|
|
107
|
+
'',
|
|
103
108
|
'Exit codes:',
|
|
104
109
|
' 0 every check passed',
|
|
105
110
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -216,6 +221,10 @@ export function register(program: Command): void {
|
|
|
216
221
|
'and the release tags merged into HEAD after that day, or never when it',
|
|
217
222
|
'carries no date.',
|
|
218
223
|
'',
|
|
224
|
+
'Refusals, read off `reason`:',
|
|
225
|
+
' unknown-kind any kind but `canonical`',
|
|
226
|
+
' unreadable-tags the release tags could not be read',
|
|
227
|
+
'',
|
|
219
228
|
'Exit codes:',
|
|
220
229
|
' 0 the reading completed, whatever it found',
|
|
221
230
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -734,7 +743,7 @@ async function runValidate(
|
|
|
734
743
|
}
|
|
735
744
|
|
|
736
745
|
/**
|
|
737
|
-
* Appends the
|
|
746
|
+
* Appends the plans findings that read across files. They sit in the command
|
|
738
747
|
* layer because `src/records/` sits below `src/tasks/`.
|
|
739
748
|
*/
|
|
740
749
|
async function validateWithStacks(
|
|
@@ -744,9 +753,16 @@ async function validateWithStacks(
|
|
|
744
753
|
const outcome = await validateRecords(root, kind)
|
|
745
754
|
if (!outcome.ok || kind !== 'plans') return outcome
|
|
746
755
|
|
|
747
|
-
const
|
|
756
|
+
const live = await readLivePlans(root)
|
|
748
757
|
|
|
749
|
-
return {
|
|
758
|
+
return {
|
|
759
|
+
...outcome,
|
|
760
|
+
findings: [
|
|
761
|
+
...outcome.findings,
|
|
762
|
+
...stackFindings(live),
|
|
763
|
+
...fanInFindings(live),
|
|
764
|
+
],
|
|
765
|
+
}
|
|
750
766
|
}
|
|
751
767
|
|
|
752
768
|
function report(
|
package/src/commands/secrets.ts
CHANGED
|
@@ -60,6 +60,12 @@ export function register(program: Command): void {
|
|
|
60
60
|
' 1 refused, with the reason on stderr',
|
|
61
61
|
' 2 at least one value was found',
|
|
62
62
|
'',
|
|
63
|
+
'Reasons:',
|
|
64
|
+
' no-manifest, no-publish, no-shipped-files',
|
|
65
|
+
' nothing is published from this tree, so the corpus is absent',
|
|
66
|
+
' no-files-field a publish would pack the whole tree and none of it was read',
|
|
67
|
+
' no-git git could not list the tree, so the corpus is unknown',
|
|
68
|
+
'',
|
|
63
69
|
'Examples:',
|
|
64
70
|
' canon secrets scan',
|
|
65
71
|
' canon secrets scan --json',
|
package/src/commands/sessions.ts
CHANGED
|
@@ -69,6 +69,14 @@ export function register(program: Command): void {
|
|
|
69
69
|
.addHelpText(
|
|
70
70
|
'after',
|
|
71
71
|
[
|
|
72
|
+
'',
|
|
73
|
+
'Refusals, read off `reason`:',
|
|
74
|
+
' no-registry no session registry exists, so nothing was read and',
|
|
75
|
+
' the roster is not empty',
|
|
76
|
+
' no-repository `--branch` resolved no repository. Run inside one or',
|
|
77
|
+
' pass `--repository`',
|
|
78
|
+
' no-self-identity `--self` found nothing to name this session with',
|
|
79
|
+
' no-self-row `--self` found no roster row for this session',
|
|
72
80
|
'',
|
|
73
81
|
'Exit codes:',
|
|
74
82
|
' 0 the roster was read',
|
package/src/commands/tasks.ts
CHANGED
|
@@ -176,6 +176,18 @@ export function register(program: Command): void {
|
|
|
176
176
|
'listed, and pending-branch while a Pending branch: line names a branch',
|
|
177
177
|
'whose own pull request is not recorded yet. A stem skips both.',
|
|
178
178
|
'',
|
|
179
|
+
'Refusals, read off `reason`:',
|
|
180
|
+
' no-board the repository holds no `.canon/tasks/` board',
|
|
181
|
+
' no-match no task matches the selector',
|
|
182
|
+
' ambiguous more than one task matches the selector',
|
|
183
|
+
' no-outcomes the task carries no outcomes to check',
|
|
184
|
+
' open-outcomes an outcome is still unchecked',
|
|
185
|
+
' earlier-slice `--pull-request` names a number that is not the last on',
|
|
186
|
+
' the task',
|
|
187
|
+
' pending-branch a `Pending branch:` line names a branch whose pull',
|
|
188
|
+
' request is not recorded yet',
|
|
189
|
+
' bad-input a malformed selector',
|
|
190
|
+
'',
|
|
179
191
|
'Exit codes:',
|
|
180
192
|
' 0 the task was archived',
|
|
181
193
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -209,6 +221,12 @@ export function register(program: Command): void {
|
|
|
209
221
|
'decided against at any outcome state, and the two never share a',
|
|
210
222
|
'refusal set since they answer different questions.',
|
|
211
223
|
'',
|
|
224
|
+
'Refusals, read off `reason`:',
|
|
225
|
+
' no-board the repository holds no `.canon/tasks/` board',
|
|
226
|
+
' no-match no task matches the selector',
|
|
227
|
+
' ambiguous more than one task matches the selector',
|
|
228
|
+
' bad-input a missing `--reason`, or a `--by` that resolves to nobody',
|
|
229
|
+
'',
|
|
212
230
|
'Exit codes:',
|
|
213
231
|
' 0 the task was declined',
|
|
214
232
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -243,6 +261,12 @@ export function register(program: Command): void {
|
|
|
243
261
|
' every Run now row names the files it touches',
|
|
244
262
|
' a file two Run now rows share is listed, never failed',
|
|
245
263
|
'',
|
|
264
|
+
'Refusals, read off `reason`:',
|
|
265
|
+
' no-board the repository holds no `.canon/tasks/` board',
|
|
266
|
+
' no-ordering the board has no ordering file',
|
|
267
|
+
' no-groups the board groups rows under headings of its own, with no',
|
|
268
|
+
' declared columns',
|
|
269
|
+
'',
|
|
246
270
|
'Exit codes:',
|
|
247
271
|
' 0 every check passed',
|
|
248
272
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -422,6 +446,12 @@ export function register(program: Command): void {
|
|
|
422
446
|
.addHelpText(
|
|
423
447
|
'after',
|
|
424
448
|
[
|
|
449
|
+
'',
|
|
450
|
+
'Refusals, read off `reason`:',
|
|
451
|
+
' no-board the repository holds no `.canon/tasks/` board',
|
|
452
|
+
' no-match no task matches the selector',
|
|
453
|
+
' ambiguous more than one task matches the selector',
|
|
454
|
+
' bad-input a number that is not a pull request number',
|
|
425
455
|
'',
|
|
426
456
|
'Exit codes:',
|
|
427
457
|
' 0 the line was added, appended to, corrected, or already correct',
|
|
@@ -468,6 +498,13 @@ export function register(program: Command): void {
|
|
|
468
498
|
.addHelpText(
|
|
469
499
|
'after',
|
|
470
500
|
[
|
|
501
|
+
'',
|
|
502
|
+
'Refusals, read off `reason`:',
|
|
503
|
+
' no-board the repository holds no `.canon/tasks/` board',
|
|
504
|
+
' no-match no task matches the selector',
|
|
505
|
+
' no-plan the plan named does not exist',
|
|
506
|
+
' several-plans the task line links more than one plan',
|
|
507
|
+
' bad-input a malformed argument',
|
|
471
508
|
'',
|
|
472
509
|
'Exit codes:',
|
|
473
510
|
' 0 the line was added, appended to, corrected, or already correct',
|
|
@@ -514,6 +551,14 @@ export function register(program: Command): void {
|
|
|
514
551
|
.addHelpText(
|
|
515
552
|
'after',
|
|
516
553
|
[
|
|
554
|
+
'',
|
|
555
|
+
'Refusals, read off `reason`:',
|
|
556
|
+
' no-board the repository holds no `.canon/tasks/` board',
|
|
557
|
+
' no-match no task matches the selector',
|
|
558
|
+
' ambiguous more than one task matches the selector',
|
|
559
|
+
' no-outcomes the task carries no outcomes',
|
|
560
|
+
' out-of-range a `--close` position past the end of the outcome list',
|
|
561
|
+
' bad-input a malformed position',
|
|
517
562
|
'',
|
|
518
563
|
'Exit codes:',
|
|
519
564
|
' 0 every named outcome is closed',
|
|
@@ -557,6 +602,10 @@ export function register(program: Command): void {
|
|
|
557
602
|
'archive holds labels the live board no longer shows and a scan',
|
|
558
603
|
'confined to the board hands out one already spent.',
|
|
559
604
|
'',
|
|
605
|
+
'Refusals, read off `reason`:',
|
|
606
|
+
' no-board the repository holds no `.canon/tasks/` board',
|
|
607
|
+
' label-contended `--claim` lost five reservations, and the verb exits 2',
|
|
608
|
+
'',
|
|
560
609
|
'Exit codes:',
|
|
561
610
|
' 0 the label is derived',
|
|
562
611
|
' 1 refused with no-board',
|
|
@@ -593,6 +642,9 @@ export function register(program: Command): void {
|
|
|
593
642
|
'or both, read from priority.md and backlog.md through the parsers',
|
|
594
643
|
'validate uses. It lists the live root only, never archive/ or declined/.',
|
|
595
644
|
'',
|
|
645
|
+
'Refusals, read off `reason`:',
|
|
646
|
+
' no-board the repository holds no `.canon/tasks/` board',
|
|
647
|
+
'',
|
|
596
648
|
'Exit codes:',
|
|
597
649
|
' 0 the list is derived',
|
|
598
650
|
' 1 refused with no-board',
|
package/src/commands/teach.ts
CHANGED
|
@@ -120,6 +120,19 @@ export function register(program: Command): void {
|
|
|
120
120
|
'rewriting anything, canon teach list <topic> prints the canon serve',
|
|
121
121
|
'line, with the teach folder written from where you stand.',
|
|
122
122
|
'',
|
|
123
|
+
'Reasons, on stderr or in the JSON record:',
|
|
124
|
+
' no-teach the root carries no .canon/teach/ folder',
|
|
125
|
+
' no-workspace no workspace matches the topic',
|
|
126
|
+
' ambiguous two workspaces claim one topic',
|
|
127
|
+
' exists a workspace already covers the topic an open names',
|
|
128
|
+
' no-file the workspace carries no file the verb writes into',
|
|
129
|
+
' no-section RESOURCES.md carries no heading the entries belong under',
|
|
130
|
+
' listed a URL is already read, or a lead is offered as listed',
|
|
131
|
+
' defined a term already carries a glossary entry',
|
|
132
|
+
' bad-input the command line is malformed',
|
|
133
|
+
' no-port up found every port in reach taken',
|
|
134
|
+
' bind-failed up could not bind, such as a privileged port',
|
|
135
|
+
'',
|
|
123
136
|
].join('\n'),
|
|
124
137
|
)
|
|
125
138
|
|