@erclx/canon 4.120.0 → 4.122.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 (152) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/SKILL.md +29 -115
  3. package/claude/skills/auto-ship/references/draft-mark.md +30 -0
  4. package/claude/skills/auto-ship/references/failure-recovery.md +27 -0
  5. package/claude/skills/auto-ship/references/tier-failures.md +26 -0
  6. package/claude/skills/auto-ship/references/verb-absent.md +37 -0
  7. package/claude/skills/auto-ship/references/worktree-name.md +22 -0
  8. package/claude/skills/backlog-triage/REQUIREMENT.md +47 -0
  9. package/claude/skills/backlog-triage/SKILL.md +139 -0
  10. package/claude/skills/bash-script/SKILL.md +1 -1
  11. package/claude/skills/bash-script/references/patterns.md +0 -99
  12. package/claude/skills/bash-script/references/prompts.md +100 -0
  13. package/claude/skills/canon-rollout/SKILL.md +6 -6
  14. package/claude/skills/context-fold/REQUIREMENT.md +75 -0
  15. package/claude/skills/context-fold/SKILL.md +162 -0
  16. package/claude/skills/{docs-fold → context-fold}/references/anchor-sweep.md +1 -1
  17. package/claude/skills/{docs-fold → context-fold}/references/classify.md +4 -2
  18. package/claude/skills/context-fold/references/context-refresh.md +48 -0
  19. package/claude/skills/context-fold/references/promoted-pages.md +29 -0
  20. package/claude/skills/context-fold/references/receipt-sweep.md +43 -0
  21. package/claude/skills/context-fold/references/unusable-baseline.md +25 -0
  22. package/claude/skills/{docs-fold → context-fold}/references/wireframe-sweep.md +1 -1
  23. package/claude/skills/create-skill/SKILL.md +1 -1
  24. package/claude/skills/create-snippet/SKILL.md +1 -1
  25. package/claude/skills/create-standard/SKILL.md +1 -1
  26. package/claude/skills/design-extract/SKILL.md +1 -1
  27. package/claude/skills/design-taste/REQUIREMENT.md +2 -2
  28. package/claude/skills/docs-fold/REQUIREMENT.md +8 -58
  29. package/claude/skills/docs-fold/SKILL.md +3 -278
  30. package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
  31. package/claude/skills/docs-sync/SKILL.md +2 -2
  32. package/claude/skills/draft-context/REQUIREMENT.md +2 -2
  33. package/claude/skills/draft-context/SKILL.md +4 -4
  34. package/claude/skills/draft-docs/REQUIREMENT.md +1 -1
  35. package/claude/skills/draft-docs/SKILL.md +1 -1
  36. package/claude/skills/draft-figure/SKILL.md +1 -1
  37. package/claude/skills/draft-readme/SKILL.md +1 -1
  38. package/claude/skills/draft-wiki/SKILL.md +1 -1
  39. package/claude/skills/draft-wireframes/REQUIREMENT.md +1 -1
  40. package/claude/skills/draft-wireframes/SKILL.md +3 -3
  41. package/claude/skills/git-issue/SKILL.md +1 -1
  42. package/claude/skills/git-issue/references/issue.md +1 -1
  43. package/claude/skills/git-pr/SKILL.md +9 -147
  44. package/claude/skills/git-pr/references/evidence.md +114 -0
  45. package/claude/skills/git-pr/references/labels.md +20 -0
  46. package/claude/skills/git-pr/references/task-number.md +26 -0
  47. package/claude/skills/git-ship/SKILL.md +1 -1
  48. package/claude/skills/git-stage/SKILL.md +1 -1
  49. package/claude/skills/memory-capture/REQUIREMENT.md +2 -2
  50. package/claude/skills/memory-capture/SKILL.md +6 -6
  51. package/claude/skills/memory-review/REQUIREMENT.md +1 -1
  52. package/claude/skills/memory-review/SKILL.md +5 -101
  53. package/claude/skills/memory-review/references/later-phases.md +108 -0
  54. package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
  55. package/claude/skills/restate-plainly/REQUIREMENT.md +1 -1
  56. package/claude/skills/review-address/REQUIREMENT.md +1 -1
  57. package/claude/skills/review-address/SKILL.md +5 -4
  58. package/claude/skills/review-branch/REQUIREMENT.md +1 -0
  59. package/claude/skills/review-branch/SKILL.md +6 -7
  60. package/claude/skills/review-craft/REQUIREMENT.md +43 -0
  61. package/claude/skills/review-craft/SKILL.md +50 -0
  62. package/claude/skills/review-craft/references/security.md +21 -0
  63. package/claude/skills/review-craft/references/ui.md +72 -0
  64. package/claude/skills/review-pr/REQUIREMENT.md +1 -0
  65. package/claude/skills/review-pr/SKILL.md +1 -1
  66. package/claude/skills/review-pr/references/post.md +1 -1
  67. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -1
  68. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -0
  69. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
  70. package/claude/skills/role-planner/REQUIREMENT.md +1 -1
  71. package/claude/skills/session-resume/REQUIREMENT.md +1 -1
  72. package/claude/skills/session-worktree/SKILL.md +4 -10
  73. package/claude/skills/standards-audit/SKILL.md +1 -1
  74. package/claude/skills/task-board/REQUIREMENT.md +1 -1
  75. package/claude/skills/task-board/SKILL.md +6 -80
  76. package/claude/skills/task-board/references/archive.md +54 -0
  77. package/claude/skills/task-board/references/decline.md +40 -0
  78. package/claude/skills/teach-workspace/SKILL.md +8 -105
  79. package/claude/skills/teach-workspace/references/lesson-craft.md +51 -1
  80. package/claude/skills/teach-workspace/references/promotion.md +41 -0
  81. package/claude/skills/write-human/REQUIREMENT.md +7 -4
  82. package/claude/skills/write-human/SKILL.md +4 -2
  83. package/claude/skills/write-human/references/machine-tells.md +1 -1
  84. package/claude/skills/write-human/references/source-material.md +5 -2
  85. package/docs/agents/architecture-record.md +26 -0
  86. package/docs/agents/audits.md +1 -1
  87. package/docs/agents/commands.md +87 -154
  88. package/docs/agents/context-audit-checks.md +2 -20
  89. package/docs/agents/context-audit.md +1 -1
  90. package/docs/agents/context-classify.md +1 -1
  91. package/docs/agents/drift-surfaces.md +192 -0
  92. package/docs/agents/index.md +12 -4
  93. package/docs/agents/install-and-sync.md +2 -181
  94. package/docs/agents/markdown-audit-weight.md +73 -0
  95. package/docs/agents/markdown-audit.md +11 -104
  96. package/docs/agents/migrate.md +34 -0
  97. package/docs/agents/pr-local.md +6 -3
  98. package/docs/agents/records-stale.md +62 -0
  99. package/docs/agents/records.md +5 -1
  100. package/docs/agents/sandbox.md +1 -1
  101. package/docs/agents/tasks-archive.md +81 -0
  102. package/docs/agents/tasks-plans.md +147 -0
  103. package/docs/agents/tasks-validate.md +131 -0
  104. package/docs/agents/tasks.md +6 -333
  105. package/docs/target-projects.md +4 -4
  106. package/docs/workflow/ai-workflow.md +33 -27
  107. package/governance/rules/claude/500-prose.md +1 -1
  108. package/governance/rules/claude/501-markdown.md +3 -3
  109. package/governance/rules/core/045-memory.md +1 -1
  110. package/package.json +1 -1
  111. package/src/autoship/paths.ts +3 -2
  112. package/src/claude/cases/setup.ts +1 -1
  113. package/src/claude/cases/workflow.ts +27 -1
  114. package/src/commands/audits.ts +2 -2
  115. package/src/commands/markdown.ts +7 -9
  116. package/src/commands/pr.ts +11 -4
  117. package/src/commands/records.ts +146 -0
  118. package/src/commands/sandbox.ts +6 -4
  119. package/src/context/classify/extract.ts +1 -1
  120. package/src/context/classify/ollama.ts +1 -1
  121. package/src/context/classify/run.ts +1 -1
  122. package/src/gate/measures.ts +5 -5
  123. package/src/gate/stages.ts +0 -1
  124. package/src/markdown/bans.ts +11 -68
  125. package/src/markdown/gate.ts +1 -1
  126. package/src/markdown/scan.ts +7 -88
  127. package/src/migrate/skill-names.ts +15 -2
  128. package/src/records/stale.ts +243 -0
  129. package/src/sandbox/expect.ts +1 -1
  130. package/src/tasks/archive.ts +28 -4
  131. package/src/tasks/record.ts +1 -1
  132. package/src/tasks/validate.ts +36 -3
  133. package/standards/docs.md +1 -1
  134. package/standards/index.md +1 -1
  135. package/standards/markdown.md +5 -7
  136. package/standards/memory.md +13 -1
  137. package/standards/pr.md +1 -1
  138. package/standards/publish.md +1 -1
  139. package/standards/readme.md +2 -2
  140. package/standards/tasks.md +1 -1
  141. package/tooling/astro/reference.md +1 -1
  142. package/tooling/base/configs/.husky/post-merge +1 -1
  143. package/tooling/base/configs/scripts/verify.sh +2 -2
  144. package/tooling/base/seeds/canon/context/development.md +1 -1
  145. package/tooling/claude/reference.md +1 -1
  146. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +5 -6
  147. package/tooling/go/reference.md +1 -1
  148. package/tooling/nextjs/reference.md +1 -1
  149. package/tooling/php/reference.md +1 -1
  150. package/tooling/python/reference.md +1 -1
  151. package/tooling/vite-react/reference.md +1 -1
  152. package/tooling/web/reference.md +1 -1
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: backlog-triage
3
+ description: Files a measured verdict for every backlog row into an intake folder, suggesting decline, archive, keep, or promote and leaning toward decline, then applies the verdicts the operator approved through the task verbs. Use when asked to "triage the backlog", "prune the backlog", "which backlog rows are still worth doing", "clear out the backlog", or "apply the backlog triage". Do NOT use to file a brain dump, which is `plan-intake`, to answer the filed items, which is `plan-intake-answer`, or to create, archive, or decline one named task, which is `task-board`.
4
+ ---
5
+
6
+ # Backlog triage
7
+
8
+ A backlog only grows while nothing can decline a row. This pass reads every row against the current tree, files one verdict per row for the operator, and later applies what they approved.
9
+
10
+ The record is an ordinary intake folder at `.canon/intake/<nn>-backlog-triage/`. Read `${CLAUDE_SKILL_DIR}/../../standards/intake.md` before writing any file in it, since it holds the item format, the frontmatter, the index shape, and the answer contract this skill is bound by. Read `${CLAUDE_SKILL_DIR}/../../standards/tasks.md` before applying, since it holds the backlog, the ordering file, and the `Declined:` line.
11
+
12
+ ## Guards
13
+
14
+ - Resolve `.canon/tasks/` and `.canon/intake/` at the main worktree root, not `pwd`. Run `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd` outside a git repo.
15
+ - If `.canon/tasks/backlog.md` does not exist at that root, stop: `❌ No backlog at .canon/tasks/backlog.md. Nothing to triage.`
16
+ - Never fill a `You:` slot, and never read an empty one as agreement. Answering runs through `plan-intake-answer` or the operator's own edit, which is what keeps silence from deciding a row.
17
+ - Branch on each `canon` record's `ok` and `reason` rather than on the exit code, which a shell function wrapping `canon` can flatten to zero.
18
+
19
+ ## Step 1: detect the phase
20
+
21
+ Run `canon intake list --json` and take the folders whose slug ends in `-backlog-triage`. Read the newest by ordinal. Where the installed binary carries no such subcommand, stop and report it, since opening a folder beside one awaiting apply leaves the first stranded and applying needs the same verb.
22
+
23
+ - **No folder, or the newest is fully applied.** Run the file phase.
24
+ - **Unread above zero.** Stop: `❌ <n> items in <slug> are unread. Run /canon:plan-intake-answer, then re-run this skill to apply.`
25
+ - **Malformed above zero.** Stop: `❌ <n> items in <slug> carry no answer slot. Repair them in the cluster files before applying.`
26
+ - **Every item answered, some not yet applied.** Run the apply phase.
27
+
28
+ An item is applied once its task has moved under `.canon/tasks/declined/` or `.canon/tasks/archive/`, or once its live task file carries a `## Findings` line naming the folder slug. A task whose file sits in none of the three folders counts as applied only once `backlog.md` no longer names it, since its dangling line is the one thing apply has left to remove. Test that per item rather than trusting a count, since a run can stop partway. An item apply skipped for an unreadable answer stays unapplied, so the folder stays in the apply phase until the operator rewrites that answer.
29
+
30
+ ## File phase
31
+
32
+ Writes only inside `.canon/intake/<nn>-backlog-triage/`, so it runs under any role, including a planner session.
33
+
34
+ ### Step 2: claim the folder
35
+
36
+ Run `canon records ordinal intake backlog-triage --claim --json` and take its `name`. The verb creates the folder in the same act, so two sessions opening at once never share one. Where the installed binary carries no such subcommand, report it and stop rather than picking an ordinal by hand.
37
+
38
+ ### Step 3: read every row
39
+
40
+ List each link in `.canon/tasks/backlog.md` and open its task file. Read its origin line (`Plan:`, `Groundwork:`, `Intake:`, or `Issue:`), its `## Outcomes`, and its `## Findings`. Read `.canon/tasks/priority.md` once, so a promote can name what sits at the bottom of `## Needs a plan`.
41
+
42
+ A row whose link resolves to no file gets an item anyway, carrying `decline` and naming the missing path, since the backlog line is the only thing left pointing at it.
43
+
44
+ ### Step 4: measure each row
45
+
46
+ Measure every claim the task makes against the tree and `git log` during this pass. Grep for the construct its defect names and count the sites, check whether each open outcome's behavior exists, and search the log for a commit or pull request that shipped it under another task. Never carry a figure from the task file forward as current, since the task states what was true when it was filed.
47
+
48
+ Name the commit the pass measured against in the overview body.
49
+
50
+ ### Step 5: pick the verdict
51
+
52
+ Keep and promote carry the burden of proof. A row nobody can argue for from this pass's measurement is declined.
53
+
54
+ - **keep**: the defect or gap reproduces this pass, stated with a count or a path.
55
+ - **promote**: it reproduces, and the thing it was waiting on has cleared, naming what cleared it.
56
+ - **archive**: every open outcome is measured shipped, naming the commit or pull request per outcome position.
57
+ - **decline**: anything else, including a defect the tree no longer holds, work superseded by another task, and a row whose argument rests only on its original filing.
58
+
59
+ Age is evidence in neither direction. A row untouched for months is not more declinable than one filed yesterday, and a row filed yesterday is not more worth keeping.
60
+
61
+ ### Step 6: write the items
62
+
63
+ Write one item per row in the intake item format, one cluster file per domain the task touches, numbered in read order. Head each item `### N. <task stem>` so apply can find the task from the heading alone.
64
+
65
+ - `Problem:` states what this pass measured, with the count, path, or commit behind it
66
+ - `Fix:` states what applying the verdict does to the board
67
+ - `Worth it:` carries the verdict's reason in one clause
68
+ - `Open:` reads `decline, archive, keep, or promote?` on every item
69
+ - `Suggested:` opens with exactly one of the four verdict tokens, then the reason. An archive suggestion lists every outcome position it closes, as `archive 1, 3: shipped in #NNN`
70
+ - `Overlaps:` names a live board task that already carries the work, where one does
71
+ - `You:` ships empty
72
+
73
+ Write `00-overview.md` last, per the intake standard, with the verdict counts by token and an open-questions list linking each item. Where the pass runs out of context before the index, write `99-next-session.md` naming the last row filed, since a compaction drops the rest.
74
+
75
+ ## Apply phase
76
+
77
+ Writes the board and task files. Refuses when this session's name starts with `planner-` or `worker-`: read it from `canon sessions list --self --json` before writing anything, and stop: `❌ Apply writes the board, which the <role> role bans. Run it from an operator or orchestrator session.` A refused or failed read proceeds, since it is indistinguishable from a solo project running no named sessions.
78
+
79
+ Every write here is a main-root write. Route a change inside an existing file the way `session-worktree` states, and report it rather than proceeding silently when that skill does not resolve.
80
+
81
+ ### Step 7: read the answers
82
+
83
+ Run `canon intake list <slug> --json` and read each item's `answer` and `suggested`.
84
+
85
+ - An answer of `ok` takes the suggested verdict. A `suggested` that opens with no verdict token is malformed: report the item and skip it, never treating it as keep.
86
+ - An answer opening with a verdict token overrides the suggestion. An override to archive names its own outcome positions, and one that names none is reported and skipped.
87
+ - Any other answer, such as a free-text note, is reported and skipped. Never guess a verdict from prose.
88
+ - An item already applied, by the test in Step 1, is skipped silently, which is what makes a re-run write nothing twice.
89
+
90
+ ### Step 8: run each verdict
91
+
92
+ Take the task stem from the item heading. Every verdict leaves the folder slug on its task, which is how Step 1 and `task-board`'s unlinked-origin scan both see the folder as acted on.
93
+
94
+ - **decline**: `canon tasks decline <stem> --reason "<reason>, backlog triage <slug> item <cluster>#<n>" --json`. The verb clears the backlog line.
95
+ - **decline on a missing file**: when the stem resolves to no file in `.canon/tasks/`, `declined/`, or `archive/`, the verb would refuse as `no-match`, so skip it. Drop the dangling line from `backlog.md` directly and report it as removed with no task behind it. Any other verdict on such an item is reported and skipped, since there is no file to keep, archive, or promote.
96
+ - **archive**: `canon tasks outcome <stem> --close <p> ... --json` for the named positions, then `canon tasks archive <stem> --json`. Outcomes already closed report as closed rather than refusing, so archive still runs. Archive leaves `backlog.md` alone, so drop the task's backlog line once it succeeds, then append the `## Findings` line naming the evidence to the archived file.
97
+ - **keep**: append the `## Findings` line naming what reproduced. The backlog line stays.
98
+ - **promote**: move the backlog line to the bottom of `## Needs a plan` in `.canon/tasks/priority.md`, with a `Waiting on` cell stating what cleared and ending in `last`, then append the `## Findings` line.
99
+
100
+ Write the `## Findings` line as `- <YYYY-MM-DD>, backlog triage <slug>: <verdict>, <evidence>`. It is the last write on every verdict with a task file behind it, since Step 1 reads it as applied and a line written ahead of a refused move would mark a row done that never moved.
101
+
102
+ Before any promote, check the roster the way `task-board` step 4 does: a live session in this repository whose name starts with `orchestrator-` and whose `sessionId` is not this one. When one is found, write the `## Findings` line but not `priority.md` or `backlog.md`, and message that session with the row and its cell so it places the row itself.
103
+
104
+ ### Step 9: route on a refusal
105
+
106
+ Report every refusal on its item and continue with the rest, since one row's refusal says nothing about another's.
107
+
108
+ - `decline` refusing with `no-match`: the task already moved elsewhere. Report and skip. A missing-file item never reaches the verb, per Step 8.
109
+ - `outcome` refusing, or `archive` refusing with `open-outcomes`: the verdict named too few positions. Report the positions still open and leave the row on the backlog. Never retry with guessed positions.
110
+ - A promote whose row already sits in `priority.md`: report it, skip the move, and still write the `## Findings` line.
111
+
112
+ Close with `canon tasks validate --json` and report every finding it raises against a row this run touched. After a shell write under `.canon/tasks/`, regenerate the index with `canon indexes regen --no-stage --root <main-root> <main-root>/.canon/tasks/index.md`, since no hook fires on `Bash`.
113
+
114
+ ## Output
115
+
116
+ File phase:
117
+
118
+ ```plaintext
119
+ 📂 Opened .canon/intake/<nn>-backlog-triage/
120
+
121
+ **Filed:** <N> rows across <N> clusters, measured against <sha>
122
+ **Suggested:** <n> decline, <n> archive, <n> keep, <n> promote
123
+
124
+ Next: /canon:plan-intake-answer to answer the `You:` slots, then /canon:backlog-triage to apply
125
+ ```
126
+
127
+ Apply phase, naming the path every write landed at:
128
+
129
+ ```plaintext
130
+ ✅ Applied .canon/intake/<nn>-backlog-triage/
131
+
132
+ **Declined:** .canon/tasks/declined/<stem>.md
133
+ **Archived:** .canon/tasks/archive/<stem>.md
134
+ **Kept:** .canon/tasks/<stem>.md
135
+ **Promoted:** .canon/tasks/<stem>.md, bottom of ## Needs a plan
136
+ **Skipped:** <item>, <reason>
137
+ ```
138
+
139
+ Drop a line that carries no rows.
@@ -7,7 +7,7 @@ description: Generates production Bash scripts with a visual timeline UI, state-
7
7
 
