@erclx/canon 5.40.0 → 5.42.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 +34 -17
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/references/adopted.md +34 -14
- package/claude/skills/auto-ship/REQUIREMENT.md +2 -0
- package/claude/skills/auto-ship/SKILL.md +28 -2
- package/claude/skills/auto-ship/references/draft-mark.md +32 -6
- package/claude/skills/auto-ship/references/failure-recovery.md +15 -13
- package/claude/skills/auto-ship/references/fan-in.md +59 -0
- package/claude/skills/auto-ship/references/tier-failures.md +31 -10
- package/claude/skills/auto-ship/references/ui-checklist.md +9 -4
- package/claude/skills/auto-ship/references/worktree-name.md +9 -3
- package/claude/skills/build-in-slices/references/adopted.md +35 -21
- package/claude/skills/canvas-board/references/canvas.md +17 -6
- package/claude/skills/canvas-board/references/design.md +26 -7
- package/claude/skills/code-craft/references/adopted.md +58 -20
- package/claude/skills/codebase-layout/references/adopted.md +49 -16
- package/claude/skills/codebase-layout/references/frontend.md +11 -4
- package/claude/skills/codebase-layout/references/tests.md +4 -2
- package/claude/skills/context-fold/references/classify.md +10 -15
- package/claude/skills/context-fold/references/completion-report.md +11 -9
- package/claude/skills/context-fold/references/context-refresh.md +26 -9
- package/claude/skills/context-fold/references/unusable-baseline.md +5 -3
- package/claude/skills/create-rule/REQUIREMENT.md +5 -0
- package/claude/skills/create-rule/SKILL.md +9 -1
- package/claude/skills/create-rule/references/checklist.md +26 -0
- package/claude/skills/create-rule/references/rule.md +98 -20
- package/claude/skills/create-skill/SKILL.md +1 -0
- package/claude/skills/create-skill/references/skill-disclosure.md +10 -2
- package/claude/skills/create-skill/references/skill-frontmatter.md +43 -0
- package/claude/skills/create-skill/references/skill-paths.md +7 -4
- package/claude/skills/create-skill/references/skill-practice.md +6 -3
- package/claude/skills/create-skill/references/skill-requirement.md +4 -2
- package/claude/skills/create-skill/references/skill.md +67 -44
- package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
- package/claude/skills/decision-escalate/SKILL.md +1 -2
- package/claude/skills/deploy-app/references/vercel.md +2 -1
- package/claude/skills/deprecation-migration/references/adopted.md +44 -25
- package/claude/skills/design-taste/references/adopted.md +9 -1
- package/claude/skills/design-taste/references/craft.md +3 -1
- package/claude/skills/design-taste/references/kinds.md +8 -7
- package/claude/skills/design-taste/references/preflight.md +12 -0
- package/claude/skills/design-taste/references/systems.md +7 -6
- package/claude/skills/design-taste/references/tells.md +24 -25
- package/claude/skills/design-taste/references/vocabulary.md +7 -5
- package/claude/skills/document-health/references/axes.md +54 -30
- package/claude/skills/draft-and-pick/references/live-arms.md +2 -2
- package/claude/skills/draft-doc/references/context.md +4 -3
- package/claude/skills/draft-doc/references/docs.md +6 -3
- package/claude/skills/draft-doc/references/readme/agent-facing.md +6 -0
- package/claude/skills/draft-doc/references/readme/application.md +8 -2
- package/claude/skills/draft-doc/references/readme/cli.md +5 -1
- package/claude/skills/draft-doc/references/readme/library.md +4 -0
- package/claude/skills/draft-doc/references/readme/plugin.md +9 -1
- package/claude/skills/draft-doc/references/readme.md +30 -13
- package/claude/skills/draft-doc/references/requirements.md +17 -4
- package/claude/skills/draft-doc/references/wiki-kind.md +20 -10
- package/claude/skills/draft-figure/references/architecture-set.md +7 -3
- package/claude/skills/draft-figure/references/figures.md +8 -6
- package/claude/skills/draft-figure/references/hand-drawn.md +3 -1
- package/claude/skills/draft-figure/references/mermaid-path.md +19 -9
- package/claude/skills/draft-figure/references/mermaid.md +10 -5
- package/claude/skills/draft-ready/references/ready.md +22 -15
- package/claude/skills/git-branch/REQUIREMENT.md +17 -6
- package/claude/skills/git-branch/SKILL.md +63 -7
- package/claude/skills/git-branch/references/branch.md +11 -1
- package/claude/skills/git-branch/references/slug.md +17 -5
- package/claude/skills/git-commit/references/commit.md +8 -0
- package/claude/skills/git-commit/references/versioning.md +23 -17
- package/claude/skills/git-followup/REQUIREMENT.md +1 -1
- package/claude/skills/git-followup/SKILL.md +5 -6
- package/claude/skills/git-issue/references/issue.md +29 -16
- package/claude/skills/git-pr/REQUIREMENT.md +1 -5
- package/claude/skills/git-pr/SKILL.md +11 -22
- package/claude/skills/git-pr/references/final-command.md +21 -8
- package/claude/skills/git-pr/references/labels.md +45 -28
- package/claude/skills/git-pr/references/pr.md +28 -9
- package/claude/skills/git-pr/references/publish.md +42 -28
- package/claude/skills/git-pr/references/task-number.md +20 -8
- package/claude/skills/git-ship/SKILL.md +1 -1
- package/claude/skills/plan-feature/references/batches.md +48 -1
- package/claude/skills/plan-feature/references/plan-lifecycle.md +10 -3
- package/claude/skills/plan-feature/references/plan.md +59 -20
- package/claude/skills/plan-groundwork/references/groundwork-spikes.md +4 -1
- package/claude/skills/plan-groundwork/references/groundwork.md +14 -16
- package/claude/skills/plan-intake/references/intake.md +29 -24
- package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
- package/claude/skills/plan-intake-answer/SKILL.md +1 -2
- package/claude/skills/pr-evidence/REQUIREMENT.md +40 -0
- package/claude/skills/pr-evidence/SKILL.md +53 -0
- package/claude/skills/pr-evidence/references/evidence.md +150 -0
- package/claude/skills/review-address/references/post-review-findings.md +10 -3
- package/claude/skills/review-address/references/rebase-conflicts.md +22 -7
- package/claude/skills/review-address/references/rest-reads.md +3 -1
- package/claude/skills/review-craft/references/adopted.md +35 -18
- package/claude/skills/review-craft/references/security.md +42 -7
- package/claude/skills/review-craft/references/ui.md +23 -8
- package/claude/skills/review-pr/SKILL.md +6 -6
- package/claude/skills/review-pr/references/body-variants.md +29 -9
- package/claude/skills/review-pr/references/close-out.md +20 -5
- package/claude/skills/review-pr/references/key-changes.md +27 -3
- package/claude/skills/review-pr/references/post.md +83 -31
- package/claude/skills/review-pr/references/scope.md +54 -19
- package/claude/skills/review-ui/SKILL.md +2 -0
- package/claude/skills/review-ui/references/close-out.md +49 -0
- package/claude/skills/review-ui/references/driving.md +24 -4
- package/claude/skills/review-ui/references/post.md +43 -13
- package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +67 -17
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +81 -40
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +52 -15
- package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +48 -26
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +12 -13
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +72 -36
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +12 -4
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +64 -24
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +80 -26
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +41 -18
- package/claude/skills/role-orchestrator/references/orchestrator-resume.md +19 -7
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +22 -12
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +15 -6
- package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +25 -7
- package/claude/skills/role-orchestrator/references/orchestrator-watch.md +34 -8
- package/claude/skills/role-orchestrator/scripts/poll.ts +16 -24
- package/claude/skills/role-worker/REQUIREMENT.md +2 -0
- package/claude/skills/role-worker/SKILL.md +17 -0
- package/claude/skills/search-craft/references/adopted.md +18 -6
- package/claude/skills/session-map/references/session.md +26 -7
- package/claude/skills/session-worktree/references/main-root-writes.md +25 -8
- package/claude/skills/systematic-debugging/references/adopted.md +30 -16
- package/claude/skills/target-check/references/domains.md +39 -11
- package/claude/skills/target-setup/references/detect.md +80 -65
- package/claude/skills/target-setup/references/indexes.md +8 -6
- package/claude/skills/task-board/references/board.md +86 -30
- package/claude/skills/task-board/references/decline.md +29 -13
- package/claude/skills/task-board/references/tasks-decline.md +12 -5
- package/claude/skills/task-board/references/tasks.md +65 -33
- package/claude/skills/task-complete/references/archive.md +41 -17
- package/claude/skills/task-complete/references/receipt-sweep.md +23 -9
- package/claude/skills/task-complete/references/unusable-baseline.md +10 -3
- package/claude/skills/teach-workspace/REQUIREMENT.md +1 -1
- package/claude/skills/teach-workspace/SKILL.md +1 -2
- package/claude/skills/teach-workspace/references/glossary.md +6 -2
- package/claude/skills/teach-workspace/references/lesson-build.md +44 -16
- package/claude/skills/teach-workspace/references/lesson-craft.md +12 -7
- package/claude/skills/teach-workspace/references/pedagogy.md +6 -3
- package/claude/skills/teach-workspace/references/promotion-fold.md +17 -11
- package/claude/skills/teach-workspace/references/promotion.md +31 -8
- package/claude/skills/teach-workspace/references/revisit.md +8 -2
- package/claude/skills/teach-workspace/references/teach.md +17 -9
- package/claude/skills/test-craft/references/adopted.md +48 -14
- package/claude/skills/test-craft/references/component.md +7 -4
- package/claude/skills/test-craft/references/e2e.md +25 -7
- package/claude/skills/test-craft/references/unit.md +3 -1
- package/claude/skills/test-first/references/adopted.md +12 -4
- package/claude/skills/ui-checklist/REQUIREMENT.md +1 -1
- package/claude/skills/ui-checklist/SKILL.md +1 -1
- package/claude/skills/video-craft/references/adopted.md +62 -18
- package/claude/skills/write-human/references/adopted.md +21 -8
- package/docs/workflow/parallel-features.md +2 -1
- package/docs/workflow/skill-map.md +2 -1
- package/package.json +1 -1
- package/src/claude/cases/git.ts +4 -0
- package/src/claude/cases/workflow.ts +5 -0
- package/src/commands/pr.ts +174 -37
- package/src/commands/records.ts +11 -3
- package/src/context/shape-gated.ts +1 -0
- package/src/markdown/scope.ts +150 -2
- package/src/pr/frames.ts +53 -9
- package/src/pr/lift.ts +6 -3
- package/src/pr/review-state.ts +182 -0
- package/src/records/validate.ts +1 -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 +130 -70
- package/claude/skills/git-pr/references/evidence.md +0 -124
|
@@ -11,25 +11,45 @@ Each number in the body traces to a source here, read through the `search-craft`
|
|
|
11
11
|
|
|
12
12
|
Field: subtitling and captioning. Authorities: broadcasters' and streamers' published subtitle guidelines, and the captioning program that sets educational practice.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
| Rule | Source |
|
|
15
|
+
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| On screen for 5/6 of a second to 7 seconds | Netflix's [Timed Text Style Guide: General Requirements](https://partnerhelp.netflixstudios.com/hc/en-us/articles/215758617-Timed-Text-Style-Guide-General-Requirements), a minimum of 20 frames at 24 fps |
|
|
17
|
+
| Two lines, 42 characters a line | Netflix's [English (USA) guide](https://partnerhelp.netflixstudios.com/hc/en-us/articles/217350977-English-USA-Timed-Text-Style-Guide) |
|
|
18
|
+
| 20 characters per second, 17 for children | The same English (USA) guide, adult and children's programs respectively |
|
|
19
|
+
| Never over what the frame is about | The DCMP [Captioning Tip Sheet](https://dcmp.org/learn/225-captioning-tip-sheet): "Placement must not interfere with existing visuals/graphics." |
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
### Start on the first frame of the action
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
Netflix's [Subtitle Timing Guidelines](https://partnerhelp.netflixstudios.com/hc/en-us/articles/360051554394-Timed-Text-Style-Guide-Subtitle-Timing-Guidelines) put a subtitle's in-time on the first frame of its audio. A software video's caption names an action rather than a line of speech, so the body keys the in-time on the action's first frame.
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
### Captions name what the frame shows
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
No source states this for a software video. It is the operator's read of an unguided render whose caption narrated the whole flow, kept because the timing rules above already assume a caption tied to one moment.
|
|
23
28
|
|
|
24
29
|
## Zoom
|
|
25
30
|
|
|
26
31
|
Fields: interface motion design, and screen recording practice. Authorities: design systems that publish motion specs, and the reference documentation of a screen recording tool.
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
### A push onto an action stays under 1 second
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
The floor is HyperFrames'. Its `viewport-change` rule states a zoom of 1.0 to 2.0 seconds with the reason beside it, "under 0.8s teleports, over 2.5s drags", and the body points at the rule rather than restating it.
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
- Measured: an unguided render pushed in over 2.2 seconds and a second over 1.4 seconds, and the operator judged both slow, so the range's lower half already reads slow on a push onto a click
|
|
38
|
+
- Published: Material Design 3's [easing and duration tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs) end at 1000 ms, the top of a band reserved for ambient transitions, so no input-driven transition there runs longer
|
|
39
|
+
|
|
40
|
+
### Zoom on a click
|
|
41
|
+
|
|
42
|
+
Screen Studio's [auto zoom guide](https://screen.studio/guide/auto-zoom) keys its zoom on click positions and zooms nowhere a click did not occur.
|
|
43
|
+
|
|
44
|
+
### Conflict, and the pick
|
|
45
|
+
|
|
46
|
+
HyperFrames' range and the measured renders disagree on the push onto an action. The body takes the overlap, HyperFrames' teleport floor below and 1 second above, which sits inside HyperFrames' range at its fast end.
|
|
47
|
+
|
|
48
|
+
- HyperFrames' full range stays with an ambient reveal, matching Material's own split between input-driven and ambient motion
|
|
49
|
+
- A tighter band was declined. Material puts large expressive transitions at 450 to 600 ms, and Nielsen Norman Group's [animation duration article](https://www.nngroup.com/articles/animation-duration/) calls 400 ms "very slow".
|
|
50
|
+
- Both write for an interface a user waits on, which a video viewer is not, and that band sits below HyperFrames' teleport floor with nothing measured to show it reads right
|
|
51
|
+
|
|
52
|
+
Narrow the band once a composed demo is compared against the unguided renders.
|
|
33
53
|
|
|
34
54
|
## Pointer
|
|
35
55
|
|
|
@@ -39,28 +59,52 @@ The two pointer rules in the body carry no number. One pointer on a frame and a
|
|
|
39
59
|
|
|
40
60
|
Fields: web accessibility, and audio post-production. Authorities: the W3C's accessibility guidelines, and the audio engineering society's streaming recommendation.
|
|
41
61
|
|
|
42
|
-
|
|
62
|
+
### Music at least 20 dB under the voice
|
|
63
|
+
|
|
64
|
+
WCAG 2.1 [Success Criterion 1.4.7](https://www.w3.org/WAI/WCAG21/Understanding/low-or-no-background-audio.html) is the source, reading "The background sounds are at least 20 decibels lower than the foreground speech content, with the exception of occasional sounds that last for only one or two seconds."
|
|
65
|
+
|
|
66
|
+
HyperFrames' `media-use` sets its narrated bed at a volume of 0.12, about 18 dB under a voice at full scale. The body takes WCAG's 20 dB, since it is the published floor and the bed default sits 2 dB short of it.
|
|
67
|
+
|
|
68
|
+
### Overall loudness is delegated
|
|
43
69
|
|
|
44
|
-
|
|
70
|
+
The AES [TD1008](https://aes.org/wp-content/uploads/2024/01/20210924_TD1008_v3.13.pdf) streaming recommendation normalizes speech to -18 LUFS with a true peak no higher than -1 dBTP. `media-use` ships -14 and -16 LUFS targets for social and podcast delivery.
|
|
45
71
|
|
|
46
|
-
|
|
72
|
+
The body states no overall target, since the right one depends on where the video is published and `media-use` owns that operation.
|
|
47
73
|
|
|
48
74
|
## Voiceover
|
|
49
75
|
|
|
50
76
|
Field: captioning, for the reading ceiling a voiceover's captions inherit. Authority: the captioning program's presentation rate guidance.
|
|
51
77
|
|
|
52
|
-
|
|
78
|
+
### At or under 160 words a minute when captioned
|
|
53
79
|
|
|
54
|
-
|
|
80
|
+
The DCMP [Captioning Key: Presentation Rate](https://dcmp.org/learn/601-captioning-key---presentation-rate) caps upper-level captions at 160 words a minute.
|
|
81
|
+
|
|
82
|
+
- `hyperframes-creative`'s narration reference states 2.5 words a second, 150 a minute, under the ceiling, so the two do not conflict
|
|
83
|
+
- An open search of voiceover practitioners put explainers at 140 to 160 words a minute, which agrees
|
|
84
|
+
- Narration has no standards body for pace, and the scoped search found none, so the body sets only the ceiling and points at HyperFrames for pace
|
|
55
85
|
|
|
56
86
|
## Declined
|
|
57
87
|
|
|
58
|
-
|
|
88
|
+
Each item below stays out of the body for the reason given.
|
|
89
|
+
|
|
90
|
+
### Restating HyperFrames' motion rules
|
|
91
|
+
|
|
92
|
+
Ease variety, anchoring, click timing, the scale and dwell of a zoom, and title-safe margins are stated in HyperFrames' own skills, which update on their own cadence. A copy here goes stale on theirs.
|
|
93
|
+
|
|
94
|
+
### A loudness target in the body
|
|
95
|
+
|
|
96
|
+
See Audio above.
|
|
97
|
+
|
|
98
|
+
### Pointer smoothing and hiding at rest
|
|
99
|
+
|
|
100
|
+
Screen Studio's [cursor guide](https://screen.studio/guide/cursor) smooths the pointer and hides it when idle, and its [guide to disabling smooth movement](https://screen.studio/guide/disable-smooth-mouse-movement) turns smoothing off for a dropdown menu.
|
|
101
|
+
|
|
102
|
+
Both are recorder settings. A take is composed with its pointer baked into the frames, so a session composing from it cannot change either, and they belong to whatever records the take.
|
|
59
103
|
|
|
60
|
-
|
|
104
|
+
### Zoom out before a cut, and a sound effect only on a meaningful click
|
|
61
105
|
|
|
62
|
-
|
|
106
|
+
Neither has a source, and no operator read stands behind either, so both stay out until one does.
|
|
63
107
|
|
|
64
|
-
|
|
108
|
+
### Vertical 9:16 framing
|
|
65
109
|
|
|
66
|
-
|
|
110
|
+
Every video measured when this skill was written was landscape. Add it when a project asks for vertical video, with its safe areas sourced from the platform's own creator documentation.
|
|
@@ -11,23 +11,34 @@ Neither was imported wholesale, and one measurement settles why. Read this befor
|
|
|
11
11
|
|
|
12
12
|
## What was adopted
|
|
13
13
|
|
|
14
|
-
Roughly half the pattern catalog had no counterpart in this corpus
|
|
14
|
+
Roughly half the pattern catalog had no counterpart in this corpus. Six of those patterns are now in `machine-tells.md`, each a shape rather than a token, which kept it out of a ban list that matches words.
|
|
15
|
+
|
|
16
|
+
- Rule-of-three, synonym cycling, and false ranges
|
|
17
|
+
- Inline-header lists, adverb propping, and passive voice with an unnamed actor
|
|
18
|
+
|
|
19
|
+
### The density layer
|
|
15
20
|
|
|
16
21
|
The density layer had no counterpart at all and is now `density.md`. Its useful half is the split between what a compression pass may cut and what it may not, since a terse register already instructs a session to cut and names nothing that has to survive.
|
|
17
22
|
|
|
18
|
-
The ambiguity rules travel with that layer
|
|
23
|
+
The ambiguity rules travel with that layer. Compression creates the ambiguity, so a rule about pronouns and modifiers belongs beside the rule that shortened the sentence.
|
|
19
24
|
|
|
20
25
|
## What was declined
|
|
21
26
|
|
|
22
|
-
- **The ban on abstract metaphor nouns.** The nouns it names are load-bearing terms in this corpus rather than decoration, and the count below
|
|
23
|
-
- **Document mode as a selector.**
|
|
24
|
-
- **A voice register per surface.** One baseline covers every surface. A surface needing a warmer register states its own voice and claims the yield, which lifts voice alone
|
|
27
|
+
- **The ban on abstract metaphor nouns.** The nouns it names are load-bearing terms in this corpus rather than decoration, and the count below decides it.
|
|
28
|
+
- **Document mode as a selector.** In this corpus the standard governing a document's type already fixes its shape, so a mode picked before drafting adds a second authority over one question.
|
|
29
|
+
- **A voice register per surface.** One baseline covers every surface. A surface needing a warmer register states its own voice and claims the yield, which lifts voice alone.
|
|
30
|
+
|
|
31
|
+
### The count behind the first
|
|
25
32
|
|
|
26
|
-
The
|
|
33
|
+
The source names `surface` among the nouns it bans. Tracked markdown in the authoring repository used `surface` 593 times, `harness` 95, `scaffolding` 17, and `primitive` 4.
|
|
34
|
+
|
|
35
|
+
Adopting the item either rewrites established vocabulary across the corpus or is ignored in silence, and a reader arriving later cannot tell which happened.
|
|
27
36
|
|
|
28
37
|
## What moved in from the markdown standard
|
|
29
38
|
|
|
30
|
-
The marketing words and vague qualifiers under `## Voice` were a gated word set in the markdown standard until the audit narrowed to characters. A word ban catches the token and misses the habit
|
|
39
|
+
The marketing words and vague qualifiers under `## Voice` were a gated word set in the markdown standard until the audit narrowed to characters. A word ban catches the token and misses the habit, so a writer swaps `robust` for a synonym and the claim stays unproven.
|
|
40
|
+
|
|
41
|
+
The list now lives here as guidance a reader applies. This body is its one copy, since no command reads it and a second copy would drift with nothing comparing the two.
|
|
31
42
|
|
|
32
43
|
## Where this diverges
|
|
33
44
|
|
|
@@ -37,4 +48,6 @@ The source material also prefers shorter sentences without bound. This skill mea
|
|
|
37
48
|
|
|
38
49
|
## The industry-guide record
|
|
39
50
|
|
|
40
|
-
The cadence rules and the bans they pair with trace to three industry style guides, being Google's developer documentation style guide, Microsoft's writing style guide, and Apple's style guide.
|
|
51
|
+
The cadence rules and the bans they pair with trace to three industry style guides, being Google's developer documentation style guide, Microsoft's writing style guide, and Apple's style guide.
|
|
52
|
+
|
|
53
|
+
The authoring toolkit keeps the per-rule attributions and the dates each guide was read. No install carries that record, so this file is the position a target reads.
|
|
@@ -11,7 +11,7 @@ Run independent features in parallel, one git worktree and one Claude Code sessi
|
|
|
11
11
|
## One worktree per feature
|
|
12
12
|
|
|
13
13
|
- Invoke `canon:plan-feature` in each session. Plans land at `.canon/plans/feature-<slug>.md` at the main root.
|
|
14
|
-
- `ui-checklist` writes a per-branch handoff at `tmp/handoff/ui-checklist/<slug>.md`, which `
|
|
14
|
+
- `ui-checklist` writes a per-branch handoff at `tmp/handoff/ui-checklist/<slug>.md`, which `pr-evidence` posts and removes. The slug is the branch name minus its leading type segment.
|
|
15
15
|
- Ship each worktree with `canon:git-ship`, or `canon:auto-ship` for a draft pull request
|
|
16
16
|
- Only the operator or the controlling session readies a draft
|
|
17
17
|
|
|
@@ -41,6 +41,7 @@ Assert the orchestrator role in one warm session with `canon:role-orchestrator`.
|
|
|
41
41
|
|
|
42
42
|
Merge order matters only when two branches touch the same file. Expect to rebase over `CLAUDE.md`, `tooling/**`, `canon/context/context-model.md`, `docs/agents/`, and any folder's `index.md`. Resolve an `index.md` conflict with `canon indexes regen` over the rebased tree.
|
|
43
43
|
|
|
44
|
+
- **Fan-in.** Batches of one change sharing edge files ship as one pull request, opened by an integrator that merges each member branch. See `claude/skills/plan-feature/references/batches.md`.
|
|
44
45
|
- **Merge order.** Merge the smallest hotspot footprint first, and a branch touching `CLAUDE.md` or a context entry last.
|
|
45
46
|
- **Rebase.** The branch merging second runs `/review-address`, which trial-merges against `origin/main` and rebases. Push, then merge.
|
|
46
47
|
- **Clean up.** Run `/git-worktree cleanup` to remove merged worktrees. To start fresh, `ExitWorktree(action: "keep")`, then `/session-worktree <new-name>`.
|
|
@@ -67,7 +67,7 @@ Groups run in the order a project meets them. Every name `canon claude skills li
|
|
|
67
67
|
| `canon:docs-sync` | A change left `README.md` or `docs/` stale |
|
|
68
68
|
| `canon:git-stage` | Staged changes span several concerns |
|
|
69
69
|
| `canon:git-commit` | Staged changes are one concern |
|
|
70
|
-
| `canon:git-branch` | A branch
|
|
70
|
+
| `canon:git-branch` | A branch needs creating, naming, or renaming |
|
|
71
71
|
| `canon:git-pr` | A pull request needs a title and body |
|
|
72
72
|
|
|
73
73
|
## After the pull request opens
|
|
@@ -78,6 +78,7 @@ Groups run in the order a project meets them. Every name `canon claude skills li
|
|
|
78
78
|
| `canon:review-ui` | Beside code review, to drive the checklist |
|
|
79
79
|
| `canon:review-address` | As the worker, to fix posted findings |
|
|
80
80
|
| `canon:git-followup` | A small edit on a branch with an open PR |
|
|
81
|
+
| `canon:pr-evidence` | A new head needs its screenshots re-posted |
|
|
81
82
|
| `canon:git-split` | A branch carries unrelated commits |
|
|
82
83
|
| `canon:git-issue` | Something belongs on the tracker |
|
|
83
84
|
| `canon:git-worktree` | After a merge, to reclaim worktrees |
|
package/package.json
CHANGED
package/src/claude/cases/git.ts
CHANGED
|
@@ -6,6 +6,10 @@ export const GIT_CASES: readonly SkillCase[] = [
|
|
|
6
6
|
prompt: 'Come up with a conventional name for this branch.',
|
|
7
7
|
expect: 'git-branch',
|
|
8
8
|
},
|
|
9
|
+
{
|
|
10
|
+
prompt: 'I am on main and about to start a feature, create its branch.',
|
|
11
|
+
expect: 'git-branch',
|
|
12
|
+
},
|
|
9
13
|
{
|
|
10
14
|
prompt: "Turn what's staged into one commit message.",
|
|
11
15
|
expect: 'git-commit',
|
|
@@ -81,6 +81,11 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
|
|
|
81
81
|
'Open the preview for that pull request and click through each box on its visual checklist, then post what happened.',
|
|
82
82
|
expect: 'review-ui',
|
|
83
83
|
},
|
|
84
|
+
{
|
|
85
|
+
prompt:
|
|
86
|
+
'I pushed another commit to the open PR. Update the screenshot comparison on it so the reviewer sees the current head.',
|
|
87
|
+
expect: 'pr-evidence',
|
|
88
|
+
},
|
|
84
89
|
{
|
|
85
90
|
prompt:
|
|
86
91
|
'The screencast draft is finished. Turn it into an actual video now.',
|
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'
|
|
@@ -106,7 +107,12 @@ import {
|
|
|
106
107
|
reviewRowOf,
|
|
107
108
|
selectBranchPull,
|
|
108
109
|
} from '@/pr/rest'
|
|
109
|
-
import {
|
|
110
|
+
import {
|
|
111
|
+
readReviewState,
|
|
112
|
+
type ReviewStateApi,
|
|
113
|
+
type ReviewStateRecord,
|
|
114
|
+
waitForReviews,
|
|
115
|
+
} from '@/pr/review-state'
|
|
110
116
|
import { PROJECT_ROOT } from '@/roots/project'
|
|
111
117
|
import { intro, logInfo, logStep, logWarn, outro, plural } from '@/cli/ui'
|
|
112
118
|
|
|
@@ -139,6 +145,10 @@ interface LiftCommandOptions extends ReadOptions {
|
|
|
139
145
|
readonly wait?: string
|
|
140
146
|
}
|
|
141
147
|
|
|
148
|
+
interface ReviewStateOptions extends ReadOptions {
|
|
149
|
+
readonly wait?: string
|
|
150
|
+
}
|
|
151
|
+
|
|
142
152
|
interface LocalOptions extends ReadOptions {
|
|
143
153
|
readonly remove?: boolean
|
|
144
154
|
}
|
|
@@ -184,6 +194,7 @@ const LOCAL_REFUSALS: Record<LocalRefusal | 'gh-failed', string> = {
|
|
|
184
194
|
interface FramesOptions extends ReadOptions {
|
|
185
195
|
readonly add?: string
|
|
186
196
|
readonly box?: string
|
|
197
|
+
readonly reference?: string
|
|
187
198
|
readonly head?: string
|
|
188
199
|
readonly pass?: string
|
|
189
200
|
readonly theme?: string
|
|
@@ -196,6 +207,7 @@ type FramesCommandRefusal =
|
|
|
196
207
|
| 'bad-mode'
|
|
197
208
|
| 'no-number'
|
|
198
209
|
| 'bad-box'
|
|
210
|
+
| 'bad-reference'
|
|
199
211
|
| 'bad-head'
|
|
200
212
|
| 'bad-pass'
|
|
201
213
|
| 'bad-theme'
|
|
@@ -212,13 +224,15 @@ const FRAMES_REFUSALS: Record<FramesCommandRefusal, string> = {
|
|
|
212
224
|
'no-number':
|
|
213
225
|
'--add and --drop write for one pull request, so name its number.',
|
|
214
226
|
'bad-box':
|
|
215
|
-
'--add takes --box <n>, a positive whole number naming the checklist box.',
|
|
227
|
+
'--add takes --box <n>, a positive whole number naming the checklist box, or --reference <page>/<frame>, never both.',
|
|
228
|
+
'bad-reference':
|
|
229
|
+
'--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.',
|
|
216
230
|
'bad-head':
|
|
217
231
|
'--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.',
|
|
218
232
|
'bad-pass':
|
|
219
233
|
'--add takes --pass <stamp>, the instant the pass started in compact UTC such as 20261002T154450Z, so a second pass never replaces an earlier one.',
|
|
220
234
|
'bad-theme':
|
|
221
|
-
'--theme takes dark or light and goes with --add, so a second shot of one box never replaces the first.',
|
|
235
|
+
'--theme takes dark or light and goes with --add --box, so a second shot of one box never replaces the first.',
|
|
222
236
|
'bad-days': '--prune takes a positive whole number of days.',
|
|
223
237
|
'unreadable-frame':
|
|
224
238
|
'The file named by --add is missing, empty, or not a PNG, so it would embed as a broken image. Nothing is pushed.',
|
|
@@ -556,11 +570,17 @@ export function register(program: Command): void {
|
|
|
556
570
|
})
|
|
557
571
|
|
|
558
572
|
pr.command('review-state')
|
|
559
|
-
.description(
|
|
573
|
+
.description(
|
|
574
|
+
'Report what the last code review and UI review passes covered at the tip',
|
|
575
|
+
)
|
|
560
576
|
.argument('[number]', 'Pull request to read, defaulting to this branch')
|
|
561
577
|
.helpOption('-h, --help', 'Show this help message')
|
|
562
578
|
.option('--root <path>', 'Repository to read, defaulting to the cwd')
|
|
563
579
|
.option('--json', 'Add a machine-readable record on stdout')
|
|
580
|
+
.option(
|
|
581
|
+
'--wait <seconds>',
|
|
582
|
+
`Hold until a pass covers the tip in every bound family, default ${DEFAULT_WAIT_SECONDS}`,
|
|
583
|
+
)
|
|
564
584
|
.addHelpText(
|
|
565
585
|
'after',
|
|
566
586
|
[
|
|
@@ -582,6 +602,31 @@ export function register(program: Command): void {
|
|
|
582
602
|
' fallback a pass posted before the marker shipped, off GitHub stamps',
|
|
583
603
|
' none the thread carries no pass, so the next one is a first pass',
|
|
584
604
|
'',
|
|
605
|
+
'Those code review fields stay at the top level of the record. Beside',
|
|
606
|
+
'them, `head` is the branch tip read from the remote, and `review` and',
|
|
607
|
+
'`ui` carry each family at that tip:',
|
|
608
|
+
' reason the refusal `canon pr lift` gives the family, absent when',
|
|
609
|
+
' its gate passes, read through the same gate function',
|
|
610
|
+
' isCovered a pass covers the tip: the gate passes, or the newest pass,',
|
|
611
|
+
' open or unmarked, names the tip',
|
|
612
|
+
'',
|
|
613
|
+
'`ui` also carries the newest `## UI review` pass (`heading`, `state`,',
|
|
614
|
+
'`commit`, `source`), `isBound`, which is whether the evidence comment',
|
|
615
|
+
'carries a checklist, and `carriedFrom` or `paintedPaths` when the lift',
|
|
616
|
+
'carried a close forward or refused to. An unbound family reads covered.',
|
|
617
|
+
'',
|
|
618
|
+
'Both families post as pull request reviews, so this reads',
|
|
619
|
+
'`pulls/<n>/reviews` alone, as the lift does. Issue comments are not',
|
|
620
|
+
'read: neither review skill posts there, and a heading found only in a',
|
|
621
|
+
'comment would let a worker move on a pass the lift then refuses.',
|
|
622
|
+
'',
|
|
623
|
+
'`--wait <seconds>` holds until `isCovered` reads true in both families',
|
|
624
|
+
'or the deadline passes, re-reading the tip every 30 seconds so a push',
|
|
625
|
+
'moves what a pass must cover. The evidence is read once at the start.',
|
|
626
|
+
'The record then adds `reason`, `covered` or `timeout`, and',
|
|
627
|
+
'`waitedSeconds`. A wait past 600 seconds outlasts a foreground shell',
|
|
628
|
+
'call, so run it in the background.',
|
|
629
|
+
'',
|
|
585
630
|
'Refusals, read off `reason`:',
|
|
586
631
|
' ambiguous-pull two open pull requests share the branch against',
|
|
587
632
|
' different bases, so name a number',
|
|
@@ -594,18 +639,22 @@ export function register(program: Command): void {
|
|
|
594
639
|
' gh-missing `gh` is not on the path, so no pull request resolved',
|
|
595
640
|
' gh-failed `gh` could not answer for this branch, so name a number',
|
|
596
641
|
' no-object-head the pull request object reported no head commit',
|
|
642
|
+
' bad-wait --wait takes a whole number of seconds',
|
|
597
643
|
'',
|
|
598
644
|
'Exit codes:',
|
|
599
|
-
' 0 the thread was read, whether it carries a pass or not',
|
|
600
|
-
'
|
|
645
|
+
' 0 the thread was read, whether it carries a pass or not, or the',
|
|
646
|
+
' wait ended covered',
|
|
647
|
+
' 1 refused, with the reason on stderr or in the JSON record, or the',
|
|
648
|
+
' wait reached its deadline as `timeout`',
|
|
601
649
|
'',
|
|
602
650
|
'Examples:',
|
|
603
651
|
' canon pr review-state',
|
|
604
652
|
' canon pr review-state 1341 --json',
|
|
653
|
+
' canon pr review-state 1341 --wait 1200 --json',
|
|
605
654
|
'',
|
|
606
655
|
].join('\n'),
|
|
607
656
|
)
|
|
608
|
-
.action(async (number: string | undefined, opts:
|
|
657
|
+
.action(async (number: string | undefined, opts: ReviewStateOptions) => {
|
|
609
658
|
process.exitCode = await runReviewState(number, opts)
|
|
610
659
|
})
|
|
611
660
|
|
|
@@ -1011,6 +1060,10 @@ export function register(program: Command): void {
|
|
|
1011
1060
|
.option('--json', 'Add a machine-readable record on stdout')
|
|
1012
1061
|
.option('--add <file>', 'Push this PNG as a frame')
|
|
1013
1062
|
.option('--box <n>', 'The checklist box the frame shows, with --add')
|
|
1063
|
+
.option(
|
|
1064
|
+
'--reference <address>',
|
|
1065
|
+
'The design reference the capture shows, as <page>/<frame>, with --add in place of --box',
|
|
1066
|
+
)
|
|
1014
1067
|
.option(
|
|
1015
1068
|
'--head <sha>',
|
|
1016
1069
|
"The head the frame was driven at, with --add, defaulting to the pull request's",
|
|
@@ -1035,7 +1088,8 @@ export function register(program: Command): void {
|
|
|
1035
1088
|
`An add writes only refs/heads/${FRAMES_BRANCH}, through the GitHub git data API,`,
|
|
1036
1089
|
'so nothing is checked out and no other branch is touched. A frame lands',
|
|
1037
1090
|
'at pr-<number>/<short-head>/<pass>/box-<n>.png, or box-<n>-<theme>.png',
|
|
1038
|
-
'with --theme,
|
|
1091
|
+
'with --theme, or ref-<page>--<frame>.png for a design reference capture',
|
|
1092
|
+
'passed as --reference <page>/<frame> in place of --box, and `link` names that',
|
|
1039
1093
|
`path on ${FRAMES_BRANCH}, so it survives a rewrite that drops other pull`,
|
|
1040
1094
|
'requests and resolves until its own pull request is dropped. The pass',
|
|
1041
1095
|
'segment keeps a second pass at one head and box from replacing an image',
|
|
@@ -1056,10 +1110,11 @@ export function register(program: Command): void {
|
|
|
1056
1110
|
' ok an add pushed, or a drop or prune finished',
|
|
1057
1111
|
' bad-mode not exactly one of --add, --drop, --prune',
|
|
1058
1112
|
' no-number --add or --drop named no pull request',
|
|
1059
|
-
' bad-box --add
|
|
1113
|
+
' bad-box --add with both --box and --reference, or neither',
|
|
1114
|
+
' bad-reference --reference is not <page>/<frame> in the safe name form',
|
|
1060
1115
|
' bad-head --head is not 7 to 40 lowercase hex characters',
|
|
1061
1116
|
' bad-pass --add without a compact UTC --pass stamp',
|
|
1062
|
-
' bad-theme --theme is not dark or light, or no --add',
|
|
1117
|
+
' bad-theme --theme is not dark or light, or no --add, or rides a --reference',
|
|
1063
1118
|
' bad-days --prune without a positive number of days',
|
|
1064
1119
|
' unreadable-frame the --add file is missing, empty, or not a PNG',
|
|
1065
1120
|
' read-only GitHub refused the write, as on a fork token',
|
|
@@ -1807,13 +1862,21 @@ async function runChecks(
|
|
|
1807
1862
|
|
|
1808
1863
|
async function runReviewState(
|
|
1809
1864
|
number: string | undefined,
|
|
1810
|
-
opts:
|
|
1865
|
+
opts: ReviewStateOptions,
|
|
1811
1866
|
): Promise<number> {
|
|
1812
1867
|
const root = resolve(opts.root ?? process.cwd())
|
|
1813
1868
|
const emitJson = opts.json ?? false
|
|
1814
1869
|
|
|
1815
1870
|
intro('canon pr review-state')
|
|
1816
1871
|
|
|
1872
|
+
let waitSeconds: number | undefined
|
|
1873
|
+
if (opts.wait !== undefined) {
|
|
1874
|
+
if (!/^\d+$/.test(opts.wait)) {
|
|
1875
|
+
return refuseWith('bad-wait', LIFT_REFUSALS['bad-wait'], emitJson, root)
|
|
1876
|
+
}
|
|
1877
|
+
waitSeconds = Number(opts.wait)
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1817
1880
|
if (Bun.which('gh') === null) {
|
|
1818
1881
|
return refuseWith('gh-missing', PULL_REFUSALS['gh-missing'], emitJson, root)
|
|
1819
1882
|
}
|
|
@@ -1830,20 +1893,84 @@ async function runReviewState(
|
|
|
1830
1893
|
)
|
|
1831
1894
|
}
|
|
1832
1895
|
|
|
1833
|
-
const
|
|
1834
|
-
if (
|
|
1835
|
-
|
|
1836
|
-
reviews.reason === 'gh-failed' ? 'gh-failed' : 'reviews-unreadable'
|
|
1837
|
-
return refuseWith(reason, PULL_REFUSALS[reason], emitJson, root)
|
|
1896
|
+
const read = await readIdentity(root, resolved.number)
|
|
1897
|
+
if (read.kind === 'refused') {
|
|
1898
|
+
return refuseWith(read.reason, PULL_REFUSALS[read.reason], emitJson, root)
|
|
1838
1899
|
}
|
|
1839
1900
|
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1901
|
+
// The module refuses every failed read as `gh-failed`. The api keeps each
|
|
1902
|
+
// read's own reason, so a caller still tells an unreadable thread from an
|
|
1903
|
+
// unreadable remote.
|
|
1904
|
+
let failure: PullRefusal | HeadRefusal = 'gh-failed'
|
|
1905
|
+
const git = gitRunner(root)
|
|
1906
|
+
const api: ReviewStateApi = {
|
|
1907
|
+
readTip: async () => {
|
|
1908
|
+
const tip = await resolveTip(read.identity.branch, refReader(root))
|
|
1909
|
+
if (tip.kind === 'read') return tip.tip
|
|
1910
|
+
failure = tip.reason
|
|
1911
|
+
return undefined
|
|
1912
|
+
},
|
|
1913
|
+
readReviews: async () => {
|
|
1914
|
+
const reviews = await listReviews(root, resolved.number)
|
|
1915
|
+
if (reviews.kind === 'read') return reviews.rows.map(reviewRowOf)
|
|
1916
|
+
failure =
|
|
1917
|
+
reviews.reason === 'gh-failed' ? 'gh-failed' : 'reviews-unreadable'
|
|
1918
|
+
return undefined
|
|
1919
|
+
},
|
|
1920
|
+
readEvidence: () => readEvidenceGate(root, resolved.number),
|
|
1921
|
+
readChanged: (from, to) => readChangedPaths(git, from, to),
|
|
1922
|
+
sleep: (ms) => new Promise((done) => setTimeout(done, ms)),
|
|
1923
|
+
now: () => Date.now(),
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
const painted = loadPaintedPatterns(root)
|
|
1927
|
+
const wait =
|
|
1928
|
+
waitSeconds === undefined
|
|
1929
|
+
? undefined
|
|
1930
|
+
: await waitForReviews(api, { waitSeconds, painted })
|
|
1931
|
+
const state = wait ?? (await readReviewState(api, painted))
|
|
1932
|
+
if (state.kind === 'refused') {
|
|
1933
|
+
return refuseWith(failure, PULL_REFUSALS[failure], emitJson, root)
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
const { record } = state
|
|
1937
|
+
const pull = Number(resolved.number)
|
|
1938
|
+
logReviewState(pull, record)
|
|
1939
|
+
const waited =
|
|
1940
|
+
wait?.kind === 'read'
|
|
1941
|
+
? { reason: wait.reason, waitedSeconds: wait.waitedSeconds }
|
|
1942
|
+
: undefined
|
|
1943
|
+
if (waited !== undefined) {
|
|
1944
|
+
logStep(waited.reason === 'covered' ? 'Covered' : 'Timeout')
|
|
1945
|
+
logInfo(
|
|
1946
|
+
waited.reason === 'covered'
|
|
1947
|
+
? `a pass covers ${record.head.slice(0, 8)} in every bound family after ${waited.waitedSeconds}s`
|
|
1948
|
+
: `no pass covered ${record.head.slice(0, 8)} in every bound family within ${waited.waitedSeconds}s`,
|
|
1949
|
+
)
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
outro()
|
|
1953
|
+
|
|
1954
|
+
if (emitJson) {
|
|
1955
|
+
const { scope, ...families } = record
|
|
1956
|
+
process.stdout.write(
|
|
1957
|
+
`${JSON.stringify({
|
|
1958
|
+
root,
|
|
1959
|
+
number: pull,
|
|
1960
|
+
...scope,
|
|
1961
|
+
...families,
|
|
1962
|
+
...waited,
|
|
1963
|
+
})}\n`,
|
|
1964
|
+
)
|
|
1965
|
+
}
|
|
1844
1966
|
|
|
1967
|
+
return waited?.reason === 'timeout' ? 1 : 0
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
function logReviewState(number: number, record: ReviewStateRecord): void {
|
|
1971
|
+
const { scope } = record
|
|
1845
1972
|
logStep('Scope')
|
|
1846
|
-
logInfo(`#${
|
|
1973
|
+
logInfo(`#${number} at ${record.head.slice(0, 8)}`)
|
|
1847
1974
|
|
|
1848
1975
|
if (scope.source === 'none') {
|
|
1849
1976
|
logStep('First pass')
|
|
@@ -1863,19 +1990,17 @@ async function runReviewState(
|
|
|
1863
1990
|
)
|
|
1864
1991
|
}
|
|
1865
1992
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
)
|
|
1993
|
+
for (const [name, family] of [
|
|
1994
|
+
['review', record.review],
|
|
1995
|
+
['ui', record.ui],
|
|
1996
|
+
] as const) {
|
|
1997
|
+
const line = `${name}: ${family.reason ?? 'clear'}, ${family.isCovered ? 'a pass covers the tip' : 'no pass covers the tip'}`
|
|
1998
|
+
if (family.isCovered) logInfo(line)
|
|
1999
|
+
else logWarn(line)
|
|
2000
|
+
}
|
|
2001
|
+
if (!record.ui.isBound) {
|
|
2002
|
+
logInfo('ui: the evidence carries no checklist, so the family is unbound')
|
|
1876
2003
|
}
|
|
1877
|
-
|
|
1878
|
-
return 0
|
|
1879
2004
|
}
|
|
1880
2005
|
|
|
1881
2006
|
interface PullFiles {
|
|
@@ -3084,10 +3209,18 @@ function framesArgumentRefusal(
|
|
|
3084
3209
|
if (number === undefined || !POSITIVE_WHOLE.test(number)) return 'no-number'
|
|
3085
3210
|
if (
|
|
3086
3211
|
opts.add !== undefined &&
|
|
3087
|
-
(opts.box === undefined
|
|
3212
|
+
(opts.box === undefined) === (opts.reference === undefined)
|
|
3088
3213
|
) {
|
|
3089
3214
|
return 'bad-box'
|
|
3090
3215
|
}
|
|
3216
|
+
if (opts.add !== undefined && opts.box !== undefined) {
|
|
3217
|
+
if (!POSITIVE_WHOLE.test(opts.box)) return 'bad-box'
|
|
3218
|
+
}
|
|
3219
|
+
if (opts.reference !== undefined) {
|
|
3220
|
+
if (opts.add === undefined || referenceSlug(opts.reference) === undefined) {
|
|
3221
|
+
return 'bad-reference'
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3091
3224
|
if (opts.head !== undefined && !isFrameHead(opts.head)) return 'bad-head'
|
|
3092
3225
|
if (
|
|
3093
3226
|
opts.add !== undefined &&
|
|
@@ -3097,7 +3230,9 @@ function framesArgumentRefusal(
|
|
|
3097
3230
|
}
|
|
3098
3231
|
if (
|
|
3099
3232
|
opts.theme !== undefined &&
|
|
3100
|
-
(opts.add === undefined ||
|
|
3233
|
+
(opts.add === undefined ||
|
|
3234
|
+
opts.reference !== undefined ||
|
|
3235
|
+
!isFrameTheme(opts.theme))
|
|
3101
3236
|
) {
|
|
3102
3237
|
return 'bad-theme'
|
|
3103
3238
|
}
|
|
@@ -3199,7 +3334,8 @@ async function runFrames(
|
|
|
3199
3334
|
if (head === undefined || head === '') return refuseFrames('no-object-head')
|
|
3200
3335
|
if (!isFrameHead(head)) return refuseFrames('bad-head')
|
|
3201
3336
|
|
|
3202
|
-
const box = Number(opts.box)
|
|
3337
|
+
const box = opts.box === undefined ? undefined : Number(opts.box)
|
|
3338
|
+
const reference = opts.reference
|
|
3203
3339
|
const pass = opts.pass ?? ''
|
|
3204
3340
|
const theme = opts.theme as FrameTheme | undefined
|
|
3205
3341
|
const result = await pushFrame(api, {
|
|
@@ -3207,6 +3343,7 @@ async function runFrames(
|
|
|
3207
3343
|
head,
|
|
3208
3344
|
pass,
|
|
3209
3345
|
box,
|
|
3346
|
+
reference,
|
|
3210
3347
|
theme,
|
|
3211
3348
|
bytes: bytes ?? new Uint8Array(),
|
|
3212
3349
|
})
|
|
@@ -3215,7 +3352,7 @@ async function runFrames(
|
|
|
3215
3352
|
return finish(
|
|
3216
3353
|
{
|
|
3217
3354
|
number: pull,
|
|
3218
|
-
box,
|
|
3355
|
+
...(box === undefined ? { reference } : { box }),
|
|
3219
3356
|
...(theme === undefined ? {} : { theme }),
|
|
3220
3357
|
head,
|
|
3221
3358
|
pass,
|