@erclx/canon 5.39.0 → 5.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -22
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
- package/claude/skills/auto-ship/SKILL.md +6 -0
- package/claude/skills/auto-ship/references/fan-in.md +59 -0
- package/claude/skills/canvas-board/SKILL.md +1 -1
- package/claude/skills/create-skill/references/skill.md +1 -1
- package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
- package/claude/skills/decision-escalate/SKILL.md +1 -2
- package/claude/skills/draft-slides/SKILL.md +2 -3
- package/claude/skills/draft-slides/references/deck.md +183 -0
- package/claude/skills/git-branch/REQUIREMENT.md +17 -6
- package/claude/skills/git-branch/SKILL.md +63 -7
- package/claude/skills/git-pr/references/evidence.md +2 -2
- package/claude/skills/git-pr/references/final-command.md +1 -1
- package/claude/skills/git-pr/references/labels.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- package/claude/skills/plan-feature/references/batches.md +48 -1
- package/claude/skills/plan-feature/references/plan.md +2 -0
- package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
- package/claude/skills/plan-intake-answer/SKILL.md +1 -2
- package/claude/skills/review-address/references/rebase-conflicts.md +7 -0
- package/claude/skills/review-ui/SKILL.md +3 -0
- package/claude/skills/review-ui/references/close-out.md +47 -0
- package/claude/skills/review-ui/references/post.md +3 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -0
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
- package/claude/skills/role-worker/REQUIREMENT.md +1 -0
- package/claude/skills/role-worker/SKILL.md +9 -0
- package/claude/skills/task-board/references/board.md +1 -1
- package/claude/skills/task-board/references/tasks.md +1 -1
- package/claude/skills/teach-workspace/REQUIREMENT.md +1 -1
- package/claude/skills/teach-workspace/SKILL.md +1 -2
- package/claude/skills/ux-measure/SKILL.md +1 -1
- package/docs/agents/audits.md +61 -60
- package/docs/agents/context-audit.md +40 -58
- package/docs/agents/index.md +5 -51
- package/docs/agents/install-and-sync.md +1 -0
- package/docs/agents/markdown-audit.md +53 -99
- package/docs/agents/records.md +37 -79
- package/docs/agents/sessions-transfer.md +26 -70
- package/docs/target/index.md +0 -1
- package/docs/target/migrations.md +57 -66
- package/docs/target/projects.md +36 -78
- package/docs/target/scenarios.md +9 -19
- package/docs/target/sync.md +27 -68
- package/docs/workflow/ai-workflow.md +22 -68
- package/docs/workflow/operating-model.md +32 -186
- package/docs/workflow/parallel-features.md +30 -66
- package/docs/workflow/skill-map-on-request.md +22 -24
- package/docs/workflow/skill-map.md +72 -76
- package/docs/workflow/visual-design-workflow.md +25 -118
- package/docs/workflow/zshrc-aliases.md +18 -37
- package/package.json +1 -1
- package/src/canvas/client/inspector/fields.css +4 -0
- package/src/canvas/index.html +6 -0
- package/src/claude/cases/git.ts +4 -0
- package/src/claude/skills-reach.ts +2 -2
- package/src/cli/ui.ts +3 -1
- package/src/commands/canvas.ts +23 -0
- package/src/commands/capture.ts +14 -0
- package/src/commands/claude.ts +13 -0
- package/src/commands/demo.ts +22 -0
- package/src/commands/deps.ts +6 -0
- package/src/commands/driver.ts +28 -2
- package/src/commands/gov.ts +52 -18
- package/src/commands/intake.ts +17 -0
- package/src/commands/labels.ts +38 -5
- package/src/commands/pr.ts +192 -52
- package/src/commands/records.ts +19 -3
- package/src/commands/secrets.ts +6 -0
- package/src/commands/sessions.ts +8 -0
- package/src/commands/tasks.ts +52 -0
- package/src/commands/teach.ts +13 -0
- package/src/commands/upstream.ts +25 -1
- package/src/commands/worktrees.ts +5 -0
- package/src/context/audit.ts +22 -4
- package/src/context/folders.ts +1 -1
- package/src/context/shape-gated.ts +5 -2
- package/src/counts/scan.ts +1 -1
- package/src/design/components.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/tokens.ts +1 -1
- package/src/gate/boundaries.ts +4 -1
- package/src/gate/measures.ts +1 -1
- package/src/gate/stages.ts +7 -7
- package/src/git/env.ts +6 -0
- package/src/markdown/scope.ts +11 -19
- package/src/pr/frames.ts +53 -9
- package/src/pr/lift.ts +153 -19
- package/src/pr/painted.ts +87 -0
- package/src/records/validate.ts +1 -0
- package/src/roots/record.ts +18 -1
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/headless/run.ts +3 -2
- package/src/sandbox/headless/watch.ts +3 -4
- package/src/slides/convert/master.ts +3 -0
- package/src/sync/format.ts +46 -0
- package/src/sync/stamp.ts +6 -0
- package/src/tasks/answers.ts +79 -2
- package/src/tasks/fan-in.ts +176 -0
- package/src/tasks/stack.ts +1 -1
- package/tooling/astro/reference.md +80 -39
- package/tooling/base/reference.md +63 -32
- package/tooling/base/seeds/canon/context/ci.md +7 -15
- package/tooling/base/seeds/canon/context/development.md +14 -14
- package/tooling/claude/reference.md +40 -22
- package/tooling/cloudflare/reference.md +31 -12
- package/tooling/go/reference.md +47 -23
- package/tooling/nextjs/reference.md +52 -19
- package/tooling/php/reference.md +35 -16
- package/tooling/python/reference.md +47 -30
- package/tooling/shell/reference.md +6 -5
- package/tooling/vite-react/reference.md +49 -21
- package/tooling/web/reference.md +131 -71
- package/docs/agents/canvas.md +0 -86
- package/docs/agents/capture.md +0 -65
- package/docs/agents/census.md +0 -23
- package/docs/agents/comments.md +0 -34
- package/docs/agents/context-audit-checks.md +0 -108
- package/docs/agents/context-classify.md +0 -101
- package/docs/agents/counts.md +0 -74
- package/docs/agents/demo.md +0 -140
- package/docs/agents/driver.md +0 -103
- package/docs/agents/gate.md +0 -90
- package/docs/agents/hooks.md +0 -78
- package/docs/agents/intake.md +0 -81
- package/docs/agents/key-changes.md +0 -116
- package/docs/agents/label-coverage.md +0 -86
- package/docs/agents/markdown-audit-density.md +0 -39
- package/docs/agents/markdown-audit-weight.md +0 -83
- package/docs/agents/pr-evidence-layout.md +0 -70
- package/docs/agents/pr-evidence.md +0 -273
- package/docs/agents/pr-frames.md +0 -131
- package/docs/agents/pr-lift.md +0 -132
- package/docs/agents/pr-local.md +0 -111
- package/docs/agents/pr-preview.md +0 -155
- package/docs/agents/pr-reads.md +0 -111
- package/docs/agents/pr-tick.md +0 -72
- package/docs/agents/records-push.md +0 -61
- package/docs/agents/records-stale.md +0 -40
- package/docs/agents/records-validate.md +0 -57
- package/docs/agents/restated.md +0 -83
- package/docs/agents/routing.md +0 -61
- package/docs/agents/rule-citations.md +0 -90
- package/docs/agents/sandbox.md +0 -126
- package/docs/agents/sessions.md +0 -128
- package/docs/agents/skills-audit.md +0 -120
- package/docs/agents/skills-reach.md +0 -64
- package/docs/agents/slides.md +0 -176
- package/docs/agents/state-scoped-risk.md +0 -105
- package/docs/agents/superseded.md +0 -85
- package/docs/agents/tasks-archive.md +0 -83
- package/docs/agents/tasks-plans.md +0 -152
- package/docs/agents/tasks-validate.md +0 -133
- package/docs/agents/tasks.md +0 -123
- package/docs/agents/teach-authoring.md +0 -118
- package/docs/agents/teach.md +0 -157
- package/docs/agents/test-order.md +0 -58
- package/docs/agents/upstream.md +0 -94
- package/docs/agents/worktrees.md +0 -118
- package/docs/target/standards-moved.md +0 -34
package/src/commands/upstream.ts
CHANGED
|
@@ -103,6 +103,14 @@ export function register(program: Command): void {
|
|
|
103
103
|
'Writes nothing. The cursor moves only through `upstream advance`, so a',
|
|
104
104
|
'run that dies midway reads the same range next time.',
|
|
105
105
|
'',
|
|
106
|
+
'Reasons:',
|
|
107
|
+
' no-cursor no cursor is stored and no --since was given',
|
|
108
|
+
' bad-since --since is not a dotted version',
|
|
109
|
+
' cursor-missing every release the feed returned is newer than the cursor',
|
|
110
|
+
' rate-limited GitHub refused the request, so set GH_TOKEN or GITHUB_TOKEN',
|
|
111
|
+
' network the feed could not be reached',
|
|
112
|
+
' http GitHub answered with an error status',
|
|
113
|
+
'',
|
|
106
114
|
'Exit codes:',
|
|
107
115
|
' 0 the range was read, possibly empty',
|
|
108
116
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -154,7 +162,19 @@ export function register(program: Command): void {
|
|
|
154
162
|
'Due when the cursor is a week old and the installed version is ahead of',
|
|
155
163
|
'it, or sooner when an Added line in the gap names a canon surface. The',
|
|
156
164
|
'gap is read from GitHub at most once a day and cached under the scratch',
|
|
157
|
-
'folder, and a failed read is held for the day as well.',
|
|
165
|
+
'folder, and a failed read is held for the day as well. The record',
|
|
166
|
+
'carries due, reason, releases, installed, and cursor, and releases is',
|
|
167
|
+
'null when the feed was not read.',
|
|
168
|
+
'',
|
|
169
|
+
'Reasons:',
|
|
170
|
+
' Due, by reason:',
|
|
171
|
+
' no-cursor no digest has been run',
|
|
172
|
+
' week the cursor is seven or more days old and the install is ahead',
|
|
173
|
+
' vocabulary an Added line in the gap names a plugin, skill, hook, or worktree',
|
|
174
|
+
' Not due, by reason:',
|
|
175
|
+
' current the install is at or below the cursor, as after a downgrade',
|
|
176
|
+
' recent the cursor is under a week old and no line in the gap matches',
|
|
177
|
+
' unknown-version claude --version could not be read',
|
|
158
178
|
'',
|
|
159
179
|
'Exit codes:',
|
|
160
180
|
' 0 answered, whether or not a digest is due',
|
|
@@ -185,6 +205,10 @@ export function register(program: Command): void {
|
|
|
185
205
|
'folder through a temp file and a rename. It refuses a version older than',
|
|
186
206
|
'the stored cursor.',
|
|
187
207
|
'',
|
|
208
|
+
'Reasons:',
|
|
209
|
+
' invalid-version the version is not a dotted number',
|
|
210
|
+
' older-than-cursor the version is older than the stored cursor',
|
|
211
|
+
'',
|
|
188
212
|
'Exit codes:',
|
|
189
213
|
' 0 the cursor moved',
|
|
190
214
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
@@ -119,6 +119,11 @@ const CONDITIONS = [
|
|
|
119
119
|
'command is running in, since git removes the directory a caller stands',
|
|
120
120
|
'in and every later call scoped to it then fails.',
|
|
121
121
|
'',
|
|
122
|
+
'A "refusals" entry is one of these reasons:',
|
|
123
|
+
...Object.entries(REFUSALS).map(
|
|
124
|
+
([reason, meaning]) => ` ${reason.padEnd(24)}${meaning}`,
|
|
125
|
+
),
|
|
126
|
+
'',
|
|
122
127
|
'An unreadable input refuses the whole reading rather than reporting',
|
|
123
128
|
'every worktree as not reclaimable, since an absent merge state and a',
|
|
124
129
|
'branch with no merged pull request produce the same empty answer.',
|
package/src/context/audit.ts
CHANGED
|
@@ -104,6 +104,19 @@ export const LAYOUT_PARAGRAPH_CAP = 1
|
|
|
104
104
|
/** The one Layout H3 not titled with a path, holding the trivial folders. */
|
|
105
105
|
export const OTHER_FOLDERS = 'Other folders'
|
|
106
106
|
|
|
107
|
+
/** The run-command table H3 only the development entry carries under `Layout`. */
|
|
108
|
+
export const SCRIPTS_HEADING = 'Scripts'
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Whether an entry path is the development entry, being the flat entry, the
|
|
112
|
+
* overview of a split one a target may still carry, or this repository's
|
|
113
|
+
* `local-loop` entry. The standard exempts the
|
|
114
|
+
* `Scripts` H3 for that entry alone, so the path is what tells.
|
|
115
|
+
*/
|
|
116
|
+
function isDevelopmentEntry(rel: string): boolean {
|
|
117
|
+
return /(?:^|\/)development(?:\/(?:overview|local-loop))?\.md$/.test(rel)
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
/**
|
|
108
121
|
* The context standard's list, kept as its own export because the audit record
|
|
109
122
|
* has published it flat under `checkpoints.requiredSections` since before any
|
|
@@ -584,13 +597,18 @@ interface FolderHeading {
|
|
|
584
597
|
* A cap reports once per section or folder heading however far past it the
|
|
585
598
|
* count runs, since the fix is one cut either way. `Other folders` is exempt
|
|
586
599
|
* from both folder caps, its one-line bullets being held by the bullet run
|
|
587
|
-
* limit `canon markdown audit` already gates.
|
|
600
|
+
* limit `canon markdown audit` already gates. The development entry's `Scripts`
|
|
601
|
+
* H3 is exempt from the heading rule and both caps the same way, its table
|
|
602
|
+
* being a run-command surface rather than a folder.
|
|
588
603
|
*/
|
|
589
604
|
function shapeIssues(
|
|
605
|
+
rel: string,
|
|
590
606
|
lines: readonly BodyLine[],
|
|
591
607
|
retired: readonly string[],
|
|
592
608
|
): ShapeIssue[] {
|
|
593
609
|
const issues: ShapeIssue[] = []
|
|
610
|
+
const exempt = new Set([OTHER_FOLDERS])
|
|
611
|
+
if (isDevelopmentEntry(rel)) exempt.add(SCRIPTS_HEADING)
|
|
594
612
|
let section: string | undefined
|
|
595
613
|
let gotchas: { line: number; bullets: number } | undefined
|
|
596
614
|
let folder: FolderHeading | undefined
|
|
@@ -599,7 +617,7 @@ function shapeIssues(
|
|
|
599
617
|
let afterBlank = false
|
|
600
618
|
|
|
601
619
|
const closeFolder = (): void => {
|
|
602
|
-
if (folder && folder.text
|
|
620
|
+
if (folder && !exempt.has(folder.text)) {
|
|
603
621
|
if (folder.paragraphs > LAYOUT_PARAGRAPH_CAP) {
|
|
604
622
|
issues.push({
|
|
605
623
|
line: folder.line,
|
|
@@ -659,7 +677,7 @@ function shapeIssues(
|
|
|
659
677
|
} else if (section === 'Layout' && level === 3) {
|
|
660
678
|
closeFolder()
|
|
661
679
|
folder = { line: line.number, text, bullets: 0, paragraphs: 0 }
|
|
662
|
-
if (!FOLDER_TITLE.test(text) && text
|
|
680
|
+
if (!FOLDER_TITLE.test(text) && !exempt.has(text)) {
|
|
663
681
|
issues.push({ line: line.number, kind: 'layout-heading', text })
|
|
664
682
|
}
|
|
665
683
|
} else if (section === 'Layout') {
|
|
@@ -780,7 +798,7 @@ export function measureEntry(
|
|
|
780
798
|
bareReferences: bareReferences(lines, siblings),
|
|
781
799
|
sections: declaredSections(lines, required),
|
|
782
800
|
otherSections: otherSections(lines, allowed, retired),
|
|
783
|
-
shape: governsContent ? shapeIssues(lines, retired) : [],
|
|
801
|
+
shape: governsContent ? shapeIssues(rel, lines, retired) : [],
|
|
784
802
|
stub: isStubSeed(source),
|
|
785
803
|
governed: governsContent,
|
|
786
804
|
}
|
package/src/context/folders.ts
CHANGED
|
@@ -56,7 +56,7 @@ export interface AuditedFolder {
|
|
|
56
56
|
* The requested folder name this was resolved under, which is what says
|
|
57
57
|
* which standard governs the entries. A nested split folder carries the name
|
|
58
58
|
* of the folder it sits beneath rather than its own, so
|
|
59
|
-
* `canon/context
|
|
59
|
+
* `canon/context/<domain>` is governed as `context`.
|
|
60
60
|
*/
|
|
61
61
|
readonly name: string
|
|
62
62
|
/**
|
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
*/
|
|
16
16
|
export const SHAPE_GATED_FOLDERS: readonly string[] = [
|
|
17
17
|
'canon/context/context-model.md',
|
|
18
|
-
'canon/context/
|
|
19
|
-
'canon/context/
|
|
18
|
+
'canon/context/development/',
|
|
19
|
+
'canon/context/features/',
|
|
20
|
+
'canon/context/surfaces/',
|
|
21
|
+
'canon/context/workflow/',
|
|
22
|
+
'tooling/base/seeds/',
|
|
20
23
|
]
|
|
21
24
|
|
|
22
25
|
/** Whether a repository-relative entry path sits under a gated prefix. */
|
package/src/counts/scan.ts
CHANGED
|
@@ -80,7 +80,7 @@ function sentencesOf(line: string): string[] {
|
|
|
80
80
|
* the 290 showed removing all of them while keeping that one.
|
|
81
81
|
*
|
|
82
82
|
* `authors` joined the list on a corpus measurement rather than on the shape
|
|
83
|
-
* argument above.
|
|
83
|
+
* argument above. A context entry asserting `the
|
|
84
84
|
* toolkit authors 69 rules under governance/rules/`, which is the assertion
|
|
85
85
|
* shape this list was built for with a verb the list did not carry, and the
|
|
86
86
|
* word costs one further sentence across the whole corpus and no finding.
|
package/src/design/components.ts
CHANGED
|
@@ -250,7 +250,7 @@ const TEACH_CHROME: Component = {
|
|
|
250
250
|
'navigation shared by every teach page. Recovered from two gitignored',
|
|
251
251
|
'course.css files that predate a regression that dropped this layer from the',
|
|
252
252
|
"generator, and rewritten onto this module's tokens rather than the",
|
|
253
|
-
'incompatible palette they carried. See canon/context/features/teach
|
|
253
|
+
'incompatible palette they carried. See canon/context/features/teach.md',
|
|
254
254
|
'for the shape.',
|
|
255
255
|
].join('\n '),
|
|
256
256
|
reads: [
|
package/src/design/document.ts
CHANGED
|
@@ -95,7 +95,7 @@ function borderRows(tokens: readonly BorderToken[]): string[][] {
|
|
|
95
95
|
*/
|
|
96
96
|
const LEAD = [
|
|
97
97
|
'Authoring guidance: `claude/skills/canvas-board/references/design.md`. Rendered from `src/design/tokens.ts` by `canon design regen`, so edit the module and never this file.',
|
|
98
|
-
'Why each value was chosen lives in `canon/context/design.md`.',
|
|
98
|
+
'Why each value was chosen lives in `canon/context/features/design.md`.',
|
|
99
99
|
].join(' ')
|
|
100
100
|
|
|
101
101
|
function bullets(rules: readonly string[]): string {
|
package/src/design/tokens.ts
CHANGED
|
@@ -59,7 +59,7 @@ export interface BorderToken {
|
|
|
59
59
|
/**
|
|
60
60
|
* Every prose slot is a list of one-line rules rather than a string, so a
|
|
61
61
|
* bullet is the unit the renderer emits and the prose budget counts, and a
|
|
62
|
-
* value needing a paragraph of why goes to `canon/context/design.md`.
|
|
62
|
+
* value needing a paragraph of why goes to `canon/context/features/design.md`.
|
|
63
63
|
*/
|
|
64
64
|
export interface DesignTokens {
|
|
65
65
|
readonly personality: string
|
package/src/gate/boundaries.ts
CHANGED
|
@@ -211,7 +211,7 @@ function collectCommands(value: unknown, into: string[] = []): string[] {
|
|
|
211
211
|
/**
|
|
212
212
|
* A hook, a workflow, or a husky script reaching one side of the seed or config
|
|
213
213
|
* boundary and not the other is a capability withheld with no recorded reason,
|
|
214
|
-
* per the criterion in `canon/context/tooling
|
|
214
|
+
* per the criterion in `canon/context/surfaces/tooling.md`.
|
|
215
215
|
*
|
|
216
216
|
* Presence on either side clears a name, and a `canon-no-seed:` comment in the
|
|
217
217
|
* file is the recorded reason. The same marker on a destination clears a
|
|
@@ -434,6 +434,9 @@ function grepLines(
|
|
|
434
434
|
* seed itself installs, so every other spelling reports. No seed root, or roots
|
|
435
435
|
* carrying no markdown, is unmeasured rather than a pass, which agrees with
|
|
436
436
|
* `seedStandards` on the no-root case.
|
|
437
|
+
*
|
|
438
|
+
* The token matches as a substring, so `canonical` reports as `canon` does. The
|
|
439
|
+
* walk covers the seed roots alone, so a shipped rule citing the CLI passes.
|
|
437
440
|
*/
|
|
438
441
|
export const seedIndependence: Measure = async (ctx) => {
|
|
439
442
|
if (!isDirectory(join(ctx.root, 'tooling'))) {
|
package/src/gate/measures.ts
CHANGED
|
@@ -1401,7 +1401,7 @@ export const pluginManifests: Measure = async (ctx) => {
|
|
|
1401
1401
|
return {
|
|
1402
1402
|
emissions: [],
|
|
1403
1403
|
unmeasured:
|
|
1404
|
-
'claude is on PATH and claude --version fails, so the install brought down no platform-native binary and no manifest was read. Raise or lower the pinned version at the Install Plugin CLI step in .github/workflows/verify.yml,
|
|
1404
|
+
'claude is on PATH and claude --version fails, so the install brought down no platform-native binary and no manifest was read. Raise or lower the pinned version at the Install Plugin CLI step in .github/workflows/verify.yml, then test the raised pin as `canon/context/development/ci.md` states.',
|
|
1405
1405
|
}
|
|
1406
1406
|
}
|
|
1407
1407
|
|
package/src/gate/stages.ts
CHANGED
|
@@ -92,12 +92,12 @@ export interface Stage {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* Corpora a `src/` test asserts over from outside `src
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
95
|
+
* Corpora a `src/` test asserts over from outside `src/`. Nothing compares
|
|
96
|
+
* this list to the tests reading those paths, so a test that starts reading a
|
|
97
|
+
* new corpus joins it here in the same change, or a branch touching only that
|
|
98
|
+
* corpus skips the Tests stage. The first four are directory prefixes because
|
|
99
|
+
* their tests walk the tree whole, which is what reaches a rule or a skill a
|
|
100
|
+
* branch adds rather than edits.
|
|
101
101
|
*/
|
|
102
102
|
export const TEST_CORPORA_PATTERNS = [
|
|
103
103
|
'^claude/skills/',
|
|
@@ -302,7 +302,7 @@ export const STAGES: readonly Stage[] = [
|
|
|
302
302
|
{
|
|
303
303
|
// A hook, a workflow, or a husky script reaching one side of the seed or
|
|
304
304
|
// config boundary and not the other is a capability withheld with no
|
|
305
|
-
// recorded reason, per the criterion in canon/context/tooling
|
|
305
|
+
// recorded reason, per the criterion in canon/context/surfaces/tooling.md.
|
|
306
306
|
id: 'capability-seeding',
|
|
307
307
|
label: 'Capability seeding',
|
|
308
308
|
checks: [{ kind: 'measure', measure: capabilitySeeding }],
|
package/src/git/env.ts
CHANGED
|
@@ -26,6 +26,12 @@ const RESOLUTION_VARS = [
|
|
|
26
26
|
* Read per call rather than captured at module load. A long-lived process can
|
|
27
27
|
* have these set after import, and a snapshot would then hand git the very
|
|
28
28
|
* variables this exists to strip.
|
|
29
|
+
*
|
|
30
|
+
* Hooks export `GIT_DIR`, `GIT_WORK_TREE`, `GIT_INDEX_FILE`, and `GIT_PREFIX`,
|
|
31
|
+
* which beat `-C`. Under pre-push an inherited `GIT_DIR` made a `-C src` read
|
|
32
|
+
* resolve against the whole repository, and in a fixture it let a config write
|
|
33
|
+
* overwrite the real repository's committer identity, so route every shelled
|
|
34
|
+
* call through this, fixtures included.
|
|
29
35
|
*/
|
|
30
36
|
export function gitEnv(): Record<string, string> {
|
|
31
37
|
return Object.fromEntries(
|
package/src/markdown/scope.ts
CHANGED
|
@@ -35,16 +35,11 @@ const DENSITY_SCOPE = [
|
|
|
35
35
|
* corpus, which is why the audit reads it only inside a canon checkout.
|
|
36
36
|
*/
|
|
37
37
|
export const GATED_PATHS: readonly string[] = [
|
|
38
|
-
'canon/context/
|
|
39
|
-
'canon/context/
|
|
38
|
+
'canon/context/surfaces/',
|
|
39
|
+
'canon/context/workflow/',
|
|
40
|
+
'canon/context/development/',
|
|
40
41
|
'canon/context/features/',
|
|
41
|
-
'canon/context/cli/',
|
|
42
|
-
'canon/context/sandbox/',
|
|
43
|
-
'canon/context/governance/',
|
|
44
|
-
'canon/context/tooling/',
|
|
45
|
-
'canon/context/web.md',
|
|
46
42
|
'canon/context/context-model.md',
|
|
47
|
-
'canon/context/design.md',
|
|
48
43
|
'canon/context/index.md',
|
|
49
44
|
'claude/skills/api-design/SKILL.md',
|
|
50
45
|
'claude/skills/auto-ship/SKILL.md',
|
|
@@ -76,6 +71,7 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
76
71
|
'claude/skills/draft-screencast/SKILL.md',
|
|
77
72
|
'claude/skills/draft-screencast/references/beats-template.md',
|
|
78
73
|
'claude/skills/draft-slides/SKILL.md',
|
|
74
|
+
'claude/skills/draft-slides/references/deck.md',
|
|
79
75
|
'claude/skills/git-branch/SKILL.md',
|
|
80
76
|
'claude/skills/git-commit/SKILL.md',
|
|
81
77
|
'claude/skills/git-followup/SKILL.md',
|
|
@@ -130,16 +126,12 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
130
126
|
'claude/skills/video-craft/SKILL.md',
|
|
131
127
|
'claude/skills/write-human/SKILL.md',
|
|
132
128
|
'claude/skills/youtube-transcripts/SKILL.md',
|
|
133
|
-
'docs/agents/indexes.md',
|
|
134
|
-
'docs/agents/install-and-sync.md',
|
|
135
|
-
'docs/agents/migrate.md',
|
|
136
|
-
'docs/agents/output-shape.md',
|
|
137
|
-
'docs/agents/overview.md',
|
|
138
|
-
'docs/agents/targets.md',
|
|
139
|
-
'canon/context/development/',
|
|
140
|
-
'canon/context/ci/',
|
|
141
|
-
'canon/context/scripts/',
|
|
142
129
|
'.claude/skills/internal-',
|
|
130
|
+
'docs/',
|
|
131
|
+
'tooling/',
|
|
132
|
+
'wiki/',
|
|
133
|
+
'README.md',
|
|
134
|
+
'CONTRIBUTING.md',
|
|
143
135
|
]
|
|
144
136
|
|
|
145
137
|
/**
|
|
@@ -148,8 +140,8 @@ export const GATED_PATHS: readonly string[] = [
|
|
|
148
140
|
* entries, the docs pages, and the skill bodies and references through a stage
|
|
149
141
|
* of its own before this measure existed, and waiting on `GATED_PATHS` would
|
|
150
142
|
* reopen that hole. The seeds, the stack references, the wiki, and the root
|
|
151
|
-
* guides were never held to it,
|
|
152
|
-
*
|
|
143
|
+
* guides were never held to it, so they joined `GATED_PATHS` only once their
|
|
144
|
+
* rewrite brought them to zero.
|
|
153
145
|
*/
|
|
154
146
|
const ALWAYS_GATED: readonly DensityKind[] = ['bullet-run']
|
|
155
147
|
|
package/src/pr/frames.ts
CHANGED
|
@@ -19,6 +19,9 @@ const PASS_PATTERN = /^\d{8}T\d{6}Z$/
|
|
|
19
19
|
/** How many times an add rebuilds on a tip another writer moved. */
|
|
20
20
|
const ADD_ATTEMPTS = 3
|
|
21
21
|
|
|
22
|
+
/** One half of a reference address. It opens on a letter or digit and holds no separator, so `..` and `/` cannot survive it. */
|
|
23
|
+
const REFERENCE_PART = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
|
|
24
|
+
|
|
22
25
|
const FOLDER_PATTERN = /^pr-(\d+)$/
|
|
23
26
|
|
|
24
27
|
const DAY_MS = 86_400_000
|
|
@@ -77,7 +80,8 @@ export interface FrameInput {
|
|
|
77
80
|
readonly number: number
|
|
78
81
|
readonly head: string
|
|
79
82
|
readonly pass: string
|
|
80
|
-
readonly box
|
|
83
|
+
readonly box?: number
|
|
84
|
+
readonly reference?: string
|
|
81
85
|
readonly theme?: FrameTheme
|
|
82
86
|
readonly bytes: Uint8Array
|
|
83
87
|
}
|
|
@@ -158,6 +162,39 @@ export function frameLink(repo: string, path: string): string {
|
|
|
158
162
|
return `https://github.com/${repo}/blob/${FRAMES_BRANCH}/${path}?raw=true`
|
|
159
163
|
}
|
|
160
164
|
|
|
165
|
+
/**
|
|
166
|
+
* The file-name slug of a `<page>/<frame>` address, or undefined when the
|
|
167
|
+
* address would not round-trip. Each half must open on a letter or digit and
|
|
168
|
+
* hold only letters, digits, dots, underscores, and dashes, and neither may
|
|
169
|
+
* carry `--`, so the joined slug reads back as the same two names and a path
|
|
170
|
+
* never gains a directory or escapes the pass folder.
|
|
171
|
+
*/
|
|
172
|
+
export function referenceSlug(address: string): string | undefined {
|
|
173
|
+
const parts = address.split('/')
|
|
174
|
+
if (parts.length !== 2) return undefined
|
|
175
|
+
const [page, frame] = parts as [string, string]
|
|
176
|
+
const valid = [page, frame].every(
|
|
177
|
+
(part) => REFERENCE_PART.test(part) && !part.includes('--'),
|
|
178
|
+
)
|
|
179
|
+
return valid ? `${page}--${frame}` : undefined
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Where a design reference capture lands, beside the evidence frames of one
|
|
184
|
+
* pass. The slug repeats for one reference, so a second capture in a pass
|
|
185
|
+
* overwrites only itself. Undefined when the address has no valid slug.
|
|
186
|
+
*/
|
|
187
|
+
export function referencePath(
|
|
188
|
+
number: number,
|
|
189
|
+
head: string,
|
|
190
|
+
pass: string,
|
|
191
|
+
address: string,
|
|
192
|
+
): string | undefined {
|
|
193
|
+
const slug = referenceSlug(address)
|
|
194
|
+
if (slug === undefined) return undefined
|
|
195
|
+
return `pr-${number}/${head.slice(0, SHORT_HEAD_LENGTH)}/${pass}/ref-${slug}.png`
|
|
196
|
+
}
|
|
197
|
+
|
|
161
198
|
/** The frame's bytes, or undefined when the file is absent, empty, or not a PNG. */
|
|
162
199
|
export async function readFrame(path: string): Promise<Uint8Array | undefined> {
|
|
163
200
|
const bytes = await readFile(path).catch(() => undefined)
|
|
@@ -179,17 +216,24 @@ export async function pushFrame(
|
|
|
179
216
|
api: FramesApi,
|
|
180
217
|
input: FrameInput,
|
|
181
218
|
): Promise<PushResult> {
|
|
182
|
-
const path =
|
|
183
|
-
input.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
219
|
+
const path =
|
|
220
|
+
input.reference === undefined
|
|
221
|
+
? framePath(
|
|
222
|
+
input.number,
|
|
223
|
+
input.head,
|
|
224
|
+
input.pass,
|
|
225
|
+
input.box ?? 0,
|
|
226
|
+
input.theme,
|
|
227
|
+
)
|
|
228
|
+
: referencePath(input.number, input.head, input.pass, input.reference)
|
|
229
|
+
if (path === undefined) return { kind: 'refused', reason: 'push-failed' }
|
|
189
230
|
const blob = await api.createBlob(Buffer.from(input.bytes).toString('base64'))
|
|
190
231
|
if (blob === undefined) return { kind: 'refused', reason: 'push-failed' }
|
|
191
232
|
const entry: TreeEntry = { path, mode: '100644', type: 'blob', sha: blob }
|
|
192
|
-
const message =
|
|
233
|
+
const message =
|
|
234
|
+
input.reference === undefined
|
|
235
|
+
? `chore(frames): add box ${input.box} of #${input.number}`
|
|
236
|
+
: `chore(frames): add reference ${input.reference} of #${input.number}`
|
|
193
237
|
|
|
194
238
|
for (let attempt = 0; attempt < ADD_ATTEMPTS; attempt += 1) {
|
|
195
239
|
const tip = await api.readTip()
|