@erclx/canon 5.32.0 → 5.34.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 (250) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/api-design/REQUIREMENT.md +1 -2
  3. package/claude/skills/api-design/SKILL.md +2 -3
  4. package/claude/skills/api-design/references/adopted.md +1 -1
  5. package/claude/skills/auto-ship/SKILL.md +1 -1
  6. package/claude/skills/auto-ship/references/worktree-name.md +1 -1
  7. package/claude/skills/backlog-triage/REQUIREMENT.md +2 -2
  8. package/claude/skills/backlog-triage/SKILL.md +1 -1
  9. package/claude/skills/canon-cli/REQUIREMENT.md +16 -13
  10. package/claude/skills/canon-cli/SKILL.md +75 -32
  11. package/claude/skills/canon-feedback/REQUIREMENT.md +1 -0
  12. package/claude/skills/canon-feedback/SKILL.md +11 -29
  13. package/claude/skills/canon-feedback/references/template.md +53 -0
  14. package/claude/skills/{canvas → canvas-board}/REQUIREMENT.md +2 -2
  15. package/claude/skills/{canvas → canvas-board}/SKILL.md +6 -3
  16. package/claude/skills/{canvas → canvas-board}/references/canvas.md +26 -3
  17. package/{standards → claude/skills/canvas-board/references}/design.md +11 -2
  18. package/claude/skills/code-craft/REQUIREMENT.md +1 -1
  19. package/claude/skills/code-craft/SKILL.md +0 -1
  20. package/claude/skills/context-fold/REQUIREMENT.md +25 -33
  21. package/claude/skills/context-fold/SKILL.md +25 -70
  22. package/claude/skills/context-fold/references/classify.md +3 -3
  23. package/claude/skills/context-fold/references/completion-report.md +4 -4
  24. package/claude/skills/context-fold/references/context-refresh.md +5 -5
  25. package/{standards → claude/skills/context-fold/references}/context.md +16 -5
  26. package/claude/skills/context-fold/references/unusable-baseline.md +3 -5
  27. package/claude/skills/create-rule/SKILL.md +3 -3
  28. package/{standards → claude/skills/create-rule/references}/rule.md +15 -6
  29. package/claude/skills/create-skill/SKILL.md +2 -2
  30. package/{standards → claude/skills/create-skill/references}/skill.md +18 -15
  31. package/claude/skills/design-taste/REQUIREMENT.md +3 -3
  32. package/claude/skills/design-taste/SKILL.md +1 -1
  33. package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
  34. package/claude/skills/docs-sync/SKILL.md +3 -3
  35. package/{standards → claude/skills/docs-sync/references}/docs.md +11 -0
  36. package/{standards → claude/skills/docs-sync/references}/readme.md +9 -2
  37. package/claude/skills/draft-and-pick/SKILL.md +5 -5
  38. package/claude/skills/draft-doc/SKILL.md +6 -6
  39. package/claude/skills/draft-figure/REQUIREMENT.md +2 -2
  40. package/claude/skills/draft-figure/SKILL.md +2 -2
  41. package/claude/skills/draft-figure/references/hand-drawn.md +1 -1
  42. package/claude/skills/draft-figure/references/mermaid-path.md +1 -1
  43. package/claude/skills/draft-identity/REQUIREMENT.md +2 -2
  44. package/claude/skills/draft-identity/SKILL.md +2 -2
  45. package/claude/skills/draft-ready/SKILL.md +1 -1
  46. package/claude/skills/git-branch/SKILL.md +1 -1
  47. package/{standards → claude/skills/git-branch/references}/branch.md +1 -1
  48. package/claude/skills/git-commit/SKILL.md +2 -2
  49. package/claude/skills/git-followup/REQUIREMENT.md +1 -1
  50. package/claude/skills/git-followup/SKILL.md +1 -1
  51. package/claude/skills/git-issue/SKILL.md +1 -1
  52. package/claude/skills/git-issue/references/issue.md +4 -1
  53. package/claude/skills/git-pr/SKILL.md +13 -13
  54. package/claude/skills/git-pr/references/evidence.md +2 -2
  55. package/claude/skills/git-pr/references/final-command.md +38 -14
  56. package/claude/skills/git-pr/references/labels.md +5 -1
  57. package/{standards → claude/skills/git-pr/references}/pr.md +19 -9
  58. package/{standards → claude/skills/git-pr/references}/publish.md +4 -2
  59. package/claude/skills/git-pr/references/task-number.md +1 -1
  60. package/claude/skills/git-ship/SKILL.md +15 -14
  61. package/claude/skills/git-split/SKILL.md +3 -3
  62. package/claude/skills/git-stage/SKILL.md +1 -1
  63. package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
  64. package/claude/skills/review-address/SKILL.md +1 -1
  65. package/claude/skills/review-address/references/post-review-findings.md +1 -1
  66. package/claude/skills/review-craft/references/ui.md +1 -1
  67. package/claude/skills/review-pr/REQUIREMENT.md +1 -1
  68. package/claude/skills/review-pr/SKILL.md +3 -3
  69. package/claude/skills/review-pr/references/body-variants.md +2 -2
  70. package/claude/skills/review-pr/references/key-changes.md +3 -3
  71. package/claude/skills/review-pr/references/post.md +4 -2
  72. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  73. package/claude/skills/review-ui/SKILL.md +8 -6
  74. package/claude/skills/review-ui/references/driving.md +8 -8
  75. package/claude/skills/review-ui/references/post.md +53 -17
  76. package/claude/skills/role-orchestrator/REQUIREMENT.md +3 -0
  77. package/claude/skills/role-orchestrator/SKILL.md +2 -2
  78. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +2 -2
  79. package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +8 -5
  80. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +32 -1
  81. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
  82. package/claude/skills/role-orchestrator/references/orchestrator-resume.md +3 -0
  83. package/claude/skills/role-planner/REQUIREMENT.md +2 -1
  84. package/claude/skills/role-planner/SKILL.md +13 -2
  85. package/claude/skills/role-reviewer/SKILL.md +1 -1
  86. package/claude/skills/role-worker/REQUIREMENT.md +1 -0
  87. package/claude/skills/role-worker/SKILL.md +9 -1
  88. package/claude/skills/session-map/SKILL.md +1 -1
  89. package/claude/skills/session-resume/REQUIREMENT.md +2 -2
  90. package/claude/skills/session-worktree/SKILL.md +5 -5
  91. package/claude/skills/target-setup/SKILL.md +10 -4
  92. package/claude/skills/target-setup/references/detect.md +20 -3
  93. package/claude/skills/task-board/REQUIREMENT.md +7 -12
  94. package/claude/skills/task-board/SKILL.md +5 -17
  95. package/claude/skills/task-board/references/tasks.md +2 -2
  96. package/claude/skills/task-complete/REQUIREMENT.md +47 -0
  97. package/claude/skills/task-complete/SKILL.md +86 -0
  98. package/claude/skills/{task-board → task-complete}/references/archive.md +4 -4
  99. package/claude/skills/{context-fold → task-complete}/references/receipt-sweep.md +5 -5
  100. package/claude/skills/task-complete/references/unusable-baseline.md +19 -0
  101. package/claude/skills/teach-workspace/REQUIREMENT.md +6 -2
  102. package/claude/skills/teach-workspace/SKILL.md +8 -4
  103. package/claude/skills/teach-workspace/references/lesson-craft.md +1 -1
  104. package/claude/skills/{context-fold/references/promoted-pages.md → teach-workspace/references/promotion-fold.md} +3 -3
  105. package/claude/skills/teach-workspace/references/promotion.md +3 -3
  106. package/claude/skills/test-craft/REQUIREMENT.md +2 -3
  107. package/claude/skills/test-craft/SKILL.md +15 -3
  108. package/claude/skills/ui-checklist/SKILL.md +1 -1
  109. package/claude/skills/ux-audit/SKILL.md +1 -1
  110. package/claude/skills/ux-measure/SKILL.md +1 -1
  111. package/docs/agents/canvas.md +6 -2
  112. package/docs/agents/commands.md +4 -3
  113. package/docs/agents/context-audit-checks.md +4 -4
  114. package/docs/agents/gov-detect.md +47 -0
  115. package/docs/agents/index.md +7 -4
  116. package/docs/agents/indexes.md +1 -1
  117. package/docs/agents/install-and-sync.md +10 -11
  118. package/docs/agents/key-changes.md +5 -5
  119. package/docs/agents/label-coverage.md +11 -0
  120. package/docs/agents/migrate.md +1 -1
  121. package/docs/agents/pr-evidence-layout.md +70 -0
  122. package/docs/agents/pr-evidence.md +8 -9
  123. package/docs/agents/pr-frames.md +7 -2
  124. package/docs/agents/pr-lift.md +132 -0
  125. package/docs/agents/records-push.md +2 -2
  126. package/docs/agents/records-stale.md +12 -47
  127. package/docs/agents/records-validate.md +3 -7
  128. package/docs/agents/records.md +5 -27
  129. package/docs/agents/rule-citations.md +1 -1
  130. package/docs/agents/sandbox.md +4 -4
  131. package/docs/agents/scripting.md +2 -1
  132. package/docs/agents/skills-audit.md +1 -1
  133. package/docs/agents/tasks-plans.md +2 -2
  134. package/docs/agents/tasks.md +2 -2
  135. package/docs/target/migrations.md +10 -2
  136. package/docs/target/projects.md +4 -4
  137. package/docs/target/sync.md +1 -1
  138. package/docs/workflow/operating-model.md +2 -2
  139. package/docs/workflow/parallel-features.md +7 -7
  140. package/docs/workflow/skill-map-on-request.md +9 -9
  141. package/docs/workflow/skill-map.md +12 -11
  142. package/docs/workflow/visual-design-workflow.md +2 -2
  143. package/governance/detect.toml +201 -0
  144. package/governance/renames.toml +0 -11
  145. package/governance/rules/canon/context.md +13 -0
  146. package/governance/rules/claude/skill.md +17 -0
  147. package/governance/rules/code/000-code.md +1 -13
  148. package/governance/rules/code/010-testing.md +2 -30
  149. package/governance/rules/ui/400-ui.md +1 -1
  150. package/governance/rules/ui/480-canvas.md +13 -0
  151. package/governance/stacks/base.toml +2 -4
  152. package/package.json +4 -4
  153. package/scripts/core/install-check.sh +3 -3
  154. package/scripts/core/regen-hero.ts +0 -6
  155. package/scripts/lib/ui.sh +0 -168
  156. package/src/audits/catalog.ts +0 -1
  157. package/src/canvas/arrange.ts +66 -0
  158. package/src/canvas/capture.ts +20 -6
  159. package/src/canvas/content.ts +39 -0
  160. package/src/canvas/fit.ts +238 -0
  161. package/src/canvas/tokens.ts +28 -10
  162. package/src/capture/frame-values.ts +2 -32
  163. package/src/claude/cases/misc.ts +1 -1
  164. package/src/claude/cases/setup.ts +4 -0
  165. package/src/claude/cases/workflow.ts +6 -1
  166. package/src/claude/seeds.ts +1 -1
  167. package/src/claude/skills-audit.ts +2 -2
  168. package/src/cli/help.ts +3 -1
  169. package/src/cli/ui.ts +2 -3
  170. package/src/commands/canvas.ts +179 -1
  171. package/src/commands/claude.ts +1 -1
  172. package/src/commands/gov.ts +146 -16
  173. package/src/commands/labels.ts +326 -7
  174. package/src/commands/migrate.ts +6 -1
  175. package/src/commands/pr.ts +377 -2
  176. package/src/commands/records.ts +13 -399
  177. package/src/commands/sandbox.ts +3 -6
  178. package/src/commands/tasks.ts +3 -3
  179. package/src/context/audit.ts +5 -5
  180. package/src/context/folders.ts +1 -1
  181. package/src/design/budget.ts +1 -1
  182. package/src/design/components.ts +1 -1
  183. package/src/design/document.ts +1 -1
  184. package/src/design/regen.ts +1 -1
  185. package/src/design/render.ts +1 -1
  186. package/src/feedback/body.ts +26 -5
  187. package/src/gate/measures.ts +59 -45
  188. package/src/gate/sequencer.ts +56 -1
  189. package/src/gate/stages.ts +4 -11
  190. package/src/gov/detect.ts +301 -0
  191. package/src/gov/install.ts +2 -3
  192. package/src/gov/list.ts +23 -2
  193. package/src/gov/stacks.ts +10 -1
  194. package/src/hooks/post-merge.ts +1 -1
  195. package/src/labels/apply.ts +39 -0
  196. package/src/labels/format.ts +3 -3
  197. package/src/labels/phase.ts +3 -3
  198. package/src/labels/sections.ts +43 -0
  199. package/src/migrate/skill-names.ts +20 -1
  200. package/src/pr/evidence.ts +218 -45
  201. package/src/pr/frames.ts +23 -3
  202. package/src/pr/lift.ts +418 -0
  203. package/src/pr/paths.ts +4 -4
  204. package/src/pr/rest.ts +3 -0
  205. package/src/pr/review-scope.ts +76 -0
  206. package/src/records/backup.ts +16 -6
  207. package/src/records/stale-canonical.ts +31 -1
  208. package/src/records/validate.ts +2 -188
  209. package/src/sandbox/census.ts +1 -1
  210. package/src/sandbox/coverage.ts +2 -6
  211. package/src/sandbox/equivalence.ts +19 -51
  212. package/src/sandbox/expect.ts +4 -4
  213. package/src/sandbox/provision.ts +13 -36
  214. package/src/sandbox/scenario.ts +26 -48
  215. package/src/sandbox/tree.ts +2 -6
  216. package/src/standards/read.ts +14 -1
  217. package/src/sync/git.ts +1 -1
  218. package/src/tasks/archive.ts +5 -5
  219. package/src/tasks/branch.ts +3 -3
  220. package/src/tasks/label.ts +1 -1
  221. package/src/tasks/record.ts +2 -2
  222. package/src/tasks/validate.ts +1 -1
  223. package/standards/index.md +0 -13
  224. package/standards/standard.md +1 -7
  225. package/tooling/base/reference.md +1 -1
  226. package/tooling/base/seeds/.github/pull_request_template.md +2 -2
  227. package/tooling/claude/reference.md +1 -1
  228. package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +9 -1
  229. package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +171 -0
  230. package/tooling/claude/seeds/.claude/settings.json +9 -0
  231. package/governance/rules/standards/context.md +0 -14
  232. package/governance/rules/standards/design.md +0 -12
  233. package/governance/rules/standards/readme.md +0 -12
  234. package/governance/rules/standards/rule.md +0 -13
  235. package/governance/rules/standards/skill.md +0 -21
  236. package/governance/rules/standards/standard.md +0 -13
  237. package/scripts/config.sh +0 -11
  238. package/scripts/lib/gov.sh +0 -14
  239. package/scripts/lib/sandbox-fixtures.sh +0 -191
  240. package/scripts/lib/sandbox-git.sh +0 -125
  241. package/scripts/lib/sandbox-path.sh +0 -50
  242. package/scripts/sandbox-hook.sh +0 -99
  243. package/src/gov/standard-rules.ts +0 -179
  244. package/src/records/migrate.ts +0 -78
  245. package/src/records/stale.ts +0 -243
  246. package/src/sandbox/hooks.ts +0 -128
  247. /package/{standards → claude/skills/draft-figure/references}/figures.md +0 -0
  248. /package/{standards → claude/skills/git-branch/references}/slug.md +0 -0
  249. /package/{standards → claude/skills/git-commit/references}/commit.md +0 -0
  250. /package/{standards → claude/skills/git-commit/references}/versioning.md +0 -0