8
8
  Generate production-ready Bash scripts for DevOps and CLI workflows. Enforce strict formatting with a visual timeline UI and state-based interactivity.
9
9
 
10
- Load `${CLAUDE_SKILL_DIR}/references/patterns.md` for the timeline lifecycle, logging, interactive prompt, help-screen, and full-script code templates. `${CLAUDE_SKILL_DIR}` expands to this skill's own directory, so the path resolves from any project. Copy those definitions verbatim, keeping only the colors and functions the script actually uses.
10
+ Load `${CLAUDE_SKILL_DIR}/references/patterns.md` for the timeline lifecycle, logging, help-screen, and full-script code templates, and `${CLAUDE_SKILL_DIR}/references/prompts.md` for the `ask()` and `select_option()` templates when the script prompts. `${CLAUDE_SKILL_DIR}` expands to this skill's own directory, so the path resolves from any project. Copy those definitions verbatim, keeping only the colors and functions the script actually uses.
11
11
 
12
12
  ## Guards
13
13
 
@@ -91,105 +91,6 @@ Renders as:
91
91
  └
92
92
  ```
93
93
 
94
- ## Interactive prompts
95
-
96
- Both prompts transition `◆` to `◇`, guard against non-TTY stdin with `[ -t 0 ]`, and write UI to stderr.
97
-
98
- ```bash
99
- ask() {
100
- local prompt_text=$1
101
- local var_name=$2
102
- local default_val=$3
103
- local input=""
104
- local char
105
- local display_default=""
106
- if [ -n "$default_val" ]; then
107
- display_default=" (${default_val})"
108
- fi
109
- if [ ! -t 0 ]; then
110
- log_error "${prompt_text} requires a TTY"
111
- fi
112
- echo -e "${GREY}│${NC}" >&2
113
- echo -ne "${GREEN}◆${NC} ${prompt_text}${display_default} " >&2
114
- while IFS= read -r -s -n1 char; do
115
- if [[ $char == $'\x1b' ]]; then
116
- read -rsn2 -t 0.001 _ || true
117
- echo -ne "\r\033[K" >&2
118
- echo -e "${GREY}◇${NC} ${prompt_text} ${RED}Cancelled${NC}" >&2
119
- exit 1
120
- elif [[ $char == $'\x7f' || $char == $'\x08' ]]; then
121
- if [ -n "$input" ]; then
122
- input="${input%?}"
123
- echo -ne "\b \b" >&2
124
- fi
125
- elif [[ -z "$char" ]]; then
126
- break
127
- else
128
- input+="$char"
129
- echo -n "$char" >&2
130
- fi
131
- done
132
- [ -z "$input" ] && input="$default_val"
133
- echo -ne "\r\033[K" >&2
134
- echo -e "${GREY}◇${NC} ${prompt_text} ${WHITE}${input}${NC}" >&2
135
- export "$var_name"="$input"
136
- }
137
- ```
138
-
139
- ```bash
140
- select_option() {
141
- local prompt_text=$1
142
- shift
143
- local options=("$@")
144
- local cur=0
145
- local count=${#options[@]}
146
-
147
- if [ ! -t 0 ]; then
148
- log_error "${prompt_text} requires a TTY"
149
- fi
150
-
151
- echo -ne "${GREY}│${NC}\n${GREEN}◆${NC} ${prompt_text}\n" >&2
152
-
153
- while true; do
154
- for i in "${!options[@]}"; do
155
- if [ "$i" -eq "$cur" ]; then
156
- echo -e "${GREY}│${NC} ${GREEN}❯ ${options[$i]}${NC}" >&2
157
- else
158
- echo -e "${GREY}│${NC} ${GREY}${options[$i]}${NC}" >&2
159
- fi
160
- done
161
-
162
- read -rsn1 key
163
- case "$key" in
164
- $'\x1b')
165
- if read -rsn2 -t 0.001 key_seq; then
166
- if [[ "$key_seq" == "[A" ]]; then cur=$(( (cur - 1 + count) % count )); fi
167
- if [[ "$key_seq" == "[B" ]]; then cur=$(( (cur + 1) % count )); fi
168
- else
169
- echo -en "\033[$((count + 1))A\033[J" >&2
170
- echo -e "\033[1A${GREY}│${NC}\n${GREY}◇${NC} ${prompt_text} ${RED}Cancelled${NC}" >&2
171
- exit 1
172
- fi
173
- ;;
174
- "k") cur=$(( (cur - 1 + count) % count ));;
175
- "j") cur=$(( (cur + 1) % count ));;
176
- "q")
177
- echo -en "\033[$((count + 1))A\033[J" >&2
178
- echo -e "\033[1A${GREY}│${NC}\n${GREY}◇${NC} ${prompt_text} ${RED}Cancelled${NC}" >&2
179
- exit 1
180
- ;;
181
- "") break ;;
182
- esac
183
-
184
- echo -en "\033[${count}A" >&2
185
- done
186
-
187
- echo -en "\033[$((count + 1))A\033[J" >&2
188
- echo -e "\033[1A${GREY}│${NC}\n${GREY}◇${NC} ${prompt_text} ${WHITE}${options[$cur]}${NC}" >&2
189
- SELECTED_OPTION="${options[$cur]}"
190
- }
191
- ```
192
-
193
94
  ## Help system
194
95
 
195
96
  ```bash
