@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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type Box, FRAME_GAP, type Frame } from '@/canvas/content'
|
|
2
|
+
|
|
3
|
+
export interface Placement {
|
|
4
|
+
readonly name: string
|
|
5
|
+
readonly from: Box
|
|
6
|
+
readonly to: Box
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function boxOf(frame: Frame): Box {
|
|
10
|
+
return {
|
|
11
|
+
x: frame.x,
|
|
12
|
+
y: frame.y,
|
|
13
|
+
width: frame.width,
|
|
14
|
+
height: frame.height,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Boxes whose vertical spans overlap share a row, so a row the operator dragged
|
|
20
|
+
* stays a row. Reading by overlap rather than by an equal `y` lets a frame a
|
|
21
|
+
* few pixels off its neighbor still join it.
|
|
22
|
+
*/
|
|
23
|
+
function rowsOf(frames: readonly Frame[]): Frame[][] {
|
|
24
|
+
const sorted = [...frames].sort((a, b) => a.y - b.y || a.x - b.x)
|
|
25
|
+
const rows: Frame[][] = []
|
|
26
|
+
let bottom = Number.NEGATIVE_INFINITY
|
|
27
|
+
for (const frame of sorted) {
|
|
28
|
+
const row = rows.at(-1)
|
|
29
|
+
if (row && frame.y < bottom) {
|
|
30
|
+
row.push(frame)
|
|
31
|
+
bottom = Math.max(bottom, frame.y + frame.height)
|
|
32
|
+
} else {
|
|
33
|
+
rows.push([frame])
|
|
34
|
+
bottom = frame.y + frame.height
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return rows.map((row) =>
|
|
38
|
+
row.sort((a, b) => a.x - b.x || a.name.localeCompare(b.name)),
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Re-lays a page's frames: each row left to right on `FRAME_GAP` from the
|
|
44
|
+
* page's leftmost edge, rows stacked on their tallest box plus the gap from
|
|
45
|
+
* the page's top edge. Returns only the frames whose position changes.
|
|
46
|
+
*/
|
|
47
|
+
export function planArrange(frames: readonly Frame[]): Placement[] {
|
|
48
|
+
if (frames.length === 0) return []
|
|
49
|
+
const left = Math.min(...frames.map((frame) => frame.x))
|
|
50
|
+
let top = Math.min(...frames.map((frame) => frame.y))
|
|
51
|
+
|
|
52
|
+
const moves: Placement[] = []
|
|
53
|
+
for (const row of rowsOf(frames)) {
|
|
54
|
+
let x = left
|
|
55
|
+
for (const frame of row) {
|
|
56
|
+
const from = boxOf(frame)
|
|
57
|
+
const to: Box = { ...from, x, y: top }
|
|
58
|
+
if (from.x !== to.x || from.y !== to.y) {
|
|
59
|
+
moves.push({ name: frame.name, from, to })
|
|
60
|
+
}
|
|
61
|
+
x += frame.width + FRAME_GAP
|
|
62
|
+
}
|
|
63
|
+
top += Math.max(...row.map((frame) => frame.height)) + FRAME_GAP
|
|
64
|
+
}
|
|
65
|
+
return moves
|
|
66
|
+
}
|
package/src/canvas/capture.ts
CHANGED
|
@@ -157,7 +157,7 @@ export async function captureCanvas(
|
|
|
157
157
|
})
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
type ServeRefused = {
|
|
160
|
+
export type ServeRefused = {
|
|
161
161
|
readonly ok: false
|
|
162
162
|
readonly reason: 'no-server' | 'capture-failed'
|
|
163
163
|
readonly detail: string
|
|
@@ -168,20 +168,34 @@ async function serveFor<T>(
|
|
|
168
168
|
deps: CaptureDeps,
|
|
169
169
|
run: (base: string, capture: CaptureEngine) => Promise<T>,
|
|
170
170
|
): Promise<T | ServeRefused> {
|
|
171
|
-
|
|
171
|
+
return serveCanvas(root, deps.start, async (base) => {
|
|
172
|
+
const capture =
|
|
173
|
+
deps.capture ?? (await import('@/capture/render')).captureSources
|
|
174
|
+
return run(base, capture)
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Runs a call against a canvas served on a free port for its length, so a verb
|
|
180
|
+
* that renders frames reads the same address the board shows.
|
|
181
|
+
*/
|
|
182
|
+
export async function serveCanvas<T>(
|
|
183
|
+
root: string,
|
|
184
|
+
startOverride: CaptureDeps['start'],
|
|
185
|
+
run: (base: string) => Promise<T>,
|
|
186
|
+
): Promise<T | ServeRefused> {
|
|
187
|
+
const start = startOverride ?? startCanvas
|
|
172
188
|
const server = start(root, { port: 0, shell: new Response('') })
|
|
173
189
|
if (!server.ok) {
|
|
174
190
|
return {
|
|
175
191
|
ok: false,
|
|
176
192
|
reason: 'no-server',
|
|
177
|
-
detail: `${server.detail}, so run canon canvas serve and
|
|
193
|
+
detail: `${server.detail}, so run canon canvas serve and try again`,
|
|
178
194
|
}
|
|
179
195
|
}
|
|
180
196
|
|
|
181
197
|
try {
|
|
182
|
-
|
|
183
|
-
deps.capture ?? (await import('@/capture/render')).captureSources
|
|
184
|
-
return await run(server.url, capture)
|
|
198
|
+
return await run(server.url)
|
|
185
199
|
} catch (error) {
|
|
186
200
|
return {
|
|
187
201
|
ok: false,
|
package/src/canvas/content.ts
CHANGED
|
@@ -578,6 +578,45 @@ export function restoreBox(
|
|
|
578
578
|
})
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
+
export type PruneOutcome =
|
|
582
|
+
| { readonly ok: true; readonly dropped: readonly string[] }
|
|
583
|
+
| ContentRefused
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Lists the layout entries whose frame file is gone, and removes them under
|
|
587
|
+
* the layout lock unless `write` is false. `readPage` never reports them, so
|
|
588
|
+
* this is the one place an entry outliving its frame is seen.
|
|
589
|
+
*/
|
|
590
|
+
export function pruneLayout(
|
|
591
|
+
root: string,
|
|
592
|
+
page: string,
|
|
593
|
+
write: boolean,
|
|
594
|
+
): PruneOutcome {
|
|
595
|
+
if (!isValidName(page)) {
|
|
596
|
+
return refuse('invalid-name', `${page} is not a valid page name`)
|
|
597
|
+
}
|
|
598
|
+
const dir = pagePath(root, page)
|
|
599
|
+
if (!isDirectory(dir)) return refuse('no-page', `page ${page} does not exist`)
|
|
600
|
+
|
|
601
|
+
const prune = (): PruneOutcome => {
|
|
602
|
+
const { boxes, malformed } = readLayout(dir)
|
|
603
|
+
if (malformed) {
|
|
604
|
+
return refuse('malformed-layout', `${page}/${LAYOUT_FILE} does not parse`)
|
|
605
|
+
}
|
|
606
|
+
const present = new Set(frameNames(dir))
|
|
607
|
+
const dropped = [...boxes.keys()].filter((name) => !present.has(name))
|
|
608
|
+
if (write && dropped.length > 0) {
|
|
609
|
+
writeLayout(
|
|
610
|
+
dir,
|
|
611
|
+
new Map([...boxes].filter(([name]) => present.has(name))),
|
|
612
|
+
)
|
|
613
|
+
}
|
|
614
|
+
return { ok: true, dropped }
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return write ? withFileLock(join(dir, LAYOUT_FILE), prune) : prune()
|
|
618
|
+
}
|
|
619
|
+
|
|
581
620
|
export interface Selection {
|
|
582
621
|
readonly page: string
|
|
583
622
|
readonly frame: string
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Box,
|
|
3
|
+
type ContentRefused,
|
|
4
|
+
readPage,
|
|
5
|
+
resizeFrame,
|
|
6
|
+
} from '@/canvas/content'
|
|
7
|
+
import {
|
|
8
|
+
type CaptureDeps,
|
|
9
|
+
resolveCaptureTargets,
|
|
10
|
+
type ServeRefused,
|
|
11
|
+
serveCanvas,
|
|
12
|
+
} from '@/canvas/capture'
|
|
13
|
+
import { resolveFrameTokens, servedFamilies } from '@/canvas/tokens'
|
|
14
|
+
|
|
15
|
+
/** What the capture browser read off one frame at its box width. */
|
|
16
|
+
export interface Measured {
|
|
17
|
+
readonly page: string
|
|
18
|
+
readonly frame: string
|
|
19
|
+
readonly box: Box
|
|
20
|
+
readonly contentHeight: number
|
|
21
|
+
readonly contentWidth: number
|
|
22
|
+
/**
|
|
23
|
+
* The lowest edge of any element in the body moved when the viewport
|
|
24
|
+
* height changed, so a viewport unit is sizing something and the content
|
|
25
|
+
* measure is not the height a viewer draws.
|
|
26
|
+
*/
|
|
27
|
+
readonly viewportBound: boolean
|
|
28
|
+
/** The first family of the body text, unquoted. */
|
|
29
|
+
readonly family: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FitRow {
|
|
33
|
+
readonly page: string
|
|
34
|
+
readonly frame: string
|
|
35
|
+
/** The box height now. */
|
|
36
|
+
readonly height: number
|
|
37
|
+
/** The content's height, which is what the box should be. */
|
|
38
|
+
readonly fitted: number
|
|
39
|
+
/** The content is wider than the box, which fit reports and never widens. */
|
|
40
|
+
readonly wide: boolean
|
|
41
|
+
/** The first family is not one the canvas serves, so the height may not hold. */
|
|
42
|
+
readonly font: boolean
|
|
43
|
+
/**
|
|
44
|
+
* The document height depends on the viewport, as a height in `vh` does, so
|
|
45
|
+
* the content measure is not the height a viewer draws and nothing is written.
|
|
46
|
+
*/
|
|
47
|
+
readonly viewport: boolean
|
|
48
|
+
readonly family: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Turns measurements into rows. The box width is the frame's declared render
|
|
53
|
+
* width, so an overflow in it is a finding and never a resize. `served` is the
|
|
54
|
+
* set of families the served sheet carries a face for.
|
|
55
|
+
*
|
|
56
|
+
* A frame whose elements moved with the viewport height is sized by a unit
|
|
57
|
+
* such as `vh`, so the row keeps its height and reports `viewport`. Scroll
|
|
58
|
+
* height cannot show it alone, since a single `100vh` section reads as short
|
|
59
|
+
* content drawn in a taller viewport.
|
|
60
|
+
*/
|
|
61
|
+
export function planFit(
|
|
62
|
+
measured: readonly Measured[],
|
|
63
|
+
served: ReadonlySet<string>,
|
|
64
|
+
): FitRow[] {
|
|
65
|
+
return measured.map((item) => {
|
|
66
|
+
const content = Math.max(1, Math.ceil(item.contentHeight))
|
|
67
|
+
const viewport = item.viewportBound
|
|
68
|
+
return {
|
|
69
|
+
page: item.page,
|
|
70
|
+
frame: item.frame,
|
|
71
|
+
height: item.box.height,
|
|
72
|
+
fitted: viewport ? item.box.height : content,
|
|
73
|
+
wide: item.contentWidth > item.box.width,
|
|
74
|
+
font: !served.has(item.family),
|
|
75
|
+
viewport,
|
|
76
|
+
family: item.family,
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Whether a row asks anything of the operator or the session. */
|
|
82
|
+
export function isOwed(row: FitRow): boolean {
|
|
83
|
+
return row.fitted !== row.height || row.wide || row.font || row.viewport
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type FitOutcome =
|
|
87
|
+
| {
|
|
88
|
+
readonly ok: true
|
|
89
|
+
readonly rows: readonly FitRow[]
|
|
90
|
+
/** Whether heights were written, which is false under a check. */
|
|
91
|
+
readonly written: boolean
|
|
92
|
+
}
|
|
93
|
+
| ContentRefused
|
|
94
|
+
| ServeRefused
|
|
95
|
+
|
|
96
|
+
export interface FitDeps extends Pick<CaptureDeps, 'start'> {
|
|
97
|
+
/** What a test stands in for, which the command never passes. */
|
|
98
|
+
readonly measure?: (targets: readonly MeasureTarget[]) => Promise<Measured[]>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface MeasureTarget {
|
|
102
|
+
readonly page: string
|
|
103
|
+
readonly frame: string
|
|
104
|
+
readonly url: string
|
|
105
|
+
readonly box: Box
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Loads each frame at its box width through the served address, which is what
|
|
110
|
+
* injects the tokens and the faces, and reads the document once fonts and the
|
|
111
|
+
* load event have settled, the moment a capture waits for. The content is read
|
|
112
|
+
* at a one pixel tall viewport, since a document shorter than the viewport
|
|
113
|
+
* reports the viewport and a frame could then never shrink. The lowest element
|
|
114
|
+
* edge is then read at the box height and at twice it, and an edge that moved
|
|
115
|
+
* is a height set in viewport units.
|
|
116
|
+
*/
|
|
117
|
+
async function measureInBrowser(
|
|
118
|
+
targets: readonly MeasureTarget[],
|
|
119
|
+
): Promise<Measured[]> {
|
|
120
|
+
const { chromium } = await import('playwright-core')
|
|
121
|
+
const browser = await chromium.launch()
|
|
122
|
+
try {
|
|
123
|
+
const measured: Measured[] = []
|
|
124
|
+
for (const target of targets) {
|
|
125
|
+
const page = await browser.newPage({
|
|
126
|
+
viewport: { width: target.box.width, height: 1 },
|
|
127
|
+
})
|
|
128
|
+
try {
|
|
129
|
+
await page.goto(target.url, { waitUntil: 'load' })
|
|
130
|
+
const read = await page.evaluate(async () => {
|
|
131
|
+
await document.fonts.ready
|
|
132
|
+
const root = document.documentElement
|
|
133
|
+
const family = getComputedStyle(document.body).fontFamily
|
|
134
|
+
return {
|
|
135
|
+
contentHeight: root.scrollHeight,
|
|
136
|
+
contentWidth: root.scrollWidth,
|
|
137
|
+
family,
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
const lowestEdge = () =>
|
|
141
|
+
page.evaluate(() => {
|
|
142
|
+
let bottom = 0
|
|
143
|
+
for (const element of document.body.querySelectorAll('*')) {
|
|
144
|
+
bottom = Math.max(
|
|
145
|
+
bottom,
|
|
146
|
+
element.getBoundingClientRect().bottom + window.scrollY,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
return Math.ceil(bottom)
|
|
150
|
+
})
|
|
151
|
+
const heights = [target.box.height, target.box.height * 2]
|
|
152
|
+
const edges: number[] = []
|
|
153
|
+
for (const height of heights) {
|
|
154
|
+
await page.setViewportSize({ width: target.box.width, height })
|
|
155
|
+
edges.push(await lowestEdge())
|
|
156
|
+
}
|
|
157
|
+
measured.push({
|
|
158
|
+
page: target.page,
|
|
159
|
+
frame: target.frame,
|
|
160
|
+
box: target.box,
|
|
161
|
+
contentHeight: read.contentHeight,
|
|
162
|
+
contentWidth: read.contentWidth,
|
|
163
|
+
viewportBound: edges[0] !== edges[1],
|
|
164
|
+
family: firstFamily(read.family),
|
|
165
|
+
})
|
|
166
|
+
} finally {
|
|
167
|
+
await page.close()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return measured
|
|
171
|
+
} finally {
|
|
172
|
+
await browser.close()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The first entry of a computed `font-family` list, without its quotes. */
|
|
177
|
+
export function firstFamily(list: string): string {
|
|
178
|
+
const first = list.split(',')[0] ?? ''
|
|
179
|
+
return first.trim().replace(/^["']|["']$/g, '')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Measures the frames `spec` names and, unless `write` is false, sets each
|
|
184
|
+
* box's height to its content through `resizeFrame`, the writer the operator's
|
|
185
|
+
* drags share. Serves the canvas for the call, so it needs no running board.
|
|
186
|
+
*/
|
|
187
|
+
export async function fitCanvas(
|
|
188
|
+
root: string,
|
|
189
|
+
spec: string,
|
|
190
|
+
write: boolean,
|
|
191
|
+
deps: FitDeps = {},
|
|
192
|
+
): Promise<FitOutcome> {
|
|
193
|
+
const early = resolveCaptureTargets(root, spec, '')
|
|
194
|
+
if (!early.ok) return early
|
|
195
|
+
|
|
196
|
+
const served = await serveCanvas(root, deps.start, async (base) => {
|
|
197
|
+
const resolved = resolveCaptureTargets(root, spec, base)
|
|
198
|
+
if (!resolved.ok) return resolved
|
|
199
|
+
const frames = readPage(root, resolved.targets[0]?.page ?? '')?.frames ?? []
|
|
200
|
+
|
|
201
|
+
const targets: MeasureTarget[] = []
|
|
202
|
+
for (const item of resolved.targets) {
|
|
203
|
+
const frame = frames.find((candidate) => candidate.name === item.frame)
|
|
204
|
+
if (!frame) continue
|
|
205
|
+
targets.push({
|
|
206
|
+
page: item.page,
|
|
207
|
+
frame: item.frame,
|
|
208
|
+
url: item.url,
|
|
209
|
+
box: {
|
|
210
|
+
x: frame.x,
|
|
211
|
+
y: frame.y,
|
|
212
|
+
width: frame.width,
|
|
213
|
+
height: frame.height,
|
|
214
|
+
},
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
const measure = deps.measure ?? measureInBrowser
|
|
218
|
+
return { ok: true as const, targets, measured: await measure(targets) }
|
|
219
|
+
})
|
|
220
|
+
if (!served.ok) return served
|
|
221
|
+
|
|
222
|
+
const rows = planFit(
|
|
223
|
+
served.measured,
|
|
224
|
+
servedFamilies(resolveFrameTokens(root).css),
|
|
225
|
+
)
|
|
226
|
+
if (!write) return { ok: true, rows, written: false }
|
|
227
|
+
|
|
228
|
+
for (const row of rows.filter((item) => item.fitted !== item.height)) {
|
|
229
|
+
const target = served.targets.find((item) => item.frame === row.frame)
|
|
230
|
+
if (!target) continue
|
|
231
|
+
const outcome = resizeFrame(root, row.page, row.frame, {
|
|
232
|
+
...target.box,
|
|
233
|
+
height: row.fitted,
|
|
234
|
+
})
|
|
235
|
+
if (!outcome.ok) return outcome
|
|
236
|
+
}
|
|
237
|
+
return { ok: true, rows, written: true }
|
|
238
|
+
}
|
package/src/canvas/tokens.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
type DroppedFace,
|
|
12
12
|
inlineProjectFaces,
|
|
13
|
-
namedFamilies,
|
|
14
13
|
projectSheets,
|
|
15
14
|
} from '@/design/project-fonts'
|
|
16
15
|
import { isOwnCheckout } from '@/roots/project'
|
|
@@ -31,7 +30,7 @@ export type FrameTokens =
|
|
|
31
30
|
/** Project faces left out of the sheet, present only when one was. */
|
|
32
31
|
readonly dropped?: readonly DroppedFace[]
|
|
33
32
|
}
|
|
34
|
-
| { readonly source: 'none'; readonly css:
|
|
33
|
+
| { readonly source: 'none'; readonly css: string; readonly notice: string }
|
|
35
34
|
|
|
36
35
|
export interface TokenOptions {
|
|
37
36
|
/** Overrides the checkout test, which reads this package's own name. */
|
|
@@ -124,6 +123,21 @@ export function tokenGroups(css: string): TokenGroup[] {
|
|
|
124
123
|
})
|
|
125
124
|
}
|
|
126
125
|
|
|
126
|
+
/**
|
|
127
|
+
* The families a served sheet declares a face for, so a caller reads what the
|
|
128
|
+
* frames carry from the same sheet the server builds rather than from a list
|
|
129
|
+
* of its own.
|
|
130
|
+
*/
|
|
131
|
+
export function servedFamilies(css: string): Set<string> {
|
|
132
|
+
const families = new Set<string>()
|
|
133
|
+
for (const [, family] of css.matchAll(
|
|
134
|
+
/@font-face\s*{[^}]*?font-family:\s*["']?([^"';}]+)/g,
|
|
135
|
+
)) {
|
|
136
|
+
if (family) families.add(family.trim())
|
|
137
|
+
}
|
|
138
|
+
return families
|
|
139
|
+
}
|
|
140
|
+
|
|
127
141
|
/** The installed files in cascade order: the base first, then each override. */
|
|
128
142
|
function installedFiles(root: string): string[] {
|
|
129
143
|
const base = join(DESIGN_INSTALL_DIR, BASE_FILE)
|
|
@@ -134,14 +148,14 @@ function installedFiles(root: string): string[] {
|
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
/**
|
|
137
|
-
* The vendored faces
|
|
138
|
-
*
|
|
139
|
-
*
|
|
151
|
+
* The vendored faces no project rule already declares, so a frame naming
|
|
152
|
+
* `Geist Variable` renders the same face in the capture and in any browser
|
|
153
|
+
* whether or not a sheet names it, and a project face of the same family wins
|
|
154
|
+
* outright.
|
|
140
155
|
*/
|
|
141
|
-
function toolkitFaces(
|
|
142
|
-
const named = namedFamilies(css)
|
|
156
|
+
function toolkitFaces(declared: ReadonlySet<string>): FontFace[] {
|
|
143
157
|
return [...FONT_FACES, ...HAND_DRAWN_FONT_FACES].filter(
|
|
144
|
-
(face) =>
|
|
158
|
+
(face) => !declared.has(face.family),
|
|
145
159
|
)
|
|
146
160
|
}
|
|
147
161
|
|
|
@@ -190,7 +204,7 @@ export function resolveFrameTokens(
|
|
|
190
204
|
const files = installedFiles(root)
|
|
191
205
|
if (files.length > 0) {
|
|
192
206
|
const { css, dropped, declared } = installedSheets(root, files)
|
|
193
|
-
const faces = toolkitFaces(
|
|
207
|
+
const faces = toolkitFaces(declared)
|
|
194
208
|
return {
|
|
195
209
|
source: 'installed',
|
|
196
210
|
css: faces.length > 0 ? `${fontFaceBlock(faces)}\n${css}` : css,
|
|
@@ -199,5 +213,9 @@ export function resolveFrameTokens(
|
|
|
199
213
|
}
|
|
200
214
|
}
|
|
201
215
|
|
|
202
|
-
return {
|
|
216
|
+
return {
|
|
217
|
+
source: 'none',
|
|
218
|
+
css: fontFaceBlock(toolkitFaces(new Set())),
|
|
219
|
+
notice: NONE_NOTICE,
|
|
220
|
+
}
|
|
203
221
|
}
|
|
@@ -31,15 +31,9 @@ const GLOB_WIDTH = 44
|
|
|
31
31
|
/** A refusal the script prints as `regen-hero: <message>` and exits 1 on. */
|
|
32
32
|
export class FrameError extends Error {}
|
|
33
33
|
|
|
34
|
-
export interface StandardRow {
|
|
35
|
-
readonly name: string
|
|
36
|
-
readonly appliesTo?: readonly string[]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
34
|
export interface FrameCatalogs {
|
|
40
35
|
readonly skills: readonly string[]
|
|
41
36
|
readonly gov: GovCatalog
|
|
42
|
-
readonly standards: readonly StandardRow[]
|
|
43
37
|
readonly toolingStacks: readonly StackSummary[]
|
|
44
38
|
readonly commandCount: number
|
|
45
39
|
readonly tokenCss: string
|
|
@@ -63,7 +57,7 @@ const escape = (value: string): string =>
|
|
|
63
57
|
/**
|
|
64
58
|
* Even spacing across the sorted catalog rather than its first N, so a column
|
|
65
59
|
* whose names share a prefix does not read as a narrower catalog than the one
|
|
66
|
-
* that ships. Rules
|
|
60
|
+
* that ships. Rules sample. Skills do not, because the even step
|
|
67
61
|
* lands on the entries closest to commodity and on none of the workflow the
|
|
68
62
|
* toolkit exists to carry.
|
|
69
63
|
*/
|
|
@@ -186,7 +180,7 @@ function faviconUri(markSvg: string, tokenCss: string): string {
|
|
|
186
180
|
* rather than shipping a frame claiming the domain has nothing in it.
|
|
187
181
|
*/
|
|
188
182
|
export function buildFrameValues(catalogs: FrameCatalogs): FrameValues {
|
|
189
|
-
const { gov, toolingStacks,
|
|
183
|
+
const { gov, toolingStacks, skills } = catalogs
|
|
190
184
|
const markSvg = catalogs.markSvg.trim()
|
|
191
185
|
if (!markSvg) {
|
|
192
186
|
throw new FrameError(
|
|
@@ -207,12 +201,10 @@ export function buildFrameValues(catalogs: FrameCatalogs): FrameValues {
|
|
|
207
201
|
const stacked = new Set(gov.stacks.flatMap((stack) => stack.rules))
|
|
208
202
|
const deliveredEntries = gov.rules.filter((entry) => stacked.has(entry.name))
|
|
209
203
|
const deliveredRules = deliveredEntries.map((entry) => slug(entry.name))
|
|
210
|
-
const standardNames = standards.map((entry) => entry.name)
|
|
211
204
|
|
|
212
205
|
for (const [label, list] of [
|
|
213
206
|
['skills', skills],
|
|
214
207
|
['rules', rules],
|
|
215
|
-
['standards', standardNames],
|
|
216
208
|
['tooling stacks', toolingStacks],
|
|
217
209
|
['gov stacks', gov.stacks],
|
|
218
210
|
['stack-delivered rules', deliveredRules],
|
|
@@ -251,23 +243,6 @@ export function buildFrameValues(catalogs: FrameCatalogs): FrameValues {
|
|
|
251
243
|
})
|
|
252
244
|
.join('\n')
|
|
253
245
|
|
|
254
|
-
// A standard either governs an artifact by path or is opened by name when a
|
|
255
|
-
// session decides it needs it. Naming the second is the contrast, since
|
|
256
|
-
// nothing else tells a reader the corpus installs into no project.
|
|
257
|
-
const standardRows = sample(standards)
|
|
258
|
-
.map((entry) => {
|
|
259
|
-
const applies = entry.appliesTo ?? []
|
|
260
|
-
const governs =
|
|
261
|
-
applies.length > 0
|
|
262
|
-
? clip(applies.join(' '), GLOB_WIDTH)
|
|
263
|
-
: 'read by name'
|
|
264
|
-
return frameRow(
|
|
265
|
-
`<span class="name">${escape(pad(entry.name, 18))}</span>` +
|
|
266
|
-
`<span class="muted">${escape(governs)}</span>`,
|
|
267
|
-
)
|
|
268
|
-
})
|
|
269
|
-
.join('\n')
|
|
270
|
-
|
|
271
246
|
// A tooling stack is counted rather than listed, because what it lays down is
|
|
272
247
|
// dev dependencies, run scripts, and ignore groups rather than named entries.
|
|
273
248
|
// The inheritance is the part worth showing, since it is why a stack carrying
|
|
@@ -286,7 +261,6 @@ export function buildFrameValues(catalogs: FrameCatalogs): FrameValues {
|
|
|
286
261
|
for (const [label, rows] of [
|
|
287
262
|
['governance stack rows', govStackRows],
|
|
288
263
|
['governance rule rows', govRuleRows],
|
|
289
|
-
['standard rows', standardRows],
|
|
290
264
|
['tooling stack rows', toolingStackRows],
|
|
291
265
|
] as const) {
|
|
292
266
|
if (rows === '') {
|
|
@@ -300,11 +274,9 @@ export function buildFrameValues(catalogs: FrameCatalogs): FrameValues {
|
|
|
300
274
|
TOKENS: tokenCss,
|
|
301
275
|
GOV_STACK_ROWS: govStackRows,
|
|
302
276
|
GOV_RULE_ROWS: govRuleRows,
|
|
303
|
-
STANDARD_ROWS: standardRows,
|
|
304
277
|
TOOLING_STACK_ROWS: toolingStackRows,
|
|
305
278
|
SKILL_COUNT: String(skills.length),
|
|
306
279
|
RULE_COUNT: String(rules.length),
|
|
307
|
-
STANDARD_COUNT: String(standardNames.length),
|
|
308
280
|
GOV_STACK_COUNT: String(gov.stacks.length),
|
|
309
281
|
TOOLING_STACK_COUNT: String(toolingStacks.length),
|
|
310
282
|
COMMAND_COUNT: String(catalogs.commandCount),
|
|
@@ -312,10 +284,8 @@ export function buildFrameValues(catalogs: FrameCatalogs): FrameValues {
|
|
|
312
284
|
FAVICON: faviconUri(markSvg, tokenCss),
|
|
313
285
|
SKILL_ENTRIES: markup(featured(skills, FEATURED_SKILLS)),
|
|
314
286
|
RULE_ENTRIES: markup(sample(deliveredRules)),
|
|
315
|
-
STANDARD_ENTRIES: markup(sample(standardNames)),
|
|
316
287
|
SKILL_MORE: remaining(skills),
|
|
317
288
|
RULE_MORE: remaining(rules),
|
|
318
|
-
STANDARD_MORE: remaining(standardNames),
|
|
319
289
|
}
|
|
320
290
|
|
|
321
291
|
const light = lightTokens(tokenCss)
|
package/src/claude/cases/misc.ts
CHANGED
|
@@ -40,6 +40,10 @@ export const SETUP_CASES: readonly SkillCase[] = [
|
|
|
40
40
|
"Before I run this sync, tell me exactly what it's going to overwrite.",
|
|
41
41
|
expect: 'canon-cli',
|
|
42
42
|
},
|
|
43
|
+
{
|
|
44
|
+
prompt: 'Which canon command tells me if my markdown breaks the rules?',
|
|
45
|
+
expect: 'canon-cli',
|
|
46
|
+
},
|
|
43
47
|
{
|
|
44
48
|
prompt:
|
|
45
49
|
'Something about the toolkit itself is broken, write it up and send it back to the maintainers.',
|
|
@@ -31,7 +31,7 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
|
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
prompt:
|
|
34
|
-
'Fold what we decided this session into the context entries
|
|
34
|
+
'Fold what we decided this session into the context entries before this branch goes out.',
|
|
35
35
|
expect: 'context-fold',
|
|
36
36
|
},
|
|
37
37
|
{
|
|
@@ -127,6 +127,11 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
|
|
|
127
127
|
prompt: 'Open a new entry on the task board for this piece of work.',
|
|
128
128
|
expect: 'task-board',
|
|
129
129
|
},
|
|
130
|
+
{
|
|
131
|
+
prompt:
|
|
132
|
+
'This branch finished the pagination work, so tick off whichever board outcomes it actually delivered.',
|
|
133
|
+
expect: 'task-complete',
|
|
134
|
+
},
|
|
130
135
|
{
|
|
131
136
|
prompt: 'Open a learning workspace so I can study this topic properly.',
|
|
132
137
|
expect: 'teach-workspace',
|
package/src/claude/seeds.ts
CHANGED
|
@@ -17,7 +17,7 @@ const HOOKS = 'hooks'
|
|
|
17
17
|
* stem in an older layout, which is what `@/sync/layout` pairs a target against
|
|
18
18
|
* to find a superseded artifact.
|
|
19
19
|
*/
|
|
20
|
-
export const SUBDIRS: readonly string[] = [HOOKS, 'context', '
|
|
20
|
+
export const SUBDIRS: readonly string[] = [HOOKS, 'context', 'tasks']
|
|
21
21
|
|
|
22
22
|
export type SeedScope = 'claude' | 'root'
|
|
23
23
|
|
|
@@ -7,7 +7,7 @@ import { bodyLines, maskDisplayed } from '@/markdown/scan'
|
|
|
7
7
|
/** Returned when a skill folder carries no `REQUIREMENT.md`, the gating check. */
|
|
8
8
|
export const EXIT_MISSING_REQUIREMENT = 2
|
|
9
9
|
|
|
10
|
-
/** The description ceiling stated in the frontmatter rules of `
|
|
10
|
+
/** The description ceiling stated in the frontmatter rules of `claude/skills/create-skill/references/skill.md`. */
|
|
11
11
|
export const DESCRIPTION_LIMIT = 1024
|
|
12
12
|
|
|
13
13
|
/** The two headings the requirement template declares, matched at any level. */
|
|
@@ -160,7 +160,7 @@ interface SkillText {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
|
-
* Measures both skill corpora against the rules `
|
|
163
|
+
* Measures both skill corpora against the rules `claude/skills/create-skill/references/skill.md` states
|
|
164
164
|
* mechanically. A corpus the project does not carry is skipped rather than
|
|
165
165
|
* reported, so a target holding only `.claude/skills/` reads as in scope.
|
|
166
166
|
*
|
package/src/cli/help.ts
CHANGED
|
@@ -10,6 +10,8 @@ type HelpStream = { isTTY?: boolean }
|
|
|
10
10
|
|
|
11
11
|
// The grouping mirrors the README's domain table and has no other source, so a
|
|
12
12
|
// command added to the program needs a row here or help-option.test.ts fails.
|
|
13
|
+
// A row here also needs a `canon <verb>` line in claude/skills/canon-cli, or a
|
|
14
|
+
// name in TOOLKIT_ONLY in canon-cli-skill.test.ts, which reads this list.
|
|
13
15
|
export const COMMAND_GROUPS: { title: string; rows: CommandRow[] }[] = [
|
|
14
16
|
{
|
|
15
17
|
title: 'Project',
|
|
@@ -25,7 +27,7 @@ export const COMMAND_GROUPS: { title: string; rows: CommandRow[] }[] = [
|
|
|
25
27
|
title: 'Domains',
|
|
26
28
|
rows: [
|
|
27
29
|
['gov [cmd]', 'Governance rules (install, sync, list)'],
|
|
28
|
-
['standards [cmd]', 'Standards (
|
|
30
|
+
['standards [cmd]', 'Standards (list, <name>)'],
|
|
29
31
|
['tooling [cmd]', 'Tooling stacks (sync, ref, create)'],
|
|
30
32
|
['claude [cmd]', 'Claude workflow (init, sync, setup)'],
|
|
31
33
|
['wiki [cmd]', 'Wiki pages (init)'],
|
package/src/cli/ui.ts
CHANGED
|
@@ -154,9 +154,8 @@ export function isNonInteractive(): boolean {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* `nonInteractiveDefault` opts a prompt into
|
|
158
|
-
*
|
|
159
|
-
* option instead of failing. Callers that would rather fail loudly than pick
|
|
157
|
+
* `nonInteractiveDefault` opts a prompt into resolving to its first option
|
|
158
|
+
* under `CANON_NON_INTERACTIVE=1` instead of failing. Callers that would rather fail loudly than pick
|
|
160
159
|
* for the user leave it off.
|
|
161
160
|
*/
|
|
162
161
|
export async function select<Value>(opts: {
|