@erclx/canon 4.116.0 → 4.118.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/SKILL.md +1 -1
- package/claude/skills/canon-cli/SKILL.md +12 -12
- package/claude/skills/canon-operator/SKILL.md +1 -1
- package/claude/skills/create-skill/REQUIREMENT.md +3 -0
- package/claude/skills/create-skill/SKILL.md +3 -1
- package/claude/skills/docs-fold/SKILL.md +1 -1
- package/claude/skills/draft-ready/references/assembly.md +8 -0
- package/claude/skills/memory-review/SKILL.md +4 -4
- package/claude/skills/plan-feature/REQUIREMENT.md +1 -0
- package/claude/skills/plan-feature/SKILL.md +7 -0
- package/claude/skills/review-pr/REQUIREMENT.md +3 -0
- package/claude/skills/review-pr/SKILL.md +5 -3
- package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -0
- package/claude/skills/role-orchestrator/SKILL.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +14 -14
- package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +2 -2
- package/claude/skills/role-worker/REQUIREMENT.md +2 -0
- package/claude/skills/role-worker/SKILL.md +2 -2
- package/claude/skills/session-compact/REQUIREMENT.md +2 -1
- package/claude/skills/session-compact/SKILL.md +5 -1
- package/claude/skills/target-check/references/domains.md +1 -1
- package/claude/skills/write-human/references/source-material.md +2 -2
- package/docs/agents/commands.md +1 -1
- package/docs/agents/context-audit-checks.md +3 -29
- package/docs/agents/context-audit.md +1 -1
- package/docs/agents/install-and-sync.md +24 -18
- package/docs/agents/markdown-audit.md +7 -3
- package/docs/agents/skills-audit.md +8 -1
- package/docs/target-projects.md +2 -2
- package/docs/workflow/ai-workflow.md +9 -9
- package/docs/workflow/operating-model.md +1 -1
- package/docs/workflow/visual-design-workflow.md +1 -1
- package/governance/rules/claude/510-context.md +0 -12
- package/governance/rules/core/005-behavior.md +1 -2
- package/governance/rules/core/015-output.md +1 -3
- package/governance/rules/core/035-tasks.md +1 -3
- package/governance/rules/core/045-memory.md +0 -1
- package/governance/rules/core/055-scratch.md +2 -4
- package/governance/rules/core/070-planning.md +1 -4
- package/governance/rules/core/087-git.md +2 -2
- package/governance/rules/core/090-code-comments.md +1 -1
- package/governance/rules/ui/440-surface-capture.md +2 -0
- package/governance/rules/ui/460-design-taste.md +0 -15
- package/package.json +1 -1
- package/scripts/core/check-capability-seeding.sh +4 -2
- package/src/claude/cases/misc.ts +5 -0
- package/src/claude/skills-audit.ts +59 -2
- package/src/commands/claude.ts +30 -0
- package/src/commands/context.ts +11 -111
- package/src/commands/markdown.ts +63 -2
- package/src/commands/sync.ts +2 -2
- package/src/context/audit.ts +9 -153
- package/src/gate/measures.ts +97 -0
- package/src/gate/stages.ts +21 -2
- package/src/gov/adapter.ts +8 -6
- package/src/markdown/ceiling.ts +70 -0
- package/src/markdown/structure.ts +22 -0
- package/src/records/validate.ts +5 -1
- package/src/sync/check.ts +7 -1
- package/src/sync/engine.ts +90 -10
- package/src/sync/stamp.ts +44 -0
- package/src/tooling/inject.ts +13 -5
- package/src/tooling/manifest.ts +9 -0
- package/src/tooling/scan.ts +23 -6
- package/src/tooling/subfolder.ts +3 -3
- package/standards/context.md +2 -2
- package/standards/markdown.md +2 -0
- package/standards/plan.md +24 -1
- package/standards/skill.md +1 -0
- package/tooling/base/reference.md +2 -2
- package/tooling/web/configs/e2e/screenshot.ts +43 -7
- package/tooling/web/reference.md +1 -1
- package/src/context/narration.ts +0 -99
- /package/tooling/base/{configs → seeds}/.github/pull_request_template.md +0 -0
- /package/tooling/base/{configs → seeds}/.github/workflows/verify.yml +0 -0
|
@@ -43,7 +43,7 @@ Every entry past the checkpoint carries the three questions the standard asks of
|
|
|
43
43
|
|
|
44
44
|
Only the third is mechanical, and it is the provenance check already reported below, so the length finding cites that count rather than measuring it again. What it cites is a count of change markers, which is why an entry recording what its own runs cost answers `no` there while carrying dates throughout. An entry outside the governed folder reads open there too, since provenance is scoped to the standard stating it and a clean list there is a scan that never ran. The other two stay open beside every entry, because whether a domain is still one domain is a judgment about its subject and recognizing reproduced content needs a reader who knows what those commands emit. Omitting them would read as an entry nothing found rather than one nothing checked.
|
|
45
45
|
|
|
46
|
-
It reports and never gates, which the standard settles rather than the usual judgment-against-fact line:
|
|
46
|
+
It reports and never gates, which the standard settles rather than the usual judgment-against-fact line: the 150 checkpoint is diagnostic, so an entry that answers all three and is still long is a correct outcome. The one hard cap is the 300-line whole-document ceiling, which `canon markdown audit` and the Document ceiling gate stage read rather than this check. The JSON record carries the joined findings as `length`, each with a `causes` array in the standard's order, and the jurisdiction the join reads as `entries[].governed`. That flag is what parts a provenance list measured and empty from one never scanned, which the count alone cannot say. The join is published rather than left to a consumer, since deriving it means restating which question the provenance count answers.
|
|
47
47
|
|
|
48
48
|
Depth and bullet weight are quoted from `standards/markdown.md`, which states both over every markdown file rather than over a context entry, so `canon markdown audit` measures them and this command no longer does. They share `renderedHeight` with the length measure, since the two checkpoints sit in one section of that standard and a reader compares them. What the split costs is that a session wanting both numbers for one entry runs two commands, and what it buys is that either number can be had for a file in a folder this audit refuses to resolve.
|
|
49
49
|
|
|
@@ -63,32 +63,6 @@ A release label reports with or without its leading `v` at three segments, since
|
|
|
63
63
|
|
|
64
64
|
Fenced blocks are excluded, which keeps a pinned version in an install command from reading as a claim the entry makes. Frontmatter is excluded with them, since the content checks read the body alone, and that is what keeps a diagram entry's dated `verified` stamp a record of its last check rather than a marker to settle. Length is the exception, counting the whole file, so a reader applying the 150-rendered-line checkpoint against the body alone lands a few lines under what the tool reports.
|
|
65
65
|
|
|
66
|
-
## Narration
|
|
67
|
-
|
|
68
|
-
The narration check reports a bullet that states the design a sibling bullet replaced instead of rewriting it. `standards/context.md` asks for the rewrite because the subject is still live and two bullets on one subject leave a reader to work out which of them is current, and no other measure sees that shape.
|
|
69
|
-
|
|
70
|
-
It reads structure rather than words, which the corpus decided. Measured across the 39 entries this toolkit held the day it shipped, the terms carrying clean signal for a supersession are too rare to catch anything: `superseded` appears twice, `previously` three times, and `formerly`, `originally`, and `at first` never. The one term that would have caught the case a review caught by hand is `now`, which appears 57 times across 24 entries in correct present-tense prose. A list including it reports 57 lines to catch one, and a list excluding it reports nothing.
|
|
71
|
-
|
|
72
|
-
What it matches instead is a bullet doing three things at once: opening with a pronoun whose antecedent is the bullet above it, carrying a past-tense verb, and following another top-level bullet. All three are required. Eight bullets in the corpus open with a back-reference, and the verb set narrows those to one.
|
|
73
|
-
|
|
74
|
-
The pronoun is matched cased and anchored to the opening, since a mid-sentence `this` is a determiner rather than a reference back. The verb is matched uncased anywhere in the bullet, and rejected when a copula sits in front of it, since `is used to resolve the folder` is the passive of `use` rather than the past habitual the set means.
|
|
75
|
-
|
|
76
|
-
Both sets are published under `## Narration pronouns` and `## Narration verbs` in a governance rule and read at run time, so widening either costs a rule edit rather than a TypeScript change. The copula list stays in code, because it is English grammar rather than corpus vocabulary and a rule publishing two of three headings would be another absent state to carry. Discovery keys on the headings rather than the filename, because rules are numbered and a renumber would empty the sets while the check kept reporting clean. A run finding no rule that publishes both says it scanned nothing rather than reporting clean.
|
|
77
|
-
|
|
78
|
-
A blank line does not end the run. Markdown reads the bullets around one as a single loose list, so a walker that broke there would leave the shape reachable by anyone who spaced their bullets out. What ends a run is content that is neither a bullet nor indented under one, which is what keeps the first bullet under a heading from reading as a reply to the last bullet above it.
|
|
79
|
-
|
|
80
|
-
A fence answers that test for itself, because every scan here skips a fenced line before reaching it. An unindented fence ends the run, since CommonMark reads one at column zero as interrupting the list and the bullets around it are then two lists with no antecedent crossing between them. A fence indented under its bullet stays inside the item and leaves the run intact.
|
|
81
|
-
|
|
82
|
-
The opening delimiter decides that for the whole block rather than each line deciding for itself. A blank line inside an indented fence carries no indentation to read, and a content line may sit at column zero inside one because CommonMark strips the fence's own indent and nothing further. Reading either as unindented ends a run that should have continued, which costs findings rather than inventing them, so no corpus count moves when it is wrong.
|
|
83
|
-
|
|
84
|
-
Two fenced blocks with nothing between them are one contiguous run of fenced lines, so the mark alone cannot say where the first ends. The line walker reports which block each line sits in, and the boundary re-reads indentation whenever that number changes, so an unindented block written directly behind an indented one ends the run rather than inheriting the answer above it. No markdown file this repository tracks holds that shape, because a formatter inserts a blank line between two fences on contact, and a target running these commands over an unformatted tree still can.
|
|
85
|
-
|
|
86
|
-
Precision is the whole value, so recall is the accepted exposure, and two shapes are knowingly out of reach. A narration written as one bullet carrying its own before and after slips through, and nothing else sees it either. So does the perfect passive, since `has been superseded` narrates a supersession and the copula guard rejects it with the passives it exists for.
|
|
87
|
-
|
|
88
|
-
A rejected alternative is a back-reference in the past tense by construction, and the standard keeps what was tried and why it lost, so a legitimate hit exists and no measure separates it from a violation. The report states that on every run, which is why the finding names a line to read rather than a line to delete.
|
|
89
|
-
|
|
90
|
-
The JSON record carries the findings per entry as `entries[].narration` and the sets as `checkpoints.narration`, which is absent under `--citations-only` where the run never loads them and null where no rule publishes both.
|
|
91
|
-
|
|
92
66
|
## The architecture record
|
|
93
67
|
|
|
94
68
|
Four findings read `canon/ARCHITECTURE.md` rather than a folder, and only the first two are facts.
|
|
@@ -127,9 +101,9 @@ All three wireframe findings are printed under a bare run. The states-mismatch f
|
|
|
127
101
|
|
|
128
102
|
## Which folders each check reaches
|
|
129
103
|
|
|
130
|
-
The provenance
|
|
104
|
+
The provenance check covers `canon/context/` alone, the reference-form check covers the split folders inside it, the required-section check covers `canon/context/` and `canon/wireframes/`, length and the table finding reach every audited folder, and the wireframe states check covers `canon/wireframes/` alone.
|
|
131
105
|
|
|
132
|
-
What narrows the
|
|
106
|
+
What narrows the provenance check is stated in `standards/context.md`, which opens its scope by handing diagrams and wireframes to `diagrams.md` and `wireframes.md`, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere. The split is between kinds of rule rather than kinds of folder, and what decides it is which tier states the rule rather than what the check measures.
|
|
133
107
|
|
|
134
108
|
Length and the table finding generalize as judgments about how far a reader travels, so both reach wherever the audit is pointed. Required sections narrow for a plainer reason: each list is one standard's own, so it reaches the folder that standard governs. The context and wireframe standards each state a set, and the diagram standard states none.
|
|
135
109
|
|
|
@@ -41,7 +41,7 @@ Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a gating find
|
|
|
41
41
|
|
|
42
42
|
`bun run check` in the toolkit repository reads both limits through its own Architecture record stage rather than through this verb, since the verb's only gating stage there runs `--citations-only` and never opens the record.
|
|
43
43
|
|
|
44
|
-
Entry length, reference form, table, provenance,
|
|
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.
|
|
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
|
|
|
@@ -17,22 +17,26 @@ matched, or reported on, which is how project-authored rules survive. It also
|
|
|
17
17
|
removes a stale `.claude/GOV.md` from the retired build. Use
|
|
18
18
|
`canon gov install` to add rules.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
a
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
20
|
+
Everything under `.claude/rules/canon/` belongs to the toolkit, so a rule
|
|
21
|
+
there that the toolkit no longer ships is deleted on sync, edited or not. That
|
|
22
|
+
is how a retired or renamed rule stops loading in a target. The sync lists each
|
|
23
|
+
deletion by path before it applies, and the target's git diff is the record
|
|
24
|
+
afterwards. `canon sync --check` lists the same file as `retired` before any
|
|
25
|
+
sync runs. A project's own rules belong under `.claude/rules/project/`, where
|
|
26
|
+
`canon standards rule` reserves `900-999` for them, and a rule placed under
|
|
27
|
+
`canon/` instead is lost on the next sync.
|
|
28
|
+
|
|
29
|
+
A sync never deletes a rule that a newer canon release installed. The stamp
|
|
30
|
+
records the newest release that synced governance, and an older binary holds
|
|
31
|
+
every rule it finds no source for, naming both releases and asking for an
|
|
32
|
+
upgrade, rather than deleting what it cannot recognize.
|
|
33
|
+
|
|
34
|
+
A renamed rule loses its old file and gains nothing under the new name unless
|
|
35
|
+
the target's stamp records the stack it installed, in which case the new name
|
|
36
|
+
shows as `missing` and `canon gov install <stack>` adds it.
|
|
37
|
+
|
|
38
|
+
Design's own orphans are the target's overrides and are meant to stay where
|
|
39
|
+
they are, so `canon design sync` deletes nothing it finds no source for.
|
|
36
40
|
|
|
37
41
|
`canon design install` copies one toolkit-owned file to
|
|
38
42
|
`.claude/design/base.css` and creates no override. A project overrides a value
|
|
@@ -79,7 +83,7 @@ no copy to reconcile. `canon snippets list --json` carries the catalog and
|
|
|
79
83
|
|
|
80
84
|
`canon tooling diff <stack> [target]` is the read-only comparison, and a session asking what differs reaches for it by default. It writes nothing and exits 1 when anything differs and 0 when nothing does, so it gates CI. `--json` adds the scan record on stdout with `ok: true`, and the frame goes to stderr. A refusal such as an unknown stack, an excluded stack, or a toolkit-root target still exits 1 and answers `{ ok: false, reason, message }`, so a caller branches on the record and reads a typo'd stack name as a refusal rather than as drift.
|
|
81
85
|
|
|
82
|
-
The record carries a `withheld` array beside `configs`, each entry `{ rel, stack, present }`. It fills only when the target sits below its git root: every `.github/` config moves there rather than into `configs`, and none of them counts toward the exit, since GitHub never reads a workflow outside the root. `present` marks a copy an earlier sync already left in the subfolder, which the sync reports and never deletes. At a root, and for a target outside any repository, `withheld` is empty.
|
|
86
|
+
The record carries a `withheld` array beside `configs`, each entry `{ rel, stack, present }`. It fills only when the target sits below its git root: every `.github/` config or seed moves there rather than into `configs` or `seeds`, and none of them counts toward the exit, since GitHub never reads a workflow outside the root. `present` marks a copy an earlier sync already left in the subfolder, which the sync reports and never deletes. At a root, and for a target outside any repository, `withheld` is empty.
|
|
83
87
|
|
|
84
88
|
`canon tooling sync <stack> [target] --check` reports the same list and always exits 0. It stays for scripts that already call it. `sync` itself writes only under `--write`, and a headless run with neither flag reports and exits 1.
|
|
85
89
|
|
|
@@ -160,7 +164,9 @@ matches what the toolkit installed, `customized` when the project edited it,
|
|
|
160
164
|
`stranded` when it sits at a path the toolkit no longer installs to, `orphaned`
|
|
161
165
|
when the project authored it, or `drifted` when no stamp covers it. Governance
|
|
162
166
|
also reports `missing`, for a rule the target's recorded stack lists that its
|
|
163
|
-
tree does not hold at all
|
|
167
|
+
tree does not hold at all, and `retired`, for a rule under
|
|
168
|
+
`.claude/rules/canon/` the toolkit no longer ships, which the next sync
|
|
169
|
+
deletes. A `retired` file counts toward `--exit-code`, since one sync clears it.
|
|
164
170
|
|
|
165
171
|
Use `--json` for the machine-readable report and `--exit-code` to fail a CI job. Orphaned
|
|
166
172
|
and missing files are both excluded from that exit code: a project-authored
|
|
@@ -27,9 +27,9 @@ A bare run measures every markdown file git lists, tracked plus untracked-and-no
|
|
|
27
27
|
|
|
28
28
|
## Where the rules come from
|
|
29
29
|
|
|
30
|
-
The three ban sets and all
|
|
30
|
+
The three ban sets and all ten checkpoints ship with the `canon` package as data, in `src/markdown/bans.ts` and `src/markdown/structure.ts`. Every project is measured against the same sets whether or not it installed any standards, and no file has to resolve for a run to mean something.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Seven of the ten are stated in `markdown.md` and the three cadence numbers are stated in the `write-human` skill. That split is the content boundary rather than an accident: `markdown.md` carries the enforced rules a scan can decide, and the skill carries the rhythm rules a ban list cannot express. A cadence number moved in the skill and left in the code drifts the same way, so move both.
|
|
33
33
|
|
|
34
34
|
Reading them out of the standards per run was the original design. It put a parser contract on a document authored for people, and the standard had to carry a paragraph of its own warning an author that a one-word backticked example in a `- Do not use ` bullet would be lifted into a literal ban set and ban that word everywhere. A rule existing to protect a parser from the prose it parses is the argument for separating them.
|
|
35
35
|
|
|
@@ -128,9 +128,13 @@ Two of the rules `write-human` states are deliberately not implemented. A senten
|
|
|
128
128
|
|
|
129
129
|
The condition on that was something identifying a finite verb rather than guessing at one, and two parsers have now been run against it over 11,389 paragraph sentences. They disagree by a factor of four. `compromise` reports 2 percent and reads a fronted past participle as a finite verb, so `Measured at <sha> on <date>.` counts as carrying one. `wink-pos-tagger` reports 9 percent, fixes that class, and is still wrong on roughly three in four, because an imperative's verb tags as a proper noun and a noun-ambiguous predicate tags as a noun, which makes `Each maps to a skill.` read verbless. Separating those needs to know which token is the predicate, and that is syntax rather than a tag. The measure stays unimplemented, now against a mechanism rather than against the idea of one.
|
|
130
130
|
|
|
131
|
+
### Length
|
|
132
|
+
|
|
133
|
+
The Length step sums rendered lines over the whole source, frontmatter and fenced blocks included, and lists each document past the 300-line ceiling, longest first. A changelog and a document carrying a whole-line `<!-- canon-length-exempt: <reason> -->` outside a fence are counted in one line rather than listed. Each `--json` entry carries `renderedLines` and `exempt`, the stated reason or `null`, and `checkpoints` carries `ceiling`.
|
|
134
|
+
|
|
131
135
|
## Exit codes
|
|
132
136
|
|
|
133
|
-
Exit codes are `0` for a completed run with no gating finding, `1` for a refusal, `2` for a ban hit or a dead relative link, and `3` for a shipped ban set that arrived empty. A banned character, word, or spelling and a relative link resolving to nothing on disk each fail the run, both facts a scan settles rather than a reader judging. Bullet, paragraph, and depth weight are judgments a reader settles, and cadence is a distribution whose healthy range moves with the surface, so all four report under every code.
|
|
137
|
+
Exit codes are `0` for a completed run with no gating finding, `1` for a refusal, `2` for a ban hit or a dead relative link, and `3` for a shipped ban set that arrived empty. A banned character, word, or spelling and a relative link resolving to nothing on disk each fail the run, both facts a scan settles rather than a reader judging. Bullet, paragraph, and depth weight are judgments a reader settles, and cadence is a distribution whose healthy range moves with the surface, so all four report under every code. Length reports the same way, since the audit also runs on a plan kept out of version control, and the Document ceiling stage in `canon gate run` owns that verdict instead.
|
|
134
138
|
|
|
135
139
|
`3` is separate from `1` because the two want different responses from a caller. A refusal means no corpus was built, and the `Markdown bans` stage in `canon gate run` is right to report it as unmeasured rather than as a pass. An empty set means the corpus was walked and nothing was looked for, so that stage fails the push on `3` rather than skipping.
|
|
136
140
|
|
|
@@ -35,18 +35,25 @@ Each check traces to a line in the standard.
|
|
|
35
35
|
- No `README.md` inside a skill folder
|
|
36
36
|
- Folder name in kebab-case
|
|
37
37
|
- Each `REQUIREMENT.md` declaring `Gap` and `Must`, matched at any heading level
|
|
38
|
+
- No ISO date in `SKILL.md` or any file under `references/`, reported as `datedProvenance` in the JSON record's `findings`
|
|
38
39
|
|
|
39
40
|
A body whose frontmatter does not parse reports as declaring neither field rather than ending the run, so one malformed file cannot hide the corpus behind it. A key present with an empty value reads as absent, since a blank `name` would otherwise report as a name disagreeing with every folder. A folder carrying no requirement is reported once under presence rather than counted again for the sections it therefore lacks.
|
|
40
41
|
|
|
42
|
+
The date check reads prose alone. A date inside a fence or a code span is example data, such as a sample frontmatter block, and a date in the body's own frontmatter is metadata, so neither reports. `REQUIREMENT.md` and `EVAL.md` are not read, since the requirement's `Gap` is where the standard sends the incident and the date that earned a rule.
|
|
43
|
+
|
|
44
|
+
Each finding names the file and the line, such as `claude/skills/<name>/references/<file>.md line 12: <date>`. A date after "measured" or "verified" reports like any other, unlike the context audit, because a skill body states the rule a session follows today rather than a reading of the tree.
|
|
45
|
+
|
|
41
46
|
## What it leaves alone
|
|
42
47
|
|
|
43
48
|
The report names its own blind spots on every run, including the run where everything passed. Whether each `Must` traces to a stated gap is the rule in that standard worth the most and no parser reads it. Whether a gap states an observed failure rather than an intent, and whether a description routes, are the same kind of judgment. The 150-line body checkpoint is the one mechanical rule still absent here, and adding it would print a count rather than a defect, since the standard makes it a prompt to look with nothing enforcing it.
|
|
44
49
|
|
|
50
|
+
The date check reads ISO dates alone. A date written in words, a month with no day, and provenance carrying no date at all pass it.
|
|
51
|
+
|
|
45
52
|
A check with no rule behind it prints an opinion as a defect, which is where the list stops.
|
|
46
53
|
|
|
47
54
|
## Exit codes
|
|
48
55
|
|
|
49
|
-
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a skill folder carrying no `REQUIREMENT.md`. Only presence sets a failing code. Name, description, folder,
|
|
56
|
+
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a skill folder carrying no `REQUIREMENT.md`. Only presence sets a failing code. Name, description, folder, requirement-section, and dated-provenance findings print and return `0`, because each is a judgment a reader settles and failing a push on one would make the check something to route around.
|
|
50
57
|
|
|
51
58
|
## The requirement gate
|
|
52
59
|
|
package/docs/target-projects.md
CHANGED
|
@@ -118,7 +118,7 @@ The plugin corpus carries runtime behavior rather than reference prose alone, be
|
|
|
118
118
|
When a new need appears after scaffold, install the one domain without re-running `canon init`.
|
|
119
119
|
|
|
120
120
|
- Governance rule for a newly adopted library: invoke `canon:target-setup gov`, or run `canon gov install <stack> --add <rule> <path>`
|
|
121
|
-
- Project-specific rule the toolkit does not ship: invoke `canon:create-rule`. It scaffolds a rule into `.claude/rules/` with a non-colliding number, and `canon gov sync` leaves it untouched.
|
|
121
|
+
- Project-specific rule the toolkit does not ship: invoke `canon:create-rule`. It scaffolds a rule into `.claude/rules/project/` with a non-colliding number, and `canon gov sync` leaves it untouched. A rule placed under `.claude/rules/canon/` instead is deleted on the next sync, since that folder belongs to the toolkit.
|
|
122
122
|
- Index.md system for a markdown-heavy folder that emerged: invoke `canon:target-setup indexes`
|
|
123
123
|
|
|
124
124
|
Standards and snippets are not on that list, and there is nothing to add for either. Neither installs into a project, by default or by flag, so a session reads a standard with `canon standards <name>` and a snippet through its `@` reference off the live plugin symlink, both resolving against the toolkit rather than a project copy. A project holding a `.claude/standards/` or `.claude/snippets/` folder from an older toolkit is carrying a stale artifact nothing reads, and deleting it is safe.
|
|
@@ -225,7 +225,7 @@ The report opens by naming the binary running it. The installed version reads ag
|
|
|
225
225
|
|
|
226
226
|
#### Then the causes
|
|
227
227
|
|
|
228
|
-
A `stale` file still matches what the toolkit installed, so the update is mechanical. A `customized` file carries local edits, so taking the upstream version is a decision and `canon:seed-sync` is the tool for it. A `stranded` file sits where an older toolkit installed it and the toolkit has since moved, which is a relocation the report names but no command runs.
|
|
228
|
+
A `stale` file still matches what the toolkit installed, so the update is mechanical. A `customized` file carries local edits, so taking the upstream version is a decision and `canon:seed-sync` is the tool for it. A `stranded` file sits where an older toolkit installed it and the toolkit has since moved, which is a relocation the report names but no command runs. A `retired` rule is one under `.claude/rules/canon/` the toolkit no longer ships, and the next `canon gov sync` deletes it.
|
|
229
229
|
|
|
230
230
|
That attribution comes from `canon/config/config.json`, which every install and sync now writes, falling back to `.claude/canon/config.json` for a target stamped before that move. A target stamped before that path shipped is read from the retired `.claude/canon.json` instead, reported rather than migrated. Governance records a hash per installed file, plus the stack `canon gov install` was given, and tooling records the stack chain it resolved instead of any file hash, since its install runs no per-file walk to attribute.
|
|
231
231
|
|
|
@@ -273,15 +273,15 @@ This section is the corpus the coverage claim is measured against: every name `c
|
|
|
273
273
|
|
|
274
274
|
### Run several tracks at once
|
|
275
275
|
|
|
276
|
-
| Skill | When to use
|
|
277
|
-
| ------------------------- |
|
|
278
|
-
| `canon:role-orchestrator` | To assert the control session that owns the queue and reviews each worker's PR
|
|
279
|
-
| `canon:role-planner` | To assert the thinking role for a cold session writing a plan, a groundwork track, or an intake pass
|
|
280
|
-
| `canon:role-worker` | To assert the worker role for a cold session building one branch under one plan
|
|
281
|
-
| `canon:session-relay` | When this session owes another session a message, with or without a role
|
|
282
|
-
| `canon:session-resume` | At the start of a session, to pick up what a previous one left
|
|
283
|
-
| `canon:session-compact` | Before a compaction, to write a plain session's handoff note outside the board
|
|
284
|
-
| `canon:session-map` | At the close of an orchestrating session, to write its board-side handoff
|
|
276
|
+
| Skill | When to use |
|
|
277
|
+
| ------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
278
|
+
| `canon:role-orchestrator` | To assert the control session that owns the queue and reviews each worker's PR |
|
|
279
|
+
| `canon:role-planner` | To assert the thinking role for a cold session writing a plan, a groundwork track, or an intake pass |
|
|
280
|
+
| `canon:role-worker` | To assert the worker role for a cold session building one branch under one plan |
|
|
281
|
+
| `canon:session-relay` | When this session owes another session a message, with or without a role |
|
|
282
|
+
| `canon:session-resume` | At the start of a session, to pick up what a previous one left |
|
|
283
|
+
| `canon:session-compact` | Before a compaction or a move to another machine, to write a plain session's handoff note outside the board |
|
|
284
|
+
| `canon:session-map` | At the close of an orchestrating session, to write its board-side handoff |
|
|
285
285
|
|
|
286
286
|
### Keep the project current with the toolkit
|
|
287
287
|
|
|
@@ -263,4 +263,4 @@ port-collision detail.
|
|
|
263
263
|
|
|
264
264
|
- [Claude Code and git worktrees](../../wiki/claude/claude-worktrees.md) for the isolation and fan-out mechanics
|
|
265
265
|
- [Claude Code subagents](../../wiki/claude/claude-subagents.md) for in-session parallelism without worktrees
|
|
266
|
-
- `canon/context/claude-plugin/skill-strategy.md` for how the skills in the loop are categorized
|
|
266
|
+
- `canon/context/claude-plugin/skill-strategy/overview.md` for how the skills in the loop are categorized
|
|
@@ -150,7 +150,7 @@ Resist over-tiering early. Moving up is cheap because tiers stack. Moving down m
|
|
|
150
150
|
## References
|
|
151
151
|
|
|
152
152
|
- [Claude Design](../../wiki/claude/claude-design.md): first-party hosted design product and handoff bundle
|
|
153
|
-
- `canon/context/claude-plugin/skill-strategy.md`: how to decide between workflow and domain-knowledge skills
|
|
153
|
+
- `canon/context/claude-plugin/skill-strategy/overview.md`: how to decide between workflow and domain-knowledge skills
|
|
154
154
|
- [`microsoft/playwright-mcp`](https://github.com/microsoft/playwright-mcp): browser automation MCP used in tier 1 and tier 2
|
|
155
155
|
- [`ChromeDevTools/chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp): live frontend debugging MCP used in tier 1 and tier 2
|
|
156
156
|
- [`yctimlin/mcp_excalidraw`](https://github.com/yctimlin/mcp_excalidraw): community MCP server behind the tier 1 wireframe companion
|
|
@@ -12,18 +12,6 @@ paths:
|
|
|
12
12
|
- Before non-trivial work in a domain, read its `canon/context/<domain>.md` entry. Use `canon/context/index.md` to find it.
|
|
13
13
|
- Leave the entry conforming when work in its domain changes what it describes.
|
|
14
14
|
|
|
15
|
-
## Narration pronouns
|
|
16
|
-
|
|
17
|
-
Do not open a bullet with one of these where the antecedent is the bullet above it.
|
|
18
|
-
|
|
19
|
-
- `It`, `That`, `This`, `These`, `Those`, `They`
|
|
20
|
-
|
|
21
|
-
## Narration verbs
|
|
22
|
-
|
|
23
|
-
Do not put a sibling bullet's design in the past with one of these.
|
|
24
|
-
|
|
25
|
-
- `was`, `were`, `became`, `replaced`, `superseded`, `used to`
|
|
26
|
-
|
|
27
15
|
## Authority
|
|
28
16
|
|
|
29
17
|
- Follow the context standard for entry shape: frontmatter, sections, length, and what goes in. It is the single source. Read it with `canon standards context`.
|
|
@@ -13,8 +13,7 @@ description: Handle judgment calls, scope discipline, and file-editing mechanics
|
|
|
13
13
|
- When facing a judgment call with 2-3 reasonable options mid-flow, pick one and state the tradeoff in one sentence. Enumerate options only when the user's preference is the deciding factor.
|
|
14
14
|
- Put a call the user's preference decides through the structured question surface, such as `AskUserQuestion` in Claude Code, and fall back to a numbered list in one message where none exists. Rank the recommendation first and mark it, order the rest behind it, and give each option its cost, since an option with no stated cost is picked blind.
|
|
15
15
|
- Author the real choices only. A structured surface appends its own trailing escapes for a free-text answer and for reopening the question as conversation, so never write either as an option. On the numbered-list fallback, say that answering outside the list is fine.
|
|
16
|
-
- Answer from the artifact when one already put the question in writing with a suggestion, rather than re-asking it.
|
|
17
|
-
- Rewrite the `- Suggested:` line rather than the blank `- Answer:` slot when execution decides against an unanswered plan question's suggestion, per the plan standard, which your toolkit resolves by name.
|
|
16
|
+
- Answer from the artifact when one already put the question in writing with a suggestion, rather than re-asking it.
|
|
18
17
|
|
|
19
18
|
## Scope discipline
|
|
20
19
|
|
|
@@ -11,9 +11,7 @@ description: Report file paths correctly for the reading surface and group them
|
|
|
11
11
|
- When it reads `claude-desktop`, emit each path as a markdown link carrying the path as its text and an absolute `file://` URI as its target, resolving a relative path against the main project root to build that target. The desktop file tree hides dotted folders, so a bare path into one names a file the reader cannot reach.
|
|
12
12
|
- On every other value, including unset, emit the path bare. A terminal emulator makes it clickable through its own path detection, and link markup defeats that.
|
|
13
13
|
- Both forms govern a path emitted in a response. A path written into a markdown file follows the markdown standard instead, which your toolkit resolves by name, and which backticks a file reference and never repeats it as a link label.
|
|
14
|
-
-
|
|
15
|
-
- In the main worktree: relative from `pwd` works because `pwd` equals the editor root.
|
|
16
|
-
- In a linked worktree (under `.claude/worktrees/<name>/`): use absolute paths. Relative paths from worktree `pwd` would not resolve against the editor's project root. A `PostToolUse` hook computes this form after each write and hands it back as additional context where it is installed. Prefer that form when it arrives, and fall back to computing the absolute path yourself when it does not.
|
|
14
|
+
- In a linked worktree (under `.claude/worktrees/<name>/`), emit the absolute path, since the editor is rooted at the main project root and a path relative to the worktree resolves nowhere there. Prefer the form a `PostToolUse` hook hands back after a write where one is installed.
|
|
17
15
|
|
|
18
16
|
## Grouping multiple files
|
|
19
17
|
|
|
@@ -6,8 +6,6 @@ description: Scope, size, and link a task file correctly
|
|
|
6
6
|
|
|
7
7
|
## Task files
|
|
8
8
|
|
|
9
|
-
- `.canon/tasks/` is gitignored local session scratch, one file per task. Edit freely. No staging or revert before commits.
|
|
10
9
|
- Only create a task for work that spans multiple sessions or has real dependencies. Handle small edits immediately without a task entry.
|
|
11
10
|
- Do not add tasks retroactively for work already completed. Completed work is visible in git.
|
|
12
|
-
- When a task needs execution detail beyond its own file,
|
|
13
|
-
- Write the plan in the same session as the task file. The session that executes the plan later inherits reasoning context it would otherwise have to re-derive.
|
|
11
|
+
- When a task needs execution detail beyond its own file, write a plan in `.canon/plans/` in the same session as the task and link to it from the task's intro paragraph. The session that executes the plan later inherits reasoning context it would otherwise have to re-derive.
|
|
@@ -9,4 +9,3 @@ description: Keep memory writes scoped to .canon/memory/ and out of context-owne
|
|
|
9
9
|
- Write all memory files to `.canon/memory/`, not `~/.claude/projects/`
|
|
10
10
|
- A fact about a domain goes to that domain's `canon/context/` entry, not to memory. `canon:memory-capture` routes it there and `canon:docs-fold` folds it in. Memory keeps only what no context entry owns. Report it rather than proceeding silently when either skill does not resolve. Both ship with the plugin and this rule ships with the CLI, so a project that installed governance alone does not have them.
|
|
11
11
|
- Never delete a memory entry. Retire one by moving it to `.canon/memory/archive/`, which `canon records push` backs. A bulk retire runs through the shell, where no file edit fires a path-scoped rule.
|
|
12
|
-
- Follow the memory standard for the filename and type prefix, the frontmatter, the body shape each type carries, and the lifecycle. Read it with `canon standards memory`. Check every entry in the pen against that standard and fix what breaks it, since nothing keeps the folder conforming on its own.
|
|
@@ -6,7 +6,5 @@ description: Write temporary files to the scratch folder structure
|
|
|
6
6
|
|
|
7
7
|
## Temporary files
|
|
8
8
|
|
|
9
|
-
- Write temporary files to `.canon/tmp/<slug>/<file>.md`
|
|
10
|
-
- Write to `.claude/.tmp/<slug>/` instead in a project that carries no `.canon/` root,
|
|
11
|
-
- "The project root" above means the current worktree by default. `085-worktrees.md` states the same mixed default for `.canon/tmp/` and defers to each skill's own body for which write resolves at the main root instead.
|
|
12
|
-
- `runs/`, `hooks/`, `handoff/`, `pr/`, and `render/` are reserved under `tmp/` for build and command output that can be regenerated, hook state, skill-to-skill handoff, pull request scratch, and render output. A session slug must not take one of these five names.
|
|
9
|
+
- Write temporary files to `.canon/tmp/<slug>/<file>.md` at the current worktree's root, a nested folder with a kebab-slug tied to the topic rather than a flat `<slug>-<file>.md`. The slug must not take one of the five reserved names `runs`, `hooks`, `handoff`, `pr`, and `render`. A skill that resolves a write at the main root instead states so in its own body.
|
|
10
|
+
- Write to `.claude/.tmp/<slug>/` instead in a project that carries no `.canon/` root, and `canon migrate records` moves it to the first spelling. <!-- canon-keep-record-root -->
|
|
@@ -6,14 +6,11 @@ description: Enforce planning standards before implementation
|
|
|
6
6
|
|
|
7
7
|
## Planning
|
|
8
8
|
|
|
9
|
-
-
|
|
9
|
+
- Confirm a numbered implementation plan before a change whose diff cannot be described in one sentence.
|
|
10
10
|
- Challenge ambiguous or over-engineered requests before implementation.
|
|
11
11
|
- Search the project, its dependencies, and the standard library for an existing implementation before writing new code.
|
|
12
12
|
- State where the search ran and why each candidate was rejected. Do not assert a search without naming its results.
|
|
13
13
|
- Propose the simplest solution that satisfies the requirement before implementing complex patterns.
|
|
14
14
|
- Write or update tests as part of every implementation plan.
|
|
15
15
|
- Write the test for a behavior before the code that implements it. Confirm visual output after implementing it, not before.
|
|
16
|
-
- Load the `canon:test-first` skill before writing the implementation for a behavior whose test does not exist yet, and report it rather than proceeding silently when the skill does not resolve.
|
|
17
16
|
- Run `canon gov test-order` before shipping a branch. Fix what it names as reaching history ahead of its test.
|
|
18
|
-
- Load the `canon:systematic-debugging` skill before proposing a fix for a failing test, a surfaced bug, or behavior nobody has explained yet, and report it rather than proceeding silently when the skill does not resolve.
|
|
19
|
-
- Do not modify code without a confirmed plan.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Route every git operation through the canon git skills,
|
|
2
|
+
description: Route every git operation through the canon git skills, which choose the surface and never authorize the operation
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Git standards
|
|
@@ -12,4 +12,4 @@ description: Route every git operation through the canon git skills, and run one
|
|
|
12
12
|
|
|
13
13
|
## Whether to act
|
|
14
14
|
|
|
15
|
-
-
|
|
15
|
+
- The bullets above decide which surface performs an operation already decided, never whether to take one.
|
|
@@ -44,5 +44,7 @@ paths:
|
|
|
44
44
|
- Commit a flagged case's evidence output so the pull request carries the comparison, rather than attaching it by hand.
|
|
45
45
|
- A project whose own established convention already commits that same comparison may route a flagged case's evidence there instead of a separate evidence folder. Only an established convention already committing the same comparison qualifies, not a preference for skipping a second folder.
|
|
46
46
|
- Commit an evidence case for the first time only after running the capture twice with no code change between the runs and confirming the two outputs are byte-identical.
|
|
47
|
+
- Mask the element carrying a value that moves between runs, such as an elapsed-time counter, a clock, or a relative date, in the state's capture case before the double capture, so the check still runs on everything else in the frame. Mask the smallest element holding the value, never the state.
|
|
48
|
+
- Do not commit the state as evidence when the masked outputs still differ, which happens when the value's width reflows its neighbors. Name the variance in the pull request instead, and never re-run toward a match.
|
|
47
49
|
- Recommitting an unbounded sweep on every run reaches a gigabyte of repository history inside a hundred merges. A small, committed evidence set is what a reviewer needs and what lets GitHub draw its own before-and-after comparison on the pull request.
|
|
48
50
|
- `git-pr` and `git-followup` maintain a comparison comment on the pull request automatically once a case is committed, so a reviewer never has to open Files Changed to see it.
|
|
@@ -11,19 +11,4 @@ paths:
|
|
|
11
11
|
## Authority
|
|
12
12
|
|
|
13
13
|
- Load the `canon:design-taste` skill for which design decision settles first, what must stay constant across a surface, and the defaults to reach past. It is the single source for all three.
|
|
14
|
-
- Load it before drafting, not after revising. A revision recovers a color and never recovers the composition a draft already settled.
|
|
15
14
|
- Report it rather than proceeding silently when the skill does not resolve. It ships with the plugin and this rule ships with the CLI, so a project that installed governance alone does not have it.
|
|
16
|
-
- Do not work the layer ordering or the coherence locks from memory.
|
|
17
|
-
|
|
18
|
-
## When this fires and when it does not
|
|
19
|
-
|
|
20
|
-
- This rule scopes to the files where an undecided visual choice lands, which is the stylesheet and the design document. Building a surface somebody already decided is implementation rather than design, and the rules below carry what implementation owes.
|
|
21
|
-
- Load the skill by name when a component edit makes a visual choice nobody has taken yet, such as a new section's shape, a spacing relationship, or a radius. The glob cannot see that intent and no glob can.
|
|
22
|
-
- Do not load it to ship a decided design. A worker wiring a picked composition pays the read and gets nothing back.
|
|
23
|
-
|
|
24
|
-
## Boundaries
|
|
25
|
-
|
|
26
|
-
- Accessibility, keyboard interaction and ARIA are a separate topic. `410-a11y` routes them, on every rendered path.
|
|
27
|
-
- States, empty and loading coverage, and destructive confirmation are a separate topic. `430-ux-completeness` routes them.
|
|
28
|
-
- Rendered copy casing is a separate topic. `465-interface-casing` routes it. Button labels and error wording are separate too, and `400-ui` routes them.
|
|
29
|
-
- The floor those three carry is not restated in the skill, and it is not traded against a preference where the two collide.
|
package/package.json
CHANGED
|
@@ -60,7 +60,8 @@ check_capability "Hooks" "$PROJECT_ROOT/.claude/hooks" \
|
|
|
60
60
|
"$PROJECT_ROOT"/tooling/claude/seeds/.claude/hooks/*
|
|
61
61
|
|
|
62
62
|
check_capability "Workflows" "$PROJECT_ROOT/.github/workflows" \
|
|
63
|
-
"$PROJECT_ROOT"/tooling/*/configs/.github/workflows/*
|
|
63
|
+
"$PROJECT_ROOT"/tooling/*/configs/.github/workflows/* \
|
|
64
|
+
"$PROJECT_ROOT"/tooling/*/seeds/.github/workflows/*
|
|
64
65
|
|
|
65
66
|
check_capability "Husky" "$PROJECT_ROOT/.husky" \
|
|
66
67
|
"$PROJECT_ROOT"/tooling/base/configs/.husky/*
|
|
@@ -90,7 +91,8 @@ check_orphans() {
|
|
|
90
91
|
check_orphans "Hooks" "$PROJECT_ROOT/.claude/hooks" \
|
|
91
92
|
"$PROJECT_ROOT/tooling/claude/seeds/.claude/hooks"
|
|
92
93
|
|
|
93
|
-
for dir in "$PROJECT_ROOT"/tooling/*/configs/.github/workflows
|
|
94
|
+
for dir in "$PROJECT_ROOT"/tooling/*/configs/.github/workflows \
|
|
95
|
+
"$PROJECT_ROOT"/tooling/*/seeds/.github/workflows; do
|
|
94
96
|
check_orphans "Workflows" "$PROJECT_ROOT/.github/workflows" "$dir"
|
|
95
97
|
done
|
|
96
98
|
|
package/src/claude/cases/misc.ts
CHANGED
|
@@ -36,6 +36,11 @@ export const MISC_CASES: readonly SkillCase[] = [
|
|
|
36
36
|
"We're about to hit the context limit, write the handoff before we lose state.",
|
|
37
37
|
expect: 'session-compact',
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
prompt:
|
|
41
|
+
"I'm moving this session to another machine, write the handoff before I pack it up.",
|
|
42
|
+
expect: 'session-compact',
|
|
43
|
+
},
|
|
39
44
|
{
|
|
40
45
|
prompt:
|
|
41
46
|
'I am the orchestrating session and about to compact, so write the board handoff as a session map on the task board.',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
3
|
import { parseFrontmatter, readField } from '@/indexes/frontmatter'
|
|
4
|
+
import { bodyLines, maskDisplayed } from '@/markdown/scan'
|
|
4
5
|
|
|
5
6
|
/** Returned when a skill folder carries no `REQUIREMENT.md`, the gating check. */
|
|
6
7
|
export const EXIT_MISSING_REQUIREMENT = 2
|
|
@@ -33,6 +34,14 @@ const KEBAB_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
|
|
33
34
|
|
|
34
35
|
const HEADING = /^#{1,6}\s+(.+?)\s*$/
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* An ISO date, read as provenance wherever a reader is told it. The context
|
|
39
|
+
* audit's detector clears a date after "measured" or "verified", and a skill
|
|
40
|
+
* body keeps no such carve-out, since the standard sends every incident and its
|
|
41
|
+
* date to the requirement's Gap or to git.
|
|
42
|
+
*/
|
|
43
|
+
const ISO_DATE = /\b\d{4}-\d{2}-\d{2}\b/g
|
|
44
|
+
|
|
36
45
|
export interface SkillFinding {
|
|
37
46
|
readonly rel: string
|
|
38
47
|
readonly detail: string
|
|
@@ -59,6 +68,7 @@ export interface SkillsAudit {
|
|
|
59
68
|
readonly nameMismatch: readonly SkillFinding[]
|
|
60
69
|
readonly longDescription: readonly SkillFinding[]
|
|
61
70
|
readonly requirementSections: readonly SkillFinding[]
|
|
71
|
+
readonly datedProvenance: readonly SkillFinding[]
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
interface SkillSource {
|
|
@@ -69,6 +79,17 @@ interface SkillSource {
|
|
|
69
79
|
readonly description: string | undefined
|
|
70
80
|
/** Undefined when the folder carries no `REQUIREMENT.md` at all. */
|
|
71
81
|
readonly requirementHeadings: readonly string[] | undefined
|
|
82
|
+
/**
|
|
83
|
+
* `SKILL.md` and every reference below the folder, keyed by the path a
|
|
84
|
+
* finding names. `REQUIREMENT.md` and `EVAL.md` are left out, since the
|
|
85
|
+
* requirement is where the standard sends a date.
|
|
86
|
+
*/
|
|
87
|
+
readonly bodies: readonly SkillText[]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface SkillText {
|
|
91
|
+
readonly rel: string
|
|
92
|
+
readonly text: string
|
|
72
93
|
}
|
|
73
94
|
|
|
74
95
|
/**
|
|
@@ -110,6 +131,9 @@ export async function auditSkills(root: string): Promise<SkillsAudit> {
|
|
|
110
131
|
nameMismatch: sources.flatMap(nameFindings),
|
|
111
132
|
longDescription: sources.flatMap(lengthFindings),
|
|
112
133
|
requirementSections: sources.flatMap(sectionFindings),
|
|
134
|
+
datedProvenance: sources.flatMap((source) =>
|
|
135
|
+
source.bodies.flatMap(dateFindings),
|
|
136
|
+
),
|
|
113
137
|
}
|
|
114
138
|
}
|
|
115
139
|
|
|
@@ -138,23 +162,41 @@ async function readSkill(
|
|
|
138
162
|
const skillDir = join(dir, folder)
|
|
139
163
|
const requirementPath = join(skillDir, 'REQUIREMENT.md')
|
|
140
164
|
|
|
141
|
-
const
|
|
165
|
+
const references = [
|
|
166
|
+
...new Bun.Glob('references/**/*.md').scanSync({
|
|
167
|
+
cwd: skillDir,
|
|
168
|
+
onlyFiles: true,
|
|
169
|
+
}),
|
|
170
|
+
].sort()
|
|
171
|
+
|
|
172
|
+
const [body, requirement, referenceTexts] = await Promise.all([
|
|
142
173
|
Bun.file(join(skillDir, 'SKILL.md')).text(),
|
|
143
174
|
existsSync(requirementPath)
|
|
144
175
|
? Bun.file(requirementPath).text()
|
|
145
176
|
: Promise.resolve(undefined),
|
|
177
|
+
Promise.all(
|
|
178
|
+
references.map((path) => Bun.file(join(skillDir, path)).text()),
|
|
179
|
+
),
|
|
146
180
|
])
|
|
147
181
|
|
|
148
182
|
const fields = parseFrontmatter(body)
|
|
183
|
+
const skillRel = join(rel, folder)
|
|
149
184
|
|
|
150
185
|
return {
|
|
151
|
-
rel:
|
|
186
|
+
rel: skillRel,
|
|
152
187
|
folder,
|
|
153
188
|
hasReadme: existsSync(join(skillDir, 'README.md')),
|
|
154
189
|
name: declared(readField(fields, 'name')),
|
|
155
190
|
description: declared(readField(fields, 'description')),
|
|
156
191
|
requirementHeadings:
|
|
157
192
|
requirement === undefined ? undefined : headings(requirement),
|
|
193
|
+
bodies: [
|
|
194
|
+
{ rel: join(skillRel, 'SKILL.md'), text: body },
|
|
195
|
+
...references.map((path, index) => ({
|
|
196
|
+
rel: join(skillRel, path),
|
|
197
|
+
text: referenceTexts[index] ?? '',
|
|
198
|
+
})),
|
|
199
|
+
],
|
|
158
200
|
}
|
|
159
201
|
}
|
|
160
202
|
|
|
@@ -209,3 +251,18 @@ function sectionFindings(source: SkillSource): SkillFinding[] {
|
|
|
209
251
|
},
|
|
210
252
|
]
|
|
211
253
|
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* A date inside a fence or a code span is example data a reader is shown, such
|
|
257
|
+
* as a sample frontmatter block, so only prose outside both is read.
|
|
258
|
+
*/
|
|
259
|
+
function dateFindings(source: SkillText): SkillFinding[] {
|
|
260
|
+
return bodyLines(source.text)
|
|
261
|
+
.filter((line) => !line.fenced)
|
|
262
|
+
.flatMap((line) =>
|
|
263
|
+
[...maskDisplayed(line.text).matchAll(ISO_DATE)].map((match) => ({
|
|
264
|
+
rel: source.rel,
|
|
265
|
+
detail: `line ${line.number}: ${match[0]}`,
|
|
266
|
+
})),
|
|
267
|
+
)
|
|
268
|
+
}
|