@@ -0,0 +1,100 @@
1
+ # Bash script prompts
2
+
3
+ Interactive prompt templates for the `bash-script` skill, read only when the script prompts. Copy verbatim, alongside the colors and logging functions from `patterns.md`.
4
+
5
+ Both prompts transition `◆` to `◇`, guard against non-TTY stdin with `[ -t 0 ]`, and write UI to stderr.
6
+
7
+ ```bash
8
+ ask() {
9
+ local prompt_text=$1
10
+ local var_name=$2
11
+ local default_val=$3
12
+ local input=""
13
+ local char
14
+ local display_default=""
15
+ if [ -n "$default_val" ]; then
16
+ display_default=" (${default_val})"
17
+ fi
18
+ if [ ! -t 0 ]; then
19
+ log_error "${prompt_text} requires a TTY"
20
+ fi
21
+ echo -e "${GREY}│${NC}" >&2
22
+ echo -ne "${GREEN}◆${NC} ${prompt_text}${display_default} " >&2
23
+ while IFS= read -r -s -n1 char; do
24
+ if [[ $char == $'\x1b' ]]; then
25
+ read -rsn2 -t 0.001 _ || true
26
+ echo -ne "\r\033[K" >&2
27
+ echo -e "${GREY}◇${NC} ${prompt_text} ${RED}Cancelled${NC}" >&2
28
+ exit 1
29
+ elif [[ $char == $'\x7f' || $char == $'\x08' ]]; then
30
+ if [ -n "$input" ]; then
31
+ input="${input%?}"
32
+ echo -ne "\b \b" >&2
33
+ fi
34
+ elif [[ -z "$char" ]]; then
35
+ break
36
+ else
37
+ input+="$char"
38
+ echo -n "$char" >&2
39
+ fi
40
+ done
41
+ [ -z "$input" ] && input="$default_val"
42
+ echo -ne "\r\033[K" >&2
43
+ echo -e "${GREY}◇${NC} ${prompt_text} ${WHITE}${input}${NC}" >&2
44
+ export "$var_name"="$input"
45
+ }
46
+ ```
47
+
48
+ ```bash
49
+ select_option() {
50
+ local prompt_text=$1
51
+ shift
52
+ local options=("$@")
53
+ local cur=0
54
+ local count=${#options[@]}
55
+
56
+ if [ ! -t 0 ]; then
57
+ log_error "${prompt_text} requires a TTY"
58
+ fi
59
+
60
+ echo -ne "${GREY}│${NC}\n${GREEN}◆${NC} ${prompt_text}\n" >&2
61
+
62
+ while true; do
63
+ for i in "${!options[@]}"; do
64
+ if [ "$i" -eq "$cur" ]; then
65
+ echo -e "${GREY}│${NC} ${GREEN}❯ ${options[$i]}${NC}" >&2
66
+ else
67
+ echo -e "${GREY}│${NC} ${GREY}${options[$i]}${NC}" >&2
68
+ fi
69
+ done
70
+
71
+ read -rsn1 key
72
+ case "$key" in
73
+ $'\x1b')
74
+ if read -rsn2 -t 0.001 key_seq; then
75
+ if [[ "$key_seq" == "[A" ]]; then cur=$(( (cur - 1 + count) % count )); fi
76
+ if [[ "$key_seq" == "[B" ]]; then cur=$(( (cur + 1) % count )); fi
77
+ else
78
+ echo -en "\033[$((count + 1))A\033[J" >&2
79
+ echo -e "\033[1A${GREY}│${NC}\n${GREY}◇${NC} ${prompt_text} ${RED}Cancelled${NC}" >&2
80
+ exit 1
81
+ fi
82
+ ;;
83
+ "k") cur=$(( (cur - 1 + count) % count ));;
84
+ "j") cur=$(( (cur + 1) % count ));;
85
+ "q")
86
+ echo -en "\033[$((count + 1))A\033[J" >&2
87
+ echo -e "\033[1A${GREY}│${NC}\n${GREY}◇${NC} ${prompt_text} ${RED}Cancelled${NC}" >&2
88
+ exit 1
89
+ ;;
90
+ "") break ;;
91
+ esac
92
+
93
+ echo -en "\033[${count}A" >&2
94
+ done
95
+
96
+ echo -en "\033[$((count + 1))A\033[J" >&2
97
+ echo -e "\033[1A${GREY}│${NC}\n${GREY}◇${NC} ${prompt_text} ${WHITE}${options[$cur]}${NC}" >&2
98
+ SELECTED_OPTION="${options[$cur]}"
99
+ }
100
+ ```
@@ -17,7 +17,7 @@ One wave takes one toolkit change to every target and ends with a pull request p
17
17
  - The invocation or the launch prompt names the worker role, a target path, or both: worker. Skip to `## The worker role` and run no phase above it.
