@erclx/canon 5.32.0 → 5.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/REQUIREMENT.md +1 -2
- package/claude/skills/api-design/SKILL.md +2 -3
- package/claude/skills/api-design/references/adopted.md +1 -1
- package/claude/skills/auto-ship/SKILL.md +1 -1
- package/claude/skills/auto-ship/references/worktree-name.md +1 -1
- package/claude/skills/backlog-triage/REQUIREMENT.md +2 -2
- package/claude/skills/backlog-triage/SKILL.md +1 -1
- package/claude/skills/canon-cli/REQUIREMENT.md +16 -13
- package/claude/skills/canon-cli/SKILL.md +75 -32
- package/claude/skills/canon-feedback/REQUIREMENT.md +1 -0
- package/claude/skills/canon-feedback/SKILL.md +11 -29
- package/claude/skills/canon-feedback/references/template.md +53 -0
- package/claude/skills/{canvas → canvas-board}/REQUIREMENT.md +2 -2
- package/claude/skills/{canvas → canvas-board}/SKILL.md +6 -3
- package/claude/skills/{canvas → canvas-board}/references/canvas.md +26 -3
- package/{standards → claude/skills/canvas-board/references}/design.md +11 -2
- package/claude/skills/code-craft/REQUIREMENT.md +1 -1
- package/claude/skills/code-craft/SKILL.md +0 -1
- package/claude/skills/context-fold/REQUIREMENT.md +25 -33
- package/claude/skills/context-fold/SKILL.md +25 -70
- package/claude/skills/context-fold/references/classify.md +3 -3
- package/claude/skills/context-fold/references/completion-report.md +4 -4
- package/claude/skills/context-fold/references/context-refresh.md +5 -5
- package/{standards → claude/skills/context-fold/references}/context.md +16 -5
- package/claude/skills/context-fold/references/unusable-baseline.md +3 -5
- package/claude/skills/create-rule/SKILL.md +3 -3
- package/{standards → claude/skills/create-rule/references}/rule.md +15 -6
- package/claude/skills/create-skill/SKILL.md +2 -2
- package/{standards → claude/skills/create-skill/references}/skill.md +18 -15
- package/claude/skills/design-taste/REQUIREMENT.md +3 -3
- package/claude/skills/design-taste/SKILL.md +1 -1
- package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
- package/claude/skills/docs-sync/SKILL.md +3 -3
- package/{standards → claude/skills/docs-sync/references}/docs.md +11 -0
- package/{standards → claude/skills/docs-sync/references}/readme.md +9 -2
- package/claude/skills/draft-and-pick/SKILL.md +5 -5
- package/claude/skills/draft-doc/SKILL.md +6 -6
- package/claude/skills/draft-figure/REQUIREMENT.md +2 -2
- package/claude/skills/draft-figure/SKILL.md +2 -2
- package/claude/skills/draft-figure/references/hand-drawn.md +1 -1
- package/claude/skills/draft-figure/references/mermaid-path.md +1 -1
- package/claude/skills/draft-identity/REQUIREMENT.md +2 -2
- package/claude/skills/draft-identity/SKILL.md +2 -2
- package/claude/skills/draft-ready/SKILL.md +1 -1
- package/claude/skills/git-branch/SKILL.md +1 -1
- package/{standards → claude/skills/git-branch/references}/branch.md +1 -1
- package/claude/skills/git-commit/SKILL.md +2 -2
- package/claude/skills/git-followup/REQUIREMENT.md +1 -1
- package/claude/skills/git-followup/SKILL.md +1 -1
- package/claude/skills/git-issue/SKILL.md +1 -1
- package/claude/skills/git-issue/references/issue.md +4 -1
- package/claude/skills/git-pr/SKILL.md +13 -13
- package/claude/skills/git-pr/references/evidence.md +2 -2
- package/claude/skills/git-pr/references/final-command.md +38 -14
- package/claude/skills/git-pr/references/labels.md +5 -1
- package/{standards → claude/skills/git-pr/references}/pr.md +19 -9
- package/{standards → claude/skills/git-pr/references}/publish.md +4 -2
- package/claude/skills/git-pr/references/task-number.md +1 -1
- package/claude/skills/git-ship/SKILL.md +15 -14
- package/claude/skills/git-split/SKILL.md +3 -3
- package/claude/skills/git-stage/SKILL.md +1 -1
- package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/SKILL.md +1 -1
- package/claude/skills/review-address/references/post-review-findings.md +1 -1
- package/claude/skills/review-craft/references/ui.md +1 -1
- package/claude/skills/review-pr/REQUIREMENT.md +1 -1
- package/claude/skills/review-pr/SKILL.md +3 -3
- package/claude/skills/review-pr/references/body-variants.md +2 -2
- package/claude/skills/review-pr/references/key-changes.md +3 -3
- package/claude/skills/review-pr/references/post.md +4 -2
- package/claude/skills/review-ui/REQUIREMENT.md +1 -1
- package/claude/skills/review-ui/SKILL.md +8 -6
- package/claude/skills/review-ui/references/driving.md +8 -8
- package/claude/skills/review-ui/references/post.md +53 -17
- package/claude/skills/role-orchestrator/REQUIREMENT.md +3 -0
- package/claude/skills/role-orchestrator/SKILL.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +8 -5
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +32 -1
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-resume.md +3 -0
- package/claude/skills/role-planner/REQUIREMENT.md +2 -1
- package/claude/skills/role-planner/SKILL.md +13 -2
- package/claude/skills/role-reviewer/SKILL.md +1 -1
- package/claude/skills/role-worker/REQUIREMENT.md +1 -0
- package/claude/skills/role-worker/SKILL.md +9 -1
- package/claude/skills/session-map/SKILL.md +1 -1
- package/claude/skills/session-resume/REQUIREMENT.md +2 -2
- package/claude/skills/session-worktree/SKILL.md +5 -5
- package/claude/skills/target-setup/SKILL.md +10 -4
- package/claude/skills/target-setup/references/detect.md +20 -3
- package/claude/skills/task-board/REQUIREMENT.md +7 -12
- package/claude/skills/task-board/SKILL.md +5 -17
- package/claude/skills/task-board/references/tasks.md +2 -2
- package/claude/skills/task-complete/REQUIREMENT.md +47 -0
- package/claude/skills/task-complete/SKILL.md +86 -0
- package/claude/skills/{task-board → task-complete}/references/archive.md +4 -4
- package/claude/skills/{context-fold → task-complete}/references/receipt-sweep.md +5 -5
- package/claude/skills/task-complete/references/unusable-baseline.md +19 -0
- package/claude/skills/teach-workspace/REQUIREMENT.md +6 -2
- package/claude/skills/teach-workspace/SKILL.md +8 -4
- package/claude/skills/teach-workspace/references/lesson-craft.md +1 -1
- package/claude/skills/{context-fold/references/promoted-pages.md → teach-workspace/references/promotion-fold.md} +3 -3
- package/claude/skills/teach-workspace/references/promotion.md +3 -3
- package/claude/skills/test-craft/REQUIREMENT.md +2 -3
- package/claude/skills/test-craft/SKILL.md +15 -3
- package/claude/skills/ui-checklist/SKILL.md +1 -1
- package/claude/skills/ux-audit/SKILL.md +1 -1
- package/claude/skills/ux-measure/SKILL.md +1 -1
- package/docs/agents/canvas.md +6 -2
- package/docs/agents/commands.md +4 -3
- package/docs/agents/context-audit-checks.md +4 -4
- package/docs/agents/gov-detect.md +47 -0
- package/docs/agents/index.md +7 -4
- package/docs/agents/indexes.md +1 -1
- package/docs/agents/install-and-sync.md +10 -11
- package/docs/agents/key-changes.md +5 -5
- package/docs/agents/label-coverage.md +11 -0
- package/docs/agents/migrate.md +1 -1
- package/docs/agents/pr-evidence-layout.md +70 -0
- package/docs/agents/pr-evidence.md +8 -9
- package/docs/agents/pr-frames.md +7 -2
- package/docs/agents/pr-lift.md +132 -0
- package/docs/agents/records-push.md +2 -2
- package/docs/agents/records-stale.md +12 -47
- package/docs/agents/records-validate.md +3 -7
- package/docs/agents/records.md +5 -27
- package/docs/agents/rule-citations.md +1 -1
- package/docs/agents/sandbox.md +4 -4
- package/docs/agents/scripting.md +2 -1
- package/docs/agents/skills-audit.md +1 -1
- package/docs/agents/tasks-plans.md +2 -2
- package/docs/agents/tasks.md +2 -2
- package/docs/target/migrations.md +10 -2
- package/docs/target/projects.md +4 -4
- package/docs/target/sync.md +1 -1
- package/docs/workflow/operating-model.md +2 -2
- package/docs/workflow/parallel-features.md +7 -7
- package/docs/workflow/skill-map-on-request.md +9 -9
- package/docs/workflow/skill-map.md +12 -11
- package/docs/workflow/visual-design-workflow.md +2 -2
- package/governance/detect.toml +201 -0
- package/governance/renames.toml +0 -11
- package/governance/rules/canon/context.md +13 -0
- package/governance/rules/claude/skill.md +17 -0
- package/governance/rules/code/000-code.md +1 -13
- package/governance/rules/code/010-testing.md +2 -30
- package/governance/rules/ui/400-ui.md +1 -1
- package/governance/rules/ui/480-canvas.md +13 -0
- package/governance/stacks/base.toml +2 -4
- package/package.json +4 -4
- package/scripts/core/install-check.sh +3 -3
- package/scripts/core/regen-hero.ts +0 -6
- package/scripts/lib/ui.sh +0 -168
- package/src/audits/catalog.ts +0 -1
- package/src/canvas/arrange.ts +66 -0
- package/src/canvas/capture.ts +20 -6
- package/src/canvas/content.ts +39 -0
- package/src/canvas/fit.ts +238 -0
- package/src/canvas/tokens.ts +28 -10
- package/src/capture/frame-values.ts +2 -32
- package/src/claude/cases/misc.ts +1 -1
- package/src/claude/cases/setup.ts +4 -0
- package/src/claude/cases/workflow.ts +6 -1
- package/src/claude/seeds.ts +1 -1
- package/src/claude/skills-audit.ts +2 -2
- package/src/cli/help.ts +3 -1
- package/src/cli/ui.ts +2 -3
- package/src/commands/canvas.ts +179 -1
- package/src/commands/claude.ts +1 -1
- package/src/commands/gov.ts +146 -16
- package/src/commands/labels.ts +326 -7
- package/src/commands/migrate.ts +6 -1
- package/src/commands/pr.ts +377 -2
- package/src/commands/records.ts +13 -399
- package/src/commands/sandbox.ts +3 -6
- package/src/commands/tasks.ts +3 -3
- package/src/context/audit.ts +5 -5
- package/src/context/folders.ts +1 -1
- package/src/design/budget.ts +1 -1
- package/src/design/components.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/regen.ts +1 -1
- package/src/design/render.ts +1 -1
- package/src/feedback/body.ts +26 -5
- package/src/gate/measures.ts +59 -45
- package/src/gate/sequencer.ts +56 -1
- package/src/gate/stages.ts +4 -11
- package/src/gov/detect.ts +301 -0
- package/src/gov/install.ts +2 -3
- package/src/gov/list.ts +23 -2
- package/src/gov/stacks.ts +10 -1
- package/src/hooks/post-merge.ts +1 -1
- package/src/labels/apply.ts +39 -0
- package/src/labels/format.ts +3 -3
- package/src/labels/phase.ts +3 -3
- package/src/labels/sections.ts +43 -0
- package/src/migrate/skill-names.ts +20 -1
- package/src/pr/evidence.ts +218 -45
- package/src/pr/frames.ts +23 -3
- package/src/pr/lift.ts +418 -0
- package/src/pr/paths.ts +4 -4
- package/src/pr/rest.ts +3 -0
- package/src/pr/review-scope.ts +76 -0
- package/src/records/backup.ts +16 -6
- package/src/records/stale-canonical.ts +31 -1
- package/src/records/validate.ts +2 -188
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/coverage.ts +2 -6
- package/src/sandbox/equivalence.ts +19 -51
- package/src/sandbox/expect.ts +4 -4
- package/src/sandbox/provision.ts +13 -36
- package/src/sandbox/scenario.ts +26 -48
- package/src/sandbox/tree.ts +2 -6
- package/src/standards/read.ts +14 -1
- package/src/sync/git.ts +1 -1
- package/src/tasks/archive.ts +5 -5
- package/src/tasks/branch.ts +3 -3
- package/src/tasks/label.ts +1 -1
- package/src/tasks/record.ts +2 -2
- package/src/tasks/validate.ts +1 -1
- package/standards/index.md +0 -13
- package/standards/standard.md +1 -7
- package/tooling/base/reference.md +1 -1
- package/tooling/base/seeds/.github/pull_request_template.md +2 -2
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +9 -1
- package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +171 -0
- package/tooling/claude/seeds/.claude/settings.json +9 -0
- package/governance/rules/standards/context.md +0 -14
- package/governance/rules/standards/design.md +0 -12
- package/governance/rules/standards/readme.md +0 -12
- package/governance/rules/standards/rule.md +0 -13
- package/governance/rules/standards/skill.md +0 -21
- package/governance/rules/standards/standard.md +0 -13
- package/scripts/config.sh +0 -11
- package/scripts/lib/gov.sh +0 -14
- package/scripts/lib/sandbox-fixtures.sh +0 -191
- package/scripts/lib/sandbox-git.sh +0 -125
- package/scripts/lib/sandbox-path.sh +0 -50
- package/scripts/sandbox-hook.sh +0 -99
- package/src/gov/standard-rules.ts +0 -179
- package/src/records/migrate.ts +0 -78
- package/src/records/stale.ts +0 -243
- package/src/sandbox/hooks.ts +0 -128
- /package/{standards → claude/skills/draft-figure/references}/figures.md +0 -0
- /package/{standards → claude/skills/git-branch/references}/slug.md +0 -0
- /package/{standards → claude/skills/git-commit/references}/commit.md +0 -0
- /package/{standards → claude/skills/git-commit/references}/versioning.md +0 -0
package/src/commands/canvas.ts
CHANGED
|
@@ -9,12 +9,15 @@ import {
|
|
|
9
9
|
DEFAULT_FRAME,
|
|
10
10
|
listPages,
|
|
11
11
|
moveFrame,
|
|
12
|
+
pruneLayout,
|
|
12
13
|
readPage,
|
|
13
14
|
readSelection,
|
|
14
15
|
renamePage,
|
|
15
16
|
resizeFrame,
|
|
16
17
|
} from '@/canvas/content'
|
|
18
|
+
import { planArrange } from '@/canvas/arrange'
|
|
17
19
|
import { captureCanvas, captureComposite } from '@/canvas/capture'
|
|
20
|
+
import { fitCanvas, isOwed } from '@/canvas/fit'
|
|
18
21
|
import {
|
|
19
22
|
type EditRefused,
|
|
20
23
|
editFrameAtIndex,
|
|
@@ -148,7 +151,7 @@ export function register(program: Command): void {
|
|
|
148
151
|
.command('canvas')
|
|
149
152
|
.helpOption('-h, --help', 'Show this help message')
|
|
150
153
|
.description(
|
|
151
|
-
'Local canvas of pages and HTML frames (serve, list, page, frame, selection, edit, editing, capture)',
|
|
154
|
+
'Local canvas of pages and HTML frames (serve, list, page, frame, selection, edit, editing, capture, fit, arrange)',
|
|
152
155
|
)
|
|
153
156
|
.addHelpText(
|
|
154
157
|
'after',
|
|
@@ -167,6 +170,8 @@ export function register(program: Command): void {
|
|
|
167
170
|
" canon canvas edit drafts/hero --element 4 --set 'color=var(--color-accent)'",
|
|
168
171
|
' canon canvas editing drafts/hero',
|
|
169
172
|
' canon canvas capture drafts/hero',
|
|
173
|
+
' canon canvas fit drafts/hero --json',
|
|
174
|
+
' canon canvas arrange drafts --check',
|
|
170
175
|
' canon canvas list --json',
|
|
171
176
|
'',
|
|
172
177
|
].join('\n'),
|
|
@@ -728,6 +733,179 @@ export function register(program: Command): void {
|
|
|
728
733
|
outro()
|
|
729
734
|
if (failed) process.exitCode = 1
|
|
730
735
|
})
|
|
736
|
+
|
|
737
|
+
withRoot(
|
|
738
|
+
canvas
|
|
739
|
+
.command('fit')
|
|
740
|
+
.description("Set each frame's height to its content")
|
|
741
|
+
.argument('<target>', '<page>/<frame> for one frame, <page> for all')
|
|
742
|
+
.option('--check', 'Report what is owed and write nothing'),
|
|
743
|
+
)
|
|
744
|
+
.addHelpText(
|
|
745
|
+
'after',
|
|
746
|
+
[
|
|
747
|
+
'',
|
|
748
|
+
'Measures each frame in the capture browser at its box width, through',
|
|
749
|
+
'the served address, so the faces the canvas serves are the ones read.',
|
|
750
|
+
'The height is the content height rounded up, with no margin. A',
|
|
751
|
+
'content wider than the box is reported and the box is never widened.',
|
|
752
|
+
'A frame whose body text names a family the canvas does not serve is',
|
|
753
|
+
'reported as font, since its height may not hold in another browser.',
|
|
754
|
+
'',
|
|
755
|
+
'Run canvas arrange after a fit that grew a frame, since rows below',
|
|
756
|
+
'do not move on their own.',
|
|
757
|
+
'',
|
|
758
|
+
'Exit codes:',
|
|
759
|
+
' 0 written, or nothing owed under --check',
|
|
760
|
+
' 1 refused or the browser failed',
|
|
761
|
+
' 2 --check found a height off, a wide frame, or a font',
|
|
762
|
+
'',
|
|
763
|
+
].join('\n'),
|
|
764
|
+
)
|
|
765
|
+
.action(async (target: string, opts: FitOptions) => {
|
|
766
|
+
process.exitCode = await runFit(target, opts)
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
withRoot(
|
|
770
|
+
canvas
|
|
771
|
+
.command('arrange')
|
|
772
|
+
.description('Re-lay a page in rows on the frame gap')
|
|
773
|
+
.argument('<page>', 'Page to arrange')
|
|
774
|
+
.option('--check', 'Report what is owed and write nothing'),
|
|
775
|
+
)
|
|
776
|
+
.addHelpText(
|
|
777
|
+
'after',
|
|
778
|
+
[
|
|
779
|
+
'',
|
|
780
|
+
'Keeps the rows the frames already sit in, orders each left to right on',
|
|
781
|
+
'the frame gap, stacks the rows on their tallest frame, and drops',
|
|
782
|
+
'layout entries whose frame file is gone.',
|
|
783
|
+
'',
|
|
784
|
+
'Exit codes:',
|
|
785
|
+
' 0 written, or nothing owed under --check',
|
|
786
|
+
' 1 refused',
|
|
787
|
+
' 2 --check found frames to move or entries to drop',
|
|
788
|
+
'',
|
|
789
|
+
].join('\n'),
|
|
790
|
+
)
|
|
791
|
+
.action(async (pageName: string, opts: FitOptions) => {
|
|
792
|
+
process.exitCode = await runArrange(pageName, opts)
|
|
793
|
+
})
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
interface FitOptions extends RootOptions {
|
|
797
|
+
readonly check?: boolean
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
async function runFit(target: string, opts: FitOptions): Promise<number> {
|
|
801
|
+
const emitJson = opts.json ?? false
|
|
802
|
+
const resolved = await resolveRoot(opts)
|
|
803
|
+
if (!resolved.ok) return refuse(resolved, emitJson)
|
|
804
|
+
|
|
805
|
+
const check = opts.check ?? false
|
|
806
|
+
let outcome: Awaited<ReturnType<typeof fitCanvas>>
|
|
807
|
+
try {
|
|
808
|
+
outcome = await fitCanvas(resolved.root, target, !check)
|
|
809
|
+
} catch (error) {
|
|
810
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
811
|
+
return refuse({ ok: false, reason: 'capture-failed', detail }, emitJson)
|
|
812
|
+
}
|
|
813
|
+
if (!outcome.ok) return refuse(outcome, emitJson)
|
|
814
|
+
|
|
815
|
+
const frames = outcome.rows.map((row) => ({ ...row, owed: isOwed(row) }))
|
|
816
|
+
const owed = frames.some((row) => row.owed)
|
|
817
|
+
if (emitJson) writeJson({ ok: true, written: outcome.written, frames })
|
|
818
|
+
intro(BANNER)
|
|
819
|
+
for (const row of frames) {
|
|
820
|
+
const name = `${row.page}/${row.frame}`
|
|
821
|
+
if (row.fitted === row.height) logInfo(`${name} fits at ${row.height}`)
|
|
822
|
+
else if (outcome.written) logAdd(`${name} ${row.height} to ${row.fitted}`)
|
|
823
|
+
else logStep(`${name} owes ${row.height} to ${row.fitted}`)
|
|
824
|
+
if (row.wide) logWarn(`${name} is wider than its box, so widen the box`)
|
|
825
|
+
if (row.viewport) {
|
|
826
|
+
logWarn(
|
|
827
|
+
`${name} sets a height in viewport units, so its height was not written and a fixed height fits it`,
|
|
828
|
+
)
|
|
829
|
+
}
|
|
830
|
+
if (row.font) {
|
|
831
|
+
logWarn(
|
|
832
|
+
`${name} sets ${row.family}, which the canvas does not serve, so its height may not hold in another browser`,
|
|
833
|
+
)
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
outro()
|
|
837
|
+
return check && owed ? 2 : 0
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async function runArrange(pageName: string, opts: FitOptions): Promise<number> {
|
|
841
|
+
const emitJson = opts.json ?? false
|
|
842
|
+
const resolved = await resolveRoot(opts)
|
|
843
|
+
if (!resolved.ok) return refuse(resolved, emitJson)
|
|
844
|
+
const { root } = resolved
|
|
845
|
+
|
|
846
|
+
const page = readPage(root, pageName)
|
|
847
|
+
if (!page) {
|
|
848
|
+
return refuse(
|
|
849
|
+
{
|
|
850
|
+
ok: false,
|
|
851
|
+
reason: 'no-page',
|
|
852
|
+
detail: `page ${pageName} does not exist`,
|
|
853
|
+
},
|
|
854
|
+
emitJson,
|
|
855
|
+
)
|
|
856
|
+
}
|
|
857
|
+
if (page.layoutIssue) {
|
|
858
|
+
return refuse(
|
|
859
|
+
{
|
|
860
|
+
ok: false,
|
|
861
|
+
reason: 'malformed-layout',
|
|
862
|
+
detail: `${pageName}/layout.json does not parse`,
|
|
863
|
+
},
|
|
864
|
+
emitJson,
|
|
865
|
+
)
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const check = opts.check ?? false
|
|
869
|
+
const pruned = pruneLayout(root, pageName, !check)
|
|
870
|
+
if (!pruned.ok) return refuse(pruned, emitJson)
|
|
871
|
+
|
|
872
|
+
const moves = planArrange(page.frames)
|
|
873
|
+
if (!check) {
|
|
874
|
+
for (const move of moves) {
|
|
875
|
+
const outcome = resizeFrame(root, pageName, move.name, move.to)
|
|
876
|
+
if (!outcome.ok) return refuse(outcome, emitJson)
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (emitJson) {
|
|
881
|
+
writeJson({
|
|
882
|
+
ok: true,
|
|
883
|
+
page: pageName,
|
|
884
|
+
written: !check,
|
|
885
|
+
moved: moves.map((move) => ({
|
|
886
|
+
frame: move.name,
|
|
887
|
+
from: { x: move.from.x, y: move.from.y },
|
|
888
|
+
to: { x: move.to.x, y: move.to.y },
|
|
889
|
+
})),
|
|
890
|
+
dropped: pruned.dropped,
|
|
891
|
+
})
|
|
892
|
+
}
|
|
893
|
+
intro(BANNER)
|
|
894
|
+
for (const move of moves) {
|
|
895
|
+
const text = `${pageName}/${move.name} ${move.from.x}, ${move.from.y} to ${move.to.x}, ${move.to.y}`
|
|
896
|
+
if (check) logStep(`${text} is owed`)
|
|
897
|
+
else logAdd(text)
|
|
898
|
+
}
|
|
899
|
+
for (const name of pruned.dropped) {
|
|
900
|
+
logWarn(
|
|
901
|
+
`${pageName}/${name} has no frame file, so its entry is ${check ? 'owed a drop' : 'dropped'}`,
|
|
902
|
+
)
|
|
903
|
+
}
|
|
904
|
+
if (moves.length === 0 && pruned.dropped.length === 0) {
|
|
905
|
+
logInfo(`${pageName} is already laid out`)
|
|
906
|
+
}
|
|
907
|
+
outro()
|
|
908
|
+
return check && (moves.length > 0 || pruned.dropped.length > 0) ? 2 : 0
|
|
731
909
|
}
|
|
732
910
|
|
|
733
911
|
interface CaptureOptions extends RootOptions {
|
package/src/commands/claude.ts
CHANGED
|
@@ -386,7 +386,7 @@ export function register(program: Command): void {
|
|
|
386
386
|
skills
|
|
387
387
|
.command('audit')
|
|
388
388
|
.description(
|
|
389
|
-
'Report both skill corpora against the mechanical rules in
|
|
389
|
+
'Report both skill corpora against the mechanical rules in claude/skills/create-skill/references/skill.md',
|
|
390
390
|
)
|
|
391
391
|
.argument('[path]', 'Project root, defaulting to the current directory')
|
|
392
392
|
.helpOption('-h, --help', 'Show this help message')
|
package/src/commands/gov.ts
CHANGED
|
@@ -12,7 +12,6 @@ import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
|
12
12
|
import { creationRel, SCRATCH } from '@/roots/record'
|
|
13
13
|
import { createGovAdapter } from '@/gov/adapter'
|
|
14
14
|
import { regenConsumedRules } from '@/gov/consumed'
|
|
15
|
-
import { regenStandardRules } from '@/gov/standard-rules'
|
|
16
15
|
import { installRules, lookupRules } from '@/gov/install'
|
|
17
16
|
import { buildGovCatalog, describeRule, describeStack } from '@/gov/list'
|
|
18
17
|
import { buildRulesPayload, listRuleFiles } from '@/gov/payload'
|
|
@@ -20,6 +19,7 @@ import {
|
|
|
20
19
|
govStackExists,
|
|
21
20
|
listGovStacks,
|
|
22
21
|
mergeExtraRules,
|
|
22
|
+
resolveExtraRules,
|
|
23
23
|
resolveRules,
|
|
24
24
|
} from '@/gov/stacks'
|
|
25
25
|
import {
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
type TestOrderReport,
|
|
53
53
|
} from '@/gov/test-order'
|
|
54
54
|
import { recordStamp, runDomainSync } from '@/sync/engine'
|
|
55
|
+
import { detectRules } from '@/gov/detect'
|
|
55
56
|
import { writeChainStamp } from '@/sync/stamp'
|
|
56
57
|
import { resolveTarget } from '@/targets/validate'
|
|
57
58
|
import {
|
|
@@ -154,6 +155,11 @@ export function register(program: Command): void {
|
|
|
154
155
|
' canon gov install react',
|
|
155
156
|
' canon gov install node ../my-app',
|
|
156
157
|
' canon gov install astro --add 200-react,260-shadcn ../my-app',
|
|
158
|
+
' canon gov install --add tasks,plan ../my-app',
|
|
159
|
+
'',
|
|
160
|
+
'With --add and no stack, the named rules install alone. A first',
|
|
161
|
+
'positional naming a stack is read as the stack, so pass . or a path',
|
|
162
|
+
'carrying a slash to force a target that shares a stack name.',
|
|
157
163
|
'',
|
|
158
164
|
].join('\n'),
|
|
159
165
|
)
|
|
@@ -191,18 +197,14 @@ export function register(program: Command): void {
|
|
|
191
197
|
|
|
192
198
|
gov
|
|
193
199
|
.command('regen')
|
|
194
|
-
.description(
|
|
195
|
-
"Rebuild the standard-pointer rules and a repository's own .claude/rules/",
|
|
196
|
-
)
|
|
200
|
+
.description("Rebuild a repository's own .claude/rules/")
|
|
197
201
|
.helpOption('-h, --help', 'Show this help message')
|
|
198
202
|
.option('--root <path>', 'Repository root to regenerate', PROJECT_ROOT)
|
|
199
203
|
.addHelpText(
|
|
200
204
|
'after',
|
|
201
205
|
[
|
|
202
206
|
'',
|
|
203
|
-
'
|
|
204
|
-
'fields in each standard under standards/. Then reads',
|
|
205
|
-
'internal/governance.toml and installs the stack it names, plus any',
|
|
207
|
+
'Reads internal/governance.toml and installs the stack it names, plus any',
|
|
206
208
|
'rules under internal/rules/. Unlike install and sync, this runs',
|
|
207
209
|
'against the toolkit root, whose .claude/rules/ is produced output.',
|
|
208
210
|
'',
|
|
@@ -226,6 +228,31 @@ export function register(program: Command): void {
|
|
|
226
228
|
process.exitCode = runList(opts)
|
|
227
229
|
})
|
|
228
230
|
|
|
231
|
+
gov
|
|
232
|
+
.command('detect')
|
|
233
|
+
.description('Propose opt-in rules from the evidence a target holds')
|
|
234
|
+
.argument('[target]', 'Target directory', '.')
|
|
235
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
236
|
+
.option('--stack <stack>', 'Leave out every rule this stack resolves')
|
|
237
|
+
.option('--json', 'Emit machine-readable JSON')
|
|
238
|
+
.addHelpText(
|
|
239
|
+
'after',
|
|
240
|
+
[
|
|
241
|
+
'',
|
|
242
|
+
'Reads governance/detect.toml and matches each rule against the',
|
|
243
|
+
"target's manifests and git-listed paths to depth two. Pass the",
|
|
244
|
+
'proposals to canon gov install --add once confirmed.',
|
|
245
|
+
'',
|
|
246
|
+
'Examples:',
|
|
247
|
+
' canon gov detect --json',
|
|
248
|
+
' canon gov detect ../my-app --stack node --json',
|
|
249
|
+
'',
|
|
250
|
+
].join('\n'),
|
|
251
|
+
)
|
|
252
|
+
.action(async (target: string, opts: DetectOptions) => {
|
|
253
|
+
process.exitCode = await runDetect(target, opts)
|
|
254
|
+
})
|
|
255
|
+
|
|
229
256
|
gov
|
|
230
257
|
.command('test-order')
|
|
231
258
|
.description(
|
|
@@ -1075,6 +1102,7 @@ function runList(opts: ListOptions): number {
|
|
|
1075
1102
|
...(sections.stacks ? { stacks: catalog.stacks } : {}),
|
|
1076
1103
|
...(sections.rules ? { rules: catalog.rules } : {}),
|
|
1077
1104
|
unreferenced: catalog.unreferenced,
|
|
1105
|
+
staleDetect: catalog.staleDetect,
|
|
1078
1106
|
})}\n`,
|
|
1079
1107
|
)
|
|
1080
1108
|
return 0
|
|
@@ -1102,6 +1130,51 @@ function runList(opts: ListOptions): number {
|
|
|
1102
1130
|
return 0
|
|
1103
1131
|
}
|
|
1104
1132
|
|
|
1133
|
+
interface DetectOptions {
|
|
1134
|
+
readonly stack?: string
|
|
1135
|
+
readonly json?: boolean
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
async function runDetect(target: string, opts: DetectOptions): Promise<number> {
|
|
1139
|
+
const resolved = resolve(target)
|
|
1140
|
+
if (!existsSync(resolved)) {
|
|
1141
|
+
logError(`Target not found: ${target}`)
|
|
1142
|
+
return 1
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
const result = await detectRules(PROJECT_ROOT, resolved, opts.stack)
|
|
1146
|
+
if (!result.ok) {
|
|
1147
|
+
logError(`Stack not found: ${result.missingStack}`)
|
|
1148
|
+
return 1
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
if (opts.json) {
|
|
1152
|
+
process.stdout.write(
|
|
1153
|
+
`${JSON.stringify({
|
|
1154
|
+
target: resolved,
|
|
1155
|
+
stack: opts.stack ?? null,
|
|
1156
|
+
proposals: result.proposals,
|
|
1157
|
+
})}\n`,
|
|
1158
|
+
)
|
|
1159
|
+
return 0
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
intro('canon gov detect')
|
|
1163
|
+
if (result.proposals.length === 0) {
|
|
1164
|
+
logInfo('No opt-in rule matched this target')
|
|
1165
|
+
} else {
|
|
1166
|
+
logStep('Proposed rules')
|
|
1167
|
+
for (const proposal of result.proposals) {
|
|
1168
|
+
const reasons = proposal.evidence
|
|
1169
|
+
.map((evidence) => `${evidence.match} in ${evidence.path}`)
|
|
1170
|
+
.join(', ')
|
|
1171
|
+
logInfo(`${proposal.rule}: ${reasons}`)
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
outro()
|
|
1175
|
+
return 0
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1105
1178
|
/**
|
|
1106
1179
|
* Silent on success so the consumed-copy stage that calls it stays quiet. It is
|
|
1107
1180
|
* the only work that stage does now. The installed set is readable on disk, so
|
|
@@ -1121,14 +1194,6 @@ async function runRegen(
|
|
|
1121
1194
|
|
|
1122
1195
|
const root = resolve(opts.root ?? PROJECT_ROOT)
|
|
1123
1196
|
|
|
1124
|
-
// The consumed copy installs from `governance/rules/`, so the band this
|
|
1125
|
-
// writes has to be fresh before that copy is built from it.
|
|
1126
|
-
const generated = await regenStandardRules(root)
|
|
1127
|
-
if (!generated.ok) {
|
|
1128
|
-
process.stderr.write(`Standard-rules regen failed: ${generated.reason}\n`)
|
|
1129
|
-
return 1
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
1197
|
const result = await regenConsumedRules(root)
|
|
1133
1198
|
|
|
1134
1199
|
if (!result.ok) {
|
|
@@ -1184,6 +1249,11 @@ async function runInstall(
|
|
|
1184
1249
|
): Promise<number> {
|
|
1185
1250
|
intro('canon gov install')
|
|
1186
1251
|
|
|
1252
|
+
const add = opts.add ?? ''
|
|
1253
|
+
if (add !== '' && isAddWithoutStack(stack, target)) {
|
|
1254
|
+
return installExtras(add, stack ?? target)
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1187
1257
|
const resolved = resolveTarget(target, PROJECT_ROOT)
|
|
1188
1258
|
if (typeof resolved === 'number') return resolved
|
|
1189
1259
|
|
|
@@ -1207,7 +1277,6 @@ async function runInstall(
|
|
|
1207
1277
|
return 1
|
|
1208
1278
|
}
|
|
1209
1279
|
|
|
1210
|
-
const add = opts.add ?? ''
|
|
1211
1280
|
const rules =
|
|
1212
1281
|
add === '' ? resolution.rules : mergeExtraRules(resolution.rules, add)
|
|
1213
1282
|
|
|
@@ -1260,6 +1329,67 @@ async function runInstall(
|
|
|
1260
1329
|
return 0
|
|
1261
1330
|
}
|
|
1262
1331
|
|
|
1332
|
+
/**
|
|
1333
|
+
* Whether `--add` runs with no stack under it. Commander fills the first
|
|
1334
|
+
* positional before the second, so `--add a,b ../my-app` arrives with the
|
|
1335
|
+
* target in `stack` and the default `.` in `target`. A first positional that
|
|
1336
|
+
* names a stack keeps today's meaning, so a folder named `node` reads as the
|
|
1337
|
+
* stack, and `.` or a path carrying a slash is how a caller forces the target.
|
|
1338
|
+
*/
|
|
1339
|
+
function isAddWithoutStack(stack: string | undefined, target: string): boolean {
|
|
1340
|
+
if (stack === undefined) return true
|
|
1341
|
+
if (target !== '.') return false
|
|
1342
|
+
if (stack === '.' || stack.includes('/')) return true
|
|
1343
|
+
return !govStackExists(PROJECT_ROOT, stack)
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* Installs `--add` names alone. The chain stamp is left as it was, since the
|
|
1348
|
+
* extras are not a stack, and overwriting the chain would cost a target the
|
|
1349
|
+
* missing-rule report its stack install recorded.
|
|
1350
|
+
*/
|
|
1351
|
+
async function installExtras(add: string, target: string): Promise<number> {
|
|
1352
|
+
const resolved = resolveTarget(target, PROJECT_ROOT)
|
|
1353
|
+
if (typeof resolved === 'number') return resolved
|
|
1354
|
+
|
|
1355
|
+
logStep('Resolving extras with no stack')
|
|
1356
|
+
|
|
1357
|
+
const { found, missing } = lookupRules(PROJECT_ROOT, resolveExtraRules(add))
|
|
1358
|
+
for (const rule of missing) logWarn(`${rule} (source not found, skipping)`)
|
|
1359
|
+
for (const source of found) logInfo(source.rule)
|
|
1360
|
+
|
|
1361
|
+
if (found.length === 0) {
|
|
1362
|
+
logError('No named rule resolved, so nothing was installed.')
|
|
1363
|
+
outro()
|
|
1364
|
+
return 1
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
const shouldInstall = await select({
|
|
1368
|
+
message: `Install ${found.length} rules to ${displayPath(target, CANON_RULES_REL)}?`,
|
|
1369
|
+
options: [
|
|
1370
|
+
{ value: true, label: 'Yes' },
|
|
1371
|
+
{ value: false, label: 'No' },
|
|
1372
|
+
],
|
|
1373
|
+
nonInteractiveDefault: true,
|
|
1374
|
+
})
|
|
1375
|
+
|
|
1376
|
+
if (!shouldInstall) {
|
|
1377
|
+
logWarn('Cancelled')
|
|
1378
|
+
outro()
|
|
1379
|
+
return 0
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
logStep('Installing rules')
|
|
1383
|
+
for (const rel of await installRules(found, resolved)) logAdd(rel)
|
|
1384
|
+
|
|
1385
|
+
await recordStamp(createGovAdapter(PROJECT_ROOT), resolved, new Date())
|
|
1386
|
+
|
|
1387
|
+
const { GREEN, NC } = palette(process.stderr)
|
|
1388
|
+
outro()
|
|
1389
|
+
process.stderr.write(`${GREEN}✓ Rules installed${NC}\n`)
|
|
1390
|
+
return 0
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1263
1393
|
/**
|
|
1264
1394
|
* Emits the rules payload the `gov-*` skills read. The output lands in the
|
|
1265
1395
|
* target's scratch directory rather than stdout because the file is the
|