@erclx/canon 5.20.0 → 5.21.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/references/verb-absent.md +1 -1
- package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
- package/claude/skills/document-health/REQUIREMENT.md +1 -1
- package/claude/skills/draft-ready/REQUIREMENT.md +2 -1
- package/claude/skills/draft-ready/SKILL.md +6 -4
- package/claude/skills/markdown-propose/REQUIREMENT.md +3 -3
- package/claude/skills/markdown-propose/SKILL.md +1 -1
- package/docs/agents/commands.md +1 -0
- package/docs/agents/index.md +1 -0
- package/docs/agents/markdown-audit.md +1 -3
- package/docs/agents/slides.md +120 -0
- package/docs/agents/tasks-validate.md +1 -1
- package/docs/agents/tasks.md +1 -1
- package/docs/target/migrations.md +1 -1
- package/docs/workflow/ai-workflow.md +1 -1
- package/docs/workflow/skill-map.md +11 -13
- package/governance/rules/lib/330-testing-py.md +1 -1
- package/governance/rules/lib/336-testing-php.md +1 -1
- package/package.json +1 -1
- package/src/claude/cases/workflow.ts +4 -20
- package/src/commands/slides.ts +13 -3
- package/src/commands/tasks.ts +46 -2
- package/src/migrate/skill-names.ts +0 -7
- package/src/slides/convert/chart.ts +165 -0
- package/src/slides/convert/deck.ts +164 -0
- package/src/slides/convert/export.ts +217 -7
- package/src/slides/convert/master.ts +195 -0
- package/src/slides/convert/shapes.ts +1 -1
- package/src/slides/convert/walk.ts +187 -2
- package/src/tasks/record.ts +11 -1
- package/src/tasks/validate.ts +14 -0
- package/standards/tasks.md +1 -1
- package/tooling/astro/configs/card-src/pages/og-card.astro +2 -2
- package/claude/skills/docs-fold/REQUIREMENT.md +0 -25
- package/claude/skills/docs-fold/SKILL.md +0 -9
- package/claude/skills/standards-audit/REQUIREMENT.md +0 -33
- package/claude/skills/standards-audit/SKILL.md +0 -99
|
@@ -29,7 +29,7 @@ Behavior paths carry two spellings, the one a surface authors at and the one it
|
|
|
29
29
|
|
|
30
30
|
Markdown under one of them states what an agent does, so a change there is a behavior change wearing a prose extension. Everything outside them is informational, which keeps `docs/`, `README.md`, and `CHANGELOG.md` skipping without naming them. One behavior file sends the whole branch to review, since documentation shipped beside a behavior change does not cancel it.
|
|
31
31
|
|
|
32
|
-
Informational prose is already gated by `docs-sync`, `
|
|
32
|
+
Informational prose is already gated by `docs-sync`, `canon markdown audit`, and pre-push hooks. Running a code-style review on it burns tokens with no signal.
|
|
33
33
|
|
|
34
34
|
The list covers this toolkit's authoring layout and the layout it installs, which is not every layout. A project keeping executable prose where neither spelling reaches adds the path, and until it does every branch touching it skips review silently.
|
|
35
35
|
|
|
@@ -38,4 +38,4 @@ The quiet one is the baseline. A diff resolved against a bare local ref equals H
|
|
|
38
38
|
- The context entries, requirements, architecture, wireframes, and task board: `context-fold`, which runs immediately before this skill in the ship chain and resolves the same baseline. The split is by audience, so a file's location decides which skill owns it rather than its subject.
|
|
39
39
|
- `CLAUDE.md` and the installed seed docs: `seed-sync`, which reconciles them per section against the toolkit source
|
|
40
40
|
- Changelog entries, which release tooling generates from commit messages
|
|
41
|
-
- Whether the prose conforms to its standards. `
|
|
41
|
+
- Whether the prose conforms to its standards. `canon markdown audit` reports violations and fixes none, and this skill writes prose it does not audit.
|
|
@@ -40,7 +40,7 @@ The third is silence on an unread axis. Both measured verbs resolve their folder
|
|
|
40
40
|
## Out of scope
|
|
41
41
|
|
|
42
42
|
- Whether a document exists or holds its required sections, which is the structural question `target-check` answers across six domains
|
|
43
|
-
-
|
|
43
|
+
- Banned tokens and structural checkpoints in markdown, which `canon markdown audit` measures from package data. This skill reads rot a ban set cannot state.
|
|
44
44
|
- Rewriting a stale claim once this run has named it, which is `markdown-propose`
|
|
45
45
|
- Running the toolkit's own checks as one gating set over its own corpus, which is `canon audits run`. That set gates a push and this reports to a reader.
|
|
46
46
|
- Repairing anything an axis names. Each routes to the command or skill that owns it.
|
|
@@ -13,6 +13,7 @@ Without this skill, a session holding finished files that hands them to a worker
|
|
|
13
13
|
- Lists a destination in the overview that the plan's `**Files to touch:**` omits, or the reverse. The omitted path is invisible to `plan-reach`, so a second track writes the same file and neither side finds out.
|
|
14
14
|
- Picks the next ordinal by reading only the live folder, and reuses a number an archived folder already spent, which the pull request that shipped it cites by name.
|
|
15
15
|
- Picks a phase label by scanning the live board alone, which is the scan that let two sessions hand out one label within minutes of each other.
|
|
16
|
+
- Reads the label bare, which reserves nothing and returns one a live claim already holds until that claim's task file lands.
|
|
16
17
|
- Leaves the plan's `**Constraints:**` without a line naming the folder as the verbatim source, so the worker reads the files as inspiration and writes its own version.
|
|
17
18
|
- Leaves a note, an alternate, or a draft passage in the folder beside the real files, so the worker guesses which one is the source.
|
|
18
19
|
- Writes into the main worktree root from a linked worktree with `Edit` or `Write`, which the isolation guard refuses, and takes the refusal's redirect into a second gitignored copy no later session reads.
|
|
@@ -22,7 +23,7 @@ Without this skill, a session holding finished files that hands them to a worker
|
|
|
22
23
|
|
|
23
24
|
- Refuse before writing when the operator has named no finished file, since a folder assembled from a description is a plan with extra steps.
|
|
24
25
|
- Read the ordinal from the live folder and its archive together, so a spent number is never reused.
|
|
25
|
-
- Take the phase label from `canon tasks next-label` rather than from a scan of its own.
|
|
26
|
+
- Take the phase label from `canon tasks next-label --claim` rather than from a scan of its own or a bare read, and stop on a binary that predates the flag rather than falling back.
|
|
26
27
|
- Derive the overview's `destinations` and the plan's `**Files to touch:**` from one list, and compare the two before reporting, so neither can name a path the other lacks.
|
|
27
28
|
- Write the plan's `**Constraints:**` line naming the folder as the verbatim source.
|
|
28
29
|
- Copy each finished file whole to its destination path inside the folder, and carry nothing else into it.
|
|
@@ -31,13 +31,15 @@ Read these in parallel before writing:
|
|
|
31
31
|
|
|
32
32
|
Read `<main-root>/.canon/ready/` and `<main-root>/.canon/ready/archive/` together and take the highest `<nn>` across both, plus one. A folder set with no entry takes `01`. Keep this skill-local. No verb allocates a ready ordinal, and two writers running at once can claim one number, so re-read the folder immediately before Step 3 writes.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Claim the label through the verb rather than reading it bare or scanning for it:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
canon tasks next-label --json
|
|
37
|
+
canon tasks next-label --claim --json
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
The claim reserves the label under `.canon/ordinal-locks/`, so a second session allocating at the same moment gets the next one. A bare read reserves nothing and can return a label a live claim already holds.
|
|
41
|
+
|
|
42
|
+
Branch on the record's `reason` rather than on the exit code, taking `label` from a record that carries one. `label-contended` means re-run the claim. Report the verb as unavailable and stop when the installed binary carries no `next-label` subcommand, or when an unknown `--claim` prints no record, since that binary predates the flag. Never fall back to a bare read or a label picked by hand, which reopens the collision the claim exists to prevent.
|
|
41
43
|
|
|
42
44
|
## Step 3: write the overview
|
|
43
45
|
|
|
@@ -67,7 +69,7 @@ Run the match check in `${CLAUDE_SKILL_DIR}/references/assembly.md` before going
|
|
|
67
69
|
|
|
68
70
|
## Step 6: write the task
|
|
69
71
|
|
|
70
|
-
Write `<main-root>/.canon/tasks/<label>-<slug>.md` by heredoc, from the label Step 2
|
|
72
|
+
Write `<main-root>/.canon/tasks/<label>-<slug>.md` by heredoc, from the label Step 2 claimed, without calling the verb again, since a second call would claim a different label. Its frontmatter, H1, and `## Outcomes` follow `${CLAUDE_SKILL_DIR}/../../standards/tasks.md`. Write a `Ready:` line beside the `Plan:` line pointing at the folder, then point the task at the plan through the verb:
|
|
71
73
|
|
|
72
74
|
```bash
|
|
73
75
|
canon tasks plan-link <task-stem> .canon/plans/feature-<slug>.md --json
|
|
@@ -7,9 +7,9 @@ description: Why a markdown rewrite is proposed per file and answered before any
|
|
|
7
7
|
|
|
8
8
|
## Gap
|
|
9
9
|
|
|
10
|
-
Rewriting a passage in a governing document today means editing it live or arguing in chat, and
|
|
10
|
+
Rewriting a passage in a governing document today means editing it live or arguing in chat, and two surfaces sit near this moment without covering it. `canon markdown audit` measures bans and structural checkpoints from package data.
|
|
11
11
|
|
|
12
|
-
`review-branch` reports findings on a diff someone already wrote.
|
|
12
|
+
`review-branch` reports findings on a diff someone already wrote. Neither drafts a replacement, carries an answer slot, or waits.
|
|
13
13
|
|
|
14
14
|
A review delivered in chat gets applied from memory across files nobody reopened, and nothing records which changes the operator approved. A change nobody agreed to either lands unreviewed, because the session acting on a chat review cannot tell an approved line from an inferred one, or never gets written down at all, because a finding with no draft behind it hands the rewrite back to whoever reads it next.
|
|
15
15
|
|
|
@@ -41,7 +41,7 @@ A second failure compounds the first. A claim copied across several files is cor
|
|
|
41
41
|
|
|
42
42
|
## Out of scope
|
|
43
43
|
|
|
44
|
-
- Reporting a violation with no drafted replacement, which `
|
|
44
|
+
- Reporting a violation with no drafted replacement, which `canon markdown audit` already owns
|
|
45
45
|
- Reviewing a diff someone already wrote, which `review-branch` owns
|
|
46
46
|
- Filing a raw brain dump as findings, which `plan-intake` owns
|
|
47
47
|
- Reviewing `.canon/memory/` and proposing promote-or-retire actions per entry, which `memory-review` owns on a different subject with a different answer contract
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: markdown-propose
|
|
3
|
-
description: Reviews a named markdown surface against a named concern, drafts a per-file proposal under `.canon/proposals/<nn>-<slug>/` carrying a diff and a reason for each change, and stops without editing a source file. Takes the concern and the surface as inputs, such as a claim stated stronger than the record, a fact gone stale, two files disagreeing, or a passage duplicated without derivation. A later invocation applies what the operator answered. Use when asked to "propose a change to CLAUDE.md", "draft a rewrite of this standard", "propose fixes to this doc", "draft alternatives for this passage", or "apply the answered proposals". Do NOT use to report without drafting a replacement (`
|
|
3
|
+
description: Reviews a named markdown surface against a named concern, drafts a per-file proposal under `.canon/proposals/<nn>-<slug>/` carrying a diff and a reason for each change, and stops without editing a source file. Takes the concern and the surface as inputs, such as a claim stated stronger than the record, a fact gone stale, two files disagreeing, or a passage duplicated without derivation. A later invocation applies what the operator answered. Use when asked to "propose a change to CLAUDE.md", "draft a rewrite of this standard", "propose fixes to this doc", "draft alternatives for this passage", or "apply the answered proposals". Do NOT use to report without drafting a replacement (`canon markdown audit`), to review a diff already made (`review-branch`), or to file a raw brain dump as findings (`plan-intake`).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Markdown propose
|
package/docs/agents/commands.md
CHANGED
|
@@ -130,6 +130,7 @@ Subcommands no other page describes:
|
|
|
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
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.
|
|
133
|
+
- A deck folder's `deck.json`, its per-slide `<body>` attributes, speaker notes, and charts are described in `slides.md`.
|
|
133
134
|
- `canvas`, with its verbs, content format, and write routes, is described in `canvas.md`.
|
|
134
135
|
- `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`).
|
|
135
136
|
- `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`).
|
package/docs/agents/index.md
CHANGED
|
@@ -55,6 +55,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
|
|
|
55
55
|
- [Sessions](sessions.md): Resolving live peer sessions to the worktree and branch each holds, reading which row is the caller, the liveness confidence field, what each session surface can see, and where moving a session to another machine is described
|
|
56
56
|
- [Skill audit](skills-audit.md): Measuring both skill corpora against the skill standards, the checks it reads, the requirement gate that is the only failing one, and the drift verb that names bodies rewritten since a ref
|
|
57
57
|
- [Citation reach](skills-reach.md): Reporting the skill bodies that cite a path no target project receives, which corpus the verb reads, the ownership key that decides what counts, the one-word qualifier that marks a citation as decided, and why the verb reports instead of gating
|
|
58
|
+
- [Slides](slides.md): What a folder of HTML slides declares for the whole deck in deck.json, what each slide declares on its own body, the master built from the project's tokens, speaker notes, charts, and what each refusal reports
|
|
58
59
|
- [Standard success criteria](standards-audit.md): Reading the corpus against the success-criterion gate, the two headings it accepts, why the check scopes to arrival rather than the whole corpus, and the exit codes it sets
|
|
59
60
|
- [State-scoped risk](state-scoped-risk.md): Reading committed state rather than an arriving change, the shipped-tree corpus the secret scan reads, what it keys on and how a deliberate value is exempted, the advisory check and its network failure mode, and why one gates while the other reports
|
|
60
61
|
- [Superseded values](superseded.md): Reading where the tree still asserts a value a changed convention no longer produces, why the sweep keys on the value rather than the file, the family stem behind a templated citation, the exemption marker, the blind spots it cannot reach, and why it reports rather than gates
|
|
@@ -87,7 +87,7 @@ A hit the closed set could not separate from correct prose was the case with no
|
|
|
87
87
|
|
|
88
88
|
### Where the rules are enforced
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
Four surfaces apply the ban set and all four go through this verb. `.claude/hooks/standards-audit.sh` runs it against a single file after each markdown edit, the seed copy a project installs does the same, the `Markdown bans` stage in `canon gate run` runs it across the whole corpus before this repository's own push, and the same-named stage in the one verify runner every stack shares, `scripts/verify.ts`, runs it across a scaffolded project's tracked markdown before its own push. Each hook parsed its own copy of the word bans in awk before that, which left a British spelling passing at edit time and failing the push with nothing in between explaining the difference.
|
|
91
91
|
|
|
92
92
|
The seed copy moved onto the verb when the sets became data, since its awk had nothing left to parse. It resolves one runner where the toolkit copy resolves two, looking for no checkout source, and a machine carrying no `canon` gets a report naming the binary to install rather than a silent pass. The Seed independence stage of `bun run check` scopes its walk to markdown and leaves the seed hooks outside it, which its own comment records as deliberate.
|
|
93
93
|
|
|
@@ -95,8 +95,6 @@ The target push stage reads the same exit codes `canon gate run`'s own stage rea
|
|
|
95
95
|
|
|
96
96
|
The target push stage excludes `CHANGELOG.md`, unlike every other surface here. A changelog a generator builds from commit subjects carries prose nobody wrote against the ban set, where this repository's own passes only because `release-please` builds it from subjects sessions already wrote to the rule. The stage passes an explicit file list rather than the bare invocation the other surfaces use, so a corpus with no file left once the changelog is set aside reads as a pass rather than falling back to the whole tree.
|
|
97
97
|
|
|
98
|
-
The fifth surface reads the standards directly and is not a consolidation left half done. `claude/skills/standards-audit/SKILL.md` greps the banned tokens agent-side, which is a session reading prose rather than a process it can shell out to, and it ships to every target. It is the likeliest place for the next drift, since nothing compares it against the verb.
|
|
99
|
-
|
|
100
98
|
The hook prefers a checkout's own `src/cli.ts` over a globally installed binary, so it and the push stage read one build. A published binary lags a branch by whatever has not been released, which would put a ban kind added on the branch into the push and not into the edit. It reads its findings out of the `--json` record rather than off the exit code, so an older binary still reports where the fallback applies. It reads `bans.emptySets` out of the same record, so a set the verb shipped empty reaches the author as a check narrowed to what it could measure rather than as a clean pass.
|
|
101
99
|
|
|
102
100
|
That field replaced `bans.missingStandards`, which answered a standard resolving under none of three roots. The set ships with the package now, so the state it named cannot occur and the narrowed check has one cause left, a defect in the build. The hook keeps reading a field either way, since a reader cannot tell a narrowed check from a clean one without it.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Slides
|
|
3
|
+
description: What a folder of HTML slides declares for the whole deck in deck.json, what each slide declares on its own body, the master built from the project's tokens, speaker notes, charts, and what each refusal reports
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Slides
|
|
7
|
+
|
|
8
|
+
`canon slides render --source <folder>` turns a folder of `.html` slides into an editable PowerPoint deck. Each slide describes its own shapes. What belongs to the whole deck, being the bands, the numbers, and the mark, sits in one file beside them, and the master that carries it takes the project's own colors and face.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
canon slides render --source slides/q3-review
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## The deck file
|
|
15
|
+
|
|
16
|
+
`deck.json` beside the slides is optional. Without it the deck gets its defaults.
|
|
17
|
+
|
|
18
|
+
| Field | Default | What it sets |
|
|
19
|
+
| -------------- | ------------------------------ | ------------------------------------------------- |
|
|
20
|
+
| `title` | the folder name | the file's title and the default footer text |
|
|
21
|
+
| `header` | off | a band at the top, with `left`, `center`, `right` |
|
|
22
|
+
| `footer` | on, with the title on the left | a band at the bottom, with the same three slots |
|
|
23
|
+
| `slideNumbers` | `true` | the number at the right of the footer |
|
|
24
|
+
| `mark` | none | an image path, relative to the folder, top right |
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"title": "Q3 review",
|
|
29
|
+
"header": { "left": "Acme" },
|
|
30
|
+
"footer": { "center": "Internal" },
|
|
31
|
+
"mark": "assets/mark.png"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A band takes `show` and the three slots. A declared band merges over its default, so `"footer": { "center": "Internal" }` keeps the title on the left. Declaring any slot turns a band on unless it also says `"show": false`. Set a slot to `""` to empty it.
|
|
36
|
+
|
|
37
|
+
Slide numbers belong to the footer and hold its right slot. They show only where the footer does, and `footer.right` needs `slideNumbers` set to `false`.
|
|
38
|
+
|
|
39
|
+
Prefer a raster mark. An SVG mark lands as a vector, and its fallback for a viewer that cannot draw SVG comes out broken.
|
|
40
|
+
|
|
41
|
+
## What each slide declares
|
|
42
|
+
|
|
43
|
+
A slide sets its own place in the deck through attributes on `<body>`.
|
|
44
|
+
|
|
45
|
+
| Attribute | Effect |
|
|
46
|
+
| --------------------------------------- | --------------------------------------------- |
|
|
47
|
+
| `data-section="Results"` | opens a section the slides after it stay in |
|
|
48
|
+
| `data-hidden` | hides the slide in the show |
|
|
49
|
+
| `data-header="off"` | drops the header on this slide |
|
|
50
|
+
| `data-footer="off"` | drops the footer and its number on this slide |
|
|
51
|
+
| `data-footer-left`, `-center`, `-right` | replaces that footer slot on this slide alone |
|
|
52
|
+
| `data-header-left`, `-center`, `-right` | replaces that header slot on this slide alone |
|
|
53
|
+
|
|
54
|
+
Once any slide opens a section, slides ahead of the first one sit in a section named after the deck, since PowerPoint wants every slide in one. A title the deck returns to after another section opens a new section numbered `Intro (2)`, so each section stays one run of slides.
|
|
55
|
+
|
|
56
|
+
A slide dropping one band keeps the other from the master. A slide replacing a slot draws its band as its own text, so editing that band once in PowerPoint's master view reaches every slide except the ones that replaced it.
|
|
57
|
+
|
|
58
|
+
## The master
|
|
59
|
+
|
|
60
|
+
The master takes its colors from the `<html>` element of the first slide as Chromium laid it out with the project's token stylesheet injected, so a theme switched on one slide's `<body>`, such as a dark cover, stays on that slide:
|
|
61
|
+
|
|
62
|
+
- `--color-background`: the slide ground
|
|
63
|
+
- `--color-text`: the header text
|
|
64
|
+
- `--color-muted`: the footer text
|
|
65
|
+
- `--color-accent`: the slide number
|
|
66
|
+
- the `<body>` font family, first name with a ` Variable` suffix dropped: the theme face
|
|
67
|
+
|
|
68
|
+
A role the project declares no token for falls back to the slide body's own color and prints one `✗` line naming it. It never falls back to the toolkit's palette.
|
|
69
|
+
|
|
70
|
+
A slide whose `<body>` background differs from the master's keeps its own. A slide whose `<body>` computes a `--color-text` apart from the master's prints one `✗` line naming it, since its bands keep the master's colors and may not read against its ground.
|
|
71
|
+
|
|
72
|
+
## Speaker notes
|
|
73
|
+
|
|
74
|
+
`<aside class="notes">` becomes the slide's notes and is never drawn. Each paragraph or list item becomes its own line, and markup is dropped.
|
|
75
|
+
|
|
76
|
+
## Charts
|
|
77
|
+
|
|
78
|
+
A `<figure data-chart="...">` holding a `<table>` becomes a native, editable chart placed where the figure was laid out. The table stays the page's accessible source and is not drawn as well.
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<figure data-chart="bar" data-labels>
|
|
82
|
+
<figcaption>Revenue by region</figcaption>
|
|
83
|
+
<table>
|
|
84
|
+
<tr>
|
|
85
|
+
<th>Region</th>
|
|
86
|
+
<th>Q1</th>
|
|
87
|
+
<th>Q2</th>
|
|
88
|
+
</tr>
|
|
89
|
+
<tr>
|
|
90
|
+
<td>North</td>
|
|
91
|
+
<td>12</td>
|
|
92
|
+
<td>16</td>
|
|
93
|
+
</tr>
|
|
94
|
+
<tr>
|
|
95
|
+
<td>South</td>
|
|
96
|
+
<td></td>
|
|
97
|
+
<td>11</td>
|
|
98
|
+
</tr>
|
|
99
|
+
</table>
|
|
100
|
+
</figure>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- `data-chart` takes `bar`, `line`, `area`, `pie`, or `doughnut`.
|
|
104
|
+
- The header row names the series and the first column names the categories.
|
|
105
|
+
- An empty cell is a gap, not a zero. A comma is a thousands separator only where it groups in threes, as in `12,345.5`, so a comma-decimal `3,5` refuses rather than reading as 35.
|
|
106
|
+
- `data-labels` shows each value. A `<figcaption>` becomes the chart title.
|
|
107
|
+
- Series take the accent first, then every other declared `--color-*` role in sheet order except the background and surface, repeating past the last.
|
|
108
|
+
|
|
109
|
+
## Refusals
|
|
110
|
+
|
|
111
|
+
| Case | Report | Exit |
|
|
112
|
+
| -------------------------------------- | ---------------------------------------------- | ---- |
|
|
113
|
+
| `deck.json` field malformed or unknown | `✗` naming the file and the field | 1 |
|
|
114
|
+
| `mark` path missing | `✗` naming the resolved path | 1 |
|
|
115
|
+
| chart cell not a number | `✗` naming the slide, the figure, and the cell | 0 |
|
|
116
|
+
| `data-chart` outside the five types | `✗` naming the value | 0 |
|
|
117
|
+
| token role not declared | `✗` naming the role | 0 |
|
|
118
|
+
| slide body sets its own `--color-text` | `✗` naming the slides | 0 |
|
|
119
|
+
|
|
120
|
+
A refused chart is left out and the rest of the deck is written.
|
|
@@ -61,7 +61,7 @@ The blocker check re-takes a measurement the board records once and never repeat
|
|
|
61
61
|
}
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
A closed outcome is not the fact the dependency half needs. The ship chain marks outcomes as its first step and opens the pull request several steps later, so a check reading the checkbox reports the row settled while the branch is still in review. A live task therefore settles the row only once it closed every outcome and carries a `Pull request:` line the trunk holds. One that names no pull request, and one whose number no trunk ref could answer for, land in the untested array below rather than being settled or left silent.
|
|
64
|
+
A closed outcome is not the fact the dependency half needs. The ship chain marks outcomes as its first step and opens the pull request several steps later, so a check reading the checkbox reports the row settled while the branch is still in review. A live task therefore settles the row only once it closed every outcome and carries a `Pull request:` line the trunk holds. One that carries a `Pending branch:` line, one that names no pull request, and one whose number no trunk ref could answer for, land in the untested array below rather than being settled or left silent.
|
|
65
65
|
|
|
66
66
|
The trunk is read as the clone already holds it, `origin/main` first and local `main` behind it, and no run fetches. A validate runs several times a sweep and a fetch per run is a cost this command does not carry, so a clone behind its remote under-reports rather than claiming work landed.
|
|
67
67
|
|
package/docs/agents/tasks.md
CHANGED
|
@@ -84,7 +84,7 @@ canon tasks outcome --plan worktree-scratch-routing --close 2 --json
|
|
|
84
84
|
|
|
85
85
|
An outcome already closed comes back under `alreadyClosed` rather than refusing, so a rerun against the same positions changes nothing. A position past the end of the list refuses as `out-of-range`, since a caller counting wrong should hear about it rather than mark a neighbor.
|
|
86
86
|
|
|
87
|
-
A close that ticks at least one box from a branch other than `main` or `master` names that branch on a `Pending branch:` line, read from `git branch --show-current` in the working directory. The line holds the task against `canon tasks archive --pull-request` until `canon tasks pull-request` records that branch's own number. A detached HEAD, a trunk checkout, or a failed git read writes no line and refuses nothing. The `pending` field lists the branches on the line after the write.
|
|
87
|
+
A close that ticks at least one box from a branch other than `main` or `master` names that branch on a `Pending branch:` line, read from `git branch --show-current` in the working directory. The line holds the task against `canon tasks archive --pull-request` until `canon tasks pull-request` records that branch's own number. A detached HEAD, a trunk checkout, or a failed git read writes no line and refuses nothing. The line is also skipped when `gh pr list --head <branch> --state all` lists a number already on the task's `Pull request:` line, since that branch has recorded itself and a marker would outlive it, as in a review-address pass. A failed, offline, or unreadable `gh` lookup counts as no listing and writes the line. The `pending` field lists the branches on the line after the write.
|
|
88
88
|
|
|
89
89
|
Positions skip fenced blocks. A checkbox inside a sample a task displays is not an outcome the task claims, and counting one would shift every position after it. `canon tasks archive` reads the list through the same walker, so the two verbs cannot disagree about which checkboxes are outcomes.
|
|
90
90
|
|
|
@@ -60,7 +60,7 @@ The first line reports the plan and the second applies it, relocating each rule
|
|
|
60
60
|
|
|
61
61
|
## Rename the skill citations, once
|
|
62
62
|
|
|
63
|
-
Twenty-five plugin skills dropped their `claude-` prefix for two-word names, so `canon:claude-docs` answers as `canon:context-fold` and `canon:claude-tasks` as `canon:task-board`. A project that installed governance or tooling before that release holds files naming the old ones, and the plugin answers to none of them. A later release renamed `docs-fold` to `context-fold`, since the skill folds a session into the context entries and the task board rather than into `docs/`, and the same sweep carries a project across that rename too. <!-- canon-keep-retired -->
|
|
63
|
+
Twenty-five plugin skills dropped their `claude-` prefix for two-word names, so `canon:claude-docs` answers as `canon:context-fold` and `canon:claude-tasks` as `canon:task-board`. A project that installed governance or tooling before that release holds files naming the old ones, and the plugin answers to none of them. A later release renamed `docs-fold` to `context-fold`, since the skill folds a session into the context entries and the task board rather than into `docs/`, and the same sweep carries a project across that rename too. The pointer that answered under the old name no longer ships, so a citation the sweep has not reached names nothing. <!-- canon-keep-retired -->
|
|
64
64
|
|
|
65
65
|
Two of those files run rather than sit there. `.husky/post-merge` runs the `canon hooks post-merge` verb, so a stale copy of the hook calls a binary that may lack it, and `.claude/hooks/pr-create-log.sh` hands a session a message naming a skill, so a stale copy tells someone to invoke something that no longer exists. A rule under `.claude/rules/canon/core/` names skills too, though a rule is read rather than run.
|
|
66
66
|
|
|
@@ -97,7 +97,7 @@ Review findings split by origin before severity is read. One the branch inherite
|
|
|
97
97
|
|
|
98
98
|
Review is skipped when the diff is prose that only informs: every changed file matches `*.md` or `*.txt`, and none sits under a behavior path. Behavior paths cover skills and rules in both the authoring and the installed spelling, so the list matches whether a repository authors those surfaces or consumed them from the toolkit. Standards, `internal/`, and `tooling/` carry the authoring spelling alone, since none of the three reaches a session through a `.claude/` copy, and root `CLAUDE.md` is named as a file because a path prefix reaches nothing sitting in no folder.
|
|
99
99
|
|
|
100
|
-
Markdown under one states what an agent does, so a branch touching it reaches review while `docs/` and `wiki/` still skip and stay gated by `docs-sync`, `
|
|
100
|
+
Markdown under one states what an agent does, so a branch touching it reaches review while `docs/` and `wiki/` still skip and stay gated by `docs-sync`, `canon markdown audit`, and pre-push hooks.
|
|
101
101
|
|
|
102
102
|
An empty changed-file list stops the chain rather than counting as prose-only. The filename test passes vacuously on an empty set, which routed a branch past review instead of through it.
|
|
103
103
|
|
|
@@ -61,7 +61,6 @@ This page is the corpus the coverage claim is measured against: every name `cano
|
|
|
61
61
|
| Skill | When to use |
|
|
62
62
|
| ------------------------ | --------------------------------------------------------------------------------------- |
|
|
63
63
|
| `canon:review-branch` | On the local branch diff, before anything is pushed |
|
|
64
|
-
| `canon:standards-audit` | When changed markdown has to answer to the authoring standards |
|
|
65
64
|
| `canon:document-health` | When the documents themselves have to answer for length, placement, and staleness |
|
|
66
65
|
| `canon:markdown-propose` | When a markdown claim needs rewriting and the change should wait for an answer per file |
|
|
67
66
|
| `canon:ux-audit` | To read UI source for missing states, edge cases, and inconsistencies |
|
|
@@ -70,18 +69,17 @@ This page is the corpus the coverage claim is measured against: every name `cano
|
|
|
70
69
|
|
|
71
70
|
## Ship it
|
|
72
71
|
|
|
73
|
-
| Skill | When to use
|
|
74
|
-
| ---------------------- |
|
|
75
|
-
| `canon:git-ship` | To run the whole post-feature chain from the verify gate through open PR
|
|
76
|
-
| `canon:memory-capture` | First skill in that chain, to route what the session learned to the surface owning it
|
|
77
|
-
| `canon:context-fold` | When decisions diverged from the plan, or a shipped task needs its outcomes marked
|
|
78
|
-
| `canon:docs-
|
|
79
|
-
| `canon:
|
|
80
|
-
| `canon:git-
|
|
81
|
-
| `canon:git-
|
|
82
|
-
| `canon:git-
|
|
83
|
-
| `canon:
|
|
84
|
-
| `canon:memory-review` | When the pen has grown, to propose where each entry belongs |
|
|
72
|
+
| Skill | When to use |
|
|
73
|
+
| ---------------------- | ------------------------------------------------------------------------------------- |
|
|
74
|
+
| `canon:git-ship` | To run the whole post-feature chain from the verify gate through open PR |
|
|
75
|
+
| `canon:memory-capture` | First skill in that chain, to route what the session learned to the surface owning it |
|
|
76
|
+
| `canon:context-fold` | When decisions diverged from the plan, or a shipped task needs its outcomes marked |
|
|
77
|
+
| `canon:docs-sync` | When a change since main left `README.md` or `docs/` stale |
|
|
78
|
+
| `canon:git-stage` | When the staged set spans several concerns and wants one commit each |
|
|
79
|
+
| `canon:git-commit` | When the staged set is one concern, or was staged hunk by hand |
|
|
80
|
+
| `canon:git-branch` | When a branch name needs generating or renaming to conventional form |
|
|
81
|
+
| `canon:git-pr` | When a pull request needs a title and body written from the diff |
|
|
82
|
+
| `canon:memory-review` | When the pen has grown, to propose where each entry belongs |
|
|
85
83
|
|
|
86
84
|
## After the pull request opens
|
|
87
85
|
|
package/package.json
CHANGED
|
@@ -38,12 +38,6 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
|
|
|
38
38
|
'Fold what we decided this session into the context entries and mark off what shipped on the task board.',
|
|
39
39
|
expect: 'context-fold',
|
|
40
40
|
},
|
|
41
|
-
{
|
|
42
|
-
prompt:
|
|
43
|
-
'I typed the old fold skill name and it says it was renamed, where did it go?',
|
|
44
|
-
// The pointer answers under its old name on purpose. canon-keep-retired
|
|
45
|
-
expect: 'docs-fold',
|
|
46
|
-
},
|
|
47
41
|
{
|
|
48
42
|
prompt:
|
|
49
43
|
'Sketch out a plan for adding this new capability before we touch any code.',
|
|
@@ -143,16 +137,10 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
|
|
|
143
137
|
prompt: 'Turn this topic into a slide deck I can render.',
|
|
144
138
|
expect: 'draft-slides',
|
|
145
139
|
},
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
},
|
|
151
|
-
// Rot across documents nobody changed is what separates this from
|
|
152
|
-
// `standards-audit`, which is diff-scoped and reports a stated rule broken on
|
|
153
|
-
// a given line. It is also not `target-check`, which asks whether a document
|
|
154
|
-
// exists and holds its sections rather than whether it still describes the
|
|
155
|
-
// tree.
|
|
140
|
+
// Rot across documents nobody changed is what this reads, rather than a
|
|
141
|
+
// stated rule broken on a given line. It is also not `target-check`, which
|
|
142
|
+
// asks whether a document exists and holds its sections rather than whether
|
|
143
|
+
// it still describes the tree.
|
|
156
144
|
{
|
|
157
145
|
prompt:
|
|
158
146
|
'Which of our docs have gone stale, grown too long, or ended up in the wrong file?',
|
|
@@ -230,10 +218,6 @@ export const WORKFLOW_CASES: readonly SkillCase[] = [
|
|
|
230
218
|
"Go through what's still unanswered in the intake folder and decide.",
|
|
231
219
|
expect: 'plan-intake-answer',
|
|
232
220
|
},
|
|
233
|
-
{
|
|
234
|
-
prompt: 'Check this markdown against the house style rules.',
|
|
235
|
-
expect: 'standards-audit',
|
|
236
|
-
},
|
|
237
221
|
{
|
|
238
222
|
prompt: 'Find the rough, unfinished-feeling spots in this interface.',
|
|
239
223
|
expect: 'ux-audit',
|
package/src/commands/slides.ts
CHANGED
|
@@ -119,9 +119,11 @@ export function register(program: Command): void {
|
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
121
|
* The converter loads on demand, so no other command resolves pptxgenjs,
|
|
122
|
-
* jszip, or the token module at startup. A fallback
|
|
123
|
-
*
|
|
124
|
-
* markdown path, since the deck is
|
|
122
|
+
* jszip, or the token module at startup. A fallback, a refused link or chart,
|
|
123
|
+
* or a token the master fell back on reports one line each and keeps the exit
|
|
124
|
+
* at 0, as an unrecognized layout does on the markdown path, since the deck is
|
|
125
|
+
* written either way. A malformed `deck.json` refuses before any slide is laid
|
|
126
|
+
* out and exits 1.
|
|
125
127
|
*/
|
|
126
128
|
async function renderHtmlFolder(
|
|
127
129
|
sourceDir: string,
|
|
@@ -156,6 +158,14 @@ async function renderHtmlFolder(
|
|
|
156
158
|
`${GREY}│${NC} ${RED}✗${NC} slide ${slide} ${selector} link "${href}" left out: ${reason}\n`,
|
|
157
159
|
)
|
|
158
160
|
}
|
|
161
|
+
for (const { slide, selector, message } of result.refusedCharts) {
|
|
162
|
+
process.stderr.write(
|
|
163
|
+
`${GREY}│${NC} ${RED}✗${NC} slide ${slide} ${selector} chart left out: ${message}\n`,
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
for (const notice of result.notices) {
|
|
167
|
+
process.stderr.write(`${GREY}│${NC} ${RED}✗${NC} ${notice}\n`)
|
|
168
|
+
}
|
|
159
169
|
if (result.mirrorPath) {
|
|
160
170
|
process.stderr.write(
|
|
161
171
|
`${GREY}│${NC} ${GREEN}✓${NC} mirrored to ${result.mirrorPath}\n`,
|
package/src/commands/tasks.ts
CHANGED
|
@@ -65,6 +65,7 @@ const EXIT_FINDINGS = 2
|
|
|
65
65
|
|
|
66
66
|
/** Matches `trunk.ts`'s bound on a git subprocess this verb also shells out to. */
|
|
67
67
|
const GIT_TIMEOUT_MS = 10_000
|
|
68
|
+
const GH_TIMEOUT_MS = 10_000
|
|
68
69
|
|
|
69
70
|
const TRUNK_BRANCHES: readonly string[] = ['main', 'master']
|
|
70
71
|
|
|
@@ -522,7 +523,9 @@ export function register(program: Command): void {
|
|
|
522
523
|
'Run from a branch other than main or master, a close that ticks a new',
|
|
523
524
|
'box names that branch on a Pending branch: line, which holds the task',
|
|
524
525
|
'against a merge-time archive until canon tasks pull-request records',
|
|
525
|
-
"the branch's own number.",
|
|
526
|
+
"the branch's own number. The marker is skipped once gh lists a pull",
|
|
527
|
+
"request for the branch whose number is already on the task's Pull",
|
|
528
|
+
'request: line, and a failed gh lookup writes it.',
|
|
526
529
|
'',
|
|
527
530
|
'Examples:',
|
|
528
531
|
' canon tasks outcome v28.1-trigger-escalation --close 1 --close 3',
|
|
@@ -728,6 +731,45 @@ async function workingBranch(): Promise<string | undefined> {
|
|
|
728
731
|
return branch
|
|
729
732
|
}
|
|
730
733
|
|
|
734
|
+
/**
|
|
735
|
+
* The pull request numbers whose head is `branch`, open or not. A failed run,
|
|
736
|
+
* an unreadable answer, or no `gh` at all returns an empty list, which writes
|
|
737
|
+
* the marker, the direction that holds a task open rather than archiving it.
|
|
738
|
+
*/
|
|
739
|
+
async function branchPullNumbers(
|
|
740
|
+
branch: string | undefined,
|
|
741
|
+
): Promise<readonly number[]> {
|
|
742
|
+
if (branch === undefined) return []
|
|
743
|
+
|
|
744
|
+
const result = await execa(
|
|
745
|
+
'gh',
|
|
746
|
+
['pr', 'list', '--head', branch, '--state', 'all', '--json', 'number'],
|
|
747
|
+
{
|
|
748
|
+
cwd: process.cwd(),
|
|
749
|
+
reject: false,
|
|
750
|
+
timeout: GH_TIMEOUT_MS,
|
|
751
|
+
env: gitEnv(),
|
|
752
|
+
extendEnv: false,
|
|
753
|
+
},
|
|
754
|
+
)
|
|
755
|
+
if (result.exitCode !== 0) return []
|
|
756
|
+
|
|
757
|
+
try {
|
|
758
|
+
const rows: unknown = JSON.parse(result.stdout)
|
|
759
|
+
if (!Array.isArray(rows)) return []
|
|
760
|
+
|
|
761
|
+
return rows
|
|
762
|
+
.map((row: unknown) =>
|
|
763
|
+
typeof row === 'object' && row !== null && 'number' in row
|
|
764
|
+
? row.number
|
|
765
|
+
: undefined,
|
|
766
|
+
)
|
|
767
|
+
.filter((number): number is number => Number.isInteger(number))
|
|
768
|
+
} catch {
|
|
769
|
+
return []
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
731
773
|
async function runPlanLink(
|
|
732
774
|
task: string,
|
|
733
775
|
plan: string,
|
|
@@ -789,11 +831,13 @@ async function runOutcome(
|
|
|
789
831
|
}
|
|
790
832
|
|
|
791
833
|
const root = opts.root ?? (await mainWorktreeRoot())
|
|
834
|
+
const branch = await workingBranch()
|
|
792
835
|
const outcome = await closeOutcomes(
|
|
793
836
|
root,
|
|
794
837
|
selector,
|
|
795
838
|
raw.map(Number),
|
|
796
|
-
|
|
839
|
+
branch,
|
|
840
|
+
await branchPullNumbers(branch),
|
|
797
841
|
)
|
|
798
842
|
|
|
799
843
|
return reportOutcome(outcome, emitJson, root)
|
|
@@ -110,11 +110,6 @@ export const SKILL_NAME_MAP: Readonly<Record<string, string>> = {
|
|
|
110
110
|
* what shipped or what a session ran under whatever name was current then, so
|
|
111
111
|
* rewriting one makes it testify to a release or a run that never happened.
|
|
112
112
|
*
|
|
113
|
-
* The pointer left at `docs-fold` is excluded because it exists to answer under
|
|
114
|
-
* the old name, so a later run would move it onto the skill it points at. The
|
|
115
|
-
* exclusion is only safe once the real body has moved, since excluding the path
|
|
116
|
-
* ahead of the first sweep leaves that body behind.
|
|
117
|
-
*
|
|
118
113
|
* The record archives need no entry here. They are gitignored, so the tracked
|
|
119
114
|
* listing every sweep reads never reaches them.
|
|
120
115
|
*/
|
|
@@ -127,7 +122,5 @@ export const SKILL_NAME_RULES: RenameRules = defineRenameRules({
|
|
|
127
122
|
'CHANGELOG.md',
|
|
128
123
|
'src/migrate/skill-names.ts',
|
|
129
124
|
'src/migrate/skill-names.test.ts',
|
|
130
|
-
'claude/skills/docs-fold/SKILL.md',
|
|
131
|
-
'claude/skills/docs-fold/REQUIREMENT.md',
|
|
132
125
|
],
|
|
133
126
|
})
|