18
18
  - Neither is named: orchestrator. Run the three phases in order.
19
19
 
20
- The role is read off the prompt because nothing else carries it. A dispatched worker starts in the target's own checkout, which is a repository like any other from the session's side, so a test on the working directory answers the same for a worker in a target and an operator who invoked this from one. Reading the role wrong in that direction starts a second wave from inside a consuming project, which is why the dispatch below names the role and the path both rather than relying on either alone.
20
+ The role is read off the prompt because nothing else carries it. A test on the working directory answers the same for a worker in a target and an operator who invoked this from one, and reading the role wrong starts a second wave from inside a consuming project.
21
21
 
22
22
  Do not invoke `canon:role-worker` from either role. That body states the role for a session building one branch under one plan in this repository, and it resolves session scratch against a main worktree root a target does not carry, so a rollout worker reading it hunts for a plan nobody wrote.
23
23
 
@@ -30,7 +30,7 @@ Do not invoke `canon:role-worker` from either role. That body states the role fo
30
30
 
31
31
  ## The fixed shape every target receives
32
32
 
33
- Every target in every wave takes the same branch and the same title, written from here rather than chosen per session. Four hand-driven repairs produced four titles across two scopes, neither of them this one, and three named the toolkit in the half of the shape that withholds it.
33
+ Every target in every wave takes the same branch and the same title, written from here rather than chosen per session.
34
34
 
