@erclx/canon 5.25.0 → 5.27.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 +4 -3
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/SKILL.md +2 -0
- package/claude/skills/auto-ship/SKILL.md +3 -11
- package/claude/skills/auto-ship/references/review-findings.md +20 -0
- package/claude/skills/backlog-triage/SKILL.md +2 -0
- package/claude/skills/bash-cli-script/SKILL.md +2 -0
- package/claude/skills/build-in-slices/SKILL.md +2 -0
- package/claude/skills/canon-cli/SKILL.md +2 -0
- package/claude/skills/canon-feedback/SKILL.md +2 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +2 -0
- package/claude/skills/canon-operator/SKILL.md +2 -0
- package/claude/skills/canon-rollout/SKILL.md +3 -11
- package/claude/skills/canon-rollout/references/failure-recovery.md +20 -0
- package/claude/skills/canvas/SKILL.md +14 -1
- package/claude/skills/ci-workflow/SKILL.md +2 -0
- package/claude/skills/code-craft/SKILL.md +2 -0
- package/claude/skills/codebase-layout/SKILL.md +2 -0
- package/claude/skills/context-fold/REQUIREMENT.md +6 -2
- package/claude/skills/context-fold/SKILL.md +9 -21
- package/claude/skills/context-fold/references/anchor-sweep.md +2 -2
- package/claude/skills/context-fold/references/completion-report.md +26 -0
- package/claude/skills/create-rule/SKILL.md +2 -0
- package/claude/skills/create-skill/SKILL.md +2 -0
- package/claude/skills/create-standard/SKILL.md +2 -0
- package/claude/skills/decision-escalate/SKILL.md +2 -0
- package/claude/skills/deploy-app/SKILL.md +2 -0
- package/claude/skills/deprecation-migration/SKILL.md +2 -0
- package/claude/skills/design-extract/SKILL.md +2 -0
- package/claude/skills/design-taste/REQUIREMENT.md +1 -1
- package/claude/skills/design-taste/SKILL.md +6 -2
- package/claude/skills/design-taste/references/adopted.md +40 -0
- package/claude/skills/design-taste/references/kinds.md +1 -1
- package/claude/skills/design-taste/references/systems.md +1 -7
- package/claude/skills/design-taste/references/tells.md +2 -24
- package/claude/skills/docs-sync/REQUIREMENT.md +2 -1
- package/claude/skills/docs-sync/SKILL.md +2 -0
- package/claude/skills/document-health/REQUIREMENT.md +2 -0
- package/claude/skills/document-health/SKILL.md +23 -0
- package/claude/skills/document-health/references/axes.md +12 -0
- package/claude/skills/draft-and-pick/SKILL.md +3 -1
- package/claude/skills/draft-doc/SKILL.md +2 -0
- package/claude/skills/draft-doc/references/wiki.md +1 -1
- package/claude/skills/draft-figure/SKILL.md +2 -0
- package/claude/skills/draft-identity/SKILL.md +3 -1
- package/claude/skills/draft-ready/SKILL.md +2 -0
- package/claude/skills/draft-screencast/SKILL.md +11 -7
- package/claude/skills/draft-slides/REQUIREMENT.md +17 -12
- package/claude/skills/draft-slides/SKILL.md +33 -20
- package/claude/skills/git-branch/SKILL.md +2 -0
- package/claude/skills/git-commit/SKILL.md +2 -0
- package/claude/skills/git-followup/SKILL.md +2 -0
- package/claude/skills/git-issue/SKILL.md +2 -0
- package/claude/skills/git-issue/references/issue.md +10 -0
- package/claude/skills/git-pr/SKILL.md +2 -0
- package/claude/skills/git-pr/references/evidence.md +14 -6
- package/claude/skills/git-pr/references/labels.md +12 -0
- package/claude/skills/git-ship/SKILL.md +2 -0
- package/claude/skills/git-split/SKILL.md +21 -8
- package/claude/skills/git-stage/SKILL.md +2 -0
- package/claude/skills/git-worktree/SKILL.md +2 -0
- package/claude/skills/index-lookup/SKILL.md +2 -0
- package/claude/skills/markdown-propose/SKILL.md +2 -0
- package/claude/skills/markdown-propose/references/format.md +9 -0
- package/claude/skills/memory-capture/SKILL.md +2 -0
- package/claude/skills/memory-review/SKILL.md +2 -0
- package/claude/skills/memory-review/references/later-phases.md +7 -0
- package/claude/skills/plan-feature/SKILL.md +2 -0
- package/claude/skills/plan-groundwork/SKILL.md +2 -0
- package/claude/skills/plan-intake/SKILL.md +2 -0
- package/claude/skills/plan-intake-answer/SKILL.md +2 -0
- package/claude/skills/project-commands/SKILL.md +2 -0
- package/claude/skills/read-frames/SKILL.md +2 -0
- package/claude/skills/record-screencast/SKILL.md +2 -0
- package/claude/skills/repo-metadata/SKILL.md +2 -0
- package/claude/skills/restate-plainly/SKILL.md +2 -0
- package/claude/skills/review-address/SKILL.md +3 -7
- package/claude/skills/review-address/references/post-review-findings.md +16 -0
- package/claude/skills/review-branch/SKILL.md +2 -0
- package/claude/skills/review-craft/SKILL.md +2 -0
- package/claude/skills/review-craft/references/ui.md +2 -2
- package/claude/skills/review-pr/SKILL.md +4 -8
- package/claude/skills/review-pr/references/body-variants.md +52 -0
- package/claude/skills/review-pr/references/key-changes.md +16 -0
- package/claude/skills/review-pr/references/post.md +15 -43
- package/claude/skills/review-ui/SKILL.md +3 -1
- package/claude/skills/review-ui/references/post.md +4 -2
- package/claude/skills/role-orchestrator/SKILL.md +5 -14
- package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +8 -0
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +13 -0
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +5 -33
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +1 -21
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +38 -0
- package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
- package/claude/skills/role-planner/SKILL.md +2 -0
- package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
- package/claude/skills/role-reviewer/SKILL.md +2 -0
- package/claude/skills/role-worker/SKILL.md +2 -0
- package/claude/skills/search-craft/REQUIREMENT.md +1 -1
- package/claude/skills/search-craft/SKILL.md +5 -1
- package/claude/skills/search-craft/references/adopted.md +30 -0
- package/claude/skills/seed-sync/SKILL.md +2 -0
- package/claude/skills/session-compact/SKILL.md +2 -0
- package/claude/skills/session-map/SKILL.md +2 -0
- package/claude/skills/session-relay/SKILL.md +2 -0
- package/claude/skills/session-resume/SKILL.md +2 -0
- package/claude/skills/session-worktree/SKILL.md +3 -11
- package/claude/skills/session-worktree/references/install-command.md +20 -0
- package/claude/skills/sketch-design/SKILL.md +2 -0
- package/claude/skills/systematic-debugging/SKILL.md +2 -0
- package/claude/skills/target-check/SKILL.md +2 -0
- package/claude/skills/target-setup/SKILL.md +2 -0
- package/claude/skills/target-setup/references/detect.md +8 -0
- package/claude/skills/target-setup/references/indexes.md +13 -0
- package/claude/skills/target-setup/references/verify.md +8 -0
- package/claude/skills/task-board/SKILL.md +2 -0
- package/claude/skills/teach-workspace/SKILL.md +5 -15
- package/claude/skills/teach-workspace/references/lesson-build.md +75 -0
- package/claude/skills/teach-workspace/references/lesson-craft.md +13 -69
- package/claude/skills/teach-workspace/references/revisit.md +22 -0
- package/claude/skills/test-craft/SKILL.md +2 -0
- package/claude/skills/test-first/SKILL.md +4 -0
- package/claude/skills/test-first/references/adopted.md +36 -0
- package/claude/skills/ui-checklist/SKILL.md +2 -0
- package/claude/skills/ux-audit/SKILL.md +2 -0
- package/claude/skills/ux-measure/SKILL.md +2 -0
- package/claude/skills/ux-walkthrough/SKILL.md +2 -0
- package/claude/skills/video-craft/SKILL.md +2 -0
- package/claude/skills/write-human/SKILL.md +3 -1
- package/claude/skills/write-human/references/machine-tells.md +13 -0
- package/claude/skills/youtube-transcripts/SKILL.md +2 -0
- package/docs/agents/canvas.md +24 -6
- package/docs/agents/capture.md +6 -6
- package/docs/agents/commands.md +5 -5
- package/docs/agents/index.md +5 -5
- package/docs/agents/install-and-sync.md +7 -0
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/pr-evidence.md +38 -15
- package/docs/agents/pr-local.md +19 -13
- package/docs/agents/pr-preview.md +44 -21
- package/docs/agents/records-stale.md +22 -9
- package/docs/agents/records.md +1 -1
- package/docs/agents/sandbox.md +29 -5
- package/docs/agents/scripting.md +8 -1
- package/docs/agents/skills-audit.md +11 -3
- package/docs/agents/slides.md +15 -3
- package/docs/workflow/ai-workflow.md +1 -1
- package/docs/workflow/index.md +1 -0
- package/docs/workflow/operating-model.md +5 -6
- package/docs/workflow/parallel-features.md +23 -2
- package/docs/workflow/skill-map-on-request.md +42 -0
- package/docs/workflow/skill-map.md +3 -34
- package/docs/workflow/zshrc-aliases.md +1 -1
- package/package.json +1 -1
- package/scripts/core/regen-hero.ts +1 -1
- package/scripts/lib/sandbox-git.sh +1 -9
- package/scripts/lib/sandbox-path.sh +6 -162
- package/scripts/sandbox-hook.sh +99 -0
- package/src/audits/run.ts +1 -1
- package/src/canvas/address.ts +12 -3
- package/src/canvas/capture.ts +1 -9
- package/src/canvas/client/inspector/align-grid.tsx +162 -0
- package/src/canvas/client/inspector/color-field.tsx +398 -0
- package/src/canvas/client/inspector/color-inputs.tsx +139 -0
- package/src/canvas/client/inspector/color-picker.tsx +668 -0
- package/src/canvas/client/inspector/color-space.ts +240 -0
- package/src/canvas/client/inspector/field.tsx +5 -1
- package/src/canvas/client/inspector/fields.css +547 -17
- package/src/canvas/client/inspector/size-field.tsx +69 -0
- package/src/canvas/client/inspector/values.ts +166 -1
- package/src/canvas/client/inspector.tsx +236 -73
- package/src/canvas/client/selection.tsx +197 -0
- package/src/canvas/client/shell.css +104 -12
- package/src/canvas/client/state.ts +69 -9
- package/src/canvas/client/surface.tsx +147 -22
- package/src/canvas/content.ts +50 -1
- package/src/canvas/edit.ts +37 -4
- package/src/canvas/editing.ts +203 -0
- package/src/canvas/global.d.ts +15 -0
- package/src/canvas/server.ts +149 -12
- package/src/canvas/tokens.ts +59 -17
- package/src/capture/render.ts +45 -7
- package/src/capture/sources.ts +43 -0
- package/src/census/count.ts +2 -2
- package/src/claude/cases/workflow.ts +5 -0
- package/src/claude/seeds.ts +2 -2
- package/src/claude/skills-audit.ts +96 -14
- package/src/claude/skills-drift.ts +1 -1
- package/src/claude/skills-families.ts +36 -0
- package/src/claude/skills-list.ts +76 -15
- package/src/commands/audits.ts +2 -2
- package/src/commands/canvas.ts +179 -2
- package/src/commands/claude.ts +94 -11
- package/src/commands/design.ts +142 -2
- package/src/commands/feedback.ts +3 -3
- package/src/commands/gov.ts +1 -1
- package/src/commands/hooks.ts +1 -1
- package/src/commands/indexes.ts +1 -1
- package/src/commands/init.ts +1 -1
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +1 -1
- package/src/commands/pr.ts +105 -58
- package/src/commands/records.ts +89 -7
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +103 -11
- package/src/commands/sessions.ts +1 -1
- package/src/commands/slides.ts +31 -77
- package/src/commands/sync.ts +1 -1
- package/src/commands/tasks.ts +2 -2
- package/src/commands/teach.ts +1 -1
- package/src/commands/transcripts.ts +1 -1
- package/src/commands/worktrees.ts +1 -1
- package/src/comments/scan.ts +1 -1
- package/src/comments/trend.ts +1 -1
- package/src/context/audit.ts +1 -1
- package/src/context/citations.ts +1 -1
- package/src/context/classify/extract.ts +2 -2
- package/src/design/project-fonts.ts +392 -0
- package/src/design/tokens.ts +2 -18
- package/src/docs/read.ts +1 -1
- package/src/{github.ts → feedback/github.ts} +2 -2
- package/src/{client-commands.ts → gate/client-commands.ts} +1 -1
- package/src/gate/measures.ts +53 -13
- package/src/gate/sequencer.ts +1 -1
- package/src/gate/stages.ts +9 -0
- package/src/{git-files.ts → git/files.ts} +2 -2
- package/src/{git-ignore.ts → git/ignore.ts} +1 -1
- package/src/{worktree.ts → git/worktree.ts} +1 -1
- package/src/gov/citations.ts +2 -2
- package/src/gov/install.ts +1 -1
- package/src/gov/payload.ts +1 -1
- package/src/gov/superseded.ts +3 -3
- package/src/gov/test-order.ts +2 -2
- package/src/help.ts +1 -1
- package/src/hooks/post-merge.ts +1 -1
- package/src/indexes/walk.ts +1 -1
- package/src/labels/audit.ts +1 -1
- package/src/markdown/ceiling.ts +1 -1
- package/src/markdown/files.ts +1 -1
- package/src/migrate/apply.ts +1 -1
- package/src/pr/bijection.ts +1 -1
- package/src/pr/evidence.ts +96 -43
- package/src/pr/local.ts +16 -10
- package/src/pr/preview.ts +78 -2
- package/src/record-root.ts +1 -0
- package/src/records/backup.ts +2 -1
- package/src/records/push-guard.ts +1 -1
- package/src/records/stale-canonical.ts +146 -0
- package/src/records/stale.ts +1 -1
- package/src/sandbox/headless/dispatch.ts +264 -0
- package/src/sandbox/headless/run.ts +653 -0
- package/src/sandbox/headless/watch.ts +145 -0
- package/src/sandbox/hooks.ts +128 -0
- package/src/sandbox/provision.ts +607 -0
- package/src/sandbox/tree.ts +99 -4
- package/src/secrets/marker.ts +2 -2
- package/src/secrets/scan.ts +2 -2
- package/src/sessions/claim.ts +1 -1
- package/src/sessions/resolve.ts +1 -1
- package/src/sessions/transfer/bundle.ts +1 -1
- package/src/shipped/references.ts +2 -2
- package/src/slides/convert/export.ts +2 -16
- package/src/slides/decks.ts +110 -0
- package/src/slides/package/fonts.ts +6 -2
- package/src/standards/audit.ts +2 -2
- package/src/standards/read.ts +1 -1
- package/src/sync/check.ts +2 -2
- package/src/sync/engine.ts +2 -2
- package/src/sync/git.ts +1 -1
- package/src/sync/history.ts +1 -1
- package/src/sync/reverse.ts +1 -1
- package/src/sync/seeds-report.ts +1 -1
- package/src/sync/target.ts +1 -1
- package/src/sync/workflow.ts +1 -1
- package/src/targets/pulls.ts +2 -2
- package/src/targets/sweep.ts +2 -2
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/tasks/reach.ts +1 -1
- package/src/tasks/trunk.ts +1 -1
- package/src/teach/nav.ts +1 -1
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/subfolder.ts +1 -1
- package/src/web/readme-citations.ts +2 -2
- package/src/worktrees/bare-flag.ts +1 -1
- package/src/worktrees/reclaim.ts +2 -2
- package/src/worktrees/remove.ts +1 -1
- package/standards/architecture.md +1 -0
- package/standards/canvas.md +3 -2
- package/standards/requirements.md +2 -0
- package/standards/skill-practice.md +2 -1
- package/standards/skill.md +2 -0
- package/standards/wiki.md +12 -3
- package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
- package/scripts/lib/sandbox-dispatch.sh +0 -190
- package/scripts/manage-sandbox.sh +0 -472
- package/src/slides/layouts.ts +0 -478
- package/src/slides/parse.ts +0 -84
- package/src/slides/render.ts +0 -110
- package/src/slides/styles.ts +0 -96
- /package/claude/skills/write-human/references/{source-material.md → adopted.md} +0 -0
- /package/src/{commands/feedback-format.ts → feedback/body.ts} +0 -0
- /package/src/{github-format.ts → feedback/github-format.ts} +0 -0
- /package/src/{git-base.ts → git/base.ts} +0 -0
- /package/src/{git-env.ts → git/env.ts} +0 -0
- /package/src/{frontmatter.ts → markdown/frontmatter.ts} +0 -0
- /package/src/{binary.ts → sweep/binary.ts} +0 -0
- /package/src/{exempt-marker.ts → sweep/exempt-marker.ts} +0 -0
- /package/src/{copy.ts → sync/copy.ts} +0 -0
- /package/src/{target.ts → targets/validate.ts} +0 -0
- /package/src/{paths.ts → tasks/paths.ts} +0 -0
- /package/src/{seed-marker.ts → tooling/seed-marker.ts} +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendFileSync,
|
|
3
|
+
copyFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
realpathSync,
|
|
9
|
+
statSync,
|
|
10
|
+
} from 'node:fs'
|
|
11
|
+
import {
|
|
12
|
+
basename,
|
|
13
|
+
dirname,
|
|
14
|
+
extname,
|
|
15
|
+
isAbsolute,
|
|
16
|
+
join,
|
|
17
|
+
relative,
|
|
18
|
+
resolve,
|
|
19
|
+
} from 'node:path'
|
|
20
|
+
import { primaryFontFamily } from '@/capture/sources'
|
|
21
|
+
import { DESIGN_INSTALL_DIR, DESIGN_PROJECT_SUBDIR } from '@/design/adapter'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A project's own faces, declared as plain `@font-face` rules in its override
|
|
25
|
+
* sheets and stored beside them under `project/`, which sync treats as
|
|
26
|
+
* project-authored by location and never writes. CSS is the declaration
|
|
27
|
+
* format because every consumer already reads it, and reading descriptors
|
|
28
|
+
* out of the font's own tables cannot open WOFF2 without a Brotli parser.
|
|
29
|
+
*
|
|
30
|
+
* A rule's `url()` is read from inside `project/` only, after `realpath`, so
|
|
31
|
+
* an override cannot carry a file from elsewhere on the machine into a frame.
|
|
32
|
+
* A hand-edited sheet reaches this module as often as `addProjectFace` does,
|
|
33
|
+
* so the check sits on the read rather than on the write.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export const PROJECT_FONTS_DIR = 'fonts'
|
|
37
|
+
export const PROJECT_FONTS_SHEET = 'fonts.css'
|
|
38
|
+
|
|
39
|
+
export type FaceProblem = 'missing' | 'outside-folder'
|
|
40
|
+
|
|
41
|
+
export interface ProjectFace {
|
|
42
|
+
readonly family: string
|
|
43
|
+
/** The `font-weight` descriptor verbatim, `400` where the rule omits it. */
|
|
44
|
+
readonly weight: string
|
|
45
|
+
readonly style: string
|
|
46
|
+
/** Relative to the root. */
|
|
47
|
+
readonly sheet: string
|
|
48
|
+
/** The first local `url()` as the rule writes it. */
|
|
49
|
+
readonly file: string
|
|
50
|
+
readonly present: boolean
|
|
51
|
+
readonly problem?: FaceProblem
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface DroppedFace {
|
|
55
|
+
readonly sheet: string
|
|
56
|
+
readonly file: string
|
|
57
|
+
readonly reason: FaceProblem
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface InlinedSheet {
|
|
61
|
+
readonly css: string
|
|
62
|
+
readonly dropped: readonly DroppedFace[]
|
|
63
|
+
/** The families of the rules that survived, in sheet order. */
|
|
64
|
+
readonly declared: readonly string[]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const FORMATS = {
|
|
68
|
+
'.woff2': { mime: 'font/woff2', format: 'woff2', magic: ['wOF2'] },
|
|
69
|
+
'.woff': { mime: 'font/woff', format: 'woff', magic: ['wOFF'] },
|
|
70
|
+
'.ttf': {
|
|
71
|
+
mime: 'font/ttf',
|
|
72
|
+
format: 'truetype',
|
|
73
|
+
magic: ['\u0000\u0001\u0000\u0000', 'true'],
|
|
74
|
+
},
|
|
75
|
+
'.otf': { mime: 'font/otf', format: 'opentype', magic: ['OTTO'] },
|
|
76
|
+
} as const
|
|
77
|
+
|
|
78
|
+
type FontExtension = keyof typeof FORMATS
|
|
79
|
+
|
|
80
|
+
function isFontExtension(ext: string): ext is FontExtension {
|
|
81
|
+
return Object.hasOwn(FORMATS, ext)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const FACE_RULE = /@font-face\s*\{[^}]*\}/g
|
|
85
|
+
const URL = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g
|
|
86
|
+
const SCHEME = /^[a-z][a-z0-9+.-]*:/i
|
|
87
|
+
const DECLARATION = /([A-Za-z-]+)\s*:\s*([^;}]*)/g
|
|
88
|
+
|
|
89
|
+
/** Comments blanked to equal length, so an index into the result fits the source. */
|
|
90
|
+
function masked(css: string): string {
|
|
91
|
+
return css.replace(/\/\*[\s\S]*?\*\//g, (comment) =>
|
|
92
|
+
' '.repeat(comment.length),
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function descriptor(rule: string, name: string): string | undefined {
|
|
97
|
+
const match = rule.match(new RegExp(`(?:^|[;{\\s])${name}\\s*:\\s*([^;}]*)`))
|
|
98
|
+
return match?.[1]?.trim()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function familyOf(rule: string): string {
|
|
102
|
+
return primaryFontFamily(descriptor(rule, 'font-family') ?? '')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Every family a declaration value lists, quoted or not, across the sheet
|
|
107
|
+
* outside its `@font-face` rules, which declare a face rather than use one.
|
|
108
|
+
* Reads every property rather than only `font-family`, since a token such as
|
|
109
|
+
* `--figure-hand` names its stack under a name that ends in neither.
|
|
110
|
+
*/
|
|
111
|
+
export function namedFamilies(css: string): Set<string> {
|
|
112
|
+
const uses = masked(css).replace(FACE_RULE, '')
|
|
113
|
+
const named = new Set<string>()
|
|
114
|
+
for (const [, , value] of uses.matchAll(DECLARATION)) {
|
|
115
|
+
for (const item of (value ?? '').split(',')) {
|
|
116
|
+
const family = primaryFontFamily(item)
|
|
117
|
+
if (family) named.add(family)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return named
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function projectDir(root: string): string {
|
|
124
|
+
return join(root, DESIGN_INSTALL_DIR, DESIGN_PROJECT_SUBDIR)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isWithin(folder: string, path: string): boolean {
|
|
128
|
+
const inside = relative(folder, path)
|
|
129
|
+
return inside !== '' && !inside.startsWith('..') && !isAbsolute(inside)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
type Located =
|
|
133
|
+
| { readonly ok: true; readonly path: string }
|
|
134
|
+
| { readonly ok: false; readonly reason: FaceProblem }
|
|
135
|
+
|
|
136
|
+
/** A URL path as the name on disk, or undefined for a malformed escape. */
|
|
137
|
+
function decoded(url: string): string | undefined {
|
|
138
|
+
try {
|
|
139
|
+
return decodeURIComponent(url.replace(/[?#].*$/, ''))
|
|
140
|
+
} catch {
|
|
141
|
+
return undefined
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Confined lexically before any read, then again after resolving links. The
|
|
147
|
+
* check runs on the decoded path, so an escaped `..` cannot pass it.
|
|
148
|
+
*/
|
|
149
|
+
function locate(root: string, sheet: string, url: string): Located {
|
|
150
|
+
const folder = projectDir(root)
|
|
151
|
+
const clean = decoded(url)
|
|
152
|
+
if (clean === undefined) return { ok: false, reason: 'missing' }
|
|
153
|
+
if (isAbsolute(clean)) return { ok: false, reason: 'outside-folder' }
|
|
154
|
+
const path = resolve(join(root, dirname(sheet)), clean)
|
|
155
|
+
if (!isWithin(folder, path)) return { ok: false, reason: 'outside-folder' }
|
|
156
|
+
if (!existsSync(path) || !statSync(path).isFile()) {
|
|
157
|
+
return { ok: false, reason: 'missing' }
|
|
158
|
+
}
|
|
159
|
+
if (!isWithin(realpathSync(folder), realpathSync(path))) {
|
|
160
|
+
return { ok: false, reason: 'outside-folder' }
|
|
161
|
+
}
|
|
162
|
+
return { ok: true, path }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function localUrls(rule: string): string[] {
|
|
166
|
+
return [...rule.matchAll(URL)]
|
|
167
|
+
.map((match) => match[2]?.trim() ?? '')
|
|
168
|
+
.filter((url) => url !== '' && !SCHEME.test(url))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function mimeOf(path: string): string {
|
|
172
|
+
const ext = extname(path).toLowerCase()
|
|
173
|
+
return isFontExtension(ext) ? FORMATS[ext].mime : 'application/octet-stream'
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Rewrites each local `url()` in a sheet's `@font-face` rules to a `data:`
|
|
178
|
+
* URI, so a frame carries the face rather than a path the frame's origin
|
|
179
|
+
* cannot serve. A rule naming a file that is missing or leads out of
|
|
180
|
+
* `project/` is dropped whole and reported, and the sheet otherwise renders.
|
|
181
|
+
* A `data:` or remote URL is left as written.
|
|
182
|
+
*/
|
|
183
|
+
export function inlineProjectFaces(
|
|
184
|
+
root: string,
|
|
185
|
+
sheet: string,
|
|
186
|
+
css: string,
|
|
187
|
+
): InlinedSheet {
|
|
188
|
+
const dropped: DroppedFace[] = []
|
|
189
|
+
const declared: string[] = []
|
|
190
|
+
let out = ''
|
|
191
|
+
let cursor = 0
|
|
192
|
+
for (const match of masked(css).matchAll(FACE_RULE)) {
|
|
193
|
+
const start = match.index
|
|
194
|
+
const end = start + match[0].length
|
|
195
|
+
const rule = css.slice(start, end)
|
|
196
|
+
out += css.slice(cursor, start)
|
|
197
|
+
cursor = end
|
|
198
|
+
|
|
199
|
+
const urls = localUrls(match[0])
|
|
200
|
+
const located = urls.map((url) => ({ url, at: locate(root, sheet, url) }))
|
|
201
|
+
const failed = located.find(({ at }) => !at.ok)
|
|
202
|
+
if (failed && !failed.at.ok) {
|
|
203
|
+
dropped.push({ sheet, file: failed.url, reason: failed.at.reason })
|
|
204
|
+
continue
|
|
205
|
+
}
|
|
206
|
+
let inlined = rule
|
|
207
|
+
for (const { url, at } of located) {
|
|
208
|
+
if (!at.ok) continue
|
|
209
|
+
const data = `data:${mimeOf(at.path)};base64,${readFileSync(at.path).toString('base64')}`
|
|
210
|
+
inlined = inlined.replace(url, data)
|
|
211
|
+
}
|
|
212
|
+
out += inlined
|
|
213
|
+
declared.push(familyOf(match[0]))
|
|
214
|
+
}
|
|
215
|
+
return { css: out + css.slice(cursor), dropped, declared }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The project's override sheets, root-relative, in the order a frame cascades them. */
|
|
219
|
+
export function projectSheets(root: string): string[] {
|
|
220
|
+
const folder = projectDir(root)
|
|
221
|
+
if (!existsSync(folder)) return []
|
|
222
|
+
return readdirSync(folder)
|
|
223
|
+
.filter((name) => name.endsWith('.css'))
|
|
224
|
+
.sort((a, b) => a.localeCompare(b))
|
|
225
|
+
.map((name) => join(DESIGN_INSTALL_DIR, DESIGN_PROJECT_SUBDIR, name))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Every face the override sheets declare, without reading a font file. */
|
|
229
|
+
export function listProjectFaces(root: string): ProjectFace[] {
|
|
230
|
+
return projectSheets(root).flatMap((sheet) => {
|
|
231
|
+
const css = masked(readFileSync(join(root, sheet), 'utf8'))
|
|
232
|
+
return [...css.matchAll(FACE_RULE)].map((match): ProjectFace => {
|
|
233
|
+
const rule = match[0]
|
|
234
|
+
const local = localUrls(rule)[0]
|
|
235
|
+
const external = [...rule.matchAll(URL)][0]?.[2]?.trim() ?? ''
|
|
236
|
+
/*
|
|
237
|
+
* A rule carrying its face inline or remotely has no file to read, and
|
|
238
|
+
* the frame keeps it as written, so it lists as present. A `data:` URI
|
|
239
|
+
* is shortened to its scheme rather than echoing the whole face.
|
|
240
|
+
*/
|
|
241
|
+
const file = local ?? (external.startsWith('data:') ? 'data:' : external)
|
|
242
|
+
const at: Located = local
|
|
243
|
+
? locate(root, sheet, local)
|
|
244
|
+
: external
|
|
245
|
+
? { ok: true, path: external }
|
|
246
|
+
: { ok: false, reason: 'missing' }
|
|
247
|
+
return {
|
|
248
|
+
family: familyOf(rule),
|
|
249
|
+
weight: descriptor(rule, 'font-weight') ?? '400',
|
|
250
|
+
style: descriptor(rule, 'font-style') ?? 'normal',
|
|
251
|
+
sheet,
|
|
252
|
+
file,
|
|
253
|
+
present: at.ok,
|
|
254
|
+
...(!at.ok && { problem: at.reason }),
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface FaceSpec {
|
|
261
|
+
readonly family: string
|
|
262
|
+
readonly weight?: string
|
|
263
|
+
readonly style?: string
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export type AddOutcome =
|
|
267
|
+
| {
|
|
268
|
+
readonly ok: true
|
|
269
|
+
readonly family: string
|
|
270
|
+
readonly weight: string
|
|
271
|
+
readonly style: string
|
|
272
|
+
/** Relative to the sheet's folder, as the rule writes it. */
|
|
273
|
+
readonly file: string
|
|
274
|
+
readonly sheet: string
|
|
275
|
+
}
|
|
276
|
+
| {
|
|
277
|
+
readonly ok: false
|
|
278
|
+
readonly reason: 'invalid-font' | 'invalid-face' | 'exists'
|
|
279
|
+
readonly detail: string
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** A name CSS takes inside single quotes with no escaping. */
|
|
283
|
+
const SAFE_FAMILY = /^[A-Za-z0-9][A-Za-z0-9 _-]*$/
|
|
284
|
+
/** A file name a single-quoted `url()` takes with no escaping. */
|
|
285
|
+
const SAFE_FILE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
|
|
286
|
+
const WEIGHT = /^\d{1,4}( \d{1,4})?$/
|
|
287
|
+
|
|
288
|
+
function weightInRange(weight: string): boolean {
|
|
289
|
+
return weight.split(' ').every((n) => Number(n) >= 1 && Number(n) <= 1000)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Copies a face into `project/fonts/` and appends one rule for it to
|
|
294
|
+
* `project/fonts.css`. Refuses before writing anything when the extension is
|
|
295
|
+
* not a font, the bytes disagree with it, a descriptor would need escaping,
|
|
296
|
+
* or the folder already holds a file of that name.
|
|
297
|
+
*/
|
|
298
|
+
export function addProjectFace(
|
|
299
|
+
root: string,
|
|
300
|
+
source: string,
|
|
301
|
+
spec: FaceSpec,
|
|
302
|
+
): AddOutcome {
|
|
303
|
+
const name = basename(source)
|
|
304
|
+
const ext = extname(name).toLowerCase()
|
|
305
|
+
if (!isFontExtension(ext)) {
|
|
306
|
+
return {
|
|
307
|
+
ok: false,
|
|
308
|
+
reason: 'invalid-font',
|
|
309
|
+
detail: `${name} is not a font. Use woff2, woff, ttf, or otf`,
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (!SAFE_FILE.test(name)) {
|
|
313
|
+
return {
|
|
314
|
+
ok: false,
|
|
315
|
+
reason: 'invalid-font',
|
|
316
|
+
detail: `${name} must be letters, digits, dots, dashes, or underscores to sit in a url(). Rename it`,
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (!existsSync(source) || !statSync(source).isFile()) {
|
|
320
|
+
return { ok: false, reason: 'invalid-font', detail: `${source} not found` }
|
|
321
|
+
}
|
|
322
|
+
const head = readFileSync(source).subarray(0, 4).toString('binary')
|
|
323
|
+
if (!FORMATS[ext].magic.some((magic) => head === magic)) {
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
reason: 'invalid-font',
|
|
327
|
+
detail: `${name} does not read as ${ext.slice(1)} from its first bytes`,
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const weight = spec.weight ?? '400'
|
|
332
|
+
const style = spec.style ?? 'normal'
|
|
333
|
+
if (!SAFE_FAMILY.test(spec.family)) {
|
|
334
|
+
return {
|
|
335
|
+
ok: false,
|
|
336
|
+
reason: 'invalid-face',
|
|
337
|
+
detail: `family ${spec.family} must be letters, digits, spaces, dashes, or underscores`,
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (!WEIGHT.test(weight) || !weightInRange(weight)) {
|
|
341
|
+
return {
|
|
342
|
+
ok: false,
|
|
343
|
+
reason: 'invalid-face',
|
|
344
|
+
detail: `weight ${weight} must be one number or a range of two, from 1 to 1000`,
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (style !== 'normal' && style !== 'italic') {
|
|
348
|
+
return {
|
|
349
|
+
ok: false,
|
|
350
|
+
reason: 'invalid-face',
|
|
351
|
+
detail: `style ${style} must be normal or italic`,
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const folder = join(projectDir(root), PROJECT_FONTS_DIR)
|
|
356
|
+
const dest = join(folder, name)
|
|
357
|
+
const file = `${PROJECT_FONTS_DIR}/${name}`
|
|
358
|
+
if (existsSync(dest)) {
|
|
359
|
+
return {
|
|
360
|
+
ok: false,
|
|
361
|
+
reason: 'exists',
|
|
362
|
+
detail: `${relative(root, dest)} already exists. Remove it or rename the source`,
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
mkdirSync(folder, { recursive: true })
|
|
367
|
+
copyFileSync(source, dest)
|
|
368
|
+
const sheetPath = join(projectDir(root), PROJECT_FONTS_SHEET)
|
|
369
|
+
const lead =
|
|
370
|
+
existsSync(sheetPath) && readFileSync(sheetPath, 'utf8').trim() !== ''
|
|
371
|
+
? '\n'
|
|
372
|
+
: ''
|
|
373
|
+
appendFileSync(
|
|
374
|
+
sheetPath,
|
|
375
|
+
`${lead}@font-face {
|
|
376
|
+
font-family: '${spec.family}';
|
|
377
|
+
font-weight: ${weight};
|
|
378
|
+
font-style: ${style};
|
|
379
|
+
font-display: swap;
|
|
380
|
+
src: url('${file}') format('${FORMATS[ext].format}');
|
|
381
|
+
}
|
|
382
|
+
`,
|
|
383
|
+
)
|
|
384
|
+
return {
|
|
385
|
+
ok: true,
|
|
386
|
+
family: spec.family,
|
|
387
|
+
weight,
|
|
388
|
+
style,
|
|
389
|
+
file,
|
|
390
|
+
sheet: relative(root, sheetPath),
|
|
391
|
+
}
|
|
392
|
+
}
|
package/src/design/tokens.ts
CHANGED
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
* The design system's one source of values.
|
|
3
3
|
*
|
|
4
4
|
* `canon/DESIGN.md` is rendered from this module rather than read by it, so
|
|
5
|
-
* the document a person opens is a view and this file is the fact.
|
|
6
|
-
*
|
|
7
|
-
* takes custom properties through `@/design/css`, and the slide renderer takes
|
|
8
|
-
* bare hex through `bareHex` below, because PowerPoint has no concept of a
|
|
9
|
-
* custom property.
|
|
5
|
+
* the document a person opens is a view and this file is the fact. A CSS
|
|
6
|
+
* surface reads it as custom properties through `@/design/css`.
|
|
10
7
|
*
|
|
11
8
|
* The two artifacts can disagree, which is what the `design` gate stage exists
|
|
12
9
|
* to catch. Nothing else compares them.
|
|
@@ -371,16 +368,3 @@ export function colorValue(
|
|
|
371
368
|
): string | undefined {
|
|
372
369
|
return tokens.color.find((token) => token.role === role)?.value
|
|
373
370
|
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* The adapter the slide renderer takes. `PptxGenJS` receives color as
|
|
377
|
-
* `{ color: theme.background }` and wants six hex digits with no leading `#`,
|
|
378
|
-
* so the shared thing is the value and this is the per-consumer form.
|
|
379
|
-
*
|
|
380
|
-
* The hex is raised to upper case because that is the spelling
|
|
381
|
-
* `src/slides/styles.ts` has always written, and the only one a diff of a
|
|
382
|
-
* rendered deck reads cleanly against.
|
|
383
|
-
*/
|
|
384
|
-
export function bareHex(value: string): string {
|
|
385
|
-
return value.replace(/^#/, '').toUpperCase()
|
|
386
|
-
}
|
package/src/docs/read.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { basename, join } from 'node:path'
|
|
3
|
-
import { stripFrontmatter } from '@/frontmatter'
|
|
3
|
+
import { stripFrontmatter } from '@/markdown/frontmatter'
|
|
4
4
|
import { SURFACE_ROOTS } from '@/surface-root'
|
|
5
5
|
|
|
6
6
|
const INDEX_TOPIC = 'index'
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { execa } from 'execa'
|
|
2
|
-
import { gitEnv } from '@/git
|
|
2
|
+
import { gitEnv } from '@/git/env'
|
|
3
3
|
import { PROJECT_ROOT } from '@/project-root'
|
|
4
4
|
import {
|
|
5
5
|
buildIssueArgs,
|
|
6
6
|
failureDetail,
|
|
7
7
|
type CreateIssueOptions,
|
|
8
8
|
type CreateIssueResult,
|
|
9
|
-
} from '@/github-format'
|
|
9
|
+
} from '@/feedback/github-format'
|
|
10
10
|
|
|
11
11
|
const GH_TIMEOUT_MS = 30_000
|
|
12
12
|
|
package/src/gate/measures.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
CLIENT_COMMANDS,
|
|
7
7
|
type ClientCommand,
|
|
8
8
|
clientCommandCitationsIn,
|
|
9
|
-
} from '@/client-commands'
|
|
9
|
+
} from '@/gate/client-commands'
|
|
10
10
|
import {
|
|
11
11
|
isOverCount,
|
|
12
12
|
isOverLength,
|
|
@@ -18,13 +18,15 @@ import {
|
|
|
18
18
|
import { isOverWordCap, measureRequirements } from '@/context/requirements'
|
|
19
19
|
import {
|
|
20
20
|
auditSkills,
|
|
21
|
+
LEDGER_SKILLS,
|
|
21
22
|
PRACTICE_LEDGER,
|
|
22
23
|
PRACTICE_SKILLS,
|
|
23
24
|
SHIPPED_CORPUS,
|
|
24
25
|
} from '@/claude/skills-audit'
|
|
26
|
+
import { familyKeys } from '@/claude/skills-families'
|
|
25
27
|
import { measureDesignProse } from '@/design/budget'
|
|
26
28
|
import { DESIGN_DOCUMENT } from '@/design/regen'
|
|
27
|
-
import { listRepositoryFiles } from '@/git
|
|
29
|
+
import { listRepositoryFiles } from '@/git/files'
|
|
28
30
|
import { ceilingFindings } from '@/markdown/ceiling'
|
|
29
31
|
import { resolveMarkdown } from '@/markdown/files'
|
|
30
32
|
import { CHECKPOINTS } from '@/markdown/structure'
|
|
@@ -165,7 +167,7 @@ export function auditsBaselineRel(root: string): string {
|
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
export const CAPTURE_STAMP_FAILURE =
|
|
168
|
-
'A capture set disagrees with the stamp written when its image was captured. Run canon capture assets/captures --selector .window --out assets/
|
|
170
|
+
'A capture set disagrees with the stamp written when its image was captured. Run canon capture assets/captures --selector .window --out assets/frames and commit each frame with its image and its stamp.'
|
|
169
171
|
|
|
170
172
|
function parseJson(payload: string): unknown {
|
|
171
173
|
try {
|
|
@@ -411,8 +413,9 @@ export const skillProvenance: Measure = async (ctx) => {
|
|
|
411
413
|
}
|
|
412
414
|
|
|
413
415
|
/**
|
|
414
|
-
* Every listed practice skill against the closing sections and
|
|
415
|
-
*
|
|
416
|
+
* Every listed practice skill against the closing sections, and every skill on
|
|
417
|
+
* the wider ledger list against the ledger, both of which
|
|
418
|
+
* `standards/skill-practice.md` requires.
|
|
416
419
|
*
|
|
417
420
|
* Read in-process for the reason `skillProvenance` is, so the verb keeps its
|
|
418
421
|
* missing-requirement-only exit. The list names shipped folders alone, so a
|
|
@@ -434,19 +437,55 @@ export const skillPracticeShape: Measure = async (ctx) => {
|
|
|
434
437
|
.join(', ')
|
|
435
438
|
return {
|
|
436
439
|
emissions: [],
|
|
437
|
-
failure: `${plural(found.length, 'practice shape finding')}: ${where}. Add the closing H2 exactly as named, or the ledger at ${PRACTICE_LEDGER}.`,
|
|
440
|
+
failure: `${plural(found.length, 'practice shape finding')}: ${where}. Add the closing H2 exactly as named to a practice skill, or the ledger at ${PRACTICE_LEDGER} to any skill on the ledger list.`,
|
|
438
441
|
}
|
|
439
442
|
}
|
|
440
443
|
|
|
441
444
|
return {
|
|
442
445
|
emissions: [
|
|
443
446
|
info(
|
|
444
|
-
`${plural(PRACTICE_SKILLS.length, 'practice skill')} carry every closing section and a ledger`,
|
|
447
|
+
`${plural(PRACTICE_SKILLS.length, 'practice skill')} carry every closing section, and ${plural(LEDGER_SKILLS.length, 'listed skill')} carry a ledger`,
|
|
445
448
|
),
|
|
446
449
|
],
|
|
447
450
|
}
|
|
448
451
|
}
|
|
449
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Every shipped skill against the family vocabulary, since the landing page
|
|
455
|
+
* groups the skill map by the field and a skill without one has no group.
|
|
456
|
+
*
|
|
457
|
+
* Read in-process for the reason `skillProvenance` is. Only the shipped corpus
|
|
458
|
+
* takes a family, so a tree without `claude/skills/` reports unmeasured rather
|
|
459
|
+
* than a vacuous pass.
|
|
460
|
+
*/
|
|
461
|
+
export const skillFamily: Measure = async (ctx) => {
|
|
462
|
+
const report = await auditSkills(ctx.root)
|
|
463
|
+
const shipped = report.corpora.find((corpus) => corpus.rel === SHIPPED_CORPUS)
|
|
464
|
+
if (shipped === undefined) {
|
|
465
|
+
return {
|
|
466
|
+
emissions: [],
|
|
467
|
+
unmeasured: `no skill corpus at ${SHIPPED_CORPUS} resolved, so no family was read.`,
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const found = report.family
|
|
472
|
+
if (found.length > 0) {
|
|
473
|
+
const where = found
|
|
474
|
+
.map((finding) => `${finding.rel} ${finding.detail}`)
|
|
475
|
+
.join(', ')
|
|
476
|
+
return {
|
|
477
|
+
emissions: [],
|
|
478
|
+
failure: `${plural(found.length, 'skill family finding')}: ${where}. Add metadata.family to the frontmatter, one of ${familyKeys().join(', ')}, matching the skill map group its row sits under.`,
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
emissions: [
|
|
484
|
+
info(`${plural(shipped.skills, 'shipped skill')} declare a known family`),
|
|
485
|
+
],
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
450
489
|
/**
|
|
451
490
|
* Every tracked markdown document against the whole-document ceiling.
|
|
452
491
|
*
|
|
@@ -980,7 +1019,7 @@ export const clientCommandCitations = async (
|
|
|
980
1019
|
return {
|
|
981
1020
|
emissions: [],
|
|
982
1021
|
failure:
|
|
983
|
-
'The client command table is empty, so the corpus was walked and nothing was looked for. Check src/client-commands.ts.',
|
|
1022
|
+
'The client command table is empty, so the corpus was walked and nothing was looked for. Check src/gate/client-commands.ts.',
|
|
984
1023
|
}
|
|
985
1024
|
}
|
|
986
1025
|
|
|
@@ -1021,8 +1060,8 @@ export const clientCommandCitations = async (
|
|
|
1021
1060
|
),
|
|
1022
1061
|
failure:
|
|
1023
1062
|
found.length === 1
|
|
1024
|
-
? `One tracked citation quotes a client command with the wrong argument. Match it against the canonical form in src/client-commands.ts, or mark the line ${CLIENT_COMMAND_MARKER}: <reason> where the argument differs on purpose.`
|
|
1025
|
-
: `${found.length} tracked citations quote a client command with the wrong argument. Match each against the canonical form in src/client-commands.ts, or mark the line ${CLIENT_COMMAND_MARKER}: <reason> where the argument differs on purpose.`,
|
|
1063
|
+
? `One tracked citation quotes a client command with the wrong argument. Match it against the canonical form in src/gate/client-commands.ts, or mark the line ${CLIENT_COMMAND_MARKER}: <reason> where the argument differs on purpose.`
|
|
1064
|
+
: `${found.length} tracked citations quote a client command with the wrong argument. Match each against the canonical form in src/gate/client-commands.ts, or mark the line ${CLIENT_COMMAND_MARKER}: <reason> where the argument differs on purpose.`,
|
|
1026
1065
|
}
|
|
1027
1066
|
}
|
|
1028
1067
|
|
|
@@ -1476,11 +1515,12 @@ async function collectPluginManifests(ctx: MeasureContext): Promise<string[]> {
|
|
|
1476
1515
|
* A capture set spans two folders. The markup and the template beside it are
|
|
1477
1516
|
* the authored half and sit under `assets/captures/`, while the image a
|
|
1478
1517
|
* document points at and the stamp answering for it sit under
|
|
1479
|
-
* `assets/
|
|
1480
|
-
* `
|
|
1518
|
+
* `assets/frames/`, which is where `--out assets/frames` sends them. They sit
|
|
1519
|
+
* outside `assets/evidence/` so a changed marketing image is not read as dev
|
|
1520
|
+
* evidence.
|
|
1481
1521
|
*/
|
|
1482
1522
|
const CAPTURE_MARKUP_DIR = 'assets/captures'
|
|
1483
|
-
const CAPTURE_OUTPUT_DIR = 'assets/
|
|
1523
|
+
const CAPTURE_OUTPUT_DIR = 'assets/frames'
|
|
1484
1524
|
|
|
1485
1525
|
/**
|
|
1486
1526
|
* Every capture under `assets/captures/`, named by the base its three files
|
package/src/gate/sequencer.ts
CHANGED
package/src/gate/stages.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
seedStandards,
|
|
23
23
|
rawFieldFileReference,
|
|
24
24
|
shippedReferences,
|
|
25
|
+
skillFamily,
|
|
25
26
|
skillPracticeShape,
|
|
26
27
|
skillProvenance,
|
|
27
28
|
standardCriteria,
|
|
@@ -496,6 +497,14 @@ export const STAGES: readonly Stage[] = [
|
|
|
496
497
|
label: 'Skill practice shape',
|
|
497
498
|
checks: [{ kind: 'measure', measure: skillPracticeShape }],
|
|
498
499
|
},
|
|
500
|
+
{
|
|
501
|
+
// The audit verb only reports a shipped skill with a missing or unknown
|
|
502
|
+
// family, so a target is told without failing. The landing page groups the
|
|
503
|
+
// skill map by the field, so the stage holds this corpus at zero.
|
|
504
|
+
id: 'skill-family',
|
|
505
|
+
label: 'Skill family',
|
|
506
|
+
checks: [{ kind: 'measure', measure: skillFamily }],
|
|
507
|
+
},
|
|
499
508
|
{
|
|
500
509
|
id: 'standard-criteria',
|
|
501
510
|
label: 'Standard success criteria',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $ } from 'bun'
|
|
2
|
-
import { baseCandidates, isMergeBase } from '@/git
|
|
3
|
-
import { gitEnv } from '@/git
|
|
2
|
+
import { baseCandidates, isMergeBase } from '@/git/base'
|
|
3
|
+
import { gitEnv } from '@/git/env'
|
|
4
4
|
|
|
5
5
|
/** Runs git under `root` with the resolution variables a hook exports stripped. */
|
|
6
6
|
async function git(
|
package/src/gov/citations.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { existsSync } from 'node:fs'
|
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
import { dirname, join, resolve } from 'node:path'
|
|
4
4
|
import { $ } from 'bun'
|
|
5
|
-
import { isMarked } from '@/exempt-marker'
|
|
6
|
-
import { gitEnv } from '@/git
|
|
5
|
+
import { isMarked } from '@/sweep/exempt-marker'
|
|
6
|
+
import { gitEnv } from '@/git/env'
|
|
7
7
|
import { listRuleFiles } from '@/gov/payload'
|
|
8
8
|
import { parseFrontmatter } from '@/indexes/frontmatter'
|
|
9
9
|
import { findBacktickSpans } from '@/markdown/backticks'
|
package/src/gov/install.ts
CHANGED
package/src/gov/payload.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
import { basename, resolve } from 'node:path'
|
|
3
|
-
import { stripFrontmatter } from '@/frontmatter'
|
|
3
|
+
import { stripFrontmatter } from '@/markdown/frontmatter'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Lists installed rules in the order the payload concatenates them. Sorting by
|