@erclx/canon 5.33.0 → 5.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -5
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/REQUIREMENT.md +2 -3
- package/claude/skills/api-design/SKILL.md +2 -3
- package/claude/skills/api-design/references/adopted.md +1 -1
- package/claude/skills/auto-ship/references/draft-mark.md +1 -1
- package/claude/skills/canon-cli/SKILL.md +10 -11
- package/claude/skills/canon-feedback/REQUIREMENT.md +1 -0
- package/claude/skills/canon-feedback/SKILL.md +10 -28
- package/claude/skills/canon-feedback/references/template.md +53 -0
- package/claude/skills/canvas-board/SKILL.md +4 -1
- package/claude/skills/canvas-board/references/canvas.md +26 -3
- package/claude/skills/code-craft/REQUIREMENT.md +1 -1
- package/claude/skills/code-craft/SKILL.md +0 -1
- package/claude/skills/create-rule/SKILL.md +1 -1
- package/claude/skills/create-rule/references/rule.md +3 -1
- package/claude/skills/create-skill/SKILL.md +2 -1
- package/claude/skills/create-skill/references/skill-paths.md +12 -15
- package/{standards/standard.md → claude/skills/create-skill/references/skill-reference.md} +50 -45
- package/claude/skills/create-skill/references/skill.md +10 -4
- package/claude/skills/draft-and-pick/SKILL.md +1 -1
- package/claude/skills/git-commit/references/versioning.md +13 -13
- package/claude/skills/git-issue/references/issue.md +4 -1
- package/claude/skills/git-pr/SKILL.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- package/claude/skills/review-address/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/SKILL.md +2 -2
- package/claude/skills/review-craft/references/ui.md +1 -1
- package/claude/skills/review-pr/SKILL.md +3 -3
- package/claude/skills/review-pr/references/post.md +1 -1
- 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 +52 -16
- package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -2
- package/claude/skills/role-orchestrator/SKILL.md +11 -12
- package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +3 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -8
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +10 -4
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +11 -2
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +11 -12
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +12 -15
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +6 -4
- package/claude/skills/role-orchestrator/scripts/poll.ts +50 -1
- package/claude/skills/role-reviewer/REQUIREMENT.md +17 -7
- package/claude/skills/role-reviewer/SKILL.md +17 -10
- package/claude/skills/role-worker/REQUIREMENT.md +1 -1
- package/claude/skills/role-worker/SKILL.md +6 -6
- package/claude/skills/session-compact/references/handoff-note.md +1 -1
- package/claude/skills/target-setup/SKILL.md +12 -6
- package/claude/skills/target-setup/references/detect.md +22 -5
- package/claude/skills/test-craft/REQUIREMENT.md +2 -3
- package/claude/skills/test-craft/SKILL.md +15 -3
- package/docs/agents/audits.md +1 -1
- package/docs/agents/canvas.md +5 -1
- package/docs/agents/commands.md +3 -5
- package/docs/agents/counts.md +5 -6
- package/docs/agents/drift-surfaces.md +15 -19
- package/docs/agents/gov-detect.md +47 -0
- package/docs/agents/index.md +6 -4
- package/docs/agents/install-and-sync.md +11 -31
- package/docs/agents/markdown-audit.md +10 -12
- 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-stale.md +1 -1
- package/docs/agents/records-validate.md +11 -23
- package/docs/agents/records.md +1 -1
- package/docs/agents/routing.md +1 -1
- package/docs/agents/rule-citations.md +11 -14
- package/docs/agents/scripting.md +4 -21
- package/docs/agents/state-scoped-risk.md +1 -1
- package/docs/agents/tasks-validate.md +1 -1
- package/docs/agents/teach.md +1 -1
- package/docs/target/index.md +1 -0
- package/docs/target/migrations.md +4 -0
- package/docs/target/projects.md +11 -11
- package/docs/target/scenarios.md +5 -6
- package/docs/target/standards-moved.md +32 -0
- package/docs/target/sync.md +5 -5
- package/docs/workflow/operating-model.md +16 -18
- package/docs/workflow/skill-map.md +1 -1
- package/governance/detect.toml +197 -0
- package/governance/renames.toml +0 -1
- 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 +15 -8
- package/governance/stacks/go.toml +2 -0
- package/governance/stacks/node.toml +1 -1
- package/governance/stacks/php.toml +2 -0
- package/governance/stacks/python.toml +1 -1
- package/package.json +1 -4
- package/scripts/core/install-check.sh +2 -3
- package/scripts/core/regen-hero.ts +0 -6
- package/scripts/lib/ui.sh +0 -9
- package/src/audits/catalog.ts +0 -9
- 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/routing.ts +1 -1
- package/src/claude/skills-reach.ts +4 -6
- package/src/cli/help.ts +3 -4
- package/src/cli.ts +0 -2
- package/src/commands/canvas.ts +179 -1
- package/src/commands/gov.ts +149 -10
- package/src/commands/init.ts +1 -1
- package/src/commands/labels.ts +4 -44
- package/src/commands/migrate.ts +1 -1
- package/src/commands/pr.ts +376 -1
- package/src/commands/records.ts +4 -20
- package/src/commands/tooling.ts +2 -4
- package/src/counts/catalogs.ts +0 -6
- package/src/feedback/body.ts +26 -5
- package/src/gate/boundaries.ts +2 -2
- package/src/gate/measures.ts +64 -72
- package/src/gate/sequencer.ts +51 -2
- package/src/gate/stages.ts +3 -22
- package/src/gov/citations.ts +11 -43
- package/src/gov/detect.ts +301 -0
- package/src/gov/list.ts +23 -2
- package/src/gov/stacks.ts +9 -0
- package/src/markdown/bans.ts +1 -1
- package/src/migrate/scratch-evidence.ts +2 -2
- package/src/pr/evidence.ts +218 -45
- package/src/pr/frames.ts +23 -3
- package/src/pr/lift.ts +418 -0
- package/src/pr/rest.ts +3 -0
- package/src/pr/review-scope.ts +76 -0
- package/src/records/validate.ts +6 -194
- package/src/sandbox/equivalence.ts +14 -7
- package/src/sandbox/provision.ts +0 -5
- package/src/secrets/shipped.ts +1 -2
- package/src/serve/static.ts +1 -2
- package/src/shipped/references.ts +29 -80
- package/src/sync/check.ts +18 -14
- package/src/tooling/inject.ts +6 -37
- package/src/tooling/read.ts +2 -3
- package/src/tooling/scan.ts +4 -17
- package/src/tooling/subfolder.ts +2 -66
- package/tooling/base/configs/scripts/verify.json +1 -1
- package/tooling/base/configs/scripts/verify.ts +1 -12
- package/tooling/base/manifest.toml +1 -2
- package/tooling/base/reference.md +10 -16
- package/tooling/base/seeds/.github/workflows/verify.yml +0 -3
- package/tooling/base/seeds/.lintstagedrc +1 -2
- package/tooling/base/seeds/canon/context/ci.md +0 -1
- package/tooling/base/seeds/canon/context/development.md +1 -1
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +13 -1
- package/tooling/go/reference.md +1 -1
- package/tooling/php/reference.md +1 -1
- package/tooling/python/reference.md +2 -2
- package/tooling/web/configs/.github/workflows/verify.yml +0 -3
- package/tooling/web/manifest.toml +0 -1
- package/tooling/web/reference.md +2 -4
- package/docs/agents/standards-audit.md +0 -40
- package/governance/rules/tooling/765-spelling.md +0 -19
- package/governance/stacks/astro.toml +0 -2
- package/governance/stacks/nextjs.toml +0 -2
- package/governance/stacks/node-server.toml +0 -2
- package/governance/stacks/python-fastapi.toml +0 -2
- package/governance/stacks/react.toml +0 -2
- package/src/commands/standards.ts +0 -278
- package/src/labels/spelling.ts +0 -120
- package/src/standards/audit.ts +0 -146
- package/src/standards/list.ts +0 -33
- package/src/standards/read.ts +0 -177
- package/src/standards/scope.ts +0 -56
- package/standards/index.md +0 -10
- package/tooling/base/configs/.vscode/extensions.json +0 -9
- package/tooling/base/configs/.vscode/settings.json +0 -3
- package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
- package/tooling/base/seeds/.cspell/tech-stack.txt +0 -25
- package/tooling/base/seeds/cspell.json +0 -20
- package/tooling/go/seeds/.cspell/tech-stack.txt +0 -16
- package/tooling/nextjs/seeds/.cspell/tech-stack.txt +0 -3
- package/tooling/php/seeds/.cspell/tech-stack.txt +0 -5
- package/tooling/python/seeds/.cspell/tech-stack.txt +0 -18
- package/tooling/vite-react/seeds/.cspell/project-terms.txt +0 -1
- package/tooling/vite-react/seeds/.cspell/tech-stack.txt +0 -2
- package/tooling/web/configs/.vscode/extensions.json +0 -13
- package/tooling/web/configs/.vscode/settings.json +0 -10
- package/tooling/web/seeds/.cspell/tech-stack.txt +0 -21
|
@@ -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/routing.ts
CHANGED
|
@@ -16,7 +16,7 @@ const PATH_TOKEN = /^[.A-Za-z0-9_][A-Za-z0-9._/-]*$/
|
|
|
16
16
|
/** A placeholder segment, which a shape carries and a real path does not. */
|
|
17
17
|
const SHAPE = /<[^>]*>/
|
|
18
18
|
|
|
19
|
-
/** An alphabetic extension, which separates `
|
|
19
|
+
/** An alphabetic extension, which separates `tsconfig.json` from `3.6.0`. */
|
|
20
20
|
const EXTENSION = /\.[a-z]{2,5}$/
|
|
21
21
|
|
|
22
22
|
/** Extensions a folder is probed with, so a glob narrowed by type still hits. */
|
|
@@ -6,10 +6,9 @@ import { SURFACE_ROOTS } from '@/roots/surface'
|
|
|
6
6
|
/**
|
|
7
7
|
* The authoring roots this repository owns and no install channel delivers.
|
|
8
8
|
*
|
|
9
|
-
* Every entry is a folder a target never holds under that spelling.
|
|
10
|
-
* install
|
|
11
|
-
*
|
|
12
|
-
* own source, docs, and catalogs.
|
|
9
|
+
* Every entry is a folder a target never holds under that spelling. Rules
|
|
10
|
+
* install under `.claude/rules/`, and the rest are this repository's own
|
|
11
|
+
* source, docs, and catalogs.
|
|
13
12
|
*
|
|
14
13
|
* `src/`, `scripts/`, and bare `docs/` are deliberately absent. A body naming
|
|
15
14
|
* one of those is describing the reader's own tree, so listing them would
|
|
@@ -28,7 +27,6 @@ const AUTHORING_ROOTS = [
|
|
|
28
27
|
'docs/agents/',
|
|
29
28
|
'governance/',
|
|
30
29
|
'internal/',
|
|
31
|
-
'standards/',
|
|
32
30
|
'tooling/',
|
|
33
31
|
'wiki/',
|
|
34
32
|
] as const
|
|
@@ -51,7 +49,7 @@ const TOKEN = /`([^`\s]+)`/g
|
|
|
51
49
|
*
|
|
52
50
|
* Requires an extension and a separator, and admits no `<`, `$`, or `*`. A
|
|
53
51
|
* body writes `canon/context/<domain>.md` to name a shape rather than a
|
|
54
|
-
* file, and `${CLAUDE_SKILL_DIR}
|
|
52
|
+
* file, and `${CLAUDE_SKILL_DIR}/../<skill>/references/<name>.md` to resolve
|
|
55
53
|
* against the plugin root, which is self-contained by construction.
|
|
56
54
|
*/
|
|
57
55
|
const CONCRETE = /^[.A-Za-z0-9_][A-Za-z0-9._/-]*\.[a-z]{1,4}$/
|
package/src/cli/help.ts
CHANGED
|
@@ -27,7 +27,6 @@ export const COMMAND_GROUPS: { title: string; rows: CommandRow[] }[] = [
|
|
|
27
27
|
title: 'Domains',
|
|
28
28
|
rows: [
|
|
29
29
|
['gov [cmd]', 'Governance rules (install, sync, list)'],
|
|
30
|
-
['standards [cmd]', 'Standards (list, <name>)'],
|
|
31
30
|
['tooling [cmd]', 'Tooling stacks (sync, ref, create)'],
|
|
32
31
|
['claude [cmd]', 'Claude workflow (init, sync, setup)'],
|
|
33
32
|
['wiki [cmd]', 'Wiki pages (init)'],
|
|
@@ -75,7 +74,7 @@ export const COMMAND_GROUPS: { title: string; rows: CommandRow[] }[] = [
|
|
|
75
74
|
['labels [cmd]', 'Read a changed set against the label map (audit)'],
|
|
76
75
|
['comments [cmd]', 'Measure comment density and trend (scan)'],
|
|
77
76
|
['context [cmd]', 'Report context folder health (audit)'],
|
|
78
|
-
['markdown [cmd]', 'Report markdown against the
|
|
77
|
+
['markdown [cmd]', 'Report markdown against the shipped bans (audit)'],
|
|
79
78
|
['census [path]', 'Report file count, extensions, and line totals'],
|
|
80
79
|
],
|
|
81
80
|
},
|
|
@@ -91,10 +90,10 @@ const SANDBOX_ROWS: CommandRow[] = [
|
|
|
91
90
|
const EXAMPLES = [
|
|
92
91
|
'canon sync ../my-app',
|
|
93
92
|
'canon sandbox git:commit',
|
|
94
|
-
'canon gov install
|
|
93
|
+
'canon gov install node',
|
|
95
94
|
'canon gov sync ../my-app',
|
|
96
95
|
'canon gov restated --json',
|
|
97
|
-
'canon
|
|
96
|
+
'canon markdown audit docs/',
|
|
98
97
|
'canon init ../my-app',
|
|
99
98
|
'canon tooling sync base',
|
|
100
99
|
'canon claude init',
|
package/src/cli.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { register as init } from '@/commands/init'
|
|
|
5
5
|
import { register as sandbox } from '@/commands/sandbox'
|
|
6
6
|
import { register as sync } from '@/commands/sync'
|
|
7
7
|
import { register as gov } from '@/commands/gov'
|
|
8
|
-
import { register as standards } from '@/commands/standards'
|
|
9
8
|
import { register as tooling } from '@/commands/tooling'
|
|
10
9
|
import { register as claude } from '@/commands/claude'
|
|
11
10
|
import { register as wiki } from '@/commands/wiki'
|
|
@@ -66,7 +65,6 @@ init(program)
|
|
|
66
65
|
sandbox(program)
|
|
67
66
|
sync(program)
|
|
68
67
|
gov(program)
|
|
69
|
-
standards(program)
|
|
70
68
|
tooling(program)
|
|
71
69
|
claude(program)
|
|
72
70
|
wiki(program)
|