@erclx/canon 5.17.0 → 5.19.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 +1 -1
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/deploy-app/REQUIREMENT.md +44 -0
- package/claude/skills/deploy-app/SKILL.md +45 -0
- package/claude/skills/{deploy-cloudflare/SKILL.md → deploy-app/references/cloudflare.md} +5 -9
- package/claude/skills/deploy-app/references/vercel.md +72 -0
- package/docs/agents/architecture-record.md +12 -4
- package/docs/agents/canvas.md +13 -1
- package/docs/agents/commands.md +3 -2
- package/docs/agents/context-audit-checks.md +1 -1
- package/docs/agents/context-audit.md +4 -4
- package/docs/agents/index.md +2 -2
- package/docs/agents/teach.md +7 -1
- package/docs/workflow/skill-map.md +9 -9
- package/package.json +2 -1
- package/src/audits/catalog.ts +46 -1
- package/src/canvas/address.ts +73 -39
- package/src/canvas/client/inspector.tsx +241 -33
- package/src/canvas/client/pages-panel.tsx +51 -0
- package/src/canvas/client/shell.css +126 -1
- package/src/canvas/client/state.ts +120 -4
- package/src/canvas/client/theme-panel.tsx +55 -0
- package/src/canvas/content.ts +14 -3
- package/src/canvas/edit.ts +360 -0
- package/src/canvas/server.ts +50 -5
- package/src/canvas/tokens.ts +82 -0
- package/src/claude/cases/misc.ts +5 -1
- package/src/commands/canvas.ts +95 -1
- package/src/commands/context.ts +85 -5
- package/src/commands/slides.ts +73 -4
- package/src/commands/teach.ts +112 -4
- package/src/context/architecture.ts +98 -18
- package/src/context/gate.ts +30 -3
- package/src/context/requirements.ts +77 -0
- package/src/gate/measures.ts +69 -28
- package/src/gate/stages.ts +5 -5
- package/src/serve/live.ts +113 -0
- package/src/serve/static.ts +60 -2
- package/src/slides/convert/export.ts +323 -0
- package/src/slides/convert/shapes.ts +764 -0
- package/src/slides/convert/svg.ts +38 -0
- package/src/slides/convert/walk.ts +444 -0
- package/src/teach/browser/reload.ts +9 -0
- package/src/teach/nav.ts +7 -6
- package/src/teach/workspace.ts +2 -1
- package/src/teach/write-if-changed.ts +17 -0
- package/standards/architecture.md +13 -7
- package/standards/requirements.md +4 -0
- package/tooling/cloudflare/configs/.github/workflows/deploy.yml +2 -2
- package/tooling/cloudflare/reference.md +1 -1
- package/claude/skills/deploy-cloudflare/REQUIREMENT.md +0 -37
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ Each domain has a canonical source in this repo and a thin install or sync CLI o
|
|
|
68
68
|
| Standards | Authoring conventions for commits, branches, plans, tasks, and markdown | Opened by name, read by name with `canon standards <name>` |
|
|
69
69
|
| Tooling stacks | Golden configs, seeds, and a reference per framework | Laid down by `canon init`, reconciled by `canon tooling sync` |
|
|
70
70
|
| Design system | Ships a `DESIGN.md` token format, a skill that drafts one from an existing project or from scratch, and a render command | `canon design render` |
|
|
71
|
-
| Slides | A `SLIDES.md` source format with a layout catalog
|
|
71
|
+
| Slides | A `SLIDES.md` source format with a layout catalog, or a folder of HTML slides rebuilt as editable shapes | `canon slides render` writes PowerPoint |
|
|
72
72
|
| Teach | Lessons, sources, and a glossary in one workspace, rendered to a small site | `canon teach` |
|
|
73
73
|
| Canvas | Pages of HTML frames on a local pan and zoom surface, each frame drawn with the project's tokens | `canon canvas serve` |
|
|
74
74
|
| Transcripts | A YouTube transcript with metadata frontmatter | `canon transcripts <url>` writes it into any repo |
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deploy-app
|
|
3
|
+
description: What the deploy setup gap is on each host, which of the manual steps this skill closes, and why the credential and the custom domain stay the operator's
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deploy app requirement
|
|
7
|
+
|
|
8
|
+
## Gap
|
|
9
|
+
|
|
10
|
+
Without this skill, a deploy setup is a sequence performed from memory. Most of its repeated acts touch a service rather than the tree, so no workflow file can absorb them. One of those acts has already cost a broken deploy: the account ID displayed in the Cloudflare dashboard is truncated, and a value copied from there is wrong in a way that only shows up at deploy time. That is the Cloudflare instance of the general rule, that an ID comes from the host CLI's own session or files and never from a page a person copies out of.
|
|
11
|
+
|
|
12
|
+
The same sequence repeats on Vercel with different calls, so a skill per host would duplicate every guard and stop. Picking the wrong host is its own failure: a project mid-migration carries both hosts' files, and a silent pick creates a project on the account the operator was leaving.
|
|
13
|
+
|
|
14
|
+
## Must
|
|
15
|
+
|
|
16
|
+
- Pick the host from the signals the project already carries, and ask the operator when it carries none or both
|
|
17
|
+
- Create the host's project through its own CLI: `wrangler pages project create` or `vercel project add` and `vercel link`
|
|
18
|
+
- Fetch every account, org, or project ID from the host CLI's own session or files rather than have it typed or pasted
|
|
19
|
+
- Stop for the operator to issue the API token and run `gh secret set` for it themselves
|
|
20
|
+
- Verify every secret the host needs is present with `gh secret list` before continuing
|
|
21
|
+
- Stop for the operator at the custom domain step, since DNS sits outside the tree
|
|
22
|
+
- Call `wrangler`, `vercel`, and `gh` directly for every step those tools already cover
|
|
23
|
+
|
|
24
|
+
## Must not
|
|
25
|
+
|
|
26
|
+
- Accept an API token as input in any form. The toolkit verifies a secret exists, never what it contains.
|
|
27
|
+
- Take an ID from the operator rather than from the host CLI's own session or files
|
|
28
|
+
- Default to a host when the signals are absent or disagree
|
|
29
|
+
- Reimplement a host CLI call or `gh secret set` as a hand-built API call
|
|
30
|
+
- Seed a deploy workflow for Vercel, whose git integration already deploys on push
|
|
31
|
+
- Open a pull request or merge
|
|
32
|
+
|
|
33
|
+
## Guards
|
|
34
|
+
|
|
35
|
+
- `wrangler` or `vercel` not authenticated: stop and name the login command
|
|
36
|
+
- `gh` not authenticated: stop and name the login command
|
|
37
|
+
- A secret missing after the token stop: stop and name which one
|
|
38
|
+
|
|
39
|
+
## Out of scope
|
|
40
|
+
|
|
41
|
+
- Attaching a custom domain through the Cloudflare REST API instead of the dashboard. Measured absent from `wrangler pages` at plan time and the REST API was not read, so the dashboard stop stays for this pass.
|
|
42
|
+
- Running the deploy itself once secrets and the domain are set. On Cloudflare that is the seeded `deploy.yml` workflow, triggered by a push to main. On Vercel it is the git integration.
|
|
43
|
+
- A Vercel `tooling/` stack or a CI-gated Vercel deploy workflow.
|
|
44
|
+
- Reconfiguring an existing project's build settings on either host.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deploy-app
|
|
3
|
+
description: Sets up a deploy for the current project on Cloudflare Pages or Vercel by picking the host the project already carries, creating the host's project, setting the IDs it needs as repository secrets, and stopping for the operator to issue the API token and attach the custom domain. Calls `wrangler`, `vercel`, and `gh` rather than reimplementing them. Use when asked to "set up the deploy", "deploy this to Cloudflare Pages", "deploy this to Vercel", "set up the Pages project", "set up a Vercel project", or "connect this repo to Cloudflare". Do NOT use to run an already-configured deploy, which the host's own push trigger does, or to reconfigure an existing project's build settings.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deploy app
|
|
7
|
+
|
|
8
|
+
Runs the one-time setup a deploy needs before the host can publish on push, stopping for the acts only the operator can take. The procedure is the same on every host, and each host's CLI calls and stop points live in its own reference.
|
|
9
|
+
|
|
10
|
+
## Guards
|
|
11
|
+
|
|
12
|
+
- Never accept an API token as input, in a prompt, an argument, or a file. Verify a secret's presence with `gh secret list` rather than asking for or reading its content.
|
|
13
|
+
- Read every account, org, or project ID from the host CLI's own session or files, never from the operator, and pipe it into `gh secret set` rather than reporting it first.
|
|
14
|
+
- Never reimplement a host CLI call or `gh secret set` as a hand-built HTTP call. Call the tools directly.
|
|
15
|
+
- If the project name is not supplied, derive it from the repository's own name (`basename` of `git remote get-url origin`, stripped of a trailing `.git`) and confirm it in the preview rather than asking first.
|
|
16
|
+
|
|
17
|
+
## Step 1: pick the host
|
|
18
|
+
|
|
19
|
+
Read the tree for these signals before running any command. A file that exists counts as a signal even when it is empty or stale, since the host reference's own guard is what judges whether it still works.
|
|
20
|
+
|
|
21
|
+
| Signal | Host |
|
|
22
|
+
| ------------------------------------------------------------------------ | ----------------- |
|
|
23
|
+
| `wrangler.toml` or `wrangler.jsonc` at the root | cloudflare |
|
|
24
|
+
| a `pages deploy` line in any `.github/workflows/` file | cloudflare |
|
|
25
|
+
| `.vercel/project.json` | vercel |
|
|
26
|
+
| `vercel.json` at the root | vercel |
|
|
27
|
+
| a `wrangler` or `vercel` entry in `package.json` scripts or dependencies | the host it names |
|
|
28
|
+
|
|
29
|
+
- Signals for one host only: take that host.
|
|
30
|
+
- Signals for both, or for neither: ask the operator through the structured question surface, naming each signal found. Never default to a host, since a silent pick deploys to the wrong account.
|
|
31
|
+
- A host the operator named in the request wins over the signals. Say which signals disagreed with it, if any.
|
|
32
|
+
|
|
33
|
+
Report the pick on one line before the next step: `Host: <host>, from <signal>`.
|
|
34
|
+
|
|
35
|
+
## Step 2: check gh
|
|
36
|
+
|
|
37
|
+
If `gh auth status` fails, stop: `❌ gh is not authenticated. Run gh auth login, then re-invoke.`
|
|
38
|
+
|
|
39
|
+
## Step 3: run the host's steps
|
|
40
|
+
|
|
41
|
+
Read `${CLAUDE_SKILL_DIR}/references/<host>.md` for the host Step 1 picked, `cloudflare.md` or `vercel.md`, and run its steps in order, stopping wherever it stops. Each reference opens with its own CLI guard, creates the project, sets the IDs as secrets, stops for the token, verifies the secrets, stops for the custom domain, and hands off.
|
|
42
|
+
|
|
43
|
+
## Step 4: hand off
|
|
44
|
+
|
|
45
|
+
Report that setup is complete and name what deploys on the next push, in the words the host reference's handoff gives. Do not invoke `git-pr` or `git-ship` from here. The operator or the controlling session decides when to open that pull request.
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
description:
|
|
2
|
+
title: Cloudflare Pages
|
|
3
|
+
description: The wrangler and gh calls a Cloudflare Pages deploy needs, the two stops for the API token and the custom domain, and the handoff to the seeded deploy workflow
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Cloudflare Pages
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Run these steps once the body's Pick step resolves to Cloudflare.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Guard
|
|
11
11
|
|
|
12
12
|
- If `wrangler whoami` fails, stop: `❌ wrangler is not authenticated. Run wrangler login, then re-invoke.`
|
|
13
|
-
- If `gh auth status` fails, stop: `❌ gh is not authenticated. Run gh auth login, then re-invoke.`
|
|
14
|
-
- Never accept a Cloudflare API token as input, in a prompt, an argument, or a file. Verify a secret's presence with `gh secret list` rather than asking for or reading its content.
|
|
15
|
-
- Never reimplement `wrangler pages project create` or `gh secret set` as a hand-built HTTP call. Call the tools directly.
|
|
16
|
-
- If the project name is not supplied, derive it from the repository's own name (`basename` of `git remote get-url origin`, stripped of a trailing `.git`) and confirm it in the preview rather than asking first.
|
|
17
13
|
|
|
18
14
|
## Step 1: create the Pages project
|
|
19
15
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Vercel
|
|
3
|
+
description: The vercel and gh calls a Vercel deploy needs, the two stops for the token and the custom domain, and the handoff to the git integration that deploys on push
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Vercel
|
|
7
|
+
|
|
8
|
+
Run these steps once the body's Pick step resolves to Vercel. Read `vercel <command> --help` before each call, since flags move between CLI releases and the installed one is the authority.
|
|
9
|
+
|
|
10
|
+
## Guard
|
|
11
|
+
|
|
12
|
+
- If `vercel whoami` fails, stop: `❌ vercel is not authenticated. Run vercel login, then re-invoke.`
|
|
13
|
+
- If `command -v jq` fails, stop: `❌ jq is not installed. Install it, then re-invoke.` Step 3 pipes its output into a secret, and a missing reader would set that secret empty while `gh secret list` still lists its name.
|
|
14
|
+
|
|
15
|
+
## Step 1: create and link the project
|
|
16
|
+
|
|
17
|
+
Skip the `project add` when `.vercel/project.json` already exists, and run the link anyway. A file left from a deleted or renamed project still counts as a signal, so the link is what judges it, failing on a project that is gone and rewriting the file against the one that answers.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
vercel project add <project-name>
|
|
21
|
+
vercel link --yes --project <project-name>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The link writes `.vercel/project.json` and adds `.vercel` to `.gitignore`. Report the command's own failure output and stop rather than retrying on a name collision. A project already existing under that name is the operator's to resolve.
|
|
25
|
+
|
|
26
|
+
## Step 2: connect the repository
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
vercel git connect
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Vercel deploys on push only once the project is connected to the repository, so this call is what makes the handoff true. Report its failure output and stop, since a project with no connection publishes nothing on push.
|
|
33
|
+
|
|
34
|
+
## Step 3: set the org and project IDs
|
|
35
|
+
|
|
36
|
+
Read both IDs from `.vercel/project.json`, which the link just wrote, rather than asking the operator to copy them from the dashboard. Confirm the file carries both before setting anything, since `jq -r` prints `null` for a missing key and exits zero, and `gh secret list` in Step 5 reads names rather than values:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
jq -e '(.orgId | type == "string" and length > 0) and (.projectId | type == "string" and length > 0)' .vercel/project.json >/dev/null
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
On a non-zero exit, stop: `❌ .vercel/project.json carries no orgId or projectId. Delete it and re-invoke so the link writes it again.` Otherwise pipe each ID straight into its secret rather than printing it first:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
jq -r .orgId .vercel/project.json | gh secret set VERCEL_ORG_ID
|
|
46
|
+
jq -r .projectId .vercel/project.json | gh secret set VERCEL_PROJECT_ID
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
No workflow reads these today. They are what a GitHub Actions job calling `vercel` reads, so a project that later moves to CI-gated deploys finds them set.
|
|
50
|
+
|
|
51
|
+
## Step 4: stop for the token
|
|
52
|
+
|
|
53
|
+
Stop: `⏸ Create a Vercel token at the Vercel account settings, then run: gh secret set VERCEL_TOKEN. Confirm here once that's done.`
|
|
54
|
+
|
|
55
|
+
This is the one credential the skill never touches. Resume only once the operator confirms the token is set.
|
|
56
|
+
|
|
57
|
+
## Step 5: verify the secrets
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
gh secret list
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- `VERCEL_ORG_ID`, `VERCEL_PROJECT_ID`, and `VERCEL_TOKEN` present: continue.
|
|
64
|
+
- Any missing: stop and name which one, with the command that sets it.
|
|
65
|
+
|
|
66
|
+
## Step 6: stop for the custom domain
|
|
67
|
+
|
|
68
|
+
`vercel domains add <domain> <project-name>` attaches the domain to the project, and the DNS record that points the domain at Vercel is the operator's. Run the add only when the operator names the domain, then stop: `⏸ Point <domain>'s DNS at Vercel as vercel domains inspect <domain> describes, then confirm here.`
|
|
69
|
+
|
|
70
|
+
## Step 7: hand off
|
|
71
|
+
|
|
72
|
+
Report that setup is complete and that Vercel's git integration deploys every push, with production on the default branch and a preview on every other. No workflow is seeded for Vercel, since one would deploy each push a second time.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Architecture record
|
|
3
|
-
description: The
|
|
3
|
+
description: The findings canon context audit reads off the architecture record, being the entry cap, revisit sentence, length, decision word, and risk bullet gates the record states for itself, the claim coverage report, and the word counts, plus the requirements record's word cap
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Architecture record
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Seven findings from `canon context audit` read `canon/ARCHITECTURE.md` rather than a folder, and only the first five are facts. One more reads `canon/REQUIREMENTS.md`. The other checks the audit runs are in `context-audit-checks.md`.
|
|
9
9
|
|
|
10
10
|
The entry cap check counts the record's decisions against the cap it states for itself, in a clause of the form `This record holds at most 12 decisions.` Like the length check, the cap belongs to the record rather than to the toolkit, so a record stating none is measured and never gated. A decision is a `###` heading outside a fenced block, so the template a standard shows does not count, and a heading carrying two decisions counts once. The JSON record carries what it read as `architecture.entryCap`, absent where the record states no cap, and the audit catalog counts a record past it as `recordOverCount`.
|
|
11
11
|
|
|
@@ -17,12 +17,20 @@ A record stating no such rule is measured and reported and never gated, which is
|
|
|
17
17
|
|
|
18
18
|
What the derivation costs where it does apply is that the ceiling rises when a decision is added and falls for nothing, so the check passes exactly when the file grew. It gates anyway, because a record that states a limit for itself and makes it computable from a count has turned it into a fact, which is what separates it from every judgment the audit reports. A rewrite of the declaring sentence past the clauses the check reads falls back to reporting rather than to a stale ceiling, so the failure shows up in the run's own output.
|
|
19
19
|
|
|
20
|
+
The decision word check counts each decision's prose against the cap the record states in a clause of the form `at most 150 words a decision`. The count runs below the `###` heading with fenced lines dropped, so neither the heading nor a template block inside a decision counts, and the report and the gate read the same number. The JSON record carries the clause as `architecture.wordCap`, and the audit catalog counts the decisions past it as `recordOverWords`, absent where the record states no clause.
|
|
21
|
+
|
|
22
|
+
The risk bullet check counts every list item under `## Risks / open questions`, nested ones included, against a clause of the form `at most 6 risk bullets`, since a nested bullet is weight the reader pays for like any other. Both clauses can share the entry cap's sentence, and each gates only a record stating it, for the reason the cap does. A record stating a risk cap and carrying no Risks section is reported as absent rather than passed as zero bullets. The JSON record carries the clause as `architecture.riskCap` and the count as `architecture.risksBullets`, and the audit catalog counts a section past its cap as `recordOverRisks`, absent where the record states no clause.
|
|
23
|
+
|
|
20
24
|
The coverage report classifies each decision as carrying a countable claim, a structural invariant, or neither, then reports each testable entry against whether it names a check that exists. A countable claim carries a figure a run could recompute, and an invariant quantifies over a named tree closely enough that a walk could falsify it. A check is a `scripts/**.sh` path the entry spells that is on disk, or a `canon` invocation matching a registered audit, so coverage reads the entry rather than the tree and a claim some check happens to cover without the entry saying so reads as unchecked.
|
|
21
25
|
|
|
22
26
|
Three limits are stated on every run rather than hidden. The countable signal reads digits alone, so a measured claim written in words reads as uncounted. Entries are counted by heading, and one heading holding several decisions counts once. Nothing is stored, so an entry rewritten tomorrow is classified afresh the next time the verb runs and no verdict goes stale.
|
|
23
27
|
|
|
24
28
|
The report gates nothing. Deciding whether a sentence states a claim is a judgment no parser settles, so the output names candidates for a reader. This answers a different question from the verification anchors `standards/architecture.md` describes, which record that one cited number was re-read. That mechanism says whether a marked figure held, and this one says how much of the record could be checked at all.
|
|
25
29
|
|
|
26
|
-
The
|
|
30
|
+
The seventh finding is a word count for the whole record, measured in words rather than lines. A paragraph written one source line to a paragraph passes the rendered-line measure other checks use while still reading heavy, which is the gap a word count closes. The whole-record figure gates nothing under any mode, since the per-decision cap above already bounds what a decision adds.
|
|
31
|
+
|
|
32
|
+
The `## Risks / open questions` section is weighed the same way, reported apart from the whole-record figure. The JSON record carries the whole-record figure as `architecture.words`, the section figure as `architecture.risksWords` where the record carries the heading, and the per-decision figure as `architecture.decisions[].words`.
|
|
33
|
+
|
|
34
|
+
## The requirements record
|
|
27
35
|
|
|
28
|
-
The
|
|
36
|
+
The audit reads `canon/REQUIREMENTS.md` under its own step and counts every word below the frontmatter against the cap the record states in a clause of the form `This record holds at most 600 words.` The cap is the record's own, so a record stating none is measured and never gated, and a project with no record is reported as out of scope. The JSON record carries the reading as `requirements`, null where the project carries no record and absent under `--citations-only`, and the audit catalog counts a record past its cap as `requirementsOverWords`.
|
package/docs/agents/canvas.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Canvas
|
|
3
|
-
description: The canvas server, its content format, and the verbs that list, arrange, select, and capture frames
|
|
3
|
+
description: The canvas server, its content format, and the verbs that list, arrange, select, edit, and capture frames
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Canvas
|
|
@@ -29,6 +29,18 @@ The operator can also pick one element inside a frame, by clicking it on the sur
|
|
|
29
29
|
|
|
30
30
|
A pick is refused as `address-mismatch` when the browser builds elements the file never states, such as a `tbody` a table leaves out or the document wrapper around a fragment. The shell sends its own element count beside the index, so a frame the two sides count differently is reported rather than recorded against the wrong element. A pick is refused as `stale-address` when the frame file changed between the frame being served and the pick arriving, since every served frame carries the hash of its file and the shell sends that hash back.
|
|
31
31
|
|
|
32
|
+
## Edit
|
|
33
|
+
|
|
34
|
+
`canvas edit <page>/<frame> --element <index> --set <property>=<value>` sets one property of the element at that index, the same index `canvas selection` reports. The properties are `text`, `color`, `background-color`, `font-size`, `font-weight`, `width`, `height`, `padding`, `gap`, and `flex-direction`. A style property is written into the element's inline `style`, replacing that property and leaving the others as they were, and an empty value drops it.
|
|
35
|
+
|
|
36
|
+
`text` replaces the text of an element holding text alone and refuses one holding other elements as `not-text-only`. Every byte outside the element stays as it was. `--json` emits the page, frame, file, path, and the file's new `hash`.
|
|
37
|
+
|
|
38
|
+
The operator edits the same set from the inspector, which posts to the same writer with the hash the frame was served with. An edit made against a version of the file Claude has since rewritten is refused as `stale-address` and the shell reloads the frame, so nothing lands on a shifted element. A color picked from the project's tokens is written as `var(--<name>)`. Both writers take the frame file's lock, and an edit that keeps the selected element where it was keeps the selection fresh.
|
|
39
|
+
|
|
40
|
+
Inline style beats a class, so an operator's edit masks a class change Claude makes later. Read a frame's inline styles before restyling it, and remove one the change should replace. A property outside the set, or a value carrying `;`, `{`, `}`, `<`, `>`, or a line break, is refused as `invalid-edit`.
|
|
41
|
+
|
|
42
|
+
The Theme tab in the shell lists what the token stylesheet defines, grouped as color, spacing, radius, font family, font size, and other. The page record at `/api/pages` carries the same list as `tokens.groups`.
|
|
43
|
+
|
|
32
44
|
## Capture
|
|
33
45
|
|
|
34
46
|
`canvas capture <page>/<frame>` renders one frame as a PNG, and `canvas capture <page>` renders every frame on the page. Each goes through a server started for the call, so the injected tokens are in the image, and renders at the frame's own width from the layout, so a media query resolves there. It never captures the file on disk.
|
package/docs/agents/commands.md
CHANGED
|
@@ -34,7 +34,7 @@ One row per top-level command, in the order `canon --help` prints them. A domain
|
|
|
34
34
|
| `canon drive <url> <run>` | Walk a page through named interactions and measure each state |
|
|
35
35
|
| `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
|
|
36
36
|
| `canon teach <verb>` | Open and author learning workspaces |
|
|
37
|
-
| `canon canvas <verb>` | Serve a local canvas of pages and HTML frames, arrange
|
|
37
|
+
| `canon canvas <verb>` | Serve a local canvas of pages and HTML frames, arrange, edit, and capture them |
|
|
38
38
|
| `canon sandbox [cat:cmd]` | Run sandbox scenarios, toolkit-only like the tree it reads |
|
|
39
39
|
| `canon tasks <verb>` | Read and write the task board, the plans it cites, and its archive |
|
|
40
40
|
| `canon intake <verb>` | Report intake folders and write answers into them |
|
|
@@ -96,7 +96,7 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
|
|
|
96
96
|
- `demo`, in `demo.md`: `compile`, `run`, `frames`
|
|
97
97
|
- `inventory`, described above: `run`
|
|
98
98
|
- `teach`, in `teach.md`: `list`, `open`, `resource`, `glossary`, `lesson`, `stylesheet`, `nav`, `render`
|
|
99
|
-
- `canvas`, described below: `serve`, `list`, `page add`, `page rename`, `frame add`, `frame move`, `selection`, `capture`
|
|
99
|
+
- `canvas`, described below: `serve`, `list`, `page add`, `page rename`, `frame add`, `frame move`, `selection`, `edit`, `capture`
|
|
100
100
|
- `sandbox`, in `sandbox.md`: `reset`, `clean`, `check`, `coverage`
|
|
101
101
|
- `tasks`, in `tasks.md`: `next-label`, `archive`, `decline`, `plan-citations`, `plan-answers`, `plan-branch`, `plan-reach`, `plan-link`, `pull-request`, `outcome`, `list`, `validate`
|
|
102
102
|
- `intake`, in `intake.md`: `list`, `answer`
|
|
@@ -129,6 +129,7 @@ Subcommands no other page describes:
|
|
|
129
129
|
- `claude skills rank` scores either skill corpus's descriptions against a case corpus by TF-IDF cosine similarity, reporting rank-one and top-three (`--cases <path>`).
|
|
130
130
|
- `design regen` rewrites this repository's `canon/DESIGN.md` from `src/design/tokens.ts` and `src/design/base.css` from `src/design/neutral.ts`. `design css` emits the tokens and components as CSS on stdout, with `--no-components` for properties alone, and `design render` declares each vendored face the typography table names under `--embed-fonts`.
|
|
131
131
|
- `slides render` renders a `.claude/SLIDES.md` source into a PowerPoint deck and reports any unrecognized layout name on stderr, and `slides list --json` emits the layout catalog.
|
|
132
|
+
- `slides render --source <folder>` lays out each `.html` file in filename order in Chromium at 1280 by 720 and rebuilds it as editable shapes. Each element drawn as a picture prints one `✗` line on stderr naming the slide, the selector, and the properties that forced it. A link left out, being a `#slide-N` past the deck's end or any target that is neither that nor an absolute URL, prints one too, and the exit stays 0.
|
|
132
133
|
- `canvas`, with its verbs, content format, and write routes, is described in `canvas.md`.
|
|
133
134
|
- `labels scan` fails a pull request or a posted review whose title, body, or review comment carries a phase label, a label a code span quotes, a gitignored record path, a session link, a title word no dictionary holds, or a title breaking `standards/pr.md`'s format, casing, or length rule (`--event`, `--body-file`, `--json`).
|
|
134
135
|
- `repo metadata propose` compares a description, homepage, and topic set computed from the README and `package.json` against what the remote carries, writing nothing, and `repo metadata apply` writes an explicitly supplied set through `gh repo edit` (`--description`, `--homepage`, `--topics`).
|
|
@@ -65,7 +65,7 @@ Fenced blocks are excluded, which keeps a pinned version in an install command f
|
|
|
65
65
|
|
|
66
66
|
## The architecture record
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
Seven findings read `canon/ARCHITECTURE.md` rather than a folder: an entry cap, a revisit sentence closing each decision, a length ceiling, a word cap a decision, and a risk bullet cap, each gating only where the record states it for itself, a claim coverage report, and a word count. One more reads `canon/REQUIREMENTS.md` against the word cap it states. What each reads and why the last two architecture findings gate nothing are in `architecture-record.md`.
|
|
69
69
|
|
|
70
70
|
## Wireframe states
|
|
71
71
|
|
|
@@ -5,7 +5,7 @@ description: Running the audit, its flags and folder scope, the exit codes, the
|
|
|
5
5
|
|
|
6
6
|
# Context audit
|
|
7
7
|
|
|
8
|
-
`canon context audit [path]` reports the structural state of the folders following the index-plus-entry contract, meaning a generated `index.md` beside entries carrying frontmatter, and it measures `canon/ARCHITECTURE.md` beside them. It reads and reports. Fixing what it finds is separate work. What each finding means is in `context-audit-checks.md`, and what the architecture
|
|
8
|
+
`canon context audit [path]` reports the structural state of the folders following the index-plus-entry contract, meaning a generated `index.md` beside entries carrying frontmatter, and it measures `canon/ARCHITECTURE.md` beside them. It reads and reports. Fixing what it finds is separate work. What each finding means is in `context-audit-checks.md`, and what the architecture and requirements records' findings mean is in `architecture-record.md`.
|
|
9
9
|
|
|
10
10
|
Findings stated over every markdown file rather than over a context entry are measured by `canon markdown audit`, described in `markdown-audit.md`. That command resolves no folder, so it reaches trees this one refuses.
|
|
11
11
|
|
|
@@ -37,11 +37,11 @@ A run where no requested name resolves refuses, whichever list it read. Naming t
|
|
|
37
37
|
|
|
38
38
|
## Exit codes
|
|
39
39
|
|
|
40
|
-
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a gating finding. An unresolved citation gates under every mode. An architecture record that states its own line allowances gates when it is past the ceiling those derive, on any run that measures it, which is every mode except `--citations-only`. A record that states an entry cap gates the same way when it holds more decisions than the cap,
|
|
40
|
+
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a gating finding. An unresolved citation gates under every mode. An architecture record that states its own line allowances gates when it is past the ceiling those derive, on any run that measures it, which is every mode except `--citations-only`. A record that states an entry cap gates the same way when it holds more decisions than the cap, one stating that every decision closes with a revisit sentence gates when a decision lacks it, and one stating a word cap a decision or a risk bullet cap gates past either. A requirements record stating a word cap gates past it. A record stating none of these is reported and never gated.
|
|
41
41
|
|
|
42
|
-
`bun run check` in the toolkit repository reads
|
|
42
|
+
`bun run check` in the toolkit repository reads every one of these rules through its own Canonical records stage rather than through this verb, since the verb's only gating stage there runs `--citations-only` and never opens either record.
|
|
43
43
|
|
|
44
|
-
Entry length, reference form, table, provenance, the record's claim classification, and every word figure print and return `0` under every mode, because each is a judgment or a weight read alongside one, and failing a push on either would make the check something to route around.
|
|
44
|
+
Entry length, reference form, table, provenance, the record's claim classification, and every word figure no record caps print and return `0` under every mode, because each is a judgment or a weight read alongside one, and failing a push on either would make the check something to route around.
|
|
45
45
|
|
|
46
46
|
Required-section, index, and wireframe-states findings sit between the two. All three are answerable from the file rather than weighed, so `--gate` promotes them to failing codes while a bare run leaves them advisory. Sketch-with-evidence sits with the printed measures instead, for the reason `context-audit-checks.md` states: it reads a whole entry against whether any of its states has evidence, not one layout against its own evidence, so a conforming file can still trip it. The toolkit runs the bare form against itself and the widened form against the seed tree, described below.
|
|
47
47
|
|
package/docs/agents/index.md
CHANGED
|
@@ -8,9 +8,9 @@ category: Agent surface
|
|
|
8
8
|
|
|
9
9
|
CLI catalog and invocation rules for agents, split by command domain. Start with overview.
|
|
10
10
|
|
|
11
|
-
- [Architecture record](architecture-record.md): The
|
|
11
|
+
- [Architecture record](architecture-record.md): The findings canon context audit reads off the architecture record, being the entry cap, revisit sentence, length, decision word, and risk bullet gates the record states for itself, the claim coverage report, and the word counts, plus the requirements record's word cap
|
|
12
12
|
- [Audits](audits.md): Running every health check as one set, what the single verdict means, the exit code each outcome takes, the retained baseline and the delta it reports, and which corpora are kept out of the record
|
|
13
|
-
- [Canvas](canvas.md): The canvas server, its content format, and the verbs that list, arrange, select, and capture frames
|
|
13
|
+
- [Canvas](canvas.md): The canvas server, its content format, and the verbs that list, arrange, select, edit, and capture frames
|
|
14
14
|
- [Capture](capture.md): Rendering HTML sources to PNG, what the command asserts about fonts, and why the selector has no default
|
|
15
15
|
- [Census](census.md): Tracked-plus-untracked file count, a breakdown by extension, and a line total that skips whatever reads as binary
|
|
16
16
|
- [Command catalog](commands.md): Every top-level command with its purpose, each domain's subcommands and the page describing them, the browser commands, and the version skew report
|
package/docs/agents/teach.md
CHANGED
|
@@ -126,9 +126,15 @@ The link opens the named workspace's contents page, or the root listing when no
|
|
|
126
126
|
|
|
127
127
|
The JSON record is one line carrying the `nav` fields, then `served`, the absolute folder, and the server's `host`, `port`, `entry`, `url`, and `entryExists`. A lesson `nav` skips lands in `skipped` and the server still starts, since a partial refresh is still worth viewing. A `nav` refusal starts no server.
|
|
128
128
|
|
|
129
|
+
While it runs, `up` watches the teach root. A change there, such as a lesson, a workspace, a glossary term, or a reference page added by hand or by another `teach` verb, runs the `nav` pass again, and every open page reloads once that pass changes what is served. Passes run one at a time, so a burst of saves costs one more pass rather than several at once. A pass `nav` refuses, such as one over a workspace deleted while it was served, is reported on stderr, the server keeps running, and no page reloads.
|
|
130
|
+
|
|
131
|
+
Each refresh is logged to stderr, so stdout stays the one record line. Dotfiles and editor backups ending in `~` start no pass.
|
|
132
|
+
|
|
133
|
+
The reload script is added to each page as the server sends it and never written to disk, so a page saved from the browser, a promoted lesson, and a committed fixture carry no trace of it. The script listens on `/__live`, which answers only a request naming the server by its loopback address or `localhost`.
|
|
134
|
+
|
|
129
135
|
Viewing writes. Every page and generated stylesheet `nav` owns is rewritten first, which on a committed fixture shows as a diff. To serve without rewriting anything, `canon teach list <topic>` ends its human output with a `canon serve` line for that workspace, with the teach folder written relative to the cwd, or absolute when it sits outside it. The JSON record of `list` carries no such field.
|
|
130
136
|
|
|
131
|
-
`canon serve` itself stays general, since nothing about serving a directory is specific to a learning workspace and the same verb carries a slide render and a design preview. What `up` adds in front of it is the refresh,
|
|
137
|
+
`canon serve` itself stays general, since nothing about serving a directory is specific to a learning workspace and the same verb carries a slide render and a design preview. What `up` adds in front of it is the refresh and the watch that repeats it, both teach-specific, and it serves through the same server rather than a second one.
|
|
132
138
|
|
|
133
139
|
Read `url` off the record rather than building one from the port that was asked for. The verb walks forward past a port already in use, which is routine when a second workspace is already open, and the port it took is the one thing a composed URL gets wrong.
|
|
134
140
|
|
|
@@ -12,15 +12,15 @@ This page is the corpus the coverage claim is measured against: every name `cano
|
|
|
12
12
|
|
|
13
13
|
## Set up a project
|
|
14
14
|
|
|
15
|
-
| Skill
|
|
16
|
-
|
|
|
17
|
-
| `canon:target-setup`
|
|
18
|
-
| `canon:canon-operator`
|
|
19
|
-
| `canon:sketch-design`
|
|
20
|
-
| `canon:design-extract`
|
|
21
|
-
| `canon:draft-diagram`
|
|
22
|
-
| `canon:deploy-
|
|
23
|
-
| `canon:repo-metadata`
|
|
15
|
+
| Skill | When to use |
|
|
16
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `canon:target-setup` | On a fresh scaffold, to detect the stack and run the install chain, or to reach one phase of it alone |
|
|
18
|
+
| `canon:canon-operator` | On a project that already exists, to read what it carries before an install is picked |
|
|
19
|
+
| `canon:sketch-design` | Before `design-extract`'s greenfield path, to trace a design direction from reference images or URLs |
|
|
20
|
+
| `canon:design-extract` | Before the first UI feature, to draft `canon/DESIGN.md` |
|
|
21
|
+
| `canon:draft-diagram` | Once the architecture is written, to draft per-kind entries under `.canon/diagrams/` |
|
|
22
|
+
| `canon:deploy-app` | Once a project is ready to publish, to set up its Cloudflare or Vercel deploy and stop for token and domain |
|
|
23
|
+
| `canon:repo-metadata` | When the GitHub About text, homepage, or topics may have drifted, to reconcile them against the README |
|
|
24
24
|
|
|
25
25
|
## Decide what to build
|
|
26
26
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erclx/canon",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.19.0",
|
|
5
5
|
"description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"commander": "^13.1.0",
|
|
57
57
|
"execa": "^9.5.2",
|
|
58
58
|
"highlight.js": "^11.12.0",
|
|
59
|
+
"jszip": "^3.10.1",
|
|
59
60
|
"playwright-core": "1.59.1",
|
|
60
61
|
"pptxgenjs": "^4.0.1",
|
|
61
62
|
"preact": "^11.0.0"
|
package/src/audits/catalog.ts
CHANGED
|
@@ -173,10 +173,17 @@ function architectureCounts(
|
|
|
173
173
|
typeof record.entryCap === 'number' ? record.entryCap : undefined
|
|
174
174
|
|
|
175
175
|
const revisitRequired = record.revisitRequired === true
|
|
176
|
+
const wordCap =
|
|
177
|
+
typeof record.wordCap === 'number' ? record.wordCap : undefined
|
|
178
|
+
const riskCap =
|
|
179
|
+
typeof record.riskCap === 'number' ? record.riskCap : undefined
|
|
180
|
+
const risksBullets =
|
|
181
|
+
typeof record.risksBullets === 'number' ? record.risksBullets : undefined
|
|
176
182
|
|
|
177
183
|
let unverifiable = 0
|
|
178
184
|
let unchecked = 0
|
|
179
185
|
let lackingRevisit = 0
|
|
186
|
+
let overWords = 0
|
|
180
187
|
for (const raw of decisions) {
|
|
181
188
|
const entry = asObject(raw)
|
|
182
189
|
const claim = entry?.claim
|
|
@@ -186,6 +193,13 @@ function architectureCounts(
|
|
|
186
193
|
if (claim === 'neither') unverifiable += 1
|
|
187
194
|
else if (checks === 0) unchecked += 1
|
|
188
195
|
if (revisitRequired && entry?.revisit !== true) lackingRevisit += 1
|
|
196
|
+
if (
|
|
197
|
+
wordCap !== undefined &&
|
|
198
|
+
typeof entry?.words === 'number' &&
|
|
199
|
+
entry.words > wordCap
|
|
200
|
+
) {
|
|
201
|
+
overWords += 1
|
|
202
|
+
}
|
|
189
203
|
}
|
|
190
204
|
|
|
191
205
|
return {
|
|
@@ -201,11 +215,39 @@ function architectureCounts(
|
|
|
201
215
|
}),
|
|
202
216
|
// Absent on a record stating no revisit clause, for the same reason.
|
|
203
217
|
...(revisitRequired && { recordMissingRevisit: lackingRevisit }),
|
|
218
|
+
// Absent on a record stating no word cap or no bullet cap, for the same
|
|
219
|
+
// reason, and the bullet key absent too on a record with no Risks section.
|
|
220
|
+
...(wordCap !== undefined && { recordOverWords: overWords }),
|
|
221
|
+
...(riskCap !== undefined &&
|
|
222
|
+
risksBullets !== undefined && {
|
|
223
|
+
recordOverRisks: risksBullets > riskCap ? 1 : 0,
|
|
224
|
+
}),
|
|
204
225
|
recordUnverifiable: unverifiable,
|
|
205
226
|
recordUnchecked: unchecked,
|
|
206
227
|
}
|
|
207
228
|
}
|
|
208
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Reads the requirements record's one cap, or nothing when the record is
|
|
232
|
+
* absent, states no cap, or the run never opened it.
|
|
233
|
+
*
|
|
234
|
+
* The key is optional rather than required the way `architecture` is, so a
|
|
235
|
+
* record from a binary predating it still counts its other findings.
|
|
236
|
+
*/
|
|
237
|
+
function requirementsCounts(
|
|
238
|
+
root: Record<string, unknown>,
|
|
239
|
+
): Record<string, number> {
|
|
240
|
+
const record = asObject(root.requirements)
|
|
241
|
+
if (
|
|
242
|
+
record === undefined ||
|
|
243
|
+
typeof record.words !== 'number' ||
|
|
244
|
+
typeof record.wordCap !== 'number'
|
|
245
|
+
) {
|
|
246
|
+
return {}
|
|
247
|
+
}
|
|
248
|
+
return { requirementsOverWords: record.words > record.wordCap ? 1 : 0 }
|
|
249
|
+
}
|
|
250
|
+
|
|
209
251
|
/**
|
|
210
252
|
* The retained key for each verdict the sweep can return. A record rather than
|
|
211
253
|
* a list, so the compiler fails here when the classifier gains a verdict,
|
|
@@ -261,6 +303,7 @@ function contextCounts(record: unknown): Record<string, number> | undefined {
|
|
|
261
303
|
|
|
262
304
|
const architecture = architectureCounts(root)
|
|
263
305
|
if (architecture === undefined) return undefined
|
|
306
|
+
const requirements = requirementsCounts(root)
|
|
264
307
|
|
|
265
308
|
const counts = allOf({
|
|
266
309
|
unresolvedCitations: lengthOf(asObject(root.citations)?.unresolved),
|
|
@@ -271,7 +314,9 @@ function contextCounts(record: unknown): Record<string, number> | undefined {
|
|
|
271
314
|
})
|
|
272
315
|
|
|
273
316
|
if (counts === undefined) return undefined
|
|
274
|
-
return architecture === 'absent'
|
|
317
|
+
return architecture === 'absent'
|
|
318
|
+
? { ...counts, ...requirements }
|
|
319
|
+
: { ...counts, ...architecture, ...requirements }
|
|
275
320
|
}
|
|
276
321
|
|
|
277
322
|
function markdownCounts(record: unknown): Record<string, number> | undefined {
|