35
35
  - Branch: `chore/agents`, in every target and every wave.
36
36
  - Title: `chore(agents): <what changed in that target>`.
@@ -48,7 +48,7 @@ One branch name across every target is also what lets the return leg resolve. `c
48
48
 
49
49
  1. Pin the wave. Record the commit of this repository that the wave carries and name it in this session's own reports. Name it in no target's body.
50
50
  2. Enumerate. Run `canon targets list --json` under `CANON_NON_INTERACTIVE=1`, adding `--sweep <root...>` to reach targets installed before the index existed. Read the `bound` object back before treating any count as the population, since it names the roots walked, the depth, where the walk stopped, and what it could not read. The bound a sweep can never state is the machine, so a target on another one sits outside every answer it gives.
51
- 3. Pick the clone that is current. A row carries every checkout of that project under `paths` and leads with the one a sync ran in, which records where an install happened rather than what the checkout holds today. Fetch each candidate, compare it against the origin's default branch, and dispatch into one that is level. Refuse one that is behind rather than branching from a stale base, since three clones read as still owing the repair in one census and every one of them was behind its remote and nothing else.
51
+ 3. Pick the clone that is current. A row carries every checkout of that project under `paths` and leads with the one a sync ran in, which records where an install happened rather than what the checkout holds today. Fetch each candidate, compare it against the origin's default branch, and dispatch into one that is level. Refuse one that is behind rather than branching from a stale base, since a stale clone reads as still owing a repair its remote already carries.
52
52
 
53
53
  ### Check, then dispatch
54
54
 
@@ -88,10 +88,10 @@ Report each dispatch by naming the target, the clone, the branch, the model, and
88
88
 
89
89
  1. Address every finding whatever its severity. A minor is a finding, and a reviewer calling one non-blocking does not close the pass that raised it.
90
90
  2. Resolve who holds the branch before dispatching anybody. Run `canon sessions list --branch chore/agents --repository <clone> --json` and read the count rather than the first row.
91
- - A live session holds it: send that session the findings and name `canon:review-address` for it to run, which is step 7 of `## The worker role` and the step that session already stands at. Never name this skill in that message. This body carries `disable-model-invocation: true`, which blocks the `Skill` tool rather than only suppressing an auto-trigger, and a session acting on an inbound message reaches a skill that way and no other, so a message naming this one halts the address leg with the branch built and the findings unread. A launch prompt is the one route to a flagged body, which is why the dispatch above names this skill and this message must not. Assuming the worker was gone is what one hand-driven pass got wrong. All four sessions that opened those pull requests were still alive holding their worktrees hours later, and of two fresh addressers sent over them, one refused on the worktree lock and one cut a second worktree on the same branch, which would have put two sessions pushing to one ref.
91
+ - A live session holds it: send that session the findings and name `canon:review-address` for it to run, which is step 7 of `## The worker role` and the step that session already stands at. Never name this skill in that message. This body carries `disable-model-invocation: true`, which blocks the `Skill` tool rather than only suppressing an auto-trigger, and a session acting on an inbound message reaches a skill that way and no other, so a message naming this one halts the address leg with the branch built and the findings unread. A launch prompt is the one route to a flagged body, which is why the dispatch above names this skill and this message must not. Never assume the worker is gone, since a fresh addresser sent over a live one either refuses on the worktree lock or cuts a second worktree on the same branch.
92
92
  - No live session holds it: dispatch a fresh worker into that clone and brief it with the findings, since it holds none of the reasoning behind the diff.