@@ -1,12 +0,0 @@
1
- ---
2
- # Generated by `canon gov regen` from standards/readme.md. Edit that frontmatter, not this file.
3
- description: Readme voice, structure, and content conventions
4
- paths:
5
- - '**/README.md'
6
- ---
7
-
8
- # Readme standards
9
-
10
- ## Authority
11
-
12
- - Follow the readme standard for readme voice, structure, and content conventions. It is the single source. Read it with `canon standards readme`.
@@ -1,13 +0,0 @@
1
- ---
2
- # Generated by `canon gov regen` from standards/rule.md. Edit that frontmatter, not this file.
3
- description: Rule frontmatter, body shape, and voice for .claude/rules files
4
- paths:
5
- - '.claude/rules/**/*.md'
6
- ---
7
-
8
- # Governance rule standards
9
-
10
- ## Authority
11
-
12
- - Follow the rule standard for rule frontmatter, body shape, and voice for .claude/rules files. It is the single source. Read it with `canon standards rule`.
13
- - Read it before writing or editing a rule. Do not work the shape from memory.
@@ -1,21 +0,0 @@
1
- ---
2
- # Generated by `canon gov regen` from standards/skill.md. Edit that frontmatter, not this file.
3
- description: Skill structure and authoring rules for Claude Code
4
- paths:
5
- - '.claude/skills/**/SKILL.md'
6
- - 'claude/skills/**/SKILL.md'
7
- - '.claude/skills/**/REQUIREMENT.md'
8
- - 'claude/skills/**/REQUIREMENT.md'
9
- ---
10
-
11
- # Claude skill standards
12
-
13
- ## Authority
14
-
15
- - Follow the skill standard for skill structure and authoring rules for Claude Code. It is the single source. Read it with `canon standards skill`.
16
- - Confirm `canon:create-skill`'s two creation-time questions are answered before a new `SKILL.md` lands, whether drafted by hand, by another skill, or by `canon:create-skill` itself. Carry the third question into the sibling `REQUIREMENT.md`'s `Must not` section as a review criterion rather than a gate.
17
- - Report it rather than proceeding silently when `canon:create-skill` does not resolve. It ships with the plugin and this rule ships with the CLI, so a project that installed governance alone does not have it.
18
- - Re-read a skill body this session edited before invoking that skill again in the same session
19
- - Do not read a resolved file path in a held body as evidence the body is current
20
- - Follow the skill requirement standard for the shape a `REQUIREMENT.md` states. Read it with `canon standards skill-requirement`.
21
- - Follow the skill path standard for which root a path in a body resolves against and how a body cites a standard. Read it with `canon standards skill-paths`.
@@ -1,13 +0,0 @@
1
- ---
2
- # Generated by `canon gov regen` from standards/standard.md. Edit that frontmatter, not this file.
3
- description: Shape and content rules for authoring a standard
4
- paths:
5
- - 'standards/**/*.md'
6
- ---
7
-
8
- # Standard standards
9
-
10
- ## Authority
11
-
12
- - Follow the standard standard for shape and content rules for authoring a standard. It is the single source. Read it with `canon standards standard`.
13
- - Read it before writing or editing a standard. Do not work the shape from memory.
package/scripts/config.sh DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- if [ -z "${GITHUB_ORG:-}" ]; then
4
- _origin_url="$(git config --get remote.origin.url 2>/dev/null || true)"
5
- if [ -n "$_origin_url" ]; then
6
- GITHUB_ORG="$(echo "$_origin_url" | sed -nE 's#^(git@github\.com:|https://github\.com/)([^/]+)/.+#\2#p')"
7
- fi
8
- unset _origin_url
9
- fi
10
-
11
- export GITHUB_ORG="${GITHUB_ORG:-}"
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # Sole survivor of the gov lib. Called once per rule file inside install and
4
- # sandbox loops, where routing through the CLI would cost a process per file.
5
-
6
- rule_subdir() {
7
- local src="$1"
8
- local rules_root="${2:-$PROJECT_ROOT/governance/rules}"
9
- local rel="${src#"$rules_root/"}"
10
- local subdir
11
- subdir=$(dirname "$rel")
12
- [ "$subdir" = "." ] && subdir=""
13
- echo "$subdir"
14
- }
@@ -1,191 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # Fixture files carry this suffix so the repository's own checks ignore them.
4
- # A stored `index.md.fixture` is not an `index.md`, so the index walker, prettier,
5
- # shfmt, and shellcheck all skip it and the bytes reach the sandbox unmodified.
6
- FIXTURE_SUFFIX=".fixture"
7
-
8
- # The tree mirrors the scenario path, so `category` and `scenario` together match
9
- # `sandbox/<category>/<scenario>.sh`. Both segments are load-bearing:
10
- # four scenario basenames repeat across categories (`claude`, `docs`, `review`,
11
- # `sync`), and `docs` is also a category of its own.
12
- fixture_stage_dir() {
13
- local category="$1"
14
- local scenario="$2"
15
- local arm="$3"
16
- local stage="$4"
17
- echo "$PROJECT_ROOT/sandbox/fixtures/$category/$scenario/$arm/$stage"
18
- }
19
-
20
- list_fixture_files() {
21
- local source_dir="$1"
22
- find "$source_dir" -type f -name "*$FIXTURE_SUFFIX" -print0 | sort -z
23
- }
24
-
25
- count_fixture_files() {
26
- local source_dir="$1"
27
- if [ ! -d "$source_dir" ]; then
28
- echo 0
29
- return 0
30
- fi
31
- find "$source_dir" -type f -name "*$FIXTURE_SUFFIX" | wc -l
32
- }
33
-
34
- # The copy loops match on the suffix, so a file without one would be skipped in
35
- # silence and the scenario would provision a tree it was never meant to have.
36
- assert_fixtures_suffixed() {
37
- local source_dir="$1"
38
- [ -d "$source_dir" ] || return 0
39
-
40
- local stray
41
- stray="$(find "$source_dir" -type f -not -name "*$FIXTURE_SUFFIX" | sort | tr '\n' ' ')"
42
- [ -z "$stray" ] || log_error "Fixture missing the $FIXTURE_SUFFIX suffix: $stray"
43
- }
44
-
45
- fixture_target_path() {
46
- local source_dir="$1"
47
- local source_file="$2"
48
- local relative="${source_file#"$source_dir"/}"
49
- echo "${relative%"$FIXTURE_SUFFIX"}"
50
- }
51
-
52
- create_from_fixtures() {
53
- local source_dir="$1"
54
- [ -d "$source_dir" ] || return 0
55
-
56
- local source_file target
57
- while IFS= read -r -d '' source_file; do
58
- target="$(fixture_target_path "$source_dir" "$source_file")"
59
- mkdir -p "$(dirname "$target")"
60
- cp "$source_file" "$target"
61
- done < <(list_fixture_files "$source_dir")
62
- }
63
-
64
- # An append fixture extends a file the anchor or the injected seeds already
65
- # provide. A missing target means that upstream shape changed, so fail rather
66
- # than create the file and silently alter what the scenario provisions.
67
- append_from_fixtures() {
68
- local source_dir="$1"
69
- [ -d "$source_dir" ] || return 0
70
-
71
- local source_file target
72
- while IFS= read -r -d '' source_file; do
73
- target="$(fixture_target_path "$source_dir" "$source_file")"
74
- if [ ! -f "$target" ]; then
75
- log_error "Append fixture has no target: $target"
76
- fi
77
- cat "$source_file" >>"$target"
78
- done < <(list_fixture_files "$source_dir")
79
- }
80
-
81
- # Stages the first `count` markdown files the toolkit ships for a domain into
82
- # `dest`, flattening any source subfolder. Source rather than fixture tree,
83
- # because an arm modelling a real install wants the files a target actually
84
- # received and a copy under `fixtures/` would drift from them silently.
85
- #
86
- # The flattening is load-bearing rather than incidental. Both `detectUnmigrated`
87
- # and the sync engine match a target file to its source by basename against the
88
- # flat domain root, so a file lifted out of a source subfolder such as `bundled/`
89
- # has no flat sibling and reads as project-authored. An arm staging one claims a
90
- # drift or an unmigrated domain it did not stage.
91
- stage_toolkit_markdown() {
92
- local src="$1"
93
- local dest="$2"
94
- local count="$3"
95
-
96
- mkdir -p "$dest"
97
- while IFS= read -r file; do
98
- cp "$file" "$dest/$(basename "$file")"
99
- done < <(find "$src" -maxdepth 1 -type f -name "*.md" ! -name "index.md" | sort | head -n "$count")
100
- }
101
-
102
- # The newest top-level path history records a deletion under that the toolkit no
103
- # longer ships. Read from history rather than hardcoded, so an arm stages a root
104
- # the walk will actually recognize instead of a name that has since come back.
105
- # `prompts` is preferred because it is the case measured in a real target, and
106
- # any other dropped root exercises the same walk.
107
- pick_dropped_root() {
108
- local preferred="prompts"
109
- local first=""
110
- local candidate
111
-
112
- while IFS= read -r candidate; do
113
- [ -e "$PROJECT_ROOT/$candidate" ] && continue
114
- [ "$candidate" = "$preferred" ] && {
115
- echo "$preferred"
116
- return 0
117
- }
118
- [ -n "$first" ] || first="$candidate"
119
- done < <(git -C "$PROJECT_ROOT" log --all --diff-filter=D --name-only --format= |
120
- awk -F/ 'NF > 1 { print $1 }' | sort -u)
121
-
122
- echo "$first"
123
- }
124
-
125
- # Restores one file's exact published bytes from the commit before it was
126
- # deleted. Content is what the attribution matches on, so a file written by hand
127
- # would report unattributed and an arm would assert the wrong verdict.
128
- #
129
- # Both reads take the whole listing through a process substitution rather than a
130
- # pipeline ending in an early exit. `set -o pipefail` is on, and a `grep -m 1`
131
- # that matches the first line closes the pipe while git is still writing, so the
132
- # substitution returns git's SIGPIPE status and the arm fails on a listing it
133
- # actually read.
134
- restore_dropped_file() {
135
- local root="$1"
136
- local rel="" commit="" line
137
-
138
- while IFS= read -r line; do
139
- case "$line" in
140
- "$root"/*)
141
- rel="$line"
142
- break
143
- ;;
144
- esac
145
- done < <(git -C "$PROJECT_ROOT" log --all --diff-filter=D --name-only \
146
- --format= -- "$root/")
147
-
148
- [ -n "$rel" ] || return 1
149
-
150
- while IFS= read -r line; do
151
- commit="$line"
152
- break
153
- done < <(git -C "$PROJECT_ROOT" log --all --diff-filter=D --format=%H -- "$rel")
154
-
155
- [ -n "$commit" ] || return 1
156
-
157
- mkdir -p "$(dirname "$rel")"
158
- git -C "$PROJECT_ROOT" show "$commit^:$rel" >"$rel" || return 1
159
-
160
- echo "$rel"
161
- }
162
-
163
- # Stages one step of a scenario arm into the sandbox working directory.
164
- # Scenarios call this once per step so their own git operations stay between
165
- # the steps, where they are visible.
166
- stage_fixtures() {
167
- local category="$1"
168
- local scenario="$2"
169
- local arm="$3"
170
- local stage="$4"
171
-
172
- local stage_dir
173
- stage_dir="$(fixture_stage_dir "$category" "$scenario" "$arm" "$stage")"
174
-
175
- if [ ! -d "$stage_dir" ]; then
176
- log_error "Fixture stage not found: $category/$scenario/$arm/$stage"
177
- fi
178
-
179
- assert_fixtures_suffixed "$stage_dir/create"
180
- assert_fixtures_suffixed "$stage_dir/append"
181
-
182
- local staged
183
- staged=$(($(count_fixture_files "$stage_dir/create") + \
184
- $(count_fixture_files "$stage_dir/append")))
185
- if [ "$staged" -eq 0 ]; then
186
- log_error "Fixture stage provisions nothing: $category/$scenario/$arm/$stage"
187
- fi
188
-
189
- create_from_fixtures "$stage_dir/create"
190
- append_from_fixtures "$stage_dir/append"
191
- }
@@ -1,125 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # The sandbox reaches GitHub over HTTPS with the gh token so a headless run needs
4
- # no SSH key and no agent. `gh auth login` authenticates the CLI but leaves git
5
- # itself without a credential, so the harness supplies the helper rather than
6
- # assuming the machine carries one.
7
- SANDBOX_GIT_CREDENTIAL_HELPER='!gh auth git-credential'
8
-
9
- # Call from the main shell before any scenario runs. sandbox_anchor_url is used
10
- # inside command substitutions, where log_error would exit only the subshell and
11
- # leave the caller building an empty remote, so the run has to stop here instead.
12
- require_sandbox_anchor_config() {
13
- if [ -z "${GITHUB_ORG:-}" ]; then
14
- log_error "GITHUB_ORG is empty. Export GITHUB_ORG=<org>, or run from a clone whose remote.origin.url points at GitHub."
15
- fi
16
- }
17
-
18
- resolve_sandbox_anchor_repo() {
19
- printf '%s\n' "${1:-${ANCHOR_REPO:-}}"
20
- }
21
-
22
- sandbox_anchor_url() {
23
- local repo_name
24
- repo_name="$(resolve_sandbox_anchor_repo "$@")"
25
-
26
- if [ -z "$repo_name" ] || [ -z "${GITHUB_ORG:-}" ]; then
27
- log_error "sandbox_anchor_url needs GITHUB_ORG and a repository name. Call require_sandbox_anchor_config first."
28
- fi
29
-
30
- printf 'https://github.com/%s/%s.git\n' "$GITHUB_ORG" "$repo_name"
31
- }
32
-
33
- # credential.helper is multi-valued and git concatenates it across system, global,
34
- # and local, trying each in order until one returns a credential. A plain set
35
- # leaves an operator's existing helper ahead of this one, so a stale token wins
36
- # and gh never runs. The empty value resets the inherited list first.
37
- configure_sandbox_git_credentials() {
38
- git config credential.helper ""
39
- git config --add credential.helper "$SANDBOX_GIT_CREDENTIAL_HELPER"
40
- }
41
-
42
- resolve_sandbox_git_identity() {
43
- SANDBOX_GIT_NAME="${SANDBOX_GIT_NAME:-$(git config --global user.name 2>/dev/null || true)}"
44
- SANDBOX_GIT_EMAIL="${SANDBOX_GIT_EMAIL:-$(git config --global user.email 2>/dev/null || true)}"
45
-
46
- : "${SANDBOX_GIT_NAME:=canon-sandbox}"
47
- : "${SANDBOX_GIT_EMAIL:=sandbox@example.com}"
48
-
49
- export SANDBOX_GIT_NAME SANDBOX_GIT_EMAIL
50
- }
51
-
52
- configure_sandbox_git_identity() {
53
- resolve_sandbox_git_identity
54
- git config user.name "$SANDBOX_GIT_NAME"
55
- git config user.email "$SANDBOX_GIT_EMAIL"
56
- }
57
-
58
- # Every scenario that needs a remote points at the same throwaway repository, so
59
- # the name lives here rather than in each one.
60
- SANDBOX_ANCHOR_REPO="canon-sandbox"
61
-
62
- # A scenario calls this from its own use_anchor hook rather than this file
63
- # defining the hook. sandbox-hook.sh keys off `type -t use_anchor`, so
64
- # declaring it here would hand an anchor to the scenarios that source this file
65
- # for the identity helpers alone.
66
- use_sandbox_anchor() {
67
- export ANCHOR_REPO="${1:-$SANDBOX_ANCHOR_REPO}"
68
- }
69
-
70
- # gh answers an absent repository and an unreachable host with the same exit
71
- # status, so the 404 is the only thing separating them. Anything else is a
72
- # network or credential fault, where creating a repository would be the wrong
73
- # answer and would fail for the same reason the read did.
74
- ensure_sandbox_anchor_repo() {
75
- local repo_name gh_error
76
-
77
- repo_name="$(resolve_sandbox_anchor_repo "$@")"
78
-
79
- if [ -z "$repo_name" ] || [ -z "${GITHUB_ORG:-}" ]; then
80
- log_error "ensure_sandbox_anchor_repo needs GITHUB_ORG and a repository name. Call require_sandbox_anchor_config first."
81
- fi
82
-
83
- if gh_error="$(gh api "repos/${GITHUB_ORG}/${repo_name}" --silent 2>&1)"; then
84
- return 0
85
- fi
86
-
87
- case "$gh_error" in
88
- *"HTTP 404"*) ;;
89
- *) log_error "Cannot reach ${GITHUB_ORG}/${repo_name}: ${gh_error}" ;;
90
- esac
91
-
92
- # An absent anchor is nearly always a wrong GITHUB_ORG or a rename nobody
93
- # performed, and provisioning stages a fixture rather than cloning while every
94
- # scenario force-pushes to main, so a repository created here would carry all
95
- # nine to a pass against something that is not the anchor. Refusing is the safe
96
- # default and creating is the opt-in, the shape `canon tooling sync --write`
97
- # already sets. `canon records push` refuses outright for the reason
98
- # `canon/context/development/records.md` records, so the two still differ.
99
- # The sibling SANDBOX_ flags are presence tests, so any non-empty value turns
100
- # them on. This one allowlists instead, because a presence test would have
101
- # SANDBOX_ANCHOR_CREATE=false provisioning a repository. Both spellings are
102
- # accepted so the `true` those siblings are set to does not land on a refusal.
103
- case "${SANDBOX_ANCHOR_CREATE:-}" in
104
- 1 | true) ;;
105
- *) log_error "${GITHUB_ORG}/${repo_name} does not exist. Check GITHUB_ORG and whether a rename is pending, then create it with 'gh repo create ${GITHUB_ORG}/${repo_name} --private' or re-run with SANDBOX_ANCHOR_CREATE=true." ;;
106
- esac
107
-
108
- log_warn "${GITHUB_ORG}/${repo_name} does not exist and SANDBOX_ANCHOR_CREATE is set. Creating it as private."
109
- if ! gh_error="$(gh repo create "${GITHUB_ORG}/${repo_name}" --private 2>&1)"; then
110
- log_error "Could not create ${GITHUB_ORG}/${repo_name}: ${gh_error}"
111
- fi
112
- log_info "Created ${GITHUB_ORG}/${repo_name} as a private repository."
113
- }
114
-
115
- # A remote is useless without an author, so the scenarios that reach one always
116
- # configure both. configure_sandbox_git_identity stays callable on its own for
117
- # the scenarios that never push. The probe runs first so an absent remote is
118
- # reported before the scenario stages anything. The remove keeps this idempotent
119
- # against a sandbox tree that already carries an origin.
120
- configure_sandbox_anchor_remote() {
121
- ensure_sandbox_anchor_repo "$@"
122
- configure_sandbox_git_identity
123
- git remote remove origin 2>/dev/null || true
124
- git remote add origin "$(sandbox_anchor_url "$@")"
125
- }
@@ -1,50 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # The sandbox tree lives outside the toolkit worktree. `canon sandbox run`
4
- # sets cwd to it for `claude -p`, and every `CLAUDE.md` between that cwd and the
5
- # filesystem root loads into the session. Under the repository the toolkit's own
6
- # instructions join that chain beside the seeded copy the scenario installed,
7
- # both carry the rule sending shared session scratch to the main worktree root,
8
- # and nothing decides which root wins. A session picking the toolkit writes its
9
- # output where no manifest reads it, so the run reports success while the verdict
10
- # reports no writes at all. The retired eval runner kept its fixture outside the
11
- # repository for the same reason.
12
- #
13
- # Mints a short random per-run identifier the first time it is asked for, then
14
- # holds it in CANON_SANDBOX_RUN_ID for the rest of this process. A direct call
15
- # (not `$(...)`) exports it into the caller's own shell. A call already carrying
16
- # the variable, inherited from a parent such as `canon sandbox run`, reuses it
17
- # rather than minting a new one, which is what keeps a hook on the parent's tree.
18
- #
19
- # Twin of `mintSandboxRunId` in `src/sandbox/tree.ts`.
20
- mint_sandbox_run_id() {
21
- if [ -z "${CANON_SANDBOX_RUN_ID:-}" ]; then
22
- CANON_SANDBOX_RUN_ID="$(date +%s)-$$-$RANDOM"
23
- fi
24
- export CANON_SANDBOX_RUN_ID
25
- }
26
-
27
- # The base every per-run tree nests under, with no run id appended. Split out
28
- # of `resolve_sandbox_dir` so a caller that needs to recognize any run's tree,
29
- # such as `require_project_root` in `scripts/lib/ui.sh`, tests against this
30
- # prefix instead of a single resolved path that changes on every call.
31
- sandbox_dir_prefix() {
32
- printf '%s/canon/sandbox\n' "${XDG_STATE_HOME:-$HOME/.local/state}"
33
- }
34
-
35
- # Twin of `sandboxTree` in `src/sandbox/tree.ts`. The exec boundary rules out
36
- # a shared constant, so a change to the default lands on both sides.
37
- #
38
- # A bare fall-through with no per-run component is one path per machine, so
39
- # two sessions resolving the default at once would provision over each other
40
- # with neither told. `mint_sandbox_run_id` is what makes two such sessions
41
- # land on two different trees rather than one.
42
- resolve_sandbox_dir() {
43
- if [ -n "${CANON_SANDBOX_DIR:-}" ]; then
44
- printf '%s\n' "$CANON_SANDBOX_DIR"
45
- return 0
46
- fi
47
-
48
- mint_sandbox_run_id
49
- printf '%s-%s\n' "$(sandbox_dir_prefix)" "$CANON_SANDBOX_RUN_ID"
50
- }
@@ -1,99 +0,0 @@
1
- #!/usr/bin/env bash
2
- # No `-u`. Scenarios ran under the retired dispatcher without it and read unset
3
- # variables as empty, so turning it on here changes what they provision.
4
- set -e
5
- set -o pipefail
6
-
7
- # Runs one scenario's hooks for the provisioning order in
8
- # `src/sandbox/provision.ts`. Scenarios and the library they call stay bash, so
9
- # the harness reaches them through this entry and nothing else.
10
- #
11
- # probe <scenario> runs `use_config` and `use_anchor` when declared
12
- # and prints every export they changed, closed by an
13
- # `@anchor=<0|1>` record
14
- # stage <scenario> <file> re-runs both hooks, runs `stage_setup` inside the
15
- # tree, and writes the exports it changed to <file>,
16
- # or an `@exited` record when the shell exits first
17
- #
18
- # Pairs are NUL-separated `NAME=value`, since a value can hold a newline. The
19
- # retired dispatcher ran `stage_setup` in its own shell, so an export, an `exit`,
20
- # or an `exec` there reached the harness, and the report carries all three: an
21
- # `exec` skips the EXIT trap and leaves no report at all. The stage re-runs the
22
- # hooks rather than receiving the probe's pairs, since they only export.
23
-
24
- SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
25
- PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
26
- export PROJECT_ROOT
27
-
28
- # Globals carry a prefix, since the scenario sourced below shares this shell.
29
- _hook_die() {
30
- echo "sandbox-hook: $1" >&2
31
- exit 2
32
- }
33
- _hook_mode="${1:-}"
34
- _hook_scenario="${2:-}"
35
- _hook_report="${3:-}"
36
-
37
- [[ "$_hook_mode" == "probe" || "$_hook_mode" == "stage" ]] || _hook_die "mode must be probe or stage, got: ${_hook_mode:-<empty>}"
38
- [ -f "$_hook_scenario" ] || _hook_die "no scenario file at: ${_hook_scenario:-<empty>}"
39
- [ "$_hook_mode" = "probe" ] || [ -n "$_hook_report" ] || _hook_die "stage needs a file to report its exports to"
40
- [ "$_hook_mode" = "probe" ] || trap 'printf "@exited\0" >"$_hook_report"' EXIT
41
-
42
- declare -A _hook_before
43
- _hook_snapshot() {
44
- local name
45
- _hook_before=()
46
- for name in $(compgen -e); do
47
- _hook_before[$name]="${!name}"
48
- done
49
- }
50
-
51
- # The shell moves PWD and OLDPWD itself on every `cd`, and neither is state.
52
- _hook_changed() {
53
- local name
54
- for name in $(compgen -e); do
55
- [[ "$name" == "PWD" || "$name" == "OLDPWD" ]] && continue
56
- if [[ ! -v _hook_before[$name] ]] || [ "${_hook_before[$name]}" != "${!name}" ]; then
57
- printf '%s=%s\0' "$name" "${!name}"
58
- fi
59
- done
60
- }
61
-
62
- # Taken before `config.sh`, so a `GITHUB_ORG` it derives from the remote reaches
63
- # the harness as an export like any other.
64
- _hook_snapshot
65
-
66
- source "$PROJECT_ROOT/scripts/config.sh"
67
- source "$PROJECT_ROOT/scripts/lib/ui.sh"
68
- source "$PROJECT_ROOT/scripts/lib/sandbox-path.sh"
69
- source "$PROJECT_ROOT/scripts/lib/sandbox-git.sh"
70
- source "$PROJECT_ROOT/scripts/lib/sandbox-fixtures.sh"
71
-
72
- # Unexported, as the dispatcher held them, so a scenario reads both while a
73
- # child it spawns sees neither. Shellcheck cannot see the scenario reading them.
74
- # shellcheck disable=SC2034
75
- SANDBOX="$(resolve_sandbox_dir)" SANDBOX_DIR="$PROJECT_ROOT/sandbox"
76
-
77
- # shellcheck source=/dev/null
78
- source "$_hook_scenario"
79
-
80
- _hook_declared() { [[ "$(type -t "$1")" == "function" ]]; }
81
- _hook_run() {
82
- if _hook_declared use_config; then use_config; fi
83
- if _hook_declared use_anchor; then use_anchor; fi
84
- }
85
-
86
- if [ "$_hook_mode" = "probe" ]; then
87
- # Stdout carries the pairs alone, so a hook that prints is moved off it.
88
- _hook_run >&2
89
- _hook_changed
90
- if _hook_declared use_anchor; then printf '@anchor=1\0'; else printf '@anchor=0\0'; fi
91
- exit 0
92
- fi
93
-
94
- _hook_run
95
- _hook_snapshot
96
- cd "$SANDBOX"
97
- stage_setup
98
- trap - EXIT
99
- _hook_changed >"$_hook_report"