@erclx/canon 5.42.0 → 5.44.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/SKILL.md +3 -3
- package/claude/skills/backlog-triage/references/apply.md +6 -6
- package/claude/skills/canon-cli/SKILL.md +4 -4
- package/claude/skills/canvas-board/REQUIREMENT.md +1 -1
- package/claude/skills/canvas-board/SKILL.md +1 -1
- package/claude/skills/ci-workflow/REQUIREMENT.md +2 -1
- package/claude/skills/ci-workflow/SKILL.md +12 -2
- package/claude/skills/ci-workflow/references/workflows.md +12 -9
- package/claude/skills/context-fold/references/context.md +1 -1
- package/claude/skills/create-rule/SKILL.md +1 -0
- package/claude/skills/create-skill/REQUIREMENT.md +1 -1
- package/claude/skills/create-skill/SKILL.md +2 -2
- package/claude/skills/create-skill/references/skill-requirement.md +68 -9
- package/claude/skills/create-skill/references/skill.md +1 -1
- package/claude/skills/decision-escalate/SKILL.md +1 -2
- package/claude/skills/design-identity/REQUIREMENT.md +40 -0
- package/claude/skills/design-identity/SKILL.md +138 -0
- package/claude/skills/{draft-identity → design-identity}/references/card-route.md +2 -2
- package/claude/skills/design-taste/REQUIREMENT.md +4 -4
- package/claude/skills/design-taste/SKILL.md +2 -2
- package/claude/skills/design-taste/references/kinds.md +1 -1
- package/claude/skills/design-taste/references/preflight.md +1 -1
- package/claude/skills/document-health/references/axes.md +2 -1
- package/claude/skills/draft-and-pick/REQUIREMENT.md +8 -69
- package/claude/skills/draft-and-pick/SKILL.md +3 -153
- package/claude/skills/draft-doc/REQUIREMENT.md +9 -30
- package/claude/skills/draft-doc/SKILL.md +4 -76
- package/claude/skills/draft-figure/REQUIREMENT.md +8 -42
- package/claude/skills/draft-figure/SKILL.md +3 -77
- package/claude/skills/draft-identity/REQUIREMENT.md +8 -23
- package/claude/skills/draft-identity/SKILL.md +3 -130
- package/claude/skills/draft-ready/REQUIREMENT.md +8 -37
- package/claude/skills/draft-ready/SKILL.md +3 -128
- package/claude/skills/draft-screencast/REQUIREMENT.md +8 -23
- package/claude/skills/draft-screencast/SKILL.md +3 -62
- package/claude/skills/draft-slides/REQUIREMENT.md +8 -27
- package/claude/skills/draft-slides/SKILL.md +3 -72
- package/claude/skills/draw-figure/REQUIREMENT.md +59 -0
- package/claude/skills/draw-figure/SKILL.md +85 -0
- package/claude/skills/{draft-figure → draw-figure}/references/architecture-set.md +2 -2
- package/claude/skills/{draft-figure → draw-figure}/references/hand-drawn.md +2 -2
- package/claude/skills/{draft-figure → draw-figure}/references/mermaid-path.md +2 -2
- package/claude/skills/{draft-figure → draw-figure}/references/mermaid.md +1 -1
- package/claude/skills/git-commit/references/commit.md +1 -5
- package/claude/skills/git-pr/references/pr.md +1 -0
- package/claude/skills/make-deck/REQUIREMENT.md +44 -0
- package/claude/skills/make-deck/SKILL.md +80 -0
- package/claude/skills/markdown-craft/REQUIREMENT.md +1 -1
- package/claude/skills/markdown-craft/SKILL.md +2 -2
- package/claude/skills/pick-by-eye/REQUIREMENT.md +86 -0
- package/claude/skills/pick-by-eye/SKILL.md +161 -0
- package/claude/skills/plan-feature/SKILL.md +3 -2
- package/claude/skills/plan-feature/references/plan.md +25 -11
- package/claude/skills/plan-groundwork/SKILL.md +1 -1
- package/claude/skills/plan-intake/SKILL.md +1 -1
- package/claude/skills/plan-ready/REQUIREMENT.md +54 -0
- package/claude/skills/plan-ready/SKILL.md +136 -0
- package/claude/skills/{draft-ready → plan-ready}/references/assembly.md +1 -1
- package/claude/skills/{draft-ready → plan-ready}/references/ready.md +1 -1
- package/claude/skills/pr-evidence/references/evidence.md +1 -1
- package/claude/skills/record-screencast/REQUIREMENT.md +3 -3
- package/claude/skills/record-screencast/SKILL.md +2 -2
- package/claude/skills/role-orchestrator/REQUIREMENT.md +1 -1
- package/claude/skills/role-orchestrator/SKILL.md +2 -0
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +5 -2
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +4 -0
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +18 -13
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/role-planner/SKILL.md +5 -4
- package/claude/skills/role-worker/REQUIREMENT.md +2 -0
- package/claude/skills/role-worker/SKILL.md +35 -2
- package/claude/skills/script-screencast/REQUIREMENT.md +40 -0
- package/claude/skills/script-screencast/SKILL.md +70 -0
- package/claude/skills/{draft-screencast → script-screencast}/references/beats-template.md +2 -2
- package/claude/skills/search-craft/REQUIREMENT.md +4 -4
- package/claude/skills/search-craft/SKILL.md +2 -2
- package/claude/skills/target-setup/SKILL.md +1 -0
- package/claude/skills/task-board/SKILL.md +11 -9
- package/claude/skills/task-board/references/board.md +15 -12
- package/claude/skills/teach-workspace/SKILL.md +1 -1
- package/claude/skills/teach-workspace/references/lesson-craft.md +2 -2
- package/claude/skills/ui-checklist/SKILL.md +2 -0
- package/claude/skills/write-doc/REQUIREMENT.md +46 -0
- package/claude/skills/write-doc/SKILL.md +83 -0
- package/claude/skills/{draft-doc → write-doc}/references/context.md +2 -2
- package/claude/skills/{draft-doc → write-doc}/references/docs.md +2 -2
- package/claude/skills/{draft-doc → write-doc}/references/readme.md +2 -2
- package/claude/skills/{draft-doc → write-doc}/references/wiki-kind.md +4 -4
- package/claude/skills/{draft-doc → write-doc}/references/wiki.md +1 -1
- package/docs/agents/audits.md +1 -0
- package/docs/agents/markdown-audit.md +4 -2
- package/docs/agents/migrate.md +13 -10
- package/docs/target/migrations.md +28 -4
- package/docs/target/projects.md +1 -1
- package/docs/workflow/parallel-features.md +3 -2
- package/docs/workflow/skill-map-on-request.md +18 -12
- package/docs/workflow/skill-map.md +13 -12
- package/docs/workflow/visual-design-workflow.md +2 -2
- package/governance/detect.toml +4 -0
- package/governance/ledger/framework/astro.md +73 -0
- package/governance/ledger/framework/fastapi.md +76 -0
- package/governance/ledger/framework/nextjs.md +86 -0
- package/governance/ledger/framework/react.md +83 -0
- package/governance/ledger/framework/shadcn.md +60 -0
- package/governance/ledger/framework/tailwind.md +55 -0
- package/governance/rules/canon/context.md +2 -1
- package/governance/rules/canon/plan.md +3 -5
- package/governance/rules/canon/scratch.md +3 -1
- package/governance/rules/canon/task-scope.md +2 -1
- package/governance/rules/canon/tasks.md +2 -1
- package/governance/rules/canon/worktrees.md +5 -2
- package/governance/rules/claude/output.md +7 -4
- package/governance/rules/claude/skill.md +6 -4
- package/governance/rules/framework/astro.md +12 -14
- package/governance/rules/framework/fastapi.md +20 -24
- package/governance/rules/framework/nextjs.md +19 -27
- package/governance/rules/framework/react.md +22 -33
- package/governance/rules/framework/shadcn.md +11 -15
- package/governance/rules/framework/tailwind.md +12 -14
- package/governance/rules/tooling/ci-workflow.md +6 -39
- package/governance/rules/ui/design-reference.md +37 -0
- package/governance/rules/writing/markdown.md +3 -2
- package/package.json +6 -6
- package/src/audits/catalog.ts +42 -0
- package/src/claude/cases/authoring.ts +15 -3
- package/src/claude/cases/misc.ts +12 -0
- package/src/claude/cases/workflow.ts +19 -1
- package/src/claude/settings.ts +8 -4
- package/src/claude/skills-audit.ts +113 -1
- package/src/commands/claude.ts +45 -12
- package/src/commands/demo.ts +1 -1
- package/src/commands/gov.ts +198 -0
- package/src/commands/markdown.ts +1 -1
- package/src/commands/migrate.ts +125 -2
- package/src/commands/pr.ts +34 -83
- package/src/commands/tasks.ts +383 -2
- package/src/commands/tooling.ts +3 -3
- package/src/commands/worktrees.ts +49 -0
- package/src/context/audit.ts +1 -1
- package/src/demo/beats.ts +1 -1
- package/src/gate/stages.ts +15 -0
- package/src/gov/audit.ts +441 -0
- package/src/listeners/belongs.ts +21 -0
- package/src/listeners/machine.ts +93 -0
- package/src/listeners/parse.ts +41 -0
- package/src/markdown/scope.ts +45 -35
- package/src/migrate/record-layout.ts +1 -1
- package/src/migrate/session-defaults.ts +138 -0
- package/src/migrate/skill-names.ts +68 -21
- package/src/pr/frames.ts +14 -3
- package/src/pr/local.ts +4 -69
- package/src/records/validate.ts +31 -0
- package/src/sync/check.ts +15 -0
- package/src/tasks/board.ts +854 -0
- package/src/tasks/record.ts +20 -1
- package/src/tasks/validate.ts +206 -9
- package/src/tooling/stamp.ts +56 -12
- package/src/worktrees/stop.ts +92 -0
- package/tooling/astro/configs/card-src/pages/og-card.astro +1 -1
- package/tooling/astro/reference.md +2 -2
- package/tooling/base/configs/.github/workflows/phase-label-gate.yml +6 -2
- package/tooling/base/configs/commitlint.config.js +0 -20
- package/tooling/base/reference.md +1 -1
- package/tooling/base/seeds/.github/workflows/verify.yml +4 -1
- package/tooling/claude/reference.md +13 -7
- package/tooling/claude/seeds/.claude/hooks/board-validate.sh +65 -0
- package/tooling/claude/seeds/.claude/settings.json +8 -0
- package/tooling/claude/user/settings.template.json +0 -4
- package/tooling/cloudflare/configs/.github/workflows/deploy.yml +5 -5
- package/tooling/public/configs/.github/dependabot.yml +12 -0
- package/tooling/public/configs/.github/workflows/release-please.yml +30 -0
- package/tooling/public/manifest.toml +8 -0
- package/tooling/public/reference.md +53 -0
- package/tooling/public/seeds/.github/ISSUE_TEMPLATE/bug.yml +48 -0
- package/tooling/public/seeds/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/tooling/public/seeds/.github/ISSUE_TEMPLATE/feature.yml +16 -0
- package/tooling/public/seeds/.release-please-manifest.json +3 -0
- package/tooling/public/seeds/CONTRIBUTING.md +50 -0
- package/tooling/public/seeds/LICENSE +21 -0
- package/tooling/public/seeds/SECURITY.md +13 -0
- package/tooling/public/seeds/release-please-config.json +12 -0
- package/tooling/shell/configs/.github/workflows/shell.yml +4 -1
- package/tooling/web/configs/.github/workflows/readme-screenshot.yml +6 -3
- package/tooling/web/configs/.github/workflows/verify.yml +15 -12
- /package/claude/skills/{draft-figure → draw-figure}/references/figures.md +0 -0
- /package/claude/skills/{draft-slides → make-deck}/references/deck.md +0 -0
- /package/claude/skills/{draft-and-pick → pick-by-eye}/references/live-arms.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/readme/agent-facing.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/readme/application.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/readme/cli.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/readme/library.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/readme/plugin.md +0 -0
- /package/claude/skills/{draft-doc → write-doc}/references/requirements.md +0 -0
package/src/commands/tasks.ts
CHANGED
|
@@ -3,6 +3,17 @@ import type { Command } from 'commander'
|
|
|
3
3
|
import { execa } from 'execa'
|
|
4
4
|
import { gitEnv } from '@/git/env'
|
|
5
5
|
import { type AnswersOutcome, planAnswers } from '@/tasks/answers'
|
|
6
|
+
import {
|
|
7
|
+
type BoardOutcome,
|
|
8
|
+
type BoardRefused,
|
|
9
|
+
blockTask,
|
|
10
|
+
moveTask,
|
|
11
|
+
PLANNED_WAITING,
|
|
12
|
+
type Position,
|
|
13
|
+
placeTask,
|
|
14
|
+
promoteTask,
|
|
15
|
+
WAITING_LIMIT,
|
|
16
|
+
} from '@/tasks/board'
|
|
6
17
|
import { type BranchOutcome, planBranch } from '@/tasks/branch'
|
|
7
18
|
import {
|
|
8
19
|
type ArchiveOutcome,
|
|
@@ -40,6 +51,7 @@ import {
|
|
|
40
51
|
recordPullRequest,
|
|
41
52
|
} from '@/tasks/record'
|
|
42
53
|
import {
|
|
54
|
+
type BoardGroup,
|
|
43
55
|
type Finding,
|
|
44
56
|
type FolderClaim,
|
|
45
57
|
type SharedFiles,
|
|
@@ -118,6 +130,7 @@ interface PullRequestCommandOptions {
|
|
|
118
130
|
|
|
119
131
|
interface PlanLinkCommandOptions {
|
|
120
132
|
readonly add?: boolean
|
|
133
|
+
readonly waiting?: string
|
|
121
134
|
readonly json?: boolean
|
|
122
135
|
readonly root?: string
|
|
123
136
|
}
|
|
@@ -140,6 +153,31 @@ interface NextLabelCommandOptions {
|
|
|
140
153
|
readonly root?: string
|
|
141
154
|
}
|
|
142
155
|
|
|
156
|
+
interface BoardCommandOptions {
|
|
157
|
+
readonly json?: boolean
|
|
158
|
+
readonly root?: string
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface PositionCommandOptions {
|
|
162
|
+
readonly top?: boolean
|
|
163
|
+
readonly bottom?: boolean
|
|
164
|
+
readonly before?: string
|
|
165
|
+
readonly after?: string
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface PlaceCommandOptions
|
|
169
|
+
extends BoardCommandOptions, PositionCommandOptions {
|
|
170
|
+
readonly group: string
|
|
171
|
+
readonly waiting?: string
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface PromoteCommandOptions extends BoardCommandOptions {
|
|
175
|
+
readonly waiting?: string
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
interface MoveCommandOptions
|
|
179
|
+
extends BoardCommandOptions, PositionCommandOptions {}
|
|
180
|
+
|
|
143
181
|
export function register(program: Command): void {
|
|
144
182
|
const tasks = program
|
|
145
183
|
.command('tasks')
|
|
@@ -259,6 +297,10 @@ export function register(program: Command): void {
|
|
|
259
297
|
' every task file carries a board row or a backlog line, never both',
|
|
260
298
|
' no task carries more than one row',
|
|
261
299
|
' every Run now row names the files it touches',
|
|
300
|
+
' every Touches cell matches what its plans derive',
|
|
301
|
+
' every group header is the set of columns the group owns',
|
|
302
|
+
` no Waiting on cell renders past ${WAITING_LIMIT} characters`,
|
|
303
|
+
' a blocker citing a bare vNNN.N label settles like a linked one',
|
|
262
304
|
' a file two Run now rows share is listed, never failed',
|
|
263
305
|
'',
|
|
264
306
|
'Refusals, read off `reason`:',
|
|
@@ -493,6 +535,10 @@ export function register(program: Command): void {
|
|
|
493
535
|
'--add',
|
|
494
536
|
'Append the plan to the line, for a task sliced over plans',
|
|
495
537
|
)
|
|
538
|
+
.option(
|
|
539
|
+
'--waiting <text>',
|
|
540
|
+
`The Waiting on cell a promoted row carries, "${PLANNED_WAITING}" when omitted`,
|
|
541
|
+
)
|
|
496
542
|
.option('--json', 'Emit a machine-readable record on stdout')
|
|
497
543
|
.option('--root <path>', 'Board root, defaulting to the main worktree')
|
|
498
544
|
.addHelpText(
|
|
@@ -521,6 +567,10 @@ export function register(program: Command): void {
|
|
|
521
567
|
'sliced task links one plan per slice. It reports unchanged when the',
|
|
522
568
|
'plan is already listed and is allowed on a line holding several.',
|
|
523
569
|
'',
|
|
570
|
+
'A row under Needs a plan moves to the bottom of Up next through the',
|
|
571
|
+
'board verbs, and `board` in the record says whether it moved and why',
|
|
572
|
+
'not. A row elsewhere, or a task with no row, stays where it is.',
|
|
573
|
+
'',
|
|
524
574
|
'Examples:',
|
|
525
575
|
' canon tasks plan-link v28.1-trigger-escalation dispatch-answer-gate',
|
|
526
576
|
' canon tasks plan-link v28.1-trigger-escalation second-slice --add',
|
|
@@ -661,6 +711,327 @@ export function register(program: Command): void {
|
|
|
661
711
|
.action(async (opts: ListCommandOptions) => {
|
|
662
712
|
process.exitCode = await runList(opts)
|
|
663
713
|
})
|
|
714
|
+
|
|
715
|
+
registerBoardWrites(tasks)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const BOARD_WRITE_REFUSALS = [
|
|
719
|
+
'Refusals, read off `reason`:',
|
|
720
|
+
' no-board the repository holds no `.canon/tasks/` board',
|
|
721
|
+
' no-ordering the board has no priority.md',
|
|
722
|
+
' no-match no task file carries the stem',
|
|
723
|
+
' no-row the task has no row on the board',
|
|
724
|
+
' unreadable-row a row names no task, or its header cannot place its blocker',
|
|
725
|
+
' board-busy another board verb held the lock past the wait, so run it again',
|
|
726
|
+
' board-changed a hand edit landed while this one rendered, so run it again',
|
|
727
|
+
' group-rule the target group forbids the row, naming the validate kind',
|
|
728
|
+
' no-column the row sits under Run now, which has no Waiting on cell',
|
|
729
|
+
' top-group the row already sits under Run now',
|
|
730
|
+
` cell-long the Waiting on cell renders past ${WAITING_LIMIT} characters`,
|
|
731
|
+
' bad-input a malformed group, position, or Waiting on text',
|
|
732
|
+
'',
|
|
733
|
+
'Exit codes:',
|
|
734
|
+
' 0 the board was written',
|
|
735
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
736
|
+
'',
|
|
737
|
+
'Every write reads priority.md once, renders every table from the task',
|
|
738
|
+
'files and their plans, and writes it once. Task, Touches, and Plan are',
|
|
739
|
+
'derived, so only Waiting on is typed. Safe from a linked worktree, since',
|
|
740
|
+
'it resolves the board root in-process.',
|
|
741
|
+
]
|
|
742
|
+
|
|
743
|
+
const GROUP_FLAGS: Readonly<Record<string, BoardGroup>> = {
|
|
744
|
+
'run-now': 'Run now',
|
|
745
|
+
'up-next': 'Up next',
|
|
746
|
+
'needs-a-plan': 'Needs a plan',
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function withPosition(command: Command): Command {
|
|
750
|
+
return command
|
|
751
|
+
.option('--top', 'Put the row first in its group')
|
|
752
|
+
.option('--bottom', 'Put the row last in its group, the default')
|
|
753
|
+
.option('--before <task>', 'Put the row right above this sibling')
|
|
754
|
+
.option('--after <task>', 'Put the row right below this sibling')
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function registerBoardWrites(tasks: Command): void {
|
|
758
|
+
withPosition(
|
|
759
|
+
tasks
|
|
760
|
+
.command('place')
|
|
761
|
+
.description(
|
|
762
|
+
'Put a task under a board group, from another group, the backlog, or nowhere',
|
|
763
|
+
)
|
|
764
|
+
.argument('<task>', 'Task filename stem, as in v28.1-trigger-escalation')
|
|
765
|
+
.requiredOption(
|
|
766
|
+
'--group <group>',
|
|
767
|
+
'Target group: run-now, up-next, or needs-a-plan',
|
|
768
|
+
)
|
|
769
|
+
.option('--waiting <text>', 'The Waiting on cell, kept when omitted'),
|
|
770
|
+
)
|
|
771
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
772
|
+
.option('--json', 'Emit a machine-readable record on stdout')
|
|
773
|
+
.option('--root <path>', 'Board root, defaulting to the main worktree')
|
|
774
|
+
.addHelpText(
|
|
775
|
+
'after',
|
|
776
|
+
[
|
|
777
|
+
'',
|
|
778
|
+
...BOARD_WRITE_REFUSALS,
|
|
779
|
+
'',
|
|
780
|
+
'A backlog line for the task is removed in the same call. The row keeps',
|
|
781
|
+
'its Waiting on cell when --waiting is omitted, and a row new to Up next',
|
|
782
|
+
'or Needs a plan has none to keep, so it needs one.',
|
|
783
|
+
'',
|
|
784
|
+
'Examples:',
|
|
785
|
+
' canon tasks place v28.1-trigger-escalation --group needs-a-plan --bottom --waiting "Not planned yet"',
|
|
786
|
+
' canon tasks place v28.1-trigger-escalation --group up-next --before v28.2-other --json',
|
|
787
|
+
'',
|
|
788
|
+
].join('\n'),
|
|
789
|
+
)
|
|
790
|
+
.action(async (task: string, opts: PlaceCommandOptions) => {
|
|
791
|
+
process.exitCode = await runPlace(task, opts)
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
tasks
|
|
795
|
+
.command('promote')
|
|
796
|
+
.description('Move a row one group closer to Run now, to its bottom')
|
|
797
|
+
.argument('<task>', 'Task filename stem, as in v28.1-trigger-escalation')
|
|
798
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
799
|
+
.option(
|
|
800
|
+
'--waiting <text>',
|
|
801
|
+
`The Waiting on cell a row entering Up next carries, "${PLANNED_WAITING}" when omitted`,
|
|
802
|
+
)
|
|
803
|
+
.option('--json', 'Emit a machine-readable record on stdout')
|
|
804
|
+
.option('--root <path>', 'Board root, defaulting to the main worktree')
|
|
805
|
+
.addHelpText(
|
|
806
|
+
'after',
|
|
807
|
+
[
|
|
808
|
+
'',
|
|
809
|
+
...BOARD_WRITE_REFUSALS,
|
|
810
|
+
'',
|
|
811
|
+
'Up next moves to Run now, which reads as dispatched, so a dispatcher',
|
|
812
|
+
'runs it once a launch succeeds. Needs a plan moves to Up next.',
|
|
813
|
+
'',
|
|
814
|
+
'Examples:',
|
|
815
|
+
' canon tasks promote v28.1-trigger-escalation',
|
|
816
|
+
' canon tasks promote v28.1-trigger-escalation --json',
|
|
817
|
+
'',
|
|
818
|
+
].join('\n'),
|
|
819
|
+
)
|
|
820
|
+
.action(async (task: string, opts: PromoteCommandOptions) => {
|
|
821
|
+
process.exitCode = await runPromote(task, opts)
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
withPosition(
|
|
825
|
+
tasks
|
|
826
|
+
.command('move')
|
|
827
|
+
.description('Reposition a row inside the group it sits in')
|
|
828
|
+
.argument('<task>', 'Task filename stem, as in v28.1-trigger-escalation'),
|
|
829
|
+
)
|
|
830
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
831
|
+
.option('--json', 'Emit a machine-readable record on stdout')
|
|
832
|
+
.option('--root <path>', 'Board root, defaulting to the main worktree')
|
|
833
|
+
.addHelpText(
|
|
834
|
+
'after',
|
|
835
|
+
[
|
|
836
|
+
'',
|
|
837
|
+
...BOARD_WRITE_REFUSALS,
|
|
838
|
+
'',
|
|
839
|
+
'Examples:',
|
|
840
|
+
' canon tasks move v28.1-trigger-escalation --top',
|
|
841
|
+
' canon tasks move v28.1-trigger-escalation --after v28.2-other --json',
|
|
842
|
+
'',
|
|
843
|
+
].join('\n'),
|
|
844
|
+
)
|
|
845
|
+
.action(async (task: string, opts: MoveCommandOptions) => {
|
|
846
|
+
process.exitCode = await runMove(task, opts)
|
|
847
|
+
})
|
|
848
|
+
|
|
849
|
+
tasks
|
|
850
|
+
.command('block')
|
|
851
|
+
.description("Rewrite a row's Waiting on cell")
|
|
852
|
+
.argument('<task>', 'Task filename stem, as in v28.1-trigger-escalation')
|
|
853
|
+
.argument(
|
|
854
|
+
'<waiting>',
|
|
855
|
+
`What the row waits on, at most ${WAITING_LIMIT} rendered characters`,
|
|
856
|
+
)
|
|
857
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
858
|
+
.option('--json', 'Emit a machine-readable record on stdout')
|
|
859
|
+
.option('--root <path>', 'Board root, defaulting to the main worktree')
|
|
860
|
+
.addHelpText(
|
|
861
|
+
'after',
|
|
862
|
+
[
|
|
863
|
+
'',
|
|
864
|
+
...BOARD_WRITE_REFUSALS,
|
|
865
|
+
'',
|
|
866
|
+
'A link counts as its label alone toward the cap, since only the label',
|
|
867
|
+
'renders.',
|
|
868
|
+
'',
|
|
869
|
+
'Examples:',
|
|
870
|
+
' canon tasks block v28.1-trigger-escalation "[v28.2](v28.2-other.md) merging"',
|
|
871
|
+
' canon tasks block v28.1-trigger-escalation "The operator\'s call on the scope" --json',
|
|
872
|
+
'',
|
|
873
|
+
].join('\n'),
|
|
874
|
+
)
|
|
875
|
+
.action(
|
|
876
|
+
async (task: string, waiting: string, opts: BoardCommandOptions) => {
|
|
877
|
+
process.exitCode = await runBlock(task, waiting, opts)
|
|
878
|
+
},
|
|
879
|
+
)
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Reads the one position flag a call may carry. Two flags at once is refused
|
|
884
|
+
* rather than resolved by precedence, since either reading moves a row the
|
|
885
|
+
* caller did not mean to.
|
|
886
|
+
*/
|
|
887
|
+
function positionFrom(
|
|
888
|
+
opts: PositionCommandOptions,
|
|
889
|
+
): Position | undefined | BoardRefused {
|
|
890
|
+
const given: Position[] = [
|
|
891
|
+
...(opts.top ? [{ at: 'top' } as const] : []),
|
|
892
|
+
...(opts.bottom ? [{ at: 'bottom' } as const] : []),
|
|
893
|
+
...(opts.before ? [{ before: opts.before }] : []),
|
|
894
|
+
...(opts.after ? [{ after: opts.after }] : []),
|
|
895
|
+
]
|
|
896
|
+
|
|
897
|
+
if (given.length > 1) {
|
|
898
|
+
return {
|
|
899
|
+
ok: false,
|
|
900
|
+
reason: 'bad-input',
|
|
901
|
+
message: 'Pass one of --top, --bottom, --before, or --after.',
|
|
902
|
+
detail: [],
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
return given[0]
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
async function runPlace(
|
|
910
|
+
task: string,
|
|
911
|
+
opts: PlaceCommandOptions,
|
|
912
|
+
): Promise<number> {
|
|
913
|
+
const emitJson = opts.json ?? false
|
|
914
|
+
const group = GROUP_FLAGS[opts.group]
|
|
915
|
+
if (!group) {
|
|
916
|
+
return reportRecord(
|
|
917
|
+
'canon tasks place',
|
|
918
|
+
{
|
|
919
|
+
ok: false,
|
|
920
|
+
reason: 'bad-input',
|
|
921
|
+
message: `Not a group: ${opts.group}. Pass run-now, up-next, or needs-a-plan.`,
|
|
922
|
+
detail: [opts.group],
|
|
923
|
+
},
|
|
924
|
+
emitJson,
|
|
925
|
+
process.cwd(),
|
|
926
|
+
)
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const position = positionFrom(opts)
|
|
930
|
+
if (position && 'ok' in position) {
|
|
931
|
+
return reportRecord('canon tasks place', position, emitJson, process.cwd())
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const root = opts.root ?? (await mainWorktreeRoot())
|
|
935
|
+
const outcome = await placeTask(root, task, group, {
|
|
936
|
+
waiting: opts.waiting,
|
|
937
|
+
position,
|
|
938
|
+
})
|
|
939
|
+
|
|
940
|
+
return reportBoardWrite('canon tasks place', outcome, emitJson, root)
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
async function runPromote(
|
|
944
|
+
task: string,
|
|
945
|
+
opts: PromoteCommandOptions,
|
|
946
|
+
): Promise<number> {
|
|
947
|
+
const root = opts.root ?? (await mainWorktreeRoot())
|
|
948
|
+
const outcome = await promoteTask(root, task, { waiting: opts.waiting })
|
|
949
|
+
|
|
950
|
+
return reportBoardWrite(
|
|
951
|
+
'canon tasks promote',
|
|
952
|
+
outcome,
|
|
953
|
+
opts.json ?? false,
|
|
954
|
+
root,
|
|
955
|
+
)
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
async function runMove(
|
|
959
|
+
task: string,
|
|
960
|
+
opts: MoveCommandOptions,
|
|
961
|
+
): Promise<number> {
|
|
962
|
+
const emitJson = opts.json ?? false
|
|
963
|
+
const position = positionFrom(opts)
|
|
964
|
+
if (position === undefined || 'ok' in position) {
|
|
965
|
+
return reportRecord(
|
|
966
|
+
'canon tasks move',
|
|
967
|
+
position ?? {
|
|
968
|
+
ok: false,
|
|
969
|
+
reason: 'bad-input',
|
|
970
|
+
message:
|
|
971
|
+
'No position named. Pass --top, --bottom, --before, or --after.',
|
|
972
|
+
detail: [],
|
|
973
|
+
},
|
|
974
|
+
emitJson,
|
|
975
|
+
process.cwd(),
|
|
976
|
+
)
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const root = opts.root ?? (await mainWorktreeRoot())
|
|
980
|
+
const outcome = await moveTask(root, task, position)
|
|
981
|
+
|
|
982
|
+
return reportBoardWrite('canon tasks move', outcome, emitJson, root)
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
async function runBlock(
|
|
986
|
+
task: string,
|
|
987
|
+
waiting: string,
|
|
988
|
+
opts: BoardCommandOptions,
|
|
989
|
+
): Promise<number> {
|
|
990
|
+
const root = opts.root ?? (await mainWorktreeRoot())
|
|
991
|
+
const outcome = await blockTask(root, task, waiting)
|
|
992
|
+
|
|
993
|
+
return reportBoardWrite(
|
|
994
|
+
'canon tasks block',
|
|
995
|
+
outcome,
|
|
996
|
+
opts.json ?? false,
|
|
997
|
+
root,
|
|
998
|
+
)
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function reportBoardWrite(
|
|
1002
|
+
title: string,
|
|
1003
|
+
outcome: BoardOutcome,
|
|
1004
|
+
emitJson: boolean,
|
|
1005
|
+
root: string,
|
|
1006
|
+
): number {
|
|
1007
|
+
if (!outcome.ok) return reportRecord(title, outcome, emitJson, root)
|
|
1008
|
+
|
|
1009
|
+
if (emitJson) {
|
|
1010
|
+
process.stdout.write(
|
|
1011
|
+
`${JSON.stringify({
|
|
1012
|
+
ok: true,
|
|
1013
|
+
root,
|
|
1014
|
+
task: outcome.stem,
|
|
1015
|
+
path: relative(root, outcome.path),
|
|
1016
|
+
group: outcome.group,
|
|
1017
|
+
from: outcome.from ?? null,
|
|
1018
|
+
position: outcome.position,
|
|
1019
|
+
backlog: outcome.backlog,
|
|
1020
|
+
})}\n`,
|
|
1021
|
+
)
|
|
1022
|
+
return 0
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
intro(title)
|
|
1026
|
+
logStep('Written')
|
|
1027
|
+
logInfo(
|
|
1028
|
+
`${outcome.stem} sits at ${outcome.position} under ${outcome.group}${outcome.from && outcome.from !== outcome.group ? `, from ${outcome.from}` : ''}`,
|
|
1029
|
+
)
|
|
1030
|
+
if (outcome.backlog) logRemove('its backlog line')
|
|
1031
|
+
logAdd(relative(root, outcome.path))
|
|
1032
|
+
outro()
|
|
1033
|
+
|
|
1034
|
+
return 0
|
|
664
1035
|
}
|
|
665
1036
|
|
|
666
1037
|
function collectPosition(value: string, previous: string[]): string[] {
|
|
@@ -832,7 +1203,13 @@ async function runPlanLink(
|
|
|
832
1203
|
): Promise<number> {
|
|
833
1204
|
const emitJson = opts.json ?? false
|
|
834
1205
|
const root = opts.root ?? (await mainWorktreeRoot())
|
|
835
|
-
const outcome = await recordPlan(
|
|
1206
|
+
const outcome = await recordPlan(
|
|
1207
|
+
root,
|
|
1208
|
+
task,
|
|
1209
|
+
plan,
|
|
1210
|
+
opts.add ?? false,
|
|
1211
|
+
opts.waiting,
|
|
1212
|
+
)
|
|
836
1213
|
|
|
837
1214
|
return reportPlanLink(outcome, emitJson, root)
|
|
838
1215
|
}
|
|
@@ -900,7 +1277,7 @@ async function runOutcome(
|
|
|
900
1277
|
|
|
901
1278
|
function reportRecord(
|
|
902
1279
|
title: string,
|
|
903
|
-
refused: RecordRefused,
|
|
1280
|
+
refused: RecordRefused | BoardRefused,
|
|
904
1281
|
emitJson: boolean,
|
|
905
1282
|
root: string,
|
|
906
1283
|
): number {
|
|
@@ -985,6 +1362,7 @@ function reportPlanLink(
|
|
|
985
1362
|
plan: outcome.plan,
|
|
986
1363
|
action: outcome.action,
|
|
987
1364
|
replaced: outcome.replaced,
|
|
1365
|
+
board: { ...outcome.board, from: outcome.board.from ?? null },
|
|
988
1366
|
})}\n`,
|
|
989
1367
|
)
|
|
990
1368
|
return 0
|
|
@@ -995,6 +1373,9 @@ function reportPlanLink(
|
|
|
995
1373
|
logInfo(`${outcome.stem} names plan ${outcome.plan}`)
|
|
996
1374
|
if (outcome.replaced) logInfo(`replaced ${outcome.replaced}`)
|
|
997
1375
|
if (outcome.action !== 'unchanged') logAdd(relative(root, outcome.path))
|
|
1376
|
+
logInfo(
|
|
1377
|
+
`board row ${outcome.board.moved ? '' : 'not moved, '}${outcome.board.note}`,
|
|
1378
|
+
)
|
|
998
1379
|
outro()
|
|
999
1380
|
|
|
1000
1381
|
return 0
|
package/src/commands/tooling.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from '@/tooling/manifest'
|
|
19
19
|
import { readReference, resolveReference } from '@/tooling/read'
|
|
20
20
|
import { scan, type ScanResult } from '@/tooling/scan'
|
|
21
|
-
import {
|
|
21
|
+
import { recordToolingChain } from '@/tooling/stamp'
|
|
22
22
|
import { subfolderPath } from '@/tooling/subfolder'
|
|
23
23
|
import { verifyStack } from '@/tooling/verify-stack'
|
|
24
24
|
import {
|
|
@@ -513,8 +513,8 @@ async function stampChain(chain: Manifest[], target: string): Promise<void> {
|
|
|
513
513
|
new Date(),
|
|
514
514
|
)
|
|
515
515
|
|
|
516
|
-
if (recorded) {
|
|
517
|
-
logInfo(`Recorded chain: ${
|
|
516
|
+
if (recorded !== undefined) {
|
|
517
|
+
logInfo(`Recorded chain: ${recorded.join(' < ')}`)
|
|
518
518
|
return
|
|
519
519
|
}
|
|
520
520
|
|
|
@@ -17,7 +17,9 @@ import {
|
|
|
17
17
|
type WorktreeVerdict,
|
|
18
18
|
} from '@/worktrees/reclaim'
|
|
19
19
|
import { mainWorktreeRoot } from '@/git/worktree'
|
|
20
|
+
import { machineListenerReader } from '@/listeners/machine'
|
|
20
21
|
import { repairBareFlag } from '@/worktrees/bare-flag'
|
|
22
|
+
import { type StopReport, stopListeners } from '@/worktrees/stop'
|
|
21
23
|
import {
|
|
22
24
|
removeReclaimable,
|
|
23
25
|
type RemovalOutcome,
|
|
@@ -57,6 +59,12 @@ export interface ReclaimRecord {
|
|
|
57
59
|
readonly removed: number
|
|
58
60
|
readonly failed: number
|
|
59
61
|
readonly pruned: boolean
|
|
62
|
+
/**
|
|
63
|
+
* Processes signaled, or that a dry run would signal, for listening inside a
|
|
64
|
+
* reclaimed worktree. Null when the machine offered no way to read
|
|
65
|
+
* listeners, which says nothing about what is still running.
|
|
66
|
+
*/
|
|
67
|
+
readonly stopped: number | null
|
|
60
68
|
readonly outcomes: readonly RemovalOutcome[]
|
|
61
69
|
}
|
|
62
70
|
|
|
@@ -79,6 +87,8 @@ export type ReclaimSource =
|
|
|
79
87
|
* from a run that was asked not to act.
|
|
80
88
|
*/
|
|
81
89
|
readonly removal: RemovalReport | null
|
|
90
|
+
/** Null when no stop ran, which is a reading with nothing reclaimable. */
|
|
91
|
+
readonly stop?: StopReport | null
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
const REFUSALS: Record<Refusal, string> = {
|
|
@@ -198,6 +208,14 @@ export function register(program: Command): void {
|
|
|
198
208
|
'deletes its branch, and a run carrying a directory that is already',
|
|
199
209
|
'gone sweeps stale registrations once before the branch deletes.',
|
|
200
210
|
'',
|
|
211
|
+
'Ahead of each removal it sends SIGTERM to every process listening from',
|
|
212
|
+
'inside a reclaimable worktree, so no dev server outlives the tree it',
|
|
213
|
+
'serves. A listener in the main checkout, in a refused worktree, or in',
|
|
214
|
+
'a worktree nested under another is left alone. A dry run signals',
|
|
215
|
+
'nothing and still reports what it would stop. "stopped" in the record',
|
|
216
|
+
'counts those processes, and reads null on a machine with neither lsof',
|
|
217
|
+
'nor /proc, where no listener could be read.',
|
|
218
|
+
'',
|
|
201
219
|
'It deletes only what a reading called reclaimable. A worktree a live',
|
|
202
220
|
'session holds is refused rather than removed here. `claude rm <id>`',
|
|
203
221
|
'is meant to remove the session and its worktree together, but its',
|
|
@@ -324,11 +342,13 @@ export function reclaimRecord(source: ReclaimSource): ReclaimRecord {
|
|
|
324
342
|
removed: 0,
|
|
325
343
|
failed: 0,
|
|
326
344
|
pruned: false,
|
|
345
|
+
stopped: 0,
|
|
327
346
|
outcomes: [],
|
|
328
347
|
}
|
|
329
348
|
}
|
|
330
349
|
|
|
331
350
|
const outcomes = source.removal?.outcomes ?? []
|
|
351
|
+
const stop = source.stop ?? null
|
|
332
352
|
|
|
333
353
|
return {
|
|
334
354
|
reason: null,
|
|
@@ -338,10 +358,30 @@ export function reclaimRecord(source: ReclaimSource): ReclaimRecord {
|
|
|
338
358
|
removed: outcomes.filter((outcome) => outcome.removed).length,
|
|
339
359
|
failed: outcomes.filter((outcome) => !outcome.removed).length,
|
|
340
360
|
pruned: source.removal?.pruned ?? false,
|
|
361
|
+
stopped: stop === null ? 0 : countStopped(stop),
|
|
341
362
|
outcomes,
|
|
342
363
|
}
|
|
343
364
|
}
|
|
344
365
|
|
|
366
|
+
function countStopped(stop: StopReport): number | null {
|
|
367
|
+
if (stop.kind === 'unread') return null
|
|
368
|
+
return stop.stopped.reduce((sum, server) => sum + server.pids.length, 0)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Names each server a stop took, or would take on a dry run, and says when none could be read. */
|
|
372
|
+
function reportStop(stop: StopReport, dryRun: boolean): void {
|
|
373
|
+
if (stop.kind === 'unread') {
|
|
374
|
+
logWarn(
|
|
375
|
+
'This machine offers neither lsof nor /proc, so listening servers were not read and none was stopped.',
|
|
376
|
+
)
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
const verb = dryRun ? 'Would stop' : 'Stopped'
|
|
380
|
+
for (const { path, ports } of stop.stopped) {
|
|
381
|
+
logInfo(`${verb} the server on ${ports.join(', ')} in ${path}`)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
345
385
|
function emitReclaim(opts: ReclaimCommandOptions, source: ReclaimSource): void {
|
|
346
386
|
if (opts.json !== true) return
|
|
347
387
|
process.stdout.write(`${JSON.stringify(reclaimRecord(source))}\n`)
|
|
@@ -381,8 +421,14 @@ async function runReclaim(opts: ReclaimCommandOptions): Promise<number> {
|
|
|
381
421
|
logInfo(plural(reclaimable.length, 'worktree'))
|
|
382
422
|
pipeOutput(reclaimable.map(describe).join('\n'))
|
|
383
423
|
|
|
424
|
+
const stop = await stopListeners(
|
|
425
|
+
reclaimable.map((entry) => entry.path),
|
|
426
|
+
{ reader: machineListenerReader(), dryRun },
|
|
427
|
+
)
|
|
428
|
+
|
|
384
429
|
if (dryRun) {
|
|
385
430
|
logStep('Dry run')
|
|
431
|
+
reportStop(stop, dryRun)
|
|
386
432
|
logWarn('Nothing was removed. Run this again without --dry-run to remove.')
|
|
387
433
|
outro()
|
|
388
434
|
emitReclaim(opts, {
|
|
@@ -390,6 +436,7 @@ async function runReclaim(opts: ReclaimCommandOptions): Promise<number> {
|
|
|
390
436
|
dryRun,
|
|
391
437
|
reclaimable: reclaimable.length,
|
|
392
438
|
removal: null,
|
|
439
|
+
stop,
|
|
393
440
|
})
|
|
394
441
|
return 0
|
|
395
442
|
}
|
|
@@ -403,6 +450,7 @@ async function runReclaim(opts: ReclaimCommandOptions): Promise<number> {
|
|
|
403
450
|
})
|
|
404
451
|
|
|
405
452
|
logStep('Removed')
|
|
453
|
+
reportStop(stop, dryRun)
|
|
406
454
|
for (const outcome of removal.outcomes) reportOutcome(outcome)
|
|
407
455
|
if (removal.pruned) {
|
|
408
456
|
logInfo(
|
|
@@ -423,6 +471,7 @@ async function runReclaim(opts: ReclaimCommandOptions): Promise<number> {
|
|
|
423
471
|
dryRun,
|
|
424
472
|
reclaimable: reclaimable.length,
|
|
425
473
|
removal,
|
|
474
|
+
stop,
|
|
426
475
|
})
|
|
427
476
|
return failed.length > 0 ? 1 : 0
|
|
428
477
|
}
|
package/src/context/audit.ts
CHANGED
|
@@ -209,7 +209,7 @@ const SENTENCE_END = /[.!?:;]\s(?=[^.!?:;]*$)/
|
|
|
209
209
|
* reading that field for a gain of one word.
|
|
210
210
|
*
|
|
211
211
|
* `claude/skills/context-fold/references/context.md` opens its scope by handing figures to the
|
|
212
|
-
* `
|
|
212
|
+
* `draw-figure` skill, so a marker reported in a diagrams entry would cite a
|
|
213
213
|
* rule that entry's own standard routes elsewhere. The length and table
|
|
214
214
|
* checkpoints are quoted from the same standard and keep reaching every audited
|
|
215
215
|
* folder, because a threshold on how far a reader travels generalizes across
|
package/src/demo/beats.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reads the human-facing draft `
|
|
2
|
+
* Reads the human-facing draft `script-screencast` writes. Nothing here knows
|
|
3
3
|
* about a browser: the draft is prose aimed at a person, and turning it into
|
|
4
4
|
* something executable is `@/demo/compile`'s job.
|
|
5
5
|
*/
|
package/src/gate/stages.ts
CHANGED
|
@@ -380,6 +380,21 @@ export const STAGES: readonly Stage[] = [
|
|
|
380
380
|
],
|
|
381
381
|
success: 'Rule citations resolve',
|
|
382
382
|
},
|
|
383
|
+
{
|
|
384
|
+
// Reads the whole rule corpus and fails only on a folder the verb lists as
|
|
385
|
+
// gated, so the finding count prints on every push while the rewrite moves
|
|
386
|
+
// folder by folder toward zero.
|
|
387
|
+
id: 'rule-shape',
|
|
388
|
+
label: 'Rule shape',
|
|
389
|
+
checks: [
|
|
390
|
+
{
|
|
391
|
+
kind: 'cli',
|
|
392
|
+
argv: ['gov', 'audit', '--gate'],
|
|
393
|
+
failure:
|
|
394
|
+
'A rule in a gated folder is past a shape limit or short of its ledger. Run bun src/cli.ts gov audit.',
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
},
|
|
383
398
|
{
|
|
384
399
|
// A citation naming the old record root resolves to nothing once the
|
|
385
400
|
// folders have moved, and the sweep that would repoint it only runs when a
|