@erclx/canon 5.26.0 → 5.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/SKILL.md +2 -0
- package/claude/skills/auto-ship/SKILL.md +3 -11
- package/claude/skills/auto-ship/references/review-findings.md +20 -0
- package/claude/skills/backlog-triage/SKILL.md +2 -0
- package/claude/skills/bash-cli-script/SKILL.md +2 -0
- package/claude/skills/build-in-slices/SKILL.md +2 -0
- package/claude/skills/canon-cli/SKILL.md +2 -0
- package/claude/skills/canon-feedback/SKILL.md +2 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +2 -0
- package/claude/skills/canon-operator/SKILL.md +2 -0
- package/claude/skills/canon-rollout/SKILL.md +3 -11
- package/claude/skills/canon-rollout/references/failure-recovery.md +20 -0
- package/claude/skills/canvas/SKILL.md +13 -0
- package/claude/skills/ci-workflow/SKILL.md +2 -0
- package/claude/skills/code-craft/SKILL.md +2 -0
- package/claude/skills/codebase-layout/SKILL.md +2 -0
- package/claude/skills/context-fold/SKILL.md +3 -17
- package/claude/skills/context-fold/references/completion-report.md +26 -0
- package/claude/skills/create-rule/SKILL.md +2 -0
- package/claude/skills/create-skill/SKILL.md +2 -0
- package/claude/skills/create-standard/SKILL.md +2 -0
- package/claude/skills/decision-escalate/SKILL.md +2 -0
- package/claude/skills/deploy-app/SKILL.md +2 -0
- package/claude/skills/deprecation-migration/SKILL.md +2 -0
- package/claude/skills/design-extract/SKILL.md +2 -0
- package/claude/skills/design-taste/SKILL.md +2 -0
- package/claude/skills/docs-sync/SKILL.md +2 -0
- package/claude/skills/document-health/SKILL.md +2 -0
- package/claude/skills/draft-and-pick/SKILL.md +2 -0
- package/claude/skills/draft-doc/SKILL.md +2 -0
- package/claude/skills/draft-doc/references/wiki.md +1 -1
- package/claude/skills/draft-figure/SKILL.md +2 -0
- package/claude/skills/draft-identity/SKILL.md +2 -0
- package/claude/skills/draft-ready/SKILL.md +5 -1
- package/claude/skills/draft-screencast/SKILL.md +2 -0
- package/claude/skills/draft-slides/SKILL.md +2 -0
- package/claude/skills/git-branch/SKILL.md +2 -0
- package/claude/skills/git-commit/SKILL.md +2 -0
- package/claude/skills/git-followup/SKILL.md +2 -0
- package/claude/skills/git-issue/SKILL.md +2 -0
- package/claude/skills/git-pr/SKILL.md +2 -0
- package/claude/skills/git-pr/references/evidence.md +7 -7
- package/claude/skills/git-ship/SKILL.md +2 -0
- package/claude/skills/git-split/SKILL.md +2 -0
- package/claude/skills/git-stage/SKILL.md +2 -0
- package/claude/skills/git-worktree/SKILL.md +2 -0
- package/claude/skills/index-lookup/SKILL.md +2 -0
- package/claude/skills/markdown-propose/SKILL.md +2 -0
- package/claude/skills/memory-capture/SKILL.md +2 -0
- package/claude/skills/memory-review/SKILL.md +2 -0
- package/claude/skills/plan-feature/SKILL.md +2 -0
- package/claude/skills/plan-groundwork/SKILL.md +2 -0
- package/claude/skills/plan-intake/SKILL.md +2 -0
- package/claude/skills/plan-intake-answer/SKILL.md +2 -0
- package/claude/skills/project-commands/SKILL.md +2 -0
- package/claude/skills/read-frames/SKILL.md +2 -0
- package/claude/skills/record-screencast/SKILL.md +2 -0
- package/claude/skills/repo-metadata/SKILL.md +2 -0
- package/claude/skills/restate-plainly/SKILL.md +2 -0
- package/claude/skills/review-address/REQUIREMENT.md +4 -2
- package/claude/skills/review-address/SKILL.md +19 -14
- package/claude/skills/review-address/references/post-review-findings.md +16 -0
- package/claude/skills/review-address/references/rebase-conflicts.md +4 -2
- package/claude/skills/review-branch/SKILL.md +2 -0
- package/claude/skills/review-craft/SKILL.md +2 -0
- package/claude/skills/review-craft/references/ui.md +6 -6
- package/claude/skills/review-pr/SKILL.md +4 -8
- package/claude/skills/review-pr/references/body-variants.md +52 -0
- package/claude/skills/review-pr/references/key-changes.md +16 -0
- package/claude/skills/review-pr/references/post.md +15 -43
- package/claude/skills/review-ui/REQUIREMENT.md +2 -2
- package/claude/skills/review-ui/SKILL.md +16 -12
- package/claude/skills/review-ui/references/driving.md +8 -8
- package/claude/skills/review-ui/references/post.md +13 -3
- package/claude/skills/review-ui/references/tick.md +22 -0
- package/claude/skills/role-orchestrator/REQUIREMENT.md +15 -15
- package/claude/skills/role-orchestrator/SKILL.md +31 -42
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +18 -16
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +13 -3
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +6 -34
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +4 -4
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +5 -5
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +12 -23
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +6 -6
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +38 -0
- package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
- package/claude/skills/role-orchestrator/scripts/poll.ts +304 -10
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/role-planner/SKILL.md +4 -2
- package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
- package/claude/skills/role-reviewer/SKILL.md +3 -1
- package/claude/skills/role-worker/SKILL.md +5 -2
- package/claude/skills/search-craft/SKILL.md +2 -0
- package/claude/skills/seed-sync/SKILL.md +2 -0
- package/claude/skills/session-compact/SKILL.md +2 -0
- package/claude/skills/session-map/SKILL.md +2 -0
- package/claude/skills/session-relay/SKILL.md +2 -0
- package/claude/skills/session-resume/SKILL.md +2 -0
- package/claude/skills/session-worktree/SKILL.md +3 -11
- package/claude/skills/session-worktree/references/install-command.md +20 -0
- package/claude/skills/sketch-design/SKILL.md +2 -0
- package/claude/skills/systematic-debugging/SKILL.md +2 -0
- package/claude/skills/target-check/SKILL.md +2 -0
- package/claude/skills/target-setup/SKILL.md +2 -0
- package/claude/skills/task-board/SKILL.md +2 -0
- package/claude/skills/teach-workspace/SKILL.md +5 -15
- package/claude/skills/teach-workspace/references/lesson-build.md +75 -0
- package/claude/skills/teach-workspace/references/lesson-craft.md +13 -69
- package/claude/skills/teach-workspace/references/revisit.md +22 -0
- package/claude/skills/test-craft/SKILL.md +2 -0
- package/claude/skills/test-first/SKILL.md +2 -0
- package/claude/skills/ui-checklist/SKILL.md +3 -1
- package/claude/skills/ux-audit/SKILL.md +2 -0
- package/claude/skills/ux-measure/SKILL.md +2 -0
- package/claude/skills/ux-walkthrough/SKILL.md +2 -0
- package/claude/skills/video-craft/SKILL.md +2 -0
- package/claude/skills/write-human/SKILL.md +2 -0
- package/claude/skills/youtube-transcripts/SKILL.md +2 -0
- package/docs/agents/canvas.md +24 -8
- package/docs/agents/capture.md +5 -5
- package/docs/agents/commands.md +2 -2
- package/docs/agents/gate.md +3 -1
- package/docs/agents/index.md +5 -4
- package/docs/agents/install-and-sync.md +7 -0
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/pr-evidence.md +47 -23
- package/docs/agents/pr-local.md +19 -13
- package/docs/agents/pr-preview.md +44 -21
- package/docs/agents/pr-tick.md +72 -0
- package/docs/agents/sandbox.md +30 -6
- package/docs/agents/scripting.md +8 -1
- package/docs/agents/skills-audit.md +2 -1
- package/docs/agents/tasks-plans.md +1 -1
- package/docs/agents/tasks-validate.md +18 -18
- package/docs/agents/tasks.md +1 -1
- package/docs/workflow/operating-model.md +15 -21
- package/docs/workflow/parallel-features.md +24 -3
- package/docs/workflow/skill-map.md +3 -1
- package/docs/workflow/zshrc-aliases.md +1 -1
- package/package.json +1 -1
- package/scripts/core/regen-hero.ts +1 -1
- package/scripts/lib/sandbox-path.sh +6 -162
- package/src/audits/baseline.ts +1 -1
- package/src/audits/run.ts +2 -2
- package/src/canvas/address.ts +12 -3
- package/src/canvas/capture.ts +1 -1
- package/src/canvas/client/inspector/align-grid.tsx +162 -0
- package/src/canvas/client/inspector/color-field.tsx +201 -171
- package/src/canvas/client/inspector/color-inputs.tsx +139 -0
- package/src/canvas/client/inspector/color-picker.tsx +668 -0
- package/src/canvas/client/inspector/color-space.ts +240 -0
- package/src/canvas/client/inspector/field.tsx +66 -3
- package/src/canvas/client/inspector/fields.css +473 -6
- package/src/canvas/client/inspector/size-field.tsx +69 -0
- package/src/canvas/client/inspector/token-select.tsx +109 -0
- package/src/canvas/client/inspector/values.ts +117 -3
- package/src/canvas/client/inspector.tsx +311 -15
- package/src/canvas/client/keys.ts +54 -0
- package/src/canvas/client/selection.tsx +199 -0
- package/src/canvas/client/shell.css +210 -12
- package/src/canvas/client/state.ts +165 -14
- package/src/canvas/client/surface.tsx +341 -39
- package/src/canvas/client/tools.tsx +64 -0
- package/src/canvas/content.ts +51 -2
- package/src/canvas/edit.ts +49 -6
- package/src/canvas/editing.ts +203 -0
- package/src/canvas/global.d.ts +15 -0
- package/src/canvas/server.ts +149 -12
- package/src/canvas/tokens.ts +60 -18
- package/src/census/count.ts +1 -1
- package/src/claude/seeds.ts +3 -3
- package/src/claude/skills-audit.ts +28 -0
- package/src/claude/skills-drift.ts +1 -1
- package/src/claude/skills-families.ts +36 -0
- package/src/claude/skills-list.ts +76 -15
- package/src/claude/skills-reach.ts +1 -1
- package/src/commands/audits.ts +2 -2
- package/src/commands/canvas.ts +180 -3
- package/src/commands/claude.ts +70 -12
- package/src/commands/design.ts +145 -5
- package/src/commands/docs.ts +1 -1
- package/src/commands/feedback.ts +2 -2
- package/src/commands/gate.ts +9 -2
- package/src/commands/gov.ts +3 -3
- package/src/commands/hooks.ts +1 -1
- package/src/commands/indexes.ts +1 -1
- package/src/commands/init.ts +2 -2
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +2 -2
- package/src/commands/pr.ts +288 -65
- package/src/commands/records.ts +1 -1
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +64 -15
- package/src/commands/sessions.ts +1 -1
- package/src/commands/slides.ts +1 -1
- package/src/commands/sync.ts +2 -2
- package/src/commands/tasks.ts +21 -4
- package/src/commands/teach.ts +1 -1
- package/src/commands/tooling.ts +1 -1
- package/src/commands/transcripts.ts +2 -2
- package/src/commands/upgrade.ts +1 -1
- package/src/commands/wiki.ts +1 -1
- package/src/commands/worktrees.ts +1 -1
- package/src/comments/scan.ts +1 -1
- package/src/comments/trend.ts +1 -1
- package/src/context/architecture.ts +1 -1
- package/src/context/citations.ts +3 -3
- package/src/context/classify/extract.ts +3 -3
- package/src/context/folders.ts +2 -2
- package/src/context/requirements.ts +1 -1
- package/src/design/project-fonts.ts +392 -0
- package/src/docs/read.ts +1 -1
- package/src/exec.ts +1 -1
- package/src/feedback/github.ts +2 -2
- package/src/gate/measures.ts +48 -10
- package/src/gate/sequencer.ts +56 -4
- package/src/gate/stages.ts +17 -0
- package/src/gate/suite-lock.ts +214 -0
- package/src/{git-files.ts → git/files.ts} +2 -2
- package/src/{git-ignore.ts → git/ignore.ts} +1 -1
- package/src/{worktree.ts → git/worktree.ts} +1 -1
- package/src/gov/citations.ts +1 -1
- package/src/gov/install.ts +1 -1
- package/src/gov/superseded.ts +1 -1
- package/src/gov/test-order.ts +2 -2
- package/src/help.ts +1 -1
- package/src/hooks/post-merge.ts +1 -1
- package/src/indexes/walk.ts +1 -1
- package/src/intake/folder.ts +1 -1
- package/src/labels/audit.ts +1 -1
- package/src/labels/map.ts +1 -1
- package/src/labels/phase.ts +3 -3
- package/src/markdown/files.ts +1 -1
- package/src/migrate/apply.ts +1 -1
- package/src/migrate/record-layout.ts +1 -1
- package/src/migrate/records.ts +11 -11
- package/src/migrate/scratch-evidence.ts +1 -1
- package/src/migrate/surface-roots.ts +6 -6
- package/src/pr/bijection.ts +1 -1
- package/src/pr/evidence.ts +250 -59
- package/src/pr/local.ts +16 -10
- package/src/pr/preview.ts +78 -2
- package/src/pr/tick.ts +56 -0
- package/src/process/harness.ts +1 -1
- package/src/records/backup.ts +2 -2
- package/src/records/ordinal.ts +1 -1
- package/src/records/prune.ts +1 -1
- package/src/records/size.ts +1 -1
- package/src/records/stale-canonical.ts +2 -2
- package/src/records/stale.ts +1 -1
- package/src/records/validate.ts +2 -2
- package/src/{project-root.ts → roots/project.ts} +1 -1
- package/src/{record-root.ts → roots/record.ts} +1 -1
- package/src/sandbox/coverage.ts +11 -12
- package/src/sandbox/equivalence.ts +57 -26
- package/src/sandbox/headless/dispatch.ts +264 -0
- package/src/sandbox/headless/run.ts +653 -0
- package/src/sandbox/headless/watch.ts +145 -0
- package/src/sandbox/hooks.ts +1 -1
- package/src/sandbox/provision.ts +90 -29
- package/src/sandbox/scenario.ts +588 -0
- package/src/sandbox/tree.ts +4 -8
- package/src/secrets/scan.ts +1 -1
- package/src/sessions/claim.ts +1 -1
- package/src/sessions/live.ts +4 -1
- package/src/sessions/resolve.ts +1 -1
- package/src/sessions/transfer/bundle.ts +2 -2
- package/src/slides/decks.ts +1 -1
- package/src/slides/package/fonts.ts +6 -2
- package/src/standards/audit.ts +2 -2
- package/src/standards/read.ts +1 -1
- package/src/sync/check.ts +2 -2
- package/src/sync/engine.ts +3 -3
- package/src/sync/git.ts +1 -1
- package/src/sync/history.ts +1 -1
- package/src/sync/layout.ts +2 -2
- package/src/sync/reverse.ts +1 -1
- package/src/sync/stamp.ts +1 -1
- package/src/sync/target.ts +1 -1
- package/src/sync/workflow.ts +1 -1
- package/src/targets/pulls.ts +2 -2
- package/src/targets/sweep.ts +2 -2
- package/src/{target.ts → targets/validate.ts} +1 -1
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/tasks/label.ts +1 -1
- package/src/tasks/reach.ts +2 -2
- package/src/tasks/trunk.ts +1 -1
- package/src/tasks/validate.ts +38 -14
- package/src/teach/nav.ts +1 -1
- package/src/teach/workspace.ts +1 -1
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/read.ts +1 -1
- package/src/tooling/scan.ts +1 -1
- package/src/tooling/subfolder.ts +1 -1
- package/src/version/installed.ts +1 -1
- package/src/version/skew.ts +1 -1
- package/src/web/readme-citations.ts +1 -1
- package/src/worktrees/bare-flag.ts +1 -1
- package/src/worktrees/reclaim.ts +2 -2
- package/src/worktrees/remove.ts +1 -1
- package/standards/board.md +3 -3
- package/standards/canvas.md +2 -1
- package/standards/plan.md +13 -7
- package/standards/skill.md +1 -0
- package/standards/wiki.md +12 -3
- package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
- package/tsconfig.json +1 -1
- package/scripts/lib/sandbox-dispatch.sh +0 -190
- /package/src/{client-commands.ts → gate/client-commands.ts} +0 -0
- /package/src/{git-base.ts → git/base.ts} +0 -0
- /package/src/{git-env.ts → git/env.ts} +0 -0
- /package/src/{legacy-path.ts → roots/legacy.ts} +0 -0
- /package/src/{surface-root.ts → roots/surface.ts} +0 -0
- /package/src/{copy.ts → sync/copy.ts} +0 -0
|
@@ -40,19 +40,33 @@ export interface Row {
|
|
|
40
40
|
passAt: number
|
|
41
41
|
prior: string
|
|
42
42
|
replyAt: number
|
|
43
|
+
/** What the pull request writes, null when its diff could not be read. */
|
|
44
|
+
paths: null | string[]
|
|
43
45
|
resp: number
|
|
46
|
+
stale: Staleness
|
|
47
|
+
staleFiles: string[]
|
|
44
48
|
ui: string
|
|
45
49
|
unmatchedCount: number
|
|
46
50
|
unmatchedHeading: string
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Whether the base moved under a path the pull request writes since the commit
|
|
55
|
+
* it branched from. `unknown` is a written set this run could not read, which
|
|
56
|
+
* must never pass for `fresh`.
|
|
57
|
+
*/
|
|
58
|
+
export type Staleness = 'fresh' | 'stale' | 'unknown'
|
|
59
|
+
|
|
49
60
|
/** One baseline line, kept as the strings it was written with. */
|
|
50
61
|
export interface Baseline {
|
|
51
62
|
head: string
|
|
52
63
|
merges: string
|
|
53
64
|
number: string
|
|
65
|
+
/** Open pull requests this one conflicts with, comma-separated, or `none`. */
|
|
66
|
+
overlaps: string
|
|
54
67
|
prior: string
|
|
55
68
|
resp: string
|
|
69
|
+
stale: string
|
|
56
70
|
state: string
|
|
57
71
|
ui: string
|
|
58
72
|
unmatched: string
|
|
@@ -211,7 +225,7 @@ export const uiState = (payload: Payload, head: string): string => {
|
|
|
211
225
|
return `${heading}-${covered ? 'head' : 'behind'}-${commit.slice(0, 7)}`
|
|
212
226
|
}
|
|
213
227
|
|
|
214
|
-
/** Takes the first
|
|
228
|
+
/** Takes the first ten fields and ignores the rest, so a short or padded line still reads. */
|
|
215
229
|
export const parseBaseline = (line: string): Baseline => {
|
|
216
230
|
const fields = line.split(' ')
|
|
217
231
|
|
|
@@ -219,14 +233,21 @@ export const parseBaseline = (line: string): Baseline => {
|
|
|
219
233
|
head: fields[1] ?? '',
|
|
220
234
|
merges: fields[4] ?? '',
|
|
221
235
|
number: fields[0] ?? '',
|
|
236
|
+
overlaps: fields[9] ?? '',
|
|
222
237
|
prior: fields[2] ?? '',
|
|
223
238
|
resp: fields[3] ?? '',
|
|
239
|
+
stale: fields[8] ?? '',
|
|
224
240
|
state: fields[5] ?? '',
|
|
225
241
|
ui: fields[7] ?? '',
|
|
226
242
|
unmatched: fields[6] ?? '',
|
|
227
243
|
}
|
|
228
244
|
}
|
|
229
245
|
|
|
246
|
+
/**
|
|
247
|
+
* New fields append at the end, so every positional read of an older line still
|
|
248
|
+
* lands. A field an older line never carried stays empty and is not written
|
|
249
|
+
* back as a trailing space.
|
|
250
|
+
*/
|
|
230
251
|
export const formatBaseline = (entry: Baseline): string =>
|
|
231
252
|
[
|
|
232
253
|
entry.number,
|
|
@@ -237,10 +258,30 @@ export const formatBaseline = (entry: Baseline): string =>
|
|
|
237
258
|
entry.state,
|
|
238
259
|
entry.unmatched,
|
|
239
260
|
entry.ui,
|
|
240
|
-
|
|
261
|
+
entry.stale,
|
|
262
|
+
entry.overlaps,
|
|
263
|
+
]
|
|
264
|
+
.join(' ')
|
|
265
|
+
.trimEnd()
|
|
241
266
|
|
|
242
267
|
const short = (head: string): string => head.slice(0, 7)
|
|
243
268
|
|
|
269
|
+
const listFiles = (files: string[]): string =>
|
|
270
|
+
files.length > 3
|
|
271
|
+
? `${files.slice(0, 3).join(', ')}, +${files.length - 3} more`
|
|
272
|
+
: files.join(', ')
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* A conflict already sends the branch to its owner for a rebase, so a pull
|
|
276
|
+
* request reading both reports the conflict alone and earns one handback.
|
|
277
|
+
*/
|
|
278
|
+
const staleLine = (row: Row, base: string): string[] =>
|
|
279
|
+
row.stale === 'stale' && row.merges !== 'conflict'
|
|
280
|
+
? [
|
|
281
|
+
`STALE #${row.number} ${base} changed ${row.staleFiles.length} file(s) it writes since its base: ${listFiles(row.staleFiles)}`,
|
|
282
|
+
]
|
|
283
|
+
: []
|
|
284
|
+
|
|
244
285
|
export interface Classification {
|
|
245
286
|
baseline: Baseline
|
|
246
287
|
isChanged: boolean
|
|
@@ -266,8 +307,10 @@ const classifyFirstSighting = (
|
|
|
266
307
|
head: row.head,
|
|
267
308
|
merges: row.merges,
|
|
268
309
|
number: row.number,
|
|
310
|
+
overlaps: 'none',
|
|
269
311
|
prior: row.prior,
|
|
270
312
|
resp: String(row.resp),
|
|
313
|
+
stale: row.stale,
|
|
271
314
|
state: row.heading,
|
|
272
315
|
ui: row.ui,
|
|
273
316
|
unmatched: String(row.unmatchedCount),
|
|
@@ -280,7 +323,11 @@ const classifyFirstSighting = (
|
|
|
280
323
|
line = `OPENED #${row.number} at ${at}`
|
|
281
324
|
}
|
|
282
325
|
|
|
283
|
-
return {
|
|
326
|
+
return {
|
|
327
|
+
baseline,
|
|
328
|
+
isChanged: true,
|
|
329
|
+
lines: [line, ...staleLine(row, context.base)],
|
|
330
|
+
}
|
|
284
331
|
}
|
|
285
332
|
|
|
286
333
|
const classifyUi = (row: Row, old: Baseline): string[] => {
|
|
@@ -312,11 +359,23 @@ export const classify = (
|
|
|
312
359
|
let state = row.heading
|
|
313
360
|
|
|
314
361
|
// A conflict arrives from the base moving, not from the branch, so it is
|
|
315
|
-
// reported on the transition rather than only when the head changes.
|
|
316
|
-
|
|
362
|
+
// reported on the transition rather than only when the head changes. A push
|
|
363
|
+
// that leaves it conflicting reports it again, for the same reason STALE does.
|
|
364
|
+
if (
|
|
365
|
+
row.merges === 'conflict' &&
|
|
366
|
+
(old.merges !== 'conflict' || row.head !== old.head)
|
|
367
|
+
) {
|
|
317
368
|
lines.push(`CONFLICT #${row.number} no longer merges into ${context.base}`)
|
|
318
369
|
}
|
|
319
370
|
|
|
371
|
+
// Staleness arrives the same way, and reports on its transition so a branch
|
|
372
|
+
// left behind across several runs earns one handback rather than one a run.
|
|
373
|
+
// A push that leaves it behind reports it again, since the draft lift reads a
|
|
374
|
+
// MOVED with no STALE beside it as the rebase having landed.
|
|
375
|
+
if (old.stale !== 'stale' || row.head !== old.head) {
|
|
376
|
+
lines.push(...staleLine(row, context.base))
|
|
377
|
+
}
|
|
378
|
+
|
|
320
379
|
// A rising count is what is new here. It fires on a tracked pull request
|
|
321
380
|
// only: a first sighting takes whatever already sits on the thread as its
|
|
322
381
|
// baseline rather than flagging history retroactively.
|
|
@@ -386,8 +445,10 @@ export const classify = (
|
|
|
386
445
|
head: row.head,
|
|
387
446
|
merges: row.merges,
|
|
388
447
|
number: row.number,
|
|
448
|
+
overlaps: old.overlaps,
|
|
389
449
|
prior: row.prior,
|
|
390
450
|
resp: String(row.resp),
|
|
451
|
+
stale: row.stale,
|
|
391
452
|
state,
|
|
392
453
|
ui: row.ui === '' ? old.ui : row.ui,
|
|
393
454
|
unmatched: String(row.unmatchedCount),
|
|
@@ -396,6 +457,29 @@ export const classify = (
|
|
|
396
457
|
return { baseline, isChanged: lines.length > 0, lines }
|
|
397
458
|
}
|
|
398
459
|
|
|
460
|
+
/**
|
|
461
|
+
* One pull request's base movement as a single line. STALE and CONFLICT print
|
|
462
|
+
* in the poll only on a transition or a push, so a reader deciding a draft
|
|
463
|
+
* lift reads this instead. A reading that could not be taken is unknown, never
|
|
464
|
+
* clear.
|
|
465
|
+
*/
|
|
466
|
+
export const standingLine = (
|
|
467
|
+
entry: Pick<Baseline, 'head' | 'merges' | 'number' | 'stale'>,
|
|
468
|
+
): string => {
|
|
469
|
+
const at = short(entry.head)
|
|
470
|
+
if (entry.merges === 'conflict') {
|
|
471
|
+
return `CONFLICT #${entry.number} standing at ${at}`
|
|
472
|
+
}
|
|
473
|
+
if (entry.stale === 'stale') {
|
|
474
|
+
return `STALE #${entry.number} standing at ${at}`
|
|
475
|
+
}
|
|
476
|
+
if (entry.merges === 'clean' && entry.stale === 'fresh') {
|
|
477
|
+
return `CLEAR #${entry.number} at ${at}`
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return `UNKNOWN #${entry.number} at ${at}, whether it merges or is current could not be read`
|
|
481
|
+
}
|
|
482
|
+
|
|
399
483
|
/**
|
|
400
484
|
* A pull request this run could not read keeps the line it had, so the GONE
|
|
401
485
|
* sweep does not read the gap as a merge. Its heading is blanked to a value no
|
|
@@ -532,6 +616,152 @@ const readMerges = (number: string, head: string, ref: string): string => {
|
|
|
532
616
|
: 'conflict'
|
|
533
617
|
}
|
|
534
618
|
|
|
619
|
+
interface Written {
|
|
620
|
+
forkPoint: string
|
|
621
|
+
paths: string[]
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const nonEmptyLines = (output: string): string[] =>
|
|
625
|
+
output.split('\n').filter((line) => line !== '')
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* The paths a pull request writes, read from its own diff against the commit it
|
|
629
|
+
* branched from. `--no-renames` lists both sides of a rename, since a change on
|
|
630
|
+
* the base to either one is a change under the branch. A head or base that does
|
|
631
|
+
* not resolve, which a fork or a deleted head leaves, reads as null.
|
|
632
|
+
*/
|
|
633
|
+
const readWritten = (head: string, ref: string): null | Written => {
|
|
634
|
+
const forkPoint = run('git', ['merge-base', ref, head])
|
|
635
|
+
if (!forkPoint.isOk) return null
|
|
636
|
+
const point = forkPoint.stdout.trim()
|
|
637
|
+
const diff = run('git', ['diff', '--name-only', '--no-renames', point, head])
|
|
638
|
+
if (!diff.isOk) return null
|
|
639
|
+
|
|
640
|
+
return { forkPoint: point, paths: nonEmptyLines(diff.stdout) }
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Re-testing after every merge to the base is a merge queue's cost without its
|
|
645
|
+
* batching, so only a change to a path the branch writes makes it stale. The
|
|
646
|
+
* net diff rather than the log is read, so a change the base made and then
|
|
647
|
+
* reverted moves nothing.
|
|
648
|
+
*/
|
|
649
|
+
const readStale = (
|
|
650
|
+
written: null | Written,
|
|
651
|
+
ref: string,
|
|
652
|
+
): { files: string[]; stale: Staleness } => {
|
|
653
|
+
if (written === null) return { files: [], stale: 'unknown' }
|
|
654
|
+
if (written.paths.length === 0) return { files: [], stale: 'fresh' }
|
|
655
|
+
|
|
656
|
+
const moved = run('git', [
|
|
657
|
+
'--literal-pathspecs',
|
|
658
|
+
'diff',
|
|
659
|
+
'--name-only',
|
|
660
|
+
'--no-renames',
|
|
661
|
+
written.forkPoint,
|
|
662
|
+
ref,
|
|
663
|
+
'--',
|
|
664
|
+
...written.paths,
|
|
665
|
+
])
|
|
666
|
+
if (!moved.isOk) return { files: [], stale: 'unknown' }
|
|
667
|
+
|
|
668
|
+
const files = nonEmptyLines(moved.stdout)
|
|
669
|
+
|
|
670
|
+
return { files, stale: files.length > 0 ? 'stale' : 'fresh' }
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export interface Overlap {
|
|
674
|
+
files: string[]
|
|
675
|
+
pair: readonly [string, string]
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Pairwise merge-tree grows with the square of open pull requests, so only a
|
|
680
|
+
* pair sharing a written path is tested. Each pair comes lower number first,
|
|
681
|
+
* which fixes the order its line and its baseline entry are read in.
|
|
682
|
+
*/
|
|
683
|
+
export const sharedPairs = (rows: Row[]): [Row, Row][] => {
|
|
684
|
+
const readable = rows
|
|
685
|
+
.filter((row) => row.paths !== null && row.paths.length > 0)
|
|
686
|
+
.toSorted((a, b) => Number(a.number) - Number(b.number))
|
|
687
|
+
const pairs: [Row, Row][] = []
|
|
688
|
+
for (const [index, first] of readable.entries()) {
|
|
689
|
+
const written = new Set(first.paths)
|
|
690
|
+
for (const second of readable.slice(index + 1)) {
|
|
691
|
+
if ((second.paths ?? []).some((path) => written.has(path))) {
|
|
692
|
+
pairs.push([first, second])
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return pairs
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const partnersOf = (entry: Baseline | undefined): string[] =>
|
|
701
|
+
entry === undefined || entry.overlaps === '' || entry.overlaps === 'none'
|
|
702
|
+
? []
|
|
703
|
+
: entry.overlaps.split(',')
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* A partner this run could not read keeps its place, so a pull request that
|
|
707
|
+
* drops out of one run and returns does not report the same pair twice.
|
|
708
|
+
*/
|
|
709
|
+
export const overlapPartners = (
|
|
710
|
+
number: string,
|
|
711
|
+
overlaps: Overlap[],
|
|
712
|
+
old: Baseline | undefined,
|
|
713
|
+
carried: Set<string>,
|
|
714
|
+
): string => {
|
|
715
|
+
const partners = new Set(
|
|
716
|
+
overlaps.flatMap(({ pair: [first, second] }) =>
|
|
717
|
+
first === number ? [second] : second === number ? [first] : [],
|
|
718
|
+
),
|
|
719
|
+
)
|
|
720
|
+
for (const partner of partnersOf(old)) {
|
|
721
|
+
if (carried.has(partner)) partners.add(partner)
|
|
722
|
+
}
|
|
723
|
+
const sorted = [...partners].toSorted((a, b) => Number(a) - Number(b))
|
|
724
|
+
|
|
725
|
+
return sorted.length === 0 ? 'none' : sorted.join(',')
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** A pair reports once, read off the lower-numbered side's baseline entry. */
|
|
729
|
+
export const overlapLines = (
|
|
730
|
+
overlaps: Overlap[],
|
|
731
|
+
known: Map<string, Baseline>,
|
|
732
|
+
): string[] =>
|
|
733
|
+
overlaps
|
|
734
|
+
.filter(
|
|
735
|
+
({ pair: [first, second] }) =>
|
|
736
|
+
!partnersOf(known.get(first)).includes(second),
|
|
737
|
+
)
|
|
738
|
+
.map(
|
|
739
|
+
({ files, pair: [first, second] }) =>
|
|
740
|
+
`OVERLAP #${first} #${second} conflict on ${listFiles(files)}`,
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Two pull request heads merged against each other, which is the merge the
|
|
745
|
+
* second to land will have to make. merge-tree exits 1 on a conflict and
|
|
746
|
+
* prints the tree then one conflicted path a line. Any other failure prints no
|
|
747
|
+
* tree, and reads as null beside a clean merge, since neither names a file.
|
|
748
|
+
*/
|
|
749
|
+
const readOverlap = (first: string, second: string): null | string[] => {
|
|
750
|
+
const merged = run('git', [
|
|
751
|
+
'merge-tree',
|
|
752
|
+
'--write-tree',
|
|
753
|
+
'--name-only',
|
|
754
|
+
'--no-messages',
|
|
755
|
+
first,
|
|
756
|
+
second,
|
|
757
|
+
])
|
|
758
|
+
if (merged.isOk) return null
|
|
759
|
+
const lines = nonEmptyLines(merged.stdout)
|
|
760
|
+
if (lines.length < 2) return null
|
|
761
|
+
|
|
762
|
+
return [...new Set(lines.slice(1))]
|
|
763
|
+
}
|
|
764
|
+
|
|
535
765
|
const buildRow = (
|
|
536
766
|
number: string,
|
|
537
767
|
payload: Payload,
|
|
@@ -545,17 +775,23 @@ const buildRow = (
|
|
|
545
775
|
run('canon', ['pr', 'review-state', number, '--json']).stdout,
|
|
546
776
|
)
|
|
547
777
|
const scope = scopeFromVerb(record, now) ?? scopeFallback(payload, now)
|
|
778
|
+
const merges = readMerges(number, head, ref)
|
|
779
|
+
const written = merges === 'unknown' ? null : readWritten(head, ref)
|
|
780
|
+
const stale = readStale(written, ref)
|
|
548
781
|
|
|
549
782
|
return {
|
|
550
783
|
age: scope.age,
|
|
551
784
|
head,
|
|
552
785
|
heading: scope.state,
|
|
553
|
-
merges
|
|
786
|
+
merges,
|
|
554
787
|
number,
|
|
555
788
|
passAt: scope.readAt,
|
|
789
|
+
paths: written?.paths ?? null,
|
|
556
790
|
prior: scope.commit,
|
|
557
791
|
replyAt: replies.newestAt,
|
|
558
792
|
resp: replies.count,
|
|
793
|
+
stale: stale.stale,
|
|
794
|
+
staleFiles: stale.files,
|
|
559
795
|
ui: uiState(payload, head),
|
|
560
796
|
unmatchedCount: unmatched.count,
|
|
561
797
|
unmatchedHeading: unmatched.heading,
|
|
@@ -573,7 +809,37 @@ const countSince =
|
|
|
573
809
|
return log.isOk ? log.stdout.split('\n').filter((l) => l !== '').length : 0
|
|
574
810
|
}
|
|
575
811
|
|
|
576
|
-
|
|
812
|
+
/**
|
|
813
|
+
* `--check <number>` reads the base and the head at call time and writes
|
|
814
|
+
* nothing. The baseline is as old as the last full run, and a sibling merging
|
|
815
|
+
* after it would leave a stored reading claiming clear.
|
|
816
|
+
*/
|
|
817
|
+
const check = (number: string): number => {
|
|
818
|
+
const ref = baseRef()
|
|
819
|
+
run('git', ['fetch', '-q', 'origin', baseBranch(ref)])
|
|
820
|
+
const head = readHead(number)
|
|
821
|
+
if (head === '') {
|
|
822
|
+
console.error(
|
|
823
|
+
`poll: #${number} returned no head, so nothing is known about it`,
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
return 1
|
|
827
|
+
}
|
|
828
|
+
const merges = readMerges(number, head, ref)
|
|
829
|
+
const written = merges === 'unknown' ? null : readWritten(head, ref)
|
|
830
|
+
console.log(
|
|
831
|
+
standingLine({
|
|
832
|
+
head,
|
|
833
|
+
merges,
|
|
834
|
+
number,
|
|
835
|
+
stale: readStale(written, ref).stale,
|
|
836
|
+
}),
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
return 0
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const main = (args: string[]): number => {
|
|
577
843
|
const root = mainRoot()
|
|
578
844
|
if (root === null) {
|
|
579
845
|
console.error('poll: not a git repository, so nothing is classified')
|
|
@@ -583,6 +849,7 @@ const main = (): number => {
|
|
|
583
849
|
|
|
584
850
|
// The baseline is per-machine mutable state, so it stays in gitignored
|
|
585
851
|
// scratch even though the script is tracked.
|
|
852
|
+
if (args[0] === '--check') return check(args[1] ?? '')
|
|
586
853
|
const stateDir = join(root, '.canon', 'tmp', 'pr', 'poll')
|
|
587
854
|
mkdirSync(stateDir, { recursive: true })
|
|
588
855
|
const statePath = join(stateDir, 'baseline.txt')
|
|
@@ -655,6 +922,22 @@ const main = (): number => {
|
|
|
655
922
|
snapshots.push({ row: buildRow(number, payload, head, ref) })
|
|
656
923
|
}
|
|
657
924
|
|
|
925
|
+
const rows = snapshots.flatMap((snapshot) =>
|
|
926
|
+
'row' in snapshot ? [snapshot.row] : [],
|
|
927
|
+
)
|
|
928
|
+
const carried = new Set(
|
|
929
|
+
snapshots.flatMap((snapshot) =>
|
|
930
|
+
'baseline' in snapshot ? [snapshot.baseline.number] : [],
|
|
931
|
+
),
|
|
932
|
+
)
|
|
933
|
+
const overlaps = sharedPairs(rows).flatMap(([first, second]) => {
|
|
934
|
+
const files = readOverlap(first.head, second.head)
|
|
935
|
+
|
|
936
|
+
return files === null
|
|
937
|
+
? []
|
|
938
|
+
: [{ files, pair: [first.number, second.number] as const }]
|
|
939
|
+
})
|
|
940
|
+
|
|
658
941
|
let isChanged = false
|
|
659
942
|
const final: string[] = []
|
|
660
943
|
const seen = new Set<string>()
|
|
@@ -667,13 +950,24 @@ const main = (): number => {
|
|
|
667
950
|
|
|
668
951
|
const { row } = snapshot
|
|
669
952
|
seen.add(row.number)
|
|
670
|
-
const
|
|
953
|
+
const old = known.get(row.number)
|
|
954
|
+
const result = classify(row, old, {
|
|
671
955
|
base,
|
|
672
956
|
countSince: countSince(row.number),
|
|
673
957
|
})
|
|
674
958
|
for (const line of result.lines) console.log(line)
|
|
675
959
|
if (result.isChanged) isChanged = true
|
|
676
|
-
final.push(
|
|
960
|
+
final.push(
|
|
961
|
+
formatBaseline({
|
|
962
|
+
...result.baseline,
|
|
963
|
+
overlaps: overlapPartners(row.number, overlaps, old, carried),
|
|
964
|
+
}),
|
|
965
|
+
)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
for (const line of overlapLines(overlaps, known)) {
|
|
969
|
+
console.log(line)
|
|
970
|
+
isChanged = true
|
|
677
971
|
}
|
|
678
972
|
|
|
679
973
|
for (const entry of previous) {
|
|
@@ -688,4 +982,4 @@ const main = (): number => {
|
|
|
688
982
|
return 0
|
|
689
983
|
}
|
|
690
984
|
|
|
691
|
-
if (import.meta.main) process.exitCode = main()
|
|
985
|
+
if (import.meta.main) process.exitCode = main(process.argv.slice(2))
|
|
@@ -89,4 +89,4 @@ four carried at least one, caught only because the planner ran
|
|
|
89
89
|
- The mechanical addressee resolution, which `session-relay` owns for every session with or without a role
|
|
90
90
|
- The branch, the build, and the pull request, which `role-worker` and `auto-ship` own
|
|
91
91
|
- Deciding which rows run and in what order, which is the controlling session's call and stated in `role-orchestrator`
|
|
92
|
-
- The dispatch itself, its collision check, and its
|
|
92
|
+
- The dispatch itself, its collision check, and its conflict check, which `orchestrator-dispatch.md` holds
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: role-planner
|
|
3
3
|
description: Asserts the planner role for a session writing one plan, groundwork track, or intake pass under one row, holding what it reads before deciding, what may be written and where, how to read what is already in flight, and what it hands back. Use when asked to "be the planner", "you are a planner session", at the start of a dispatched or hand-launched planning, groundwork, or intake run, or when a planning session needs to know what it may not write. Do NOT use to write the record itself, which is `plan-feature`, `plan-groundwork`, or `plan-intake`, to make the cross-feature merge call, or to implement.
|
|
4
|
+
metadata:
|
|
5
|
+
family: parallel
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Role planner
|
|
@@ -40,7 +42,7 @@ Each item below is something a record needed and a launch string did not carry.
|
|
|
40
42
|
- The task file, and its `## Findings` before deciding anything. A row can carry its own disproof under a title that still states the original claim, which is how one trial nearly planned against a premise the file had already recorded as dead.
|
|
41
43
|
- The source files themselves, opened rather than summarized. Never a count quoted from the task file, which was wrong or stale in ten places across four plans.
|
|
42
44
|
- `CLAUDE.md` and `canon/ARCHITECTURE.md`, for the decision and the alternative it was taken against rather than for the decision alone.
|
|
43
|
-
- `.canon/tasks/priority.md` for the row's Touches column and its stated blocker. That column is the file set the dispatch
|
|
45
|
+
- `.canon/tasks/priority.md` for the row's Touches column and its stated blocker. That column is the file set the dispatch conflict check already reads. Read it for the shared-path lines and the holds of the constraint block, never as a gate a plan must clear.
|
|
44
46
|
|
|
45
47
|
Two reads belong to the `plan-feature` path alone, since neither a groundwork
|
|
46
48
|
track nor an intake pass is sequenced behind another row or validated as a plan:
|
|
@@ -58,7 +60,7 @@ it.
|
|
|
58
60
|
- Run `canon sessions list --json`, take the `branch` field of every session whose `repository` matches this one and whose branch is neither null nor the trunk branch, union that with every `headRefName` from `gh pr list --json number,headRefName`, dedupe by branch name, then diff each with `git diff --name-only main...<branch>` for its file set. That resolves locally whether or not a pull request exists, since every track in this repository shares one git directory across its worktrees.
|
|
59
61
|
- A bare branch or worktree with neither a live session nor a pull request behind it is still not evidence, since this repository squash-merges and leaves both behind. The roster read is already filtered to live sessions, so what changed is the second source composed with it rather than a raw count. It still misses a live session that reached its worktree through the direct-path fallback rather than `EnterWorktree`, since that session's registered branch never moves off the trunk.
|
|
60
62
|
- Run the read once per row rather than once per batch. A session working several rows ages its picture of the tree while it goes, and this read is what dates it.
|
|
61
|
-
- Name each in-flight set as a constraint,
|
|
63
|
+
- Name each in-flight set as a constraint, being a hold that says which act it forbids or a shared path that says which branch rebases, and stamp the block with the commit the tree was read at. A bare path list leaves the reader guessing. The plan standard fixes that block for a feature plan, and a groundwork or intake record carries the same three parts in whatever shape its own standard states.
|
|
62
64
|
|
|
63
65
|
## The board is read-only
|
|
64
66
|
|
|
@@ -57,7 +57,7 @@ a tree the pull request never changed.
|
|
|
57
57
|
- Restate a boundary `role-orchestrator`, `role-worker`, or `role-planner` states about itself
|
|
58
58
|
- Report progress through the channel, which rebuilds on the sender's side the poll the announcement exists to retire
|
|
59
59
|
- Post outside `review-pr` and `review-ui`, since every reader of the review headings breaks at once when one pass invents its own
|
|
60
|
-
- Be a skill nothing invokes but its author typing the name. `orchestrator-launch.md` names it on the reviewer launch the way
|
|
60
|
+
- Be a skill nothing invokes but its author typing the name. `orchestrator-reviewer-launch.md` names it on the reviewer launch the way `orchestrator-launch.md` names `role-planner` on a planning one, so a stretch where only a typed invocation reaches it is the signal that the role never took.
|
|
61
61
|
|
|
62
62
|
## Guards
|
|
63
63
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: role-reviewer
|
|
3
3
|
description: Asserts the reviewer role for a session dispatched to review one pull request another session built, holding what it may write, what it reads and never reads, the one message it owes on posting, and the acts it refuses. Use when asked to "be the reviewer", "you are a reviewer session", at the start of a dispatched or hand-launched review of one pull request, or when a reviewing session needs to know what it may not write or who it answers to. Do NOT use to run the pass itself, which is `review-pr` for the code and `review-ui` for the running app, to review local changes, which is `review-branch`, or to make the cross-branch call across a wave.
|
|
4
|
+
metadata:
|
|
5
|
+
family: parallel
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Role reviewer
|
|
@@ -23,7 +25,7 @@ from here.
|
|
|
23
25
|
|
|
24
26
|
## Where the session stands
|
|
25
27
|
|
|
26
|
-
- Write the review comment through `review-pr` and the body file it names under `.canon/tmp/pr/review/`, and nothing else. A UI reviewer writes the comment `review-ui` posts, its body file, and the browser's snapshots and frames, all under `.canon/tmp/pr/review-ui
|
|
28
|
+
- Write the review comment through `review-pr` and the body file it names under `.canon/tmp/pr/review/`, and nothing else. A UI reviewer writes the comment `review-ui` posts, its body file, and the browser's snapshots and frames, all under `.canon/tmp/pr/review-ui/`, and ticks the boxes it passed on the evidence comment through `canon pr tick`. A tracked file, a branch, a worktree, a board file, and a commit all sit outside what this session may touch.
|
|
27
29
|
- Never enter a worktree and never check the branch out. The pass reads a change, and a checkout is the first step toward editing it.
|
|
28
30
|
- Read a file a finding rests on at the head `review-pr` resolved, through `git show <head>:<path>`. The main worktree holds the trunk, so a finding read off its copy is about a tree the pull request never changed, and it reads as confidently as one that is right.
|
|
29
31
|
- Resolve `.canon/plans/`, `.canon/tasks/`, and `.canon/tmp/` at the main worktree root, and send the body file as a heredoc, the main-root route `session-worktree` states. Report a plan or task that fails to resolve there as unreadable, naming the path, rather than reviewing as though the branch had none.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: role-worker
|
|
3
3
|
description: Asserts the worker role for a building session, holding the boundary set, the lifetime, and the two channel obligations a session owes whoever dispatched it. Use when asked to "be the worker", "you are a worker session", at the start of a dispatched or hand-launched build, or when a building session needs to know what it may not write. Do NOT use to plan the next feature, to run the independent review pass, or to merge.
|
|
4
|
+
metadata:
|
|
5
|
+
family: parallel
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Role worker
|
|
@@ -47,7 +49,7 @@ The controlling session cannot watch this one build, so the messages below are
|
|
|
47
49
|
owed and nothing else.
|
|
48
50
|
|
|
49
51
|
- Announce the pull request as the ship chain's pull request step returns, carrying the number, the branch, and the task it closes. That transition is the one moment only this session knows, and the controller's review poll no longer starts on a dispatch because of it.
|
|
50
|
-
- Carry the reach in that same message: the paths this branch wrote that another live plan or `## Run now` row holds, and who holds each, which `canon tasks plan-reach` reports as `claimed`. The gate cleared this branch against a prediction it has since outgrown, so the controller holds a
|
|
52
|
+
- Carry the reach in that same message: the paths this branch wrote that another live plan or `## Run now` row holds, and who holds each, which `canon tasks plan-reach` reports as `claimed`. Report each as a rebase the controller should order, never as a breach, since sharing a path is not a hold. The gate cleared this branch against a prediction it has since outgrown, so the controller holds a reading of what this branch shares that stopped being true hours ago and nothing else tells it. Say the reach was unread rather than clear when the installed binary carries no such subcommand, and leave the undeclared list to the pull request.
|
|
51
53
|
- Carry a row this build turned up in that same message, with its title, its origin, and the surface it would take. Send it as its own message only when the session will end before a pull request opens, since the row has no other way to reach the controller.
|
|
52
54
|
- Announce when an address-review pass finishes, as `review-address` Step 8 returns, carrying what was addressed and the PR's new CI state. That transition is the other moment only this session knows, and it is what tells the controller to re-review rather than leaving it to poll for an answer nothing marks as landed.
|
|
53
55
|
- Send a block out as a message before it becomes an interactive prompt.
|
|
@@ -82,7 +84,8 @@ feature branch and a controller does not.
|
|
|
82
84
|
- Refuse an instruction the tree contradicts, and carry the evidence with it. Name the commands read and the consequences of complying, rather than reporting reluctance.
|
|
83
85
|
- Halt on a plan question written as needing the operator's call. The plan standard defines that as a stop for an executing session, so the dispatch that sent it unattended is the defect and the halt is not.
|
|
84
86
|
- Halting is cheap and guessing is not. Four measured halts each cost the dispatcher one reply, and the correction that mattered most reached the right place because a worker argued back rather than complying.
|
|
85
|
-
-
|
|
87
|
+
- Write a file a constraint names as shared, and keep both sides on the rebase. A shared path forbids nothing, so the hunk comparison below does not apply to it.
|
|
88
|
+
- Read a file named as held, whether by a plan's Constraints block or by a controller's message sent mid-build, at the hunk against the branch or pull request holding it before conceding it. A hold is a dependency, a contract, a relocation, or a sweep.
|
|
86
89
|
- Report that reading to the controller as the hunks or line ranges on each side, and take its reply as the call on whether the file is kept or given up, not a decision this session makes alone.
|
|
87
90
|
- When the holding track carries no commits yet, there is nothing to diff. Compare against the file set its plan declares instead, and report that the hunk comparison could not be taken, rather than conceding on the name or proceeding as if the sets never collided.
|
|
88
91
|
- A dispatcher once named three files a branch was writing. The worker diffed that branch, found its hunks at two lines against its own at three others, and reported the comparison, and the dispatcher reversed the instruction and returned every file.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: search-craft
|
|
3
3
|
description: Carries the thinking step a session takes before it searches outside the project, naming the field, deriving who that field treats as authoritative, and running a search scoped to those sources beside an open one, plus a dated note on which sites refuse access. Use before any outside search for evidence, papers, documentation, statistics, or opinion, when a procedure loads it at its sourcing step, or when asked "find me the strongest evidence on X", "where should I look for this", "what does the research say", "find papers on", "look up the docs for", or "what are people saying about". Do NOT use to format a citation, which belongs to the surface writing it, to source a Claude Code subject, which is the `claude-code-guide` agent, or to generate an image, which is not search.
|
|
4
|
+
metadata:
|
|
5
|
+
family: build
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Search craft
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: seed-sync
|
|
3
3
|
description: Audits a project's installed Claude seed docs against the toolkit's current seed source and proposes per-section edits without overwriting customizations. Use when asked to "sync seeds", "update my seeds", "check seed drift", "did the toolkit seeds change", or when reconciling `CLAUDE.md` and `.claude/` preambles after an upstream toolkit update.
|
|
4
|
+
metadata:
|
|
5
|
+
family: upkeep
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Seed sync
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: session-compact
|
|
3
3
|
description: Captures a session's memory, then writes one handoff note to .canon/compact/ so the session after a compaction picks up where this one stopped. Use before running /compact, when asked to "write a handoff", "prepare for compaction", "save where we got to", or before moving a session to another machine, or when a PreCompact hook blocks and names this skill. Do NOT use for a session holding the orchestrator role, whose board handoff is `session-map`. Do NOT use to record a decision a groundwork track owns, which is `plan-groundwork`.
|
|
4
|
+
metadata:
|
|
5
|
+
family: parallel
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Session compact
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: session-map
|
|
3
3
|
description: Writes the session map, the board-side handoff at `.canon/tasks/session-<slug>.md`, for an orchestrating session or on a request naming the session map or the task board, running the skill-drift step the write procedure opens with. Use when asked to "write the session map", "write the board handoff", or when a session holding `canon:role-orchestrator` hands off before a compaction. Do NOT use for a plain session about to compact, which is `session-compact` and writes a note outside the board. Do NOT use to route session facts to a context entry or the memory folder, which is `memory-capture` and writes a different artifact, and do NOT use to read a handoff back, which is `session-resume`.
|
|
4
|
+
metadata:
|
|
5
|
+
family: parallel
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Session map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: session-relay
|
|
3
3
|
description: Relays a message to another session, turning the sessionId the caller named into an address at the moment of sending and composing a copyable block where no send tool exists. Use when asked to "relay this", "message the controller", "send this over to the session that dispatched me", or "tell the other session", with or without a role, and when a session stuck on a question owes the session that dispatched it a message before it stops to ask. Do NOT use to decide what the message says, and do NOT use to assert what a session may write or is on the hook for, which is `role-worker` or `role-planner`.
|
|
4
|
+
metadata:
|
|
5
|
+
family: parallel
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Session relay
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: session-resume
|
|
3
3
|
description: Resumes a previous session by reading the handoff it left behind, tracked work, and relevant context. Use when starting a new session, or when asked to "pick up where we left off", "what was I working on", or "resume".
|
|
4
|
+
metadata:
|
|
5
|
+
family: parallel
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Session resume
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: session-worktree
|
|
3
3
|
description: Enters a Claude Code worktree at `.claude/worktrees/<name>/` with a name derived from the active plan or branch. Use when asked to "enter a worktree", "start a worktree", "work in a worktree", or at the plan-to-execute boundary after `/plan-feature`. Also use when an `Edit` or `Write` to a main-root file such as a plan, a task, or a memory entry was refused for session isolation. Do NOT use to list, clean up, or rotate worktrees (use `git-worktree`).
|
|
4
|
+
metadata:
|
|
5
|
+
family: build
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# Session worktree
|
|
@@ -125,17 +127,7 @@ Read the worktree root and evaluate node and python independently against a lite
|
|
|
125
127
|
|
|
126
128
|
Name the ecosystem in both installed lines rather than leaving `Dependencies are installed.` unqualified. Both checks can fire on one project, so an unqualified line reported the same sentence twice for a dual-root project with both folders present, and a reader could not tell which half each line answered.
|
|
127
129
|
|
|
128
|
-
Take `<install>` from the lockfile beside the manifest, checked in
|
|
129
|
-
|
|
130
|
-
| Lockfile | Install command |
|
|
131
|
-
| ------------------------- | --------------- |
|
|
132
|
-
| `bun.lock` or `bun.lockb` | `bun install` |
|
|
133
|
-
| `pnpm-lock.yaml` | `pnpm install` |
|
|
134
|
-
| `yarn.lock` | `yarn install` |
|
|
135
|
-
| `package-lock.json` | `npm install` |
|
|
136
|
-
| none of the above | `bun install` |
|
|
137
|
-
|
|
138
|
-
The order matters only when more than one lockfile sits beside the manifest, such as a project mid-migration between package managers. It is fixed rather than derived from anything about the project, so a reader hitting that rare case checks which manager the project actually uses rather than trusting the row the table picked first.
|
|
130
|
+
Take `<install>` from the lockfile beside the manifest. Read `${CLAUDE_SKILL_DIR}/references/install-command.md` when the Node line reports dependencies not installed, for the order the lockfiles are checked in and the command each one names.
|
|
139
131
|
|
|
140
132
|
Emit the closing line only on its own direct test, run ahead of the two checks above rather than reached by falling through them unmatched: `[ ! -f package.json ] && [ ! -f pyproject.toml ] && [ ! -f requirements.txt ]`: `No package manifest, so there is nothing to install.` A dual-root project matches both checks above, and a fallthrough test would route it here by accident.
|
|
141
133
|
|