93
93
  - More than one row: report the ambiguity and stop rather than picking among candidates.
94
- 3. Re-review when the answer lands, scoped to the commits added since. A worker's reply never closes a target. One fix for two minor findings closed both and introduced four more, every one of them in prose that fix added, and a loop trusting the reply merges that.
94
+ 3. Re-review when the answer lands, scoped to the commits added since. A worker's reply never closes a target, since a fix can introduce findings of its own in the prose it adds.
95
95
  4. A target leaves the wave when a pass posts `## Review closed` and not before.
96
96
  5. Run until every target closes. No count bounds the loop. What guards it is this session reviewing every round itself, so it holds what each pass found and can see a target failing to converge, where a dispatched reviewer reads one pass and knows nothing of the one before it. A worker introducing a finding per fix has nothing mechanical stopping it, which is accepted rather than overlooked.
97
97
 
@@ -101,7 +101,7 @@ One session, one target, from the worktree entry to the merge of the branch it o
101
101
 
102
102
  1. Confirm the clone is current. Fetch, then compare against the origin's default branch. Report a checkout that is behind and stop, rather than branching from a stale base.
103
103
  2. Enter a worktree. Invoke `canon:session-worktree chore/agents`, which takes the branch as its tier 0 argument and enters a linked worktree inside this target. Branching in the checkout itself is what this avoids, since the operator may be working in it.
104
- 3. Check what this target holds. Invoke `canon:target-check`, which reports per domain what the target carries against what the toolkit ships and repairs nothing. It runs before any repair so the wave reads one stated set of domains per target rather than whatever each session decided to examine.
104
+ 3. Check what this target holds. Invoke `canon:target-check`, which reports per domain what the target carries against what the toolkit ships and repairs nothing, so the wave reads one stated set of domains per target.
105
105
  4. Diagnose and repair. Invoke `canon:canon-operator`, which reads `canon sync --check . --json` and routes each finding to the command or the skill that owns it. Do not restate that routing here and do not edit a managed file by hand.
106
106
  5. Commit and open the pull request through `canon:git-commit` and `canon:git-pr`, handing each the fixed shape above rather than taking the title the generator derives from the diff.
107
107
  6. Announce as the pull request opens, carrying its URL, its number, and the branch, to whoever dispatched this session, sending it through `canon:session-relay`. That transition is the one moment only this session can observe.
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: context-fold
3
+ description: What the planning-doc reconcile is for, the gaps it closes, and why the diff decides completion alone
4
+ ---
5
+
6
+ # Context fold requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, the planning docs describe the scope the session opened with. An outcome the diff shipped stays unchecked, so the board reports finished work as open and the next session re-plans it. A requirement or architecture decision that changed mid-cycle lives only in the conversation and dies with it. Plans for closed tasks accumulate in the live folder, so the folder stops indicating what is in flight.
11
+
12
+ A decision's verification anchor has the same shape of gap in the other direction. The reasoning keeps reading as current while the number it cites moves underneath, and an amendment written without an anchor leaves nothing recording which numbers were checked and which nobody has read since.
13
+
14
+ A handoff file has the same shape of gap as an unmarked outcome. A page a learning workspace produced and an operator already picked a destination for sits in gitignored scratch until something lands it, and the session that produced it is gone by the time a branch exists to carry the write. Left unfolded it reads as promoted while the destination holds nothing.
15
+
16
+ The receipt half of that sweep was missing entirely. A review receipt was deleted per shipped branch and a memory-review receipt was skipped, because the exclusion protecting the checklist and audit prefixes caught a third by accident. Nothing but an operator asking collected it, and a collection that waits on someone remembering to ask is one a folder outgrows, so the skipped population grew per shipped branch and never shrank until it was most of the folder.
17
+
18
+ The trigger side carries a gap of its own. "Sync the docs" names either corpus to the person saying it, so a description leaving its corpus to the opening clause alone competes with its public-facing sibling on nothing the routing field states, and the planning surface the request was about goes untouched.
19
+
20
+ This skill writes canonical docs at the end of a long build and never reviews what it wrote. An appended figure or a branch-narrated re-measurement lands unchecked the same way a session's own edits do, so what the fold produces carries the exact defect the standards it cites already ban.
21
+
22
+ ## Must
23
+
24
+ - Name the context entries and the task board in a trigger phrase rather than in the opening clause alone, so a bare request to sync the docs separates this skill from `docs-sync` on something both descriptions state
25
+ - Take completion from the diff and everything else from the session, since completion is a fact about the repository rather than about the conversation
26
+ - Match an outcome on the behavior it names, never on a filename or a commit subject
27
+ - Leave an outcome unchecked when the diff is ambiguous. An unmarked shipped outcome costs one manual edit and a wrongly marked one hides work that never happened.
28
+ - Write tracked docs at the current worktree root and the task board at the main root, since only the first commits with the branch
29
+ - Count every other citation before archiving a plan, comparing resolved targets rather than raw strings or bare filenames
30
+ - Retarget a closed task at the archived plan, so the reasoning behind finished work stays reachable
31
+ - Write a decision to the domain context entry it constrains unless it fills an architecture slot, since reach admits nearly every decision to an always-loaded file
32
+ - Merge or retire an architecture entry before adding one at the record's stated cap, and name which in the report, so the cap never turns into compressed prose or two decisions packed under one heading
33
+ - Anchor a decision entry this run writes or amends whose reasoning cites a measured number, re-reading the number against the tree before writing the marker
34
+ - Report an anchored decision whose cited path the diff touched, since the number was read before the branch moved what it counted
35
+ - Scan every memory-review receipt rather than the one matching this slug, since the skill that writes them runs after this one in the ship chain and a slug is unique per feature
36
+ - Collect a memory-review receipt whose items are all decided, folding each skip into its memory entry first, since a declined item is recorded nowhere else and a promoted one is already in git
37
+ - Leave a memory-review receipt holding a pending item, and report the count. A branch shipping is not an operator deciding what the receipt proposed.
38
+ - Leave the current branch's review receipt alone, since the chain that wrote it cites it in its own closing line and this skill cannot read whether that citation is still live
39
+ - Land each block of a promotion handoff at the destination its heading names, then delete the file so a later run does not fold it twice
40
+ - Take a promotion destination as already decided, since the operator confirmed it where the page was produced
41
+ - Classify the fold's whole diff baseline through `canon context classify diff`, calling the verb rather than reimplementing its pattern or its prompt in the skill body, since an earlier commit on the branch carries a doc edit the fold is equally responsible for
42
+ - Apply a `REPLACE` or `HISTORY` finding, and a regex-decided `MOVE` finding, against the quote it names, report a model-decided `MOVE` finding naming the surface it belongs on rather than cutting it, or keep a finding with a one-line reason, rather than leaving a finding unanswered
43
+ - Rewrite a restated or superseded statement in place rather than appending the replacement beside it, on every canonical doc type this skill writes
44
+ - Report a refusal or a missing `context classify` subcommand as one line and continue the fold either way, since a check that cannot run is not a reason to leave the fold's own output unshipped
45
+
46
+ ## Must not
47
+
48
+ - Infer a new task from the diff. Only outcomes already on the board get marked.
49
+ - Touch task files the session did not change
50
+ - Widen what a writing step reads when the baseline is unusable. Widening a read is safe and widening a write stubs a surface for every file in the repository.
51
+ - Edit `CLAUDE.md` inline. Every change there goes through a diff-and-approve gate, so this skill only flags.
52
+ - Create a context entry, or move or delete a plan. A plan is settled by the merge, and `canon tasks archive` carries it.
53
+ - Overwrite a file a promotion block routes to. A destination that already holds a page is a merge for a person, and folding over it discards work this skill never read.
54
+ - Write an anchor onto a decision the run did not amend, or refresh one without re-reading the number. A date from a pass that measured nothing is the false confidence the marker exists to prevent.
55
+ - Anchor an entry written before the rule, which dates it by blame rather than by a read
56
+ - Pick or configure a classifier backend. The project setting decides it, and this skill reports the resolved `modelLayer` as returned.
57
+ - Stop the fold on a classify finding, a refusal, or a missing subcommand. All three report and continue.
58
+ - Apply a finding against a quote found more than once or not found at all. Report that it could not be located instead of guessing.
59
+ - Re-run the classifier after applying a finding, which loops it over its own edit
60
+ - Cut a model-decided `MOVE` finding. The model's own prompt defines `MOVE` more broadly than the regex layer does, for correct content sitting on the wrong surface rather than a deletion candidate, so cutting one can discard content that belongs elsewhere rather than removing detail that never belonged at all.
61
+ - Paste a regex-decided `MOVE` finding's content into another canonical doc after cutting it. It never belonged on the wireframe surface at all, so cutting is the whole fix, not a relocation.
62
+ - Scope Step 10 to the files Steps 3 and 7 wrote this run. An earlier commit on the branch carries a doc edit the fold is equally responsible for, and the verb's own extraction already answers whether anything in range qualifies.
63
+
64
+ ## Guards
65
+
66
+ - Neither a `canon/` nor a `.claude/` directory: stop and name the command that sets one up
67
+ - No session divergence and no queued outcome the diff shipped: stop with a pass, since both conditions have to hold
68
+
69
+ ## Out of scope
70
+
71
+ - Creating a task file or moving one off the board, which `task-board` owns
72
+ - Public-facing docs, which `docs-sync` owns, apart from landing a page a promotion handoff already carries a confirmed destination for. This skill reconciles the context entries, the planning records, and the task board, and both descriptions name their corpus in the trigger so a request saying only "sync the docs" lands on one of the pair rather than on either.
73
+ - Deciding where a promoted page belongs, which is settled with the operator by the surface that produced the page
74
+ - Regenerating the task index, owned by a hook
75
+ - Re-measuring an architecture claim to decide whether its number moved. The sweep keys on a cited path entering the diff, so a claim whose number moved with no cited path in the diff goes unflagged.