@erclx/canon 5.52.0 → 5.54.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/CHANGELOG.md +17 -0
- package/README.md +11 -13
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/canon-cli/SKILL.md +5 -8
- package/claude/skills/context-fold/REQUIREMENT.md +0 -1
- package/claude/skills/design-identity/REQUIREMENT.md +2 -2
- package/claude/skills/design-identity/SKILL.md +6 -6
- package/claude/skills/design-record/REQUIREMENT.md +2 -2
- package/claude/skills/design-record/SKILL.md +4 -4
- package/claude/skills/design-record/references/folder.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +1 -1
- package/claude/skills/design-taste/references/kinds.md +0 -1
- package/claude/skills/draw-figure/REQUIREMENT.md +3 -4
- package/claude/skills/draw-figure/SKILL.md +5 -6
- package/claude/skills/draw-figure/references/figures.md +0 -1
- package/claude/skills/draw-figure/references/hand-drawn.md +1 -1
- package/claude/skills/pick-by-eye/REQUIREMENT.md +4 -5
- package/claude/skills/pick-by-eye/SKILL.md +34 -41
- package/claude/skills/pick-by-eye/references/board.md +42 -0
- package/claude/skills/pick-by-eye/references/live-arms.md +3 -3
- package/claude/skills/plan-groundwork/SKILL.md +1 -1
- package/claude/skills/teach-craft/REQUIREMENT.md +30 -0
- package/claude/skills/teach-craft/SKILL.md +70 -0
- package/claude/skills/teach-craft/references/adopted.md +113 -0
- package/claude/skills/teach-craft/references/lesson-craft.md +60 -0
- package/claude/skills/{teach-workspace → teach-craft}/references/pedagogy.md +41 -6
- package/docs/agents/audits.md +0 -1
- package/docs/agents/install-and-sync.md +0 -8
- package/docs/target/migrations.md +16 -2
- package/docs/target/projects.md +1 -1
- package/docs/workflow/parallel-features.md +1 -1
- package/docs/workflow/skill-map-on-request.md +10 -22
- package/docs/workflow/skill-map.md +11 -14
- package/docs/workflow/visual-design-workflow.md +1 -4
- package/governance/detect.toml +1 -6
- package/governance/renames.toml +0 -1
- package/package.json +2 -8
- package/scripts/core/regen-web-previews.ts +2 -67
- package/src/audits/catalog.ts +0 -1
- package/src/browser/engine.ts +3 -6
- package/src/capture/render.ts +1 -1
- package/src/claude/cases/authoring.ts +0 -12
- package/src/claude/cases/misc.ts +0 -22
- package/src/claude/cases/workflow.ts +2 -38
- package/src/claude/skills-audit.ts +1 -1
- package/src/cli/help.ts +0 -10
- package/src/cli.ts +0 -8
- package/src/commands/design.ts +16 -315
- package/src/commands/records.ts +0 -1
- package/src/commands/serve.ts +2 -2
- package/src/commands/sync.ts +4 -5
- package/src/design/components.ts +8 -1514
- package/src/design/css.ts +5 -6
- package/src/design/fonts.ts +3 -5
- package/src/design/regen.ts +5 -32
- package/src/design/render.ts +1 -1
- package/src/design/tokens.ts +3 -2
- package/src/driver/drive.ts +3 -4
- package/src/driver/steps.ts +2 -5
- package/src/gate/stages.ts +4 -10
- package/src/gov/audit.ts +1 -6
- package/src/inventory/walk.ts +1 -1
- package/src/markdown/scope.ts +6 -35
- package/src/records/validate.ts +4 -176
- package/src/sandbox/equivalence.ts +0 -5
- package/src/shipped/references.ts +1 -2
- package/src/sync/check.ts +4 -29
- package/src/sync/stamp.ts +6 -6
- package/src/sync/target.ts +1 -4
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/canon/DESIGN.md +1 -1
- package/claude/skills/canvas-board/REQUIREMENT.md +0 -35
- package/claude/skills/canvas-board/SKILL.md +0 -143
- package/claude/skills/canvas-board/references/canvas.md +0 -170
- package/claude/skills/draft-and-pick/REQUIREMENT.md +0 -26
- package/claude/skills/draft-and-pick/SKILL.md +0 -11
- package/claude/skills/draft-doc/REQUIREMENT.md +0 -26
- package/claude/skills/draft-doc/SKILL.md +0 -11
- package/claude/skills/draft-figure/REQUIREMENT.md +0 -26
- package/claude/skills/draft-figure/SKILL.md +0 -11
- package/claude/skills/draft-identity/REQUIREMENT.md +0 -26
- package/claude/skills/draft-identity/SKILL.md +0 -11
- package/claude/skills/draft-ready/REQUIREMENT.md +0 -26
- package/claude/skills/draft-ready/SKILL.md +0 -11
- package/claude/skills/draft-screencast/REQUIREMENT.md +0 -26
- package/claude/skills/draft-screencast/SKILL.md +0 -11
- package/claude/skills/draft-slides/REQUIREMENT.md +0 -26
- package/claude/skills/draft-slides/SKILL.md +0 -11
- package/claude/skills/make-deck/REQUIREMENT.md +0 -36
- package/claude/skills/make-deck/SKILL.md +0 -80
- package/claude/skills/make-deck/references/deck.md +0 -183
- package/claude/skills/read-frames/REQUIREMENT.md +0 -30
- package/claude/skills/read-frames/SKILL.md +0 -55
- package/claude/skills/record-screencast/REQUIREMENT.md +0 -35
- package/claude/skills/record-screencast/SKILL.md +0 -99
- package/claude/skills/script-screencast/REQUIREMENT.md +0 -34
- package/claude/skills/script-screencast/SKILL.md +0 -70
- package/claude/skills/script-screencast/references/beats-template.md +0 -119
- package/claude/skills/teach-workspace/REQUIREMENT.md +0 -34
- package/claude/skills/teach-workspace/SKILL.md +0 -196
- package/claude/skills/teach-workspace/references/glossary.md +0 -90
- package/claude/skills/teach-workspace/references/lesson-build.md +0 -106
- package/claude/skills/teach-workspace/references/lesson-craft.md +0 -135
- package/claude/skills/teach-workspace/references/promotion-fold.md +0 -35
- package/claude/skills/teach-workspace/references/promotion.md +0 -116
- package/claude/skills/teach-workspace/references/revisit.md +0 -28
- package/claude/skills/teach-workspace/references/teach.md +0 -211
- package/claude/skills/video-craft/REQUIREMENT.md +0 -35
- package/claude/skills/video-craft/SKILL.md +0 -96
- package/claude/skills/video-craft/references/adopted.md +0 -110
- package/governance/rules/ui/canvas.md +0 -13
- package/src/canvas/address.ts +0 -227
- package/src/canvas/arrange.ts +0 -66
- package/src/canvas/capture.ts +0 -391
- package/src/canvas/client/app.tsx +0 -188
- package/src/canvas/client/inspector/align-grid.tsx +0 -162
- package/src/canvas/client/inspector/color-field.tsx +0 -398
- package/src/canvas/client/inspector/color-inputs.tsx +0 -139
- package/src/canvas/client/inspector/color-picker.tsx +0 -668
- package/src/canvas/client/inspector/color-space.ts +0 -240
- package/src/canvas/client/inspector/field.tsx +0 -263
- package/src/canvas/client/inspector/fields.css +0 -763
- package/src/canvas/client/inspector/section.tsx +0 -27
- package/src/canvas/client/inspector/size-field.tsx +0 -69
- package/src/canvas/client/inspector/token-select.tsx +0 -109
- package/src/canvas/client/inspector/values.ts +0 -297
- package/src/canvas/client/inspector.tsx +0 -692
- package/src/canvas/client/keys.ts +0 -71
- package/src/canvas/client/layers.tsx +0 -118
- package/src/canvas/client/main.tsx +0 -71
- package/src/canvas/client/pages-panel.tsx +0 -212
- package/src/canvas/client/panel-handle.tsx +0 -90
- package/src/canvas/client/selection.tsx +0 -199
- package/src/canvas/client/shell.css +0 -762
- package/src/canvas/client/state.ts +0 -792
- package/src/canvas/client/surface.tsx +0 -803
- package/src/canvas/client/theme-panel.tsx +0 -55
- package/src/canvas/client/tools.tsx +0 -153
- package/src/canvas/content.ts +0 -781
- package/src/canvas/edit.ts +0 -575
- package/src/canvas/editing.ts +0 -203
- package/src/canvas/fit.ts +0 -238
- package/src/canvas/global.d.ts +0 -15
- package/src/canvas/history.ts +0 -116
- package/src/canvas/index.html +0 -20
- package/src/canvas/server.ts +0 -766
- package/src/canvas/tokens.ts +0 -221
- package/src/commands/canvas.ts +0 -1097
- package/src/commands/demo.ts +0 -542
- package/src/commands/slides.ts +0 -184
- package/src/commands/teach.ts +0 -1401
- package/src/demo/beats.ts +0 -135
- package/src/demo/compile.ts +0 -350
- package/src/demo/container.ts +0 -219
- package/src/demo/cursors.ts +0 -55
- package/src/demo/drive.ts +0 -442
- package/src/demo/pointer.ts +0 -178
- package/src/demo/theme.ts +0 -112
- package/src/demo/timeline.ts +0 -64
- package/src/design/adapter.ts +0 -59
- package/src/design/base.css +0 -203
- package/src/design/brand-favicon.ts +0 -18
- package/src/design/neutral.ts +0 -127
- package/src/design/project-fonts.ts +0 -392
- package/src/slides/convert/chart.ts +0 -165
- package/src/slides/convert/deck.ts +0 -244
- package/src/slides/convert/export.ts +0 -567
- package/src/slides/convert/master.ts +0 -198
- package/src/slides/convert/shapes.ts +0 -804
- package/src/slides/convert/svg.ts +0 -38
- package/src/slides/convert/walk.ts +0 -671
- package/src/slides/decks.ts +0 -110
- package/src/slides/open.ts +0 -18
- package/src/slides/package/fonts.ts +0 -301
- package/src/slides/package/motion.ts +0 -316
- package/src/slides/package/test-face.ts +0 -113
- package/src/teach/browser/compile.ts +0 -44
- package/src/teach/browser/dismiss.ts +0 -76
- package/src/teach/browser/focus-line.ts +0 -19
- package/src/teach/browser/glossary-filter.ts +0 -44
- package/src/teach/browser/head.ts +0 -30
- package/src/teach/browser/quiz.ts +0 -19
- package/src/teach/browser/reload.ts +0 -9
- package/src/teach/browser/sidebar.ts +0 -233
- package/src/teach/browser/theme.ts +0 -18
- package/src/teach/components/code.tsx +0 -24
- package/src/teach/components/heading.tsx +0 -11
- package/src/teach/components/list.tsx +0 -17
- package/src/teach/components/paragraph.tsx +0 -29
- package/src/teach/components/refs.tsx +0 -28
- package/src/teach/cross-references.ts +0 -78
- package/src/teach/fonts.ts +0 -16
- package/src/teach/highlight.ts +0 -71
- package/src/teach/html/jsx-dev-runtime.ts +0 -13
- package/src/teach/html/jsx-runtime.ts +0 -113
- package/src/teach/html/raw.ts +0 -28
- package/src/teach/lesson.ts +0 -178
- package/src/teach/nav.ts +0 -1337
- package/src/teach/render.ts +0 -240
- package/src/teach/workspace.ts +0 -1164
- package/src/teach/write-if-changed.ts +0 -17
package/src/records/validate.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readdir, readFile } from 'node:fs/promises'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { parseFrontmatter, readField } from '@/indexes/frontmatter'
|
|
@@ -12,17 +12,8 @@ import {
|
|
|
12
12
|
recordDir as resolveRecordDir,
|
|
13
13
|
recordDirs as resolveRecordDirs,
|
|
14
14
|
} from '@/roots/record'
|
|
15
|
-
import {
|
|
16
|
-
TEACH_GLOSSARY,
|
|
17
|
-
TEACH_MISSION,
|
|
18
|
-
TEACH_RECORDS,
|
|
19
|
-
TEACH_REFERENCE,
|
|
20
|
-
TEACH_RESOURCES,
|
|
21
|
-
TEACH_SUCCESS_HEADING,
|
|
22
|
-
WORKSPACE_NAME,
|
|
23
|
-
} from '@/teach/workspace'
|
|
24
15
|
|
|
25
|
-
export const RECORD_KINDS = ['plans', 'groundwork', 'intake'
|
|
16
|
+
export const RECORD_KINDS = ['plans', 'groundwork', 'intake'] as const
|
|
26
17
|
|
|
27
18
|
export type RecordKind = (typeof RECORD_KINDS)[number]
|
|
28
19
|
|
|
@@ -759,171 +750,9 @@ async function checkDump(dir: string, slug: string): Promise<Finding[]> {
|
|
|
759
750
|
return [...findings, ...perCluster.flat()]
|
|
760
751
|
}
|
|
761
752
|
|
|
762
|
-
const NUMBERED_RECORD = /^\d{4}-[a-z0-9]+(-[a-z0-9]+)*\.md$/
|
|
763
|
-
/**
|
|
764
|
-
* A kebab slug that does not open with an ordinal. The lookahead rejects a
|
|
765
|
-
* leading run of digits followed by a hyphen and nothing else, so a subject
|
|
766
|
-
* whose own name starts with a digit still passes.
|
|
767
|
-
*/
|
|
768
|
-
const REFERENCE_NAME = /^(?!\d+-)[a-z0-9]+(-[a-z0-9]+)*\.md$/
|
|
769
|
-
|
|
770
|
-
/** The two fields every markdown file in a workspace carries. */
|
|
771
|
-
async function checkTeachFile(
|
|
772
|
-
dir: string,
|
|
773
|
-
slug: string,
|
|
774
|
-
file: string,
|
|
775
|
-
subject: string,
|
|
776
|
-
): Promise<Finding[]> {
|
|
777
|
-
const frontmatter = parseFrontmatter(await readFile(join(dir, file), 'utf8'))
|
|
778
|
-
|
|
779
|
-
const missing = ['title', 'description'].filter(
|
|
780
|
-
(field) => !readField(frontmatter, field),
|
|
781
|
-
)
|
|
782
|
-
|
|
783
|
-
if (missing.length === 0) return []
|
|
784
|
-
|
|
785
|
-
return [
|
|
786
|
-
finding(
|
|
787
|
-
'frontmatter-incomplete',
|
|
788
|
-
slug,
|
|
789
|
-
subject,
|
|
790
|
-
`carries no ${missing.join(' and no ')}.`,
|
|
791
|
-
),
|
|
792
|
-
]
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
/**
|
|
796
|
-
* One markdown subfolder of a workspace. `lessons/` and `assets/` are never
|
|
797
|
-
* reached, because a lesson is generated markup carrying no frontmatter and a
|
|
798
|
-
* walk over it would report every one as malformed.
|
|
799
|
-
*/
|
|
800
|
-
async function checkTeachSubfolder(
|
|
801
|
-
dir: string,
|
|
802
|
-
slug: string,
|
|
803
|
-
folder: string,
|
|
804
|
-
name: RegExp,
|
|
805
|
-
message: string,
|
|
806
|
-
): Promise<Finding[]> {
|
|
807
|
-
const path = join(dir, folder)
|
|
808
|
-
|
|
809
|
-
// Tested as a directory rather than for presence. Every other walk in this
|
|
810
|
-
// module takes its path from `listFolders`, and this one is built from a
|
|
811
|
-
// fixed name, so a workspace holding a plain file called `reference` would
|
|
812
|
-
// reach `readdir` and take the whole run down with `ENOTDIR`.
|
|
813
|
-
if (!statSync(path, { throwIfNoEntry: false })?.isDirectory()) return []
|
|
814
|
-
|
|
815
|
-
const files = await listMarkdown(path)
|
|
816
|
-
|
|
817
|
-
const malformed = files
|
|
818
|
-
.filter((file) => !name.test(file))
|
|
819
|
-
.map((file) =>
|
|
820
|
-
finding('name-malformed', slug, `${folder}/${file}`, message),
|
|
821
|
-
)
|
|
822
|
-
|
|
823
|
-
const perFile = await Promise.all(
|
|
824
|
-
files.map((file) => checkTeachFile(path, slug, file, `${folder}/${file}`)),
|
|
825
|
-
)
|
|
826
|
-
|
|
827
|
-
return [...malformed, ...perFile.flat()]
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
async function checkWorkspace(dir: string, slug: string): Promise<Finding[]> {
|
|
831
|
-
const findings: Finding[] = []
|
|
832
|
-
|
|
833
|
-
if (!WORKSPACE_NAME.test(slug)) {
|
|
834
|
-
findings.push(
|
|
835
|
-
finding(
|
|
836
|
-
'name-malformed',
|
|
837
|
-
slug,
|
|
838
|
-
slug,
|
|
839
|
-
'is not named NN-<topic> with a two-digit ordinal, so a listing sorts alphabetically rather than by when each workspace opened.',
|
|
840
|
-
),
|
|
841
|
-
)
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
const files = await listMarkdown(dir)
|
|
845
|
-
|
|
846
|
-
if (!files.includes(TEACH_MISSION)) {
|
|
847
|
-
findings.push(
|
|
848
|
-
finding(
|
|
849
|
-
'index-missing',
|
|
850
|
-
slug,
|
|
851
|
-
TEACH_MISSION,
|
|
852
|
-
'is absent, so the workspace states no subject and no success to finish against.',
|
|
853
|
-
),
|
|
854
|
-
)
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
for (const required of [TEACH_RESOURCES, TEACH_GLOSSARY]) {
|
|
858
|
-
if (!files.includes(required)) {
|
|
859
|
-
findings.push(
|
|
860
|
-
finding(
|
|
861
|
-
'section-missing',
|
|
862
|
-
slug,
|
|
863
|
-
required,
|
|
864
|
-
'is required and the workspace carries no such file.',
|
|
865
|
-
),
|
|
866
|
-
)
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
const perFile = await Promise.all(
|
|
871
|
-
files.map((file) => checkTeachFile(dir, slug, file, file)),
|
|
872
|
-
)
|
|
873
|
-
findings.push(...perFile.flat())
|
|
874
|
-
|
|
875
|
-
if (files.includes(TEACH_MISSION)) {
|
|
876
|
-
const text = await readFile(join(dir, TEACH_MISSION), 'utf8')
|
|
877
|
-
|
|
878
|
-
if (!hasOpeningDate(parseFrontmatter(text)?.raw ?? '')) {
|
|
879
|
-
findings.push(
|
|
880
|
-
finding(
|
|
881
|
-
'date-malformed',
|
|
882
|
-
slug,
|
|
883
|
-
TEACH_MISSION,
|
|
884
|
-
'carries no date field as YYYY-MM-DD, so the workspace states no opening day.',
|
|
885
|
-
),
|
|
886
|
-
)
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
if (
|
|
890
|
-
!linesOutsideFences(text).some(
|
|
891
|
-
(line) => line.trim() === TEACH_SUCCESS_HEADING,
|
|
892
|
-
)
|
|
893
|
-
) {
|
|
894
|
-
findings.push(
|
|
895
|
-
finding(
|
|
896
|
-
'section-missing',
|
|
897
|
-
slug,
|
|
898
|
-
TEACH_SUCCESS_HEADING,
|
|
899
|
-
'is absent, so the mission names no observable thing the learner will be able to do.',
|
|
900
|
-
),
|
|
901
|
-
)
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
return [
|
|
906
|
-
...findings,
|
|
907
|
-
...(await checkTeachSubfolder(
|
|
908
|
-
dir,
|
|
909
|
-
slug,
|
|
910
|
-
TEACH_REFERENCE,
|
|
911
|
-
REFERENCE_NAME,
|
|
912
|
-
'is not named <slug>.md as a kebab slug opening with no ordinal, so a page looked up rather than worked through implies an order no reader follows.',
|
|
913
|
-
)),
|
|
914
|
-
...(await checkTeachSubfolder(
|
|
915
|
-
dir,
|
|
916
|
-
slug,
|
|
917
|
-
TEACH_RECORDS,
|
|
918
|
-
NUMBERED_RECORD,
|
|
919
|
-
'is not numbered NNNN-<slug>.md, so the records carry no read order.',
|
|
920
|
-
)),
|
|
921
|
-
]
|
|
922
|
-
}
|
|
923
|
-
|
|
924
753
|
/**
|
|
925
|
-
* The
|
|
926
|
-
* a
|
|
754
|
+
* The two kinds whose records are folders rather than files. Keyed by kind so
|
|
755
|
+
* a third arrives as an entry here and the compiler names the walk it owes,
|
|
927
756
|
* where a ternary chain would silently fall through to whichever branch is last.
|
|
928
757
|
*/
|
|
929
758
|
const FOLDER_CHECK: Readonly<
|
|
@@ -934,7 +763,6 @@ const FOLDER_CHECK: Readonly<
|
|
|
934
763
|
> = {
|
|
935
764
|
groundwork: checkTrack,
|
|
936
765
|
intake: checkDump,
|
|
937
|
-
teach: checkWorkspace,
|
|
938
766
|
}
|
|
939
767
|
|
|
940
768
|
/**
|
|
@@ -74,11 +74,6 @@ export const MASKS: readonly Mask[] = [
|
|
|
74
74
|
reason:
|
|
75
75
|
'the install stamp carries a `syncedAt` wall-clock time, so two provisions of one commit differ',
|
|
76
76
|
},
|
|
77
|
-
{
|
|
78
|
-
arm: 'claude:read-frames/resolved',
|
|
79
|
-
key: /^file:demos\/cold-open\.webm$/,
|
|
80
|
-
reason: 'ffmpeg output carries an encoder timestamp, so two runs differ',
|
|
81
|
-
},
|
|
82
77
|
{
|
|
83
78
|
key: /^log$/,
|
|
84
79
|
replace: /sandbox-occupant-\d+|\(pid \d+\)/g,
|
|
@@ -15,8 +15,7 @@ import { isMarked } from '@/sweep/exempt-marker'
|
|
|
15
15
|
* number resolves.
|
|
16
16
|
*
|
|
17
17
|
* That is also what keeps the check a prose pattern rather than a parser.
|
|
18
|
-
* `src/design/
|
|
19
|
-
* `#191512`, which no width or boundary rule separates from a pull request
|
|
18
|
+
* `src/design/tokens.ts` carries twenty values shaped `#191512`, which no width or boundary rule separates from a pull request
|
|
20
19
|
* number, so telling them apart would need the line's syntactic position.
|
|
21
20
|
*/
|
|
22
21
|
export const SHIPPED_CORPORA = [
|
package/src/sync/check.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { existsSync } from 'node:fs'
|
|
|
2
2
|
import { basename, join, sep } from 'node:path'
|
|
3
3
|
import { execa } from 'execa'
|
|
4
4
|
import { gitEnv } from '@/git/env'
|
|
5
|
-
import { createDesignAdapter, DESIGN_INSTALL_DIR } from '@/design/adapter'
|
|
6
5
|
import { createGovAdapter } from '@/gov/adapter'
|
|
7
6
|
import { successorResolver } from '@/gov/renames'
|
|
8
7
|
import { lookupRules } from '@/gov/install'
|
|
@@ -46,17 +45,11 @@ import { readSkew, type SkewReport } from '@/version/skew'
|
|
|
46
45
|
* being one of these, because `src/tooling/` never calls `planSync`, so the
|
|
47
46
|
* three lookups below have no entry to offer it. Standards and snippets left
|
|
48
47
|
* the list with their install channels: nothing writes either corpus into a
|
|
49
|
-
* target, so there is no installed copy to attribute.
|
|
50
|
-
*
|
|
51
|
-
* Design joins as a scanned domain rather than a stamp-only one because its
|
|
52
|
-
* base file is attributed the same way a rule is, and its install marker is
|
|
53
|
-
* what keeps it off a target that never asked for it: a project with no
|
|
54
|
-
* `.claude/design/` is never scanned, so design values arrive on an install
|
|
55
|
-
* rather than on the next sync.
|
|
48
|
+
* target, so there is no installed copy to attribute. Design left with its
|
|
49
|
+
* install verb for the same reason.
|
|
56
50
|
*/
|
|
57
51
|
export const SCANNED_DOMAINS = [
|
|
58
52
|
'governance',
|
|
59
|
-
'design',
|
|
60
53
|
] as const satisfies readonly StampDomain[]
|
|
61
54
|
|
|
62
55
|
export type ScannedDomain = (typeof SCANNED_DOMAINS)[number]
|
|
@@ -68,17 +61,14 @@ export type ScannedDomain = (typeof SCANNED_DOMAINS)[number]
|
|
|
68
61
|
*/
|
|
69
62
|
const SYNCED_SOURCES: Record<ScannedDomain, string> = {
|
|
70
63
|
governance: 'governance/rules/',
|
|
71
|
-
design: 'src/design/',
|
|
72
64
|
}
|
|
73
65
|
|
|
74
66
|
const ADAPTERS: Record<ScannedDomain, (root: string) => SyncAdapter> = {
|
|
75
67
|
governance: createGovAdapter,
|
|
76
|
-
design: createDesignAdapter,
|
|
77
68
|
}
|
|
78
69
|
|
|
79
70
|
const INSTALL_MARKERS: Record<ScannedDomain, readonly string[]> = {
|
|
80
71
|
governance: ['.claude', 'rules', 'canon'],
|
|
81
|
-
design: DESIGN_INSTALL_DIR.split(sep),
|
|
82
72
|
}
|
|
83
73
|
|
|
84
74
|
export interface StateCounts {
|
|
@@ -215,33 +205,18 @@ export interface CheckReport {
|
|
|
215
205
|
readonly skew: SkewReport
|
|
216
206
|
}
|
|
217
207
|
|
|
218
|
-
/**
|
|
219
|
-
* Scanned domains a target takes deliberately rather than by being managed.
|
|
220
|
-
*
|
|
221
|
-
* An absent one is a choice, so it is never reported as unstamped. Governance
|
|
222
|
-
* wants the opposite reading, since a managed target without it has yet to
|
|
223
|
-
* install what every project is expected to carry, and naming it is the only
|
|
224
|
-
* place that shows up.
|
|
225
|
-
*/
|
|
226
|
-
export const OPT_IN_DOMAINS: readonly ScannedDomain[] = ['design']
|
|
227
|
-
|
|
228
208
|
/**
|
|
229
209
|
* Scanned domains this target should have stamped and has not, which is the
|
|
230
210
|
* one line a domain nobody installed ever appears on.
|
|
231
211
|
*
|
|
232
212
|
* An unmigrated domain is excluded because the relocation is its remedy rather
|
|
233
|
-
* than a sync
|
|
234
|
-
* because there is nothing there to stamp and the sync it would name refuses.
|
|
213
|
+
* than a sync.
|
|
235
214
|
*/
|
|
236
215
|
export function uncoveredDomains(report: CheckReport): ScannedDomain[] {
|
|
237
216
|
const unmigrated = new Set(report.unmigrated.map((entry) => entry.domain))
|
|
238
|
-
const installed = new Set(report.domains.map((entry) => entry.domain))
|
|
239
217
|
|
|
240
218
|
return SCANNED_DOMAINS.filter(
|
|
241
|
-
(domain) =>
|
|
242
|
-
!report.covers.includes(domain) &&
|
|
243
|
-
!unmigrated.has(domain) &&
|
|
244
|
-
(!OPT_IN_DOMAINS.includes(domain) || installed.has(domain)),
|
|
219
|
+
(domain) => !report.covers.includes(domain) && !unmigrated.has(domain),
|
|
245
220
|
)
|
|
246
221
|
}
|
|
247
222
|
|
package/src/sync/stamp.ts
CHANGED
|
@@ -10,17 +10,17 @@ import { compareVersions, parseVersion } from '@/version/compare'
|
|
|
10
10
|
import { readInstalled } from '@/version/installed'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Domains the stamp can record. Governance
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* file hashes.
|
|
13
|
+
* Domains the stamp can record. Governance attributes file by file through
|
|
14
|
+
* the sync engine. Tooling runs its own inject and manifest machinery, so it
|
|
15
|
+
* records the stack chain it resolved instead and carries no file hashes.
|
|
17
16
|
*
|
|
18
17
|
* A stamp written before the standards or snippets install channel closed
|
|
19
|
-
* still carries a `standards` or `snippets` record
|
|
18
|
+
* still carries a `standards` or `snippets` record, and one written before the
|
|
19
|
+
* design install retired carries a `design` record. `isStamp` ignores the key
|
|
20
20
|
* and `sortDomains` drops it on the next write, so the target loses a domain
|
|
21
21
|
* nothing can refresh rather than losing the whole file.
|
|
22
22
|
*/
|
|
23
|
-
export const STAMP_DOMAINS = ['governance', '
|
|
23
|
+
export const STAMP_DOMAINS = ['governance', 'tooling'] as const
|
|
24
24
|
|
|
25
25
|
export type StampDomain = (typeof STAMP_DOMAINS)[number]
|
|
26
26
|
|
package/src/sync/target.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
-
import { DESIGN_INSTALL_DIR } from '@/design/adapter'
|
|
4
3
|
import { isDirectory } from '@/targets/validate'
|
|
5
4
|
|
|
6
|
-
export const SYNC_DOMAINS = ['governance', '
|
|
5
|
+
export const SYNC_DOMAINS = ['governance', 'claude'] as const
|
|
7
6
|
|
|
8
7
|
export type SyncDomain = (typeof SYNC_DOMAINS)[number]
|
|
9
8
|
|
|
@@ -14,7 +13,6 @@ export interface DomainState {
|
|
|
14
13
|
|
|
15
14
|
const DOMAIN_MARKERS: Record<SyncDomain, string> = {
|
|
16
15
|
governance: join('.claude', 'rules'),
|
|
17
|
-
design: DESIGN_INSTALL_DIR,
|
|
18
16
|
claude: '.claude',
|
|
19
17
|
}
|
|
20
18
|
|
|
@@ -26,7 +24,6 @@ const DOMAIN_MARKERS: Record<SyncDomain, string> = {
|
|
|
26
24
|
*/
|
|
27
25
|
const DOMAIN_PATHS: Record<SyncDomain, readonly string[]> = {
|
|
28
26
|
governance: ['.claude/rules/', '.claude/GOV.md'],
|
|
29
|
-
design: ['.claude/design/'],
|
|
30
27
|
claude: ['.gitignore'],
|
|
31
28
|
}
|
|
32
29
|
|
|
@@ -16,7 +16,7 @@ The claude stack installs the Claude Code workflow into a project through `canon
|
|
|
16
16
|
|
|
17
17
|
- `.canon/tasks/` holds one file per task plus a generated `index.md`, local scratch only, with `archive/` holding the tasks that shipped
|
|
18
18
|
- `.canon/plans/` holds execution detail for multi-step tasks, and `canon tasks archive` moves a `feature-*.md` plan into `archive/` when the `post-merge` hook archives its task
|
|
19
|
-
- `.canon/review/` holds
|
|
19
|
+
- `.canon/review/` holds the reports a review or measurement skill writes, such as `ux-measure-<slug>.md`
|
|
20
20
|
- `.canon/tmp/` is ephemeral scratch, safe to delete without loss
|
|
21
21
|
- `.claude/worktrees/` is where `EnterWorktree` creates a linked worktree, which is why it stays under `.claude/`
|
|
22
22
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Design
|
|
2
2
|
|
|
3
|
-
Authoring guidance: the design standard. Values live in `<your stylesheet>`, such as `src/styles/tokens.css` or a Tailwind `@theme` block. A project with none writes
|
|
3
|
+
Authoring guidance: the design standard. Values live in `<your stylesheet>`, such as `src/styles/tokens.css` or a Tailwind `@theme` block. A project with none writes a stylesheet of its own and names it here.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: canvas-board
|
|
3
|
-
description: Scope boundary for driving the local design canvas, and the line between a drafted frame and the design it feeds
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Canvas board requirement
|
|
7
|
-
|
|
8
|
-
## Gap
|
|
9
|
-
|
|
10
|
-
Without this skill, a session asked to draw on the canvas writes frames nothing is serving and reports them drawn. It trusts a passing capture, though a blank shell passes every capture while the operator sees an empty tab. From a linked worktree it writes a copy the canvas never serves, and it captures after every edit.
|
|
11
|
-
|
|
12
|
-
It reads "this one" from the conversation rather than the operator's selection, or acts on a stale element pick and edits the wrong element. It restyles through a class while the operator's inspector edit sits inline, so the change never shows. A picked frame stays on the canvas as the only record, and the project's design record never learns it.
|
|
13
|
-
|
|
14
|
-
## Must
|
|
15
|
-
|
|
16
|
-
- The canvas is serving and its live shell loads at the address the server reported before any frame is written, and a refusal or a blank shell is reported rather than drawn into
|
|
17
|
-
- Pages and frames go through the verbs, so the session and the operator's drags share one writer, and a frame from a linked worktree takes the main-root route
|
|
18
|
-
- The operator's selection decides what "this one" means, and a stale element pick reads as unknown
|
|
19
|
-
- A frame's inline styles are read before a restyle, the run names which operator edits it kept and dropped, and capture runs once per finished frame or page
|
|
20
|
-
- A picked frame is handed to `design-record` by page and frame, while a picked layout stays on the canvas for the plan that builds it
|
|
21
|
-
|
|
22
|
-
## Must not
|
|
23
|
-
|
|
24
|
-
- Present the canvas as the project's source of truth for design, or promise it survives a lost checkout
|
|
25
|
-
- Write frames when `canon` or its canvas command is missing
|
|
26
|
-
- Name any third-party design tool, or the canvas's predecessor surface
|
|
27
|
-
- Be reached only by the author typing its name, since a request to draw, select, or capture on the canvas should reach it by description
|
|
28
|
-
|
|
29
|
-
## Out of scope
|
|
30
|
-
|
|
31
|
-
- Drafting candidates for a taste decision and taking the pick, which `pick-by-eye` owns
|
|
32
|
-
- What the canvas verbs do internally, which the CLI's own contract owns
|
|
33
|
-
- The content format a frame and a layout take, which the canvas standard owns
|
|
34
|
-
- Landing a pick in the reference folder and carrying its values into the design document, which `design-record` owns
|
|
35
|
-
- Tracing reference images into design values and backing up the canvas folder, which no skill owns
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: canvas-board
|
|
3
|
-
description: Drives the local design canvas, where pages hold HTML frames the operator sees, drags, selects, and edits in the browser. Starts the canvas when it is not running, confirms the live shell loads, makes pages and frames, edits a frame through its file, reads what the operator selected or restyled, captures finished frames, and hands a picked frame to `design-record`. Use when asked to "open the canvas", "put this on the canvas", "draw a frame for X", "change the one I selected", "what did I pick", "capture this frame", or "move that frame on the canvas". Do NOT use to draft candidates for a taste decision, which is `pick-by-eye`.
|
|
4
|
-
metadata:
|
|
5
|
-
family: decide
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Canvas board
|
|
9
|
-
|
|
10
|
-
Drive the canvas the operator is looking at. A page is a folder of HTML frames and a frame is one file, so a session draws by writing files and the operator sees each write land in the browser. The content format is fixed by `${CLAUDE_SKILL_DIR}/references/canvas.md`. Read it before writing a frame.
|
|
11
|
-
|
|
12
|
-
Every verb below is `canon canvas`. Run `canon canvas --help` for the verbs and their refusals, and read `${CLAUDE_SKILL_DIR}/references/canvas.md` for the content format.
|
|
13
|
-
|
|
14
|
-
## Guards
|
|
15
|
-
|
|
16
|
-
- If `canon` is not on PATH, stop: `❌ canon CLI not found.`
|
|
17
|
-
- If `canon canvas --help` reports no such command, stop: `❌ The installed canon carries no canvas command. Update the CLI and re-run.`
|
|
18
|
-
- Write no frame before the canvas is serving and the shell has loaded. A frame nobody can see is a draft reported as delivered.
|
|
19
|
-
- Never present a frame as the project's design. The canvas is a drafting surface and the project's design record holds what was decided.
|
|
20
|
-
- Never promise the canvas survives a lost checkout. Its folder is gitignored and nothing backs it up.
|
|
21
|
-
|
|
22
|
-
## Step 1: start the canvas and confirm the shell
|
|
23
|
-
|
|
24
|
-
Read the pages first, which also proves the verb resolves:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
canon canvas list --json
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
The record carries `content`, the folder every page lives in. It sits at the main worktree root whichever worktree the session runs in, so write through that path rather than one composed from the current directory.
|
|
31
|
-
|
|
32
|
-
Start the server in the background unless the operator named an address that already answers, since it runs until interrupted. No verb reports a running server, and a second one is harmless, since it walks to the next free port and serves the same folder:
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
canon canvas serve --json
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Read `url` off the record rather than composing one, since the server walks past a port already in use. Report the refusal and its `reason` when `ok` is false, and stop there.
|
|
39
|
-
|
|
40
|
-
### Check the shell
|
|
41
|
-
|
|
42
|
-
Fetch the printed address and check the shell itself, not a capture, since every capture goes through the frame route alone and passes on a blank shell.
|
|
43
|
-
|
|
44
|
-
- The body is non-empty and carries a `<script` tag. An empty `200` means the shell failed to build, usually a missing dependency, so report a blank shell and stop.
|
|
45
|
-
- `<url>/api/pages` answers with JSON listing the pages.
|
|
46
|
-
|
|
47
|
-
Hand the operator the address as a markdown link carrying the URL as its text and its target.
|
|
48
|
-
|
|
49
|
-
## Step 2: make pages and frames
|
|
50
|
-
|
|
51
|
-
- Add a page for each decision being drafted: `canon canvas page add <page> --json`.
|
|
52
|
-
- Follow the house style in `${CLAUDE_SKILL_DIR}/references/canvas.md` before drafting an explanation frame.
|
|
53
|
-
- Add each frame with `canon canvas frame add <page> <frame> --width <px> --height <px> --json`, which writes the file and its box in one locked step
|
|
54
|
-
|
|
55
|
-
Give each frame the width it should render at, since a capture and the canvas both render at it. The verb places the frame after the last one, and a position the operator asks for goes through `frame move`.
|
|
56
|
-
|
|
57
|
-
### Layout and naming
|
|
58
|
-
|
|
59
|
-
Lay frames out through the verbs rather than by hand, since the operator's drags write the same `layout.json` under a lock.
|
|
60
|
-
|
|
61
|
-
- Move a frame with `canon canvas frame move <page> <frame> --x <px> --y <px> --json`
|
|
62
|
-
- Name pages and frames for what they hold, such as `pricing` and `pricing-dense`, so the operator can say which one by name.
|
|
63
|
-
- After an insert or a resize, re-lay the page with `canon canvas arrange <page> --json`. It keeps dragged rows, sets the standard gap, and drops entries whose file is gone.
|
|
64
|
-
- Run `arrange --check` to report without writing
|
|
65
|
-
|
|
66
|
-
## Step 3: edit a frame through its file
|
|
67
|
-
|
|
68
|
-
Write the frame's whole document to the `path` the add verb reported. The open canvas reloads the frame when its file changes.
|
|
69
|
-
|
|
70
|
-
- From a linked worktree, `Edit` and `Write` refuse a main-root path. Write the whole file through one plain `Bash` heredoc, the route `session-worktree` states, never a worktree copy.
|
|
71
|
-
- Change one property with `canon canvas edit <page>/<frame> --element <index> --set <property>=<value> --json` when its set covers the change, since it leaves every other byte as it was
|
|
72
|
-
- Use the project's custom properties for every value the token stylesheet defines. The `serve` record names the source under `tokens`, and `none` means frames render unstyled, so say so.
|
|
73
|
-
|
|
74
|
-
### Fit the box
|
|
75
|
-
|
|
76
|
-
Fit the box to the content once the write lands, with `canon canvas fit <page>/<frame> --json`, and read each row's `wide` and `font`.
|
|
77
|
-
|
|
78
|
-
- A `wide` frame needs its box widened, which fit never does
|
|
79
|
-
- A `font` frame names a family the canvas does not serve, so set its text to a served face and fit again
|
|
80
|
-
- A fit that grew a frame leaves the rows below overlapped until `canvas arrange` runs
|
|
81
|
-
|
|
82
|
-
### Mark the frame
|
|
83
|
-
|
|
84
|
-
Mark the frame before the first write, so the operator sees which frame is being worked on before anything lands, and clear the mark once the edits to it are done:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
canon canvas editing <page>/<frame> --json
|
|
88
|
-
canon canvas editing <page>/<frame> --done --json
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
- A mark lapses after five minutes, so mark the frame again during a longer edit.
|
|
92
|
-
- Clear it on every exit from the edit, including one that stopped on a refusal. Clearing a frame that holds no mark succeeds.
|
|
93
|
-
- On `unknown command 'editing'`, the CLI predates the mark, so go on without it. Never run `canvas edit` in its place, since that verb writes the frame.
|
|
94
|
-
|
|
95
|
-
## Step 4: read what the operator pointed at
|
|
96
|
-
|
|
97
|
-
Read the selection when the operator says "this one", "the selected frame", or "that button":
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
canon canvas selection --json
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
- `selection: null` means nothing is selected or the selected frame is gone. Ask which one rather than guessing.
|
|
104
|
-
- An `element` carrying `stale: true` was picked against an earlier file, so its `index` may land elsewhere. Ask the operator to pick again rather than matching its `tag` and `text` by eye.
|
|
105
|
-
- A fresh `element` gives the `index` that `canvas edit` takes.
|
|
106
|
-
|
|
107
|
-
## Step 5: respect the operator's inspector edits
|
|
108
|
-
|
|
109
|
-
The operator restyles elements from the inspector, and each edit lands as inline `style` on the element. Inline style beats a class, so a class change made afterwards does not show.
|
|
110
|
-
|
|
111
|
-
- Read the frame's inline `style` attributes before restyling anything in it.
|
|
112
|
-
- Keep an inline property the operator set unless the change replaces it, and drop it, through `canvas edit` with an empty value or by rewriting the file, when it does.
|
|
113
|
-
- Say which operator edits were kept and which were dropped.
|
|
114
|
-
|
|
115
|
-
## Step 6: capture finished work
|
|
116
|
-
|
|
117
|
-
Capture once per finished frame or page, not after every edit. The live canvas is the working view, and a capture is the record handed on.
|
|
118
|
-
|
|
119
|
-
- Capture a frame with `canon canvas capture <page>/<frame> --json`. Each PNG is the whole document, so its height follows the content rather than the box.
|
|
120
|
-
- Capture a page with `canon canvas capture <page> --composite --json`, which shows each frame clipped to its box at its layout position
|
|
121
|
-
- Read each `path` off the record rather than composing one.
|
|
122
|
-
|
|
123
|
-
A capture refused because a font is not installed names the family it read off the frame's text. Set that text's `font-family` to a named family the machine has, never a generic keyword such as `sans-serif`, and capture again rather than reporting the frame as captured.
|
|
124
|
-
|
|
125
|
-
## Step 7: hand a picked frame on
|
|
126
|
-
|
|
127
|
-
When the operator picks a frame, the decision leaves the canvas. Hand it to `canon:design-record`, which lands the frame in the project's reference folder and carries its values into the design document. Report it rather than proceeding silently when that skill does not resolve.
|
|
128
|
-
|
|
129
|
-
- Name the page and frame the decision came from in the handoff and the reply, and leave the frame on the canvas until the operator clears it
|
|
130
|
-
- Name the frame in the plan that builds a picked layout, since the layout reaches code through that plan
|
|
131
|
-
- Never cite a frame path from a tracked file. The folder is gitignored, so the citation resolves on this machine alone.
|
|
132
|
-
|
|
133
|
-
## Output
|
|
134
|
-
|
|
135
|
-
```plaintext
|
|
136
|
-
✅ Canvas: <url the serve verb reported>
|
|
137
|
-
Page: <page>, <n> frames
|
|
138
|
-
Selected: <page>/<frame>, element <index>, or none, or stale
|
|
139
|
-
Capture: <path of each capture written>
|
|
140
|
-
Handed: <the frame handed to design-record, the plan that cites it, or not yet>
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Write the canvas line's URL as a markdown link. Omit the capture line when nothing was captured and the handed line when no frame was picked. Emit every path from the project root.
|