@erclx/canon 5.41.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.
Files changed (154) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/api-design/references/adopted.md +34 -14
  3. package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
  4. package/claude/skills/auto-ship/SKILL.md +22 -2
  5. package/claude/skills/auto-ship/references/draft-mark.md +32 -6
  6. package/claude/skills/auto-ship/references/failure-recovery.md +15 -13
  7. package/claude/skills/auto-ship/references/tier-failures.md +31 -10
  8. package/claude/skills/auto-ship/references/ui-checklist.md +9 -4
  9. package/claude/skills/auto-ship/references/worktree-name.md +9 -3
  10. package/claude/skills/build-in-slices/references/adopted.md +35 -21
  11. package/claude/skills/canvas-board/references/canvas.md +17 -6
  12. package/claude/skills/canvas-board/references/design.md +26 -7
  13. package/claude/skills/code-craft/references/adopted.md +58 -20
  14. package/claude/skills/codebase-layout/references/adopted.md +49 -16
  15. package/claude/skills/codebase-layout/references/frontend.md +11 -4
  16. package/claude/skills/codebase-layout/references/tests.md +4 -2
  17. package/claude/skills/context-fold/references/classify.md +10 -15
  18. package/claude/skills/context-fold/references/completion-report.md +11 -9
  19. package/claude/skills/context-fold/references/context-refresh.md +26 -9
  20. package/claude/skills/context-fold/references/unusable-baseline.md +5 -3
  21. package/claude/skills/create-rule/REQUIREMENT.md +5 -0
  22. package/claude/skills/create-rule/SKILL.md +9 -1
  23. package/claude/skills/create-rule/references/checklist.md +26 -0
  24. package/claude/skills/create-rule/references/rule.md +98 -20
  25. package/claude/skills/create-skill/SKILL.md +1 -0
  26. package/claude/skills/create-skill/references/skill-disclosure.md +10 -2
  27. package/claude/skills/create-skill/references/skill-frontmatter.md +43 -0
  28. package/claude/skills/create-skill/references/skill-paths.md +7 -4
  29. package/claude/skills/create-skill/references/skill-practice.md +6 -3
  30. package/claude/skills/create-skill/references/skill-requirement.md +4 -2
  31. package/claude/skills/create-skill/references/skill.md +67 -44
  32. package/claude/skills/deploy-app/references/vercel.md +2 -1
  33. package/claude/skills/deprecation-migration/references/adopted.md +44 -25
  34. package/claude/skills/design-taste/references/adopted.md +9 -1
  35. package/claude/skills/design-taste/references/craft.md +3 -1
  36. package/claude/skills/design-taste/references/kinds.md +8 -7
  37. package/claude/skills/design-taste/references/preflight.md +12 -0
  38. package/claude/skills/design-taste/references/systems.md +7 -6
  39. package/claude/skills/design-taste/references/tells.md +24 -25
  40. package/claude/skills/design-taste/references/vocabulary.md +7 -5
  41. package/claude/skills/document-health/references/axes.md +54 -30
  42. package/claude/skills/draft-and-pick/references/live-arms.md +2 -2
  43. package/claude/skills/draft-doc/references/context.md +4 -3
  44. package/claude/skills/draft-doc/references/docs.md +6 -3
  45. package/claude/skills/draft-doc/references/readme/agent-facing.md +6 -0
  46. package/claude/skills/draft-doc/references/readme/application.md +8 -2
  47. package/claude/skills/draft-doc/references/readme/cli.md +5 -1
  48. package/claude/skills/draft-doc/references/readme/library.md +4 -0
  49. package/claude/skills/draft-doc/references/readme/plugin.md +9 -1
  50. package/claude/skills/draft-doc/references/readme.md +30 -13
  51. package/claude/skills/draft-doc/references/requirements.md +17 -4
  52. package/claude/skills/draft-doc/references/wiki-kind.md +20 -10
  53. package/claude/skills/draft-figure/references/architecture-set.md +7 -3
  54. package/claude/skills/draft-figure/references/figures.md +8 -6
  55. package/claude/skills/draft-figure/references/hand-drawn.md +3 -1
  56. package/claude/skills/draft-figure/references/mermaid-path.md +19 -9
  57. package/claude/skills/draft-figure/references/mermaid.md +10 -5
  58. package/claude/skills/draft-ready/references/ready.md +22 -15
  59. package/claude/skills/git-branch/references/branch.md +11 -1
  60. package/claude/skills/git-branch/references/slug.md +17 -5
  61. package/claude/skills/git-commit/references/commit.md +8 -0
  62. package/claude/skills/git-commit/references/versioning.md +23 -17
  63. package/claude/skills/git-followup/REQUIREMENT.md +1 -1
  64. package/claude/skills/git-followup/SKILL.md +5 -6
  65. package/claude/skills/git-issue/references/issue.md +29 -16
  66. package/claude/skills/git-pr/REQUIREMENT.md +1 -5
  67. package/claude/skills/git-pr/SKILL.md +11 -22
  68. package/claude/skills/git-pr/references/final-command.md +21 -8
  69. package/claude/skills/git-pr/references/labels.md +45 -28
  70. package/claude/skills/git-pr/references/pr.md +28 -9
  71. package/claude/skills/git-pr/references/publish.md +42 -28
  72. package/claude/skills/git-pr/references/task-number.md +20 -8
  73. package/claude/skills/git-ship/SKILL.md +1 -1
  74. package/claude/skills/plan-feature/references/plan-lifecycle.md +10 -3
  75. package/claude/skills/plan-feature/references/plan.md +57 -20
  76. package/claude/skills/plan-groundwork/references/groundwork-spikes.md +4 -1
  77. package/claude/skills/plan-groundwork/references/groundwork.md +14 -16
  78. package/claude/skills/plan-intake/references/intake.md +29 -24
  79. package/claude/skills/pr-evidence/REQUIREMENT.md +40 -0
  80. package/claude/skills/pr-evidence/SKILL.md +53 -0
  81. package/claude/skills/pr-evidence/references/evidence.md +150 -0
  82. package/claude/skills/review-address/references/post-review-findings.md +10 -3
  83. package/claude/skills/review-address/references/rebase-conflicts.md +15 -7
  84. package/claude/skills/review-address/references/rest-reads.md +3 -1
  85. package/claude/skills/review-craft/references/adopted.md +35 -18
  86. package/claude/skills/review-craft/references/security.md +42 -7
  87. package/claude/skills/review-craft/references/ui.md +23 -8
  88. package/claude/skills/review-pr/SKILL.md +6 -6
  89. package/claude/skills/review-pr/references/body-variants.md +29 -9
  90. package/claude/skills/review-pr/references/close-out.md +20 -5
  91. package/claude/skills/review-pr/references/key-changes.md +27 -3
  92. package/claude/skills/review-pr/references/post.md +83 -31
  93. package/claude/skills/review-pr/references/scope.md +54 -19
  94. package/claude/skills/review-ui/references/close-out.md +7 -5
  95. package/claude/skills/review-ui/references/driving.md +24 -4
  96. package/claude/skills/review-ui/references/post.md +43 -15
  97. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +67 -17
  98. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +80 -40
  99. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +52 -15
  100. package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +48 -26
  101. package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +12 -13
  102. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +72 -36
  103. package/claude/skills/role-orchestrator/references/orchestrator-output.md +12 -4
  104. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +64 -24
  105. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +80 -26
  106. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +41 -18
  107. package/claude/skills/role-orchestrator/references/orchestrator-resume.md +19 -7
  108. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +22 -12
  109. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +15 -6
  110. package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +25 -7
  111. package/claude/skills/role-orchestrator/references/orchestrator-watch.md +34 -8
  112. package/claude/skills/role-orchestrator/scripts/poll.ts +16 -24
  113. package/claude/skills/role-worker/REQUIREMENT.md +1 -0
  114. package/claude/skills/role-worker/SKILL.md +8 -0
  115. package/claude/skills/search-craft/references/adopted.md +18 -6
  116. package/claude/skills/session-map/references/session.md +26 -7
  117. package/claude/skills/session-worktree/references/main-root-writes.md +25 -8
  118. package/claude/skills/systematic-debugging/references/adopted.md +30 -16
  119. package/claude/skills/target-check/references/domains.md +39 -11
  120. package/claude/skills/target-setup/references/detect.md +80 -65
  121. package/claude/skills/target-setup/references/indexes.md +8 -6
  122. package/claude/skills/task-board/references/board.md +86 -30
  123. package/claude/skills/task-board/references/decline.md +29 -13
  124. package/claude/skills/task-board/references/tasks-decline.md +12 -5
  125. package/claude/skills/task-board/references/tasks.md +65 -33
  126. package/claude/skills/task-complete/references/archive.md +41 -17
  127. package/claude/skills/task-complete/references/receipt-sweep.md +23 -9
  128. package/claude/skills/task-complete/references/unusable-baseline.md +10 -3
  129. package/claude/skills/teach-workspace/references/glossary.md +6 -2
  130. package/claude/skills/teach-workspace/references/lesson-build.md +44 -16
  131. package/claude/skills/teach-workspace/references/lesson-craft.md +12 -7
  132. package/claude/skills/teach-workspace/references/pedagogy.md +6 -3
  133. package/claude/skills/teach-workspace/references/promotion-fold.md +17 -11
  134. package/claude/skills/teach-workspace/references/promotion.md +31 -8
  135. package/claude/skills/teach-workspace/references/revisit.md +8 -2
  136. package/claude/skills/teach-workspace/references/teach.md +17 -9
  137. package/claude/skills/test-craft/references/adopted.md +48 -14
  138. package/claude/skills/test-craft/references/component.md +7 -4
  139. package/claude/skills/test-craft/references/e2e.md +25 -7
  140. package/claude/skills/test-craft/references/unit.md +3 -1
  141. package/claude/skills/test-first/references/adopted.md +12 -4
  142. package/claude/skills/ui-checklist/REQUIREMENT.md +1 -1
  143. package/claude/skills/ui-checklist/SKILL.md +1 -1
  144. package/claude/skills/video-craft/references/adopted.md +62 -18
  145. package/claude/skills/write-human/references/adopted.md +21 -8
  146. package/docs/workflow/parallel-features.md +1 -1
  147. package/docs/workflow/skill-map.md +1 -0
  148. package/package.json +1 -1
  149. package/src/claude/cases/workflow.ts +5 -0
  150. package/src/commands/pr.ts +142 -28
  151. package/src/markdown/scope.ts +144 -0
  152. package/src/pr/lift.ts +6 -3
  153. package/src/pr/review-state.ts +182 -0
  154. 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
- **On screen for 5/6 of a second to 7 seconds, two lines, 42 characters a line.** Netflix's [Timed Text Style Guide: General Requirements](https://partnerhelp.netflixstudios.com/hc/en-us/articles/215758617-Timed-Text-Style-Guide-General-Requirements) states a minimum of 5/6 of a second (20 frames at 24 fps) and a maximum of 7 seconds per event, and its [English (USA) guide](https://partnerhelp.netflixstudios.com/hc/en-us/articles/217350977-English-USA-Timed-Text-Style-Guide) states 42 characters a line and two lines at most.
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
- **20 characters per second, 17 for children.** The same English (USA) guide, adult and children's programs respectively.
21
+ ### Start on the first frame of the action
17
22
 
18
- **Start on the first frame of the action.** 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.
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
- **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."
25
+ ### Captions name what the frame shows
21
26
 
22
- **Captions name what the frame shows.** 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.
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
- **A push onto an action no faster than HyperFrames' floor, never past 1 second.** HyperFrames' `viewport-change` rule states a zoom of 1.0 to 2.0 seconds and gives its reason beside the value: "under 0.8s teleports, over 2.5s drags". The body keeps that floor by pointing at the rule rather than restating it. The 1 second ceiling has two grounds. 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. Published: Material Design 3's [easing and duration tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs) end their scale at 1000 ms, the top of the extra-long band they reserve for ambient transitions, so no input-driven transition in that system runs longer.
33
+ ### A push onto an action stays under 1 second
29
34
 
30
- **Zoom on a click.** 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.
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
- **Conflict, and the pick.** HyperFrames' range and the measured renders disagree on the push onto an action, and the body takes the overlap: HyperFrames' teleport floor below, and 1 second above, which sits inside HyperFrames' range at its fast end. HyperFrames' full range stays with an ambient reveal, which matches Material's own split between input-driven and ambient motion. A tighter band was considered and 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", but both write for an interface a user waits on, which a video viewer is not, and that band sits below HyperFrames' stated teleport floor with nothing measured to show it reads right. Narrow the band once a composed demo is compared against the unguided renders.
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
- **Music at least 20 dB under the voice.** WCAG 2.1 [Success Criterion 1.4.7](https://www.w3.org/WAI/WCAG21/Understanding/low-or-no-background-audio.html): "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."
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
- **Conflict, and the pick.** 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.
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
- **Overall loudness is delegated.** 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, while `media-use` ships -14 and -16 LUFS targets for social and podcast delivery. The body states no overall target, since the right one depends on where the video is published and `media-use` owns that operation.
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
- **At or under 160 words a minute when captioned.** The DCMP [Captioning Key: Presentation Rate](https://dcmp.org/learn/601-captioning-key---presentation-rate) caps upper-level captions at 160 words a minute. `hyperframes-creative`'s narration reference states 2.5 words a second, 150 a minute, which sits under the ceiling, so the two do not conflict. An open search of voiceover practitioners put explainers at 140 to 160 words a minute, which agrees.
78
+ ### At or under 160 words a minute when captioned
53
79
 
54
- Narration has no standards body for pace, and the scoped search found none, so the body sets only the ceiling captions impose and points at HyperFrames for the pace itself.
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
- **Restating HyperFrames' motion rules.** 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.
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
- **A loudness target in the body.** See Audio above.
104
+ ### Zoom out before a cut, and a sound effect only on a meaningful click
61
105
 
62
- **Pointer smoothing and hiding at rest.** 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. Declined because both are recorder settings. A take is composed with its pointer already baked into the frames, so a session composing from it cannot change either. They belong to whatever records the take.
106
+ Neither has a source, and no operator read stands behind either, so both stay out until one does.
63
107
 
64
- **Zoom out before a cut, and a sound effect only on a click that changes something.** Neither has a source, and no operator read stands behind either, so both stay out until one does.
108
+ ### Vertical 9:16 framing
65
109
 
66
- **Vertical 9:16 framing.** 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.
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, and six of those patterns are now in `machine-tells.md`: rule-of-three, synonym cycling, false ranges, inline-header lists, adverb propping, and passive voice with an unnamed actor. Each is a shape rather than a token, which is what kept it out of a ban list that matches words.
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 for the same reason. Compression is what creates the ambiguity, so a rule about pronouns and modifiers belongs beside the rule that shortened the sentence.
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 is what decides it.
23
- - **Document mode as a selector.** The source has the writer pick a mode before drafting. In this corpus a document's shape is already fixed by the standard governing its type, so a mode selector adds a second authority over one question and the two will disagree.
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 and leaves the bans, the rhythm rules, and the tells in force.
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 first of those three rests on a count. The source names `surface` among the nouns it bans, and tracked markdown in the authoring repository used `surface` 593 times, `harness` 95, `scaffolding` 17, and `primitive` 4. Adopting the item either rewrites established vocabulary across the corpus or is ignored in silence, and a reader arriving later cannot tell which happened.
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 behind it, so a writer swaps `robust` for a synonym and the claim stays unproven. The list now lives here as guidance a reader applies, and this body is its one copy, since no command reads it and a second copy would drift with nothing comparing the two.
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. The authoring toolkit keeps the per-rule attributions and the dates each guide was read, and no install carries that record, so this file is the position a target reads.
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 `git-pr` posts and removes. The slug is the branch name minus its leading type segment.
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
 
@@ -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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.41.0",
4
+ "version": "5.42.0",
5
5
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -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.',
@@ -107,7 +107,12 @@ import {
107
107
  reviewRowOf,
108
108
  selectBranchPull,
109
109
  } from '@/pr/rest'
110
- import { resolveReviewScope } from '@/pr/review-scope'
110
+ import {
111
+ readReviewState,
112
+ type ReviewStateApi,
113
+ type ReviewStateRecord,
114
+ waitForReviews,
115
+ } from '@/pr/review-state'
111
116
  import { PROJECT_ROOT } from '@/roots/project'
112
117
  import { intro, logInfo, logStep, logWarn, outro, plural } from '@/cli/ui'
113
118
 
@@ -140,6 +145,10 @@ interface LiftCommandOptions extends ReadOptions {
140
145
  readonly wait?: string
141
146
  }
142
147
 
148
+ interface ReviewStateOptions extends ReadOptions {
149
+ readonly wait?: string
150
+ }
151
+
143
152
  interface LocalOptions extends ReadOptions {
144
153
  readonly remove?: boolean
145
154
  }
@@ -561,11 +570,17 @@ export function register(program: Command): void {
561
570
  })
562
571
 
563
572
  pr.command('review-state')
564
- .description('Report the commit and instant the last review pass covered')
573
+ .description(
574
+ 'Report what the last code review and UI review passes covered at the tip',
575
+ )
565
576
  .argument('[number]', 'Pull request to read, defaulting to this branch')
566
577
  .helpOption('-h, --help', 'Show this help message')
567
578
  .option('--root <path>', 'Repository to read, defaulting to the cwd')
568
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
+ )
569
584
  .addHelpText(
570
585
  'after',
571
586
  [
@@ -587,6 +602,31 @@ export function register(program: Command): void {
587
602
  ' fallback a pass posted before the marker shipped, off GitHub stamps',
588
603
  ' none the thread carries no pass, so the next one is a first pass',
589
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
+ '',
590
630
  'Refusals, read off `reason`:',
591
631
  ' ambiguous-pull two open pull requests share the branch against',
592
632
  ' different bases, so name a number',
@@ -599,18 +639,22 @@ export function register(program: Command): void {
599
639
  ' gh-missing `gh` is not on the path, so no pull request resolved',
600
640
  ' gh-failed `gh` could not answer for this branch, so name a number',
601
641
  ' no-object-head the pull request object reported no head commit',
642
+ ' bad-wait --wait takes a whole number of seconds',
602
643
  '',
603
644
  'Exit codes:',
604
- ' 0 the thread was read, whether it carries a pass or not',
605
- ' 1 refused, with the reason on stderr or in the JSON record',
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`',
606
649
  '',
607
650
  'Examples:',
608
651
  ' canon pr review-state',
609
652
  ' canon pr review-state 1341 --json',
653
+ ' canon pr review-state 1341 --wait 1200 --json',
610
654
  '',
611
655
  ].join('\n'),
612
656
  )
613
- .action(async (number: string | undefined, opts: ReadOptions) => {
657
+ .action(async (number: string | undefined, opts: ReviewStateOptions) => {
614
658
  process.exitCode = await runReviewState(number, opts)
615
659
  })
616
660
 
@@ -1818,13 +1862,21 @@ async function runChecks(
1818
1862
 
1819
1863
  async function runReviewState(
1820
1864
  number: string | undefined,
1821
- opts: ReadOptions,
1865
+ opts: ReviewStateOptions,
1822
1866
  ): Promise<number> {
1823
1867
  const root = resolve(opts.root ?? process.cwd())
1824
1868
  const emitJson = opts.json ?? false
1825
1869
 
1826
1870
  intro('canon pr review-state')
1827
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
+
1828
1880
  if (Bun.which('gh') === null) {
1829
1881
  return refuseWith('gh-missing', PULL_REFUSALS['gh-missing'], emitJson, root)
1830
1882
  }
@@ -1841,20 +1893,84 @@ async function runReviewState(
1841
1893
  )
1842
1894
  }
1843
1895
 
1844
- const reviews = await listReviews(root, resolved.number)
1845
- if (reviews.kind === 'refused') {
1846
- const reason =
1847
- reviews.reason === 'gh-failed' ? 'gh-failed' : 'reviews-unreadable'
1848
- 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)
1849
1899
  }
1850
1900
 
1851
- const listing = { number: Number(resolved.number) }
1852
- const scope = resolveReviewScope({
1853
- reviews: reviews.rows.map(reviewRowOf),
1854
- })
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()
1855
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
+ }
1966
+
1967
+ return waited?.reason === 'timeout' ? 1 : 0
1968
+ }
1969
+
1970
+ function logReviewState(number: number, record: ReviewStateRecord): void {
1971
+ const { scope } = record
1856
1972
  logStep('Scope')
1857
- logInfo(`#${listing.number}`)
1973
+ logInfo(`#${number} at ${record.head.slice(0, 8)}`)
1858
1974
 
1859
1975
  if (scope.source === 'none') {
1860
1976
  logStep('First pass')
@@ -1874,19 +1990,17 @@ async function runReviewState(
1874
1990
  )
1875
1991
  }
1876
1992
 
1877
- outro()
1878
-
1879
- if (emitJson) {
1880
- process.stdout.write(
1881
- `${JSON.stringify({
1882
- root,
1883
- number: listing.number,
1884
- ...scope,
1885
- })}\n`,
1886
- )
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')
1887
2003
  }
1888
-
1889
- return 0
1890
2004
  }
1891
2005
 
1892
2006
  interface PullFiles {