@erclx/canon 5.14.1 → 5.16.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 +3 -2
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/draft-screencast/REQUIREMENT.md +2 -1
- package/claude/skills/draft-screencast/SKILL.md +14 -9
- package/claude/skills/draft-wiki/SKILL.md +1 -1
- package/claude/skills/plan-groundwork/SKILL.md +1 -1
- package/claude/skills/read-frames/SKILL.md +3 -2
- package/claude/skills/record-screencast/REQUIREMENT.md +3 -0
- package/claude/skills/record-screencast/SKILL.md +28 -5
- package/claude/skills/search-craft/REQUIREMENT.md +57 -0
- package/claude/skills/search-craft/SKILL.md +83 -0
- package/docs/agents/canvas.md +36 -0
- package/docs/agents/commands.md +3 -0
- package/docs/agents/demo.md +34 -7
- package/docs/agents/index.md +1 -0
- package/docs/agents/migrate.md +1 -1
- package/docs/agents/sandbox.md +25 -0
- package/docs/target/migrations.md +1 -1
- package/docs/workflow/ai-workflow.md +2 -3
- package/docs/workflow/skill-map.md +2 -1
- package/governance/renames.toml +7 -5
- package/governance/rules/canon/630-requirements.md +1 -1
- package/package.json +5 -2
- package/scripts/lib/sandbox-git.sh +2 -2
- package/src/canvas/address.ts +184 -0
- package/src/canvas/capture.ts +172 -0
- package/src/canvas/client/app.tsx +87 -0
- package/src/canvas/client/inspector.tsx +121 -0
- package/src/canvas/client/layers.tsx +118 -0
- package/src/canvas/client/main.tsx +62 -0
- package/src/canvas/client/pages-panel.tsx +157 -0
- package/src/canvas/client/shell.css +449 -0
- package/src/canvas/client/state.ts +372 -0
- package/src/canvas/client/surface.tsx +465 -0
- package/src/canvas/content.ts +617 -0
- package/src/canvas/index.html +14 -0
- package/src/canvas/server.ts +471 -0
- package/src/canvas/tokens.ts +71 -0
- package/src/claude/cases/misc.ts +5 -0
- package/src/claude/seeds.ts +0 -1
- package/src/cli.ts +2 -0
- package/src/commands/canvas.ts +522 -0
- package/src/commands/claude.ts +9 -14
- package/src/commands/demo.ts +6 -6
- package/src/commands/migrate.ts +1 -1
- package/src/commands/sandbox.ts +108 -0
- package/src/context/classify/prompts.ts +1 -1
- package/src/context/folders.ts +0 -1
- package/src/demo/compile.ts +8 -4
- package/src/demo/container.ts +1 -1
- package/src/gov/renames.ts +3 -2
- package/src/help.ts +1 -0
- package/src/migrate/rename.ts +5 -4
- package/src/record-root.ts +1 -0
- package/src/sandbox/equivalence.ts +847 -0
- package/src/serve/static.ts +25 -11
- package/src/surface-root.ts +0 -1
- package/src/teach/browser/compile.ts +44 -0
- package/src/teach/browser/dismiss.ts +76 -0
- package/src/teach/browser/focus-line.ts +19 -0
- package/src/teach/browser/glossary-filter.ts +44 -0
- package/src/teach/browser/head.ts +30 -0
- package/src/teach/browser/quiz.ts +19 -0
- package/src/teach/browser/sidebar.ts +233 -0
- package/src/teach/browser/theme.ts +18 -0
- package/src/teach/nav.ts +19 -374
- package/standards/architecture.md +1 -1
- package/standards/context.md +1 -1
- package/standards/design.md +1 -1
- package/standards/index.md +0 -1
- package/standards/requirements.md +4 -6
- package/standards/wireframes.md +2 -2
- package/tooling/claude/reference.md +0 -1
- package/tooling/claude/seeds/canon/REQUIREMENTS.md +0 -2
- package/governance/rules/canon/645-decisions.md +0 -12
- package/standards/decisions.md +0 -105
- package/tooling/claude/seeds/canon/decisions/index.md +0 -8
package/src/commands/sandbox.ts
CHANGED
|
@@ -13,6 +13,11 @@ import {
|
|
|
13
13
|
coveragePercent,
|
|
14
14
|
type CoverageReport,
|
|
15
15
|
} from '@/sandbox/coverage'
|
|
16
|
+
import {
|
|
17
|
+
equivalenceExitCode,
|
|
18
|
+
runEquivalence as collectEquivalence,
|
|
19
|
+
type EquivalenceRecord,
|
|
20
|
+
} from '@/sandbox/equivalence'
|
|
16
21
|
import {
|
|
17
22
|
expectFilePath,
|
|
18
23
|
parseTarget,
|
|
@@ -338,6 +343,71 @@ function runCoverage(options: CoverageOptions): void {
|
|
|
338
343
|
process.exitCode = wrongExemption || rolloutIncomplete ? 1 : 0
|
|
339
344
|
}
|
|
340
345
|
|
|
346
|
+
interface EquivalenceOptions {
|
|
347
|
+
readonly base: string
|
|
348
|
+
readonly includeAnchor?: boolean
|
|
349
|
+
readonly out?: string
|
|
350
|
+
readonly masks: boolean
|
|
351
|
+
readonly json?: boolean
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Provisions every arm on a base ref and on this checkout, then names what
|
|
356
|
+
* differs. The record is the only thing on stdout, and a red arm that is red the
|
|
357
|
+
* same way on both sides is reported without failing the run, since it proves
|
|
358
|
+
* nothing either way.
|
|
359
|
+
*/
|
|
360
|
+
async function runEquivalence(
|
|
361
|
+
targets: string[],
|
|
362
|
+
options: EquivalenceOptions,
|
|
363
|
+
): Promise<void> {
|
|
364
|
+
if (reportAbsentScenarioTree()) return
|
|
365
|
+
|
|
366
|
+
intro('canon sandbox equivalence')
|
|
367
|
+
|
|
368
|
+
let record: EquivalenceRecord
|
|
369
|
+
try {
|
|
370
|
+
record = await collectEquivalence({
|
|
371
|
+
root: PROJECT_ROOT,
|
|
372
|
+
targets,
|
|
373
|
+
base: options.base,
|
|
374
|
+
includeAnchor: options.includeAnchor === true,
|
|
375
|
+
useMasks: options.masks,
|
|
376
|
+
out: options.out,
|
|
377
|
+
})
|
|
378
|
+
} catch (error) {
|
|
379
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
380
|
+
logError(`Cannot provision the baseline ${options.base}: ${reason}`)
|
|
381
|
+
outro()
|
|
382
|
+
process.exitCode = 1
|
|
383
|
+
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
logStep(`Base ${record.base} (${record.baseCommit.slice(0, 8)})`)
|
|
388
|
+
logInfo(`canon ${record.canonVersion}`)
|
|
389
|
+
|
|
390
|
+
for (const arm of record.arms) {
|
|
391
|
+
if (arm.state === 'identical' || arm.state === 'skipped-anchor') continue
|
|
392
|
+
const detail = arm.differs.length > 0 ? `: ${arm.differs.join(', ')}` : ''
|
|
393
|
+
if (arm.state === 'differs') logRemove(`${arm.arm} differs${detail}`)
|
|
394
|
+
else logWarn(`${arm.arm} is ${arm.state}`)
|
|
395
|
+
}
|
|
396
|
+
for (const line of record.masksApplied) logInfo(`masked ${line}`)
|
|
397
|
+
for (const error of record.errors) logError(error)
|
|
398
|
+
|
|
399
|
+
const { counts } = record
|
|
400
|
+
logStep('Equivalence')
|
|
401
|
+
const summary = `${counts.identical} identical, ${counts.differs} differs, ${counts['red-on-base']} red-on-base, ${counts['skipped-anchor']} skipped-anchor`
|
|
402
|
+
if (equivalenceExitCode(record) === 0) logInfo(summary)
|
|
403
|
+
else logWarn(summary)
|
|
404
|
+
|
|
405
|
+
if (options.json === true) process.stdout.write(`${JSON.stringify(record)}\n`)
|
|
406
|
+
|
|
407
|
+
outro()
|
|
408
|
+
process.exitCode = equivalenceExitCode(record)
|
|
409
|
+
}
|
|
410
|
+
|
|
341
411
|
function runCheck(
|
|
342
412
|
target: string,
|
|
343
413
|
arm: string | undefined,
|
|
@@ -482,4 +552,42 @@ export function register(program: Command): void {
|
|
|
482
552
|
.action((options: CoverageOptions) => {
|
|
483
553
|
runCoverage(options)
|
|
484
554
|
})
|
|
555
|
+
|
|
556
|
+
sandbox
|
|
557
|
+
.command('equivalence')
|
|
558
|
+
.description(
|
|
559
|
+
'Provision every arm on a base ref and on this checkout, and diff the trees',
|
|
560
|
+
)
|
|
561
|
+
.argument(
|
|
562
|
+
'[target...]',
|
|
563
|
+
'Category or <category>:<command>, defaulting to every category',
|
|
564
|
+
)
|
|
565
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
566
|
+
.option('--base <ref>', 'Ref the baseline is provisioned from', 'main')
|
|
567
|
+
.option(
|
|
568
|
+
'--include-anchor',
|
|
569
|
+
'Also run the arms that push to the shared GitHub anchor, one at a time (attended)',
|
|
570
|
+
)
|
|
571
|
+
.option('--out <dir>', 'Keep the provisioned trees and logs in this folder')
|
|
572
|
+
.option(
|
|
573
|
+
'--no-masks',
|
|
574
|
+
'Compare every path, listing what each mask would hide',
|
|
575
|
+
)
|
|
576
|
+
.option('--json', 'Emit the record as JSON on stdout')
|
|
577
|
+
.addHelpText(
|
|
578
|
+
'after',
|
|
579
|
+
[
|
|
580
|
+
'',
|
|
581
|
+
'Examples:',
|
|
582
|
+
' canon sandbox equivalence claude --json',
|
|
583
|
+
' canon sandbox equivalence claude:plan-feature --base origin/main',
|
|
584
|
+
'',
|
|
585
|
+
'Provisions offline only and never runs sandbox/run.sh or a claude binary.',
|
|
586
|
+
'Exit codes: 0 when every compared arm is identical or red the same way',
|
|
587
|
+
'on both sides, 1 on any difference, exit mismatch, or enumeration error.',
|
|
588
|
+
].join('\n'),
|
|
589
|
+
)
|
|
590
|
+
.action((targets: string[], options: EquivalenceOptions) =>
|
|
591
|
+
runEquivalence(targets, options),
|
|
592
|
+
)
|
|
485
593
|
}
|
|
@@ -20,7 +20,7 @@ File types and what they hold:
|
|
|
20
20
|
Verdicts (pick exactly one):
|
|
21
21
|
- KEEP: states current design, a rule, copy, a decision with its rejected alternative, or a live gotcha, on the right surface.
|
|
22
22
|
- REPLACE: restates a fact or figure the existing section already carries ("moved again", "now", "the count moved", a newer number appended after an older one). The old statement should be rewritten in place, not appended to.
|
|
23
|
-
- HISTORY: narrates how things got here: which branch/PR changed what, "closed on", "did not survive", review-pass stories, pick-by-pick rounds. The current state is what belongs; the trail goes to the PR
|
|
23
|
+
- HISTORY: narrates how things got here: which branch/PR changed what, "closed on", "did not survive", review-pass stories, pick-by-pick rounds. The current state is what belongs; the trail goes to the PR.
|
|
24
24
|
- MOVE: correct content on the wrong surface (domain mechanism in ARCHITECTURE.md, implementation detail in a wireframe).
|
|
25
25
|
|
|
26
26
|
Return only JSON: {"verdict": "...", "quote": "<the shortest added phrase that decided it>", "reason": "<one sentence>"}
|
package/src/context/folders.ts
CHANGED
package/src/demo/compile.ts
CHANGED
|
@@ -134,8 +134,8 @@ export function compilePlan(draft: Draft, options: CompileOptions): DemoPlan {
|
|
|
134
134
|
url: '',
|
|
135
135
|
viewport: VIEWPORT,
|
|
136
136
|
output: {
|
|
137
|
-
video: `${options.outDir}/${options.slug}.webm`,
|
|
138
|
-
still: `${options.outDir}/${options.slug}.png`,
|
|
137
|
+
video: `${options.outDir}/${options.slug}/take/${options.slug}.webm`,
|
|
138
|
+
still: `${options.outDir}/${options.slug}/take/${options.slug}.png`,
|
|
139
139
|
},
|
|
140
140
|
pointer: { travelMs: POINTER_TRAVEL_MS, typeDelayMs: TYPE_DELAY_MS },
|
|
141
141
|
annotations: ANNOTATIONS,
|
|
@@ -252,8 +252,12 @@ export function parsePlan(text: string): PlanParse {
|
|
|
252
252
|
},
|
|
253
253
|
...(colorScheme.value && { colorScheme: colorScheme.value }),
|
|
254
254
|
output: {
|
|
255
|
-
video:
|
|
256
|
-
|
|
255
|
+
video:
|
|
256
|
+
asText(output.video) ||
|
|
257
|
+
`demos/${slug || 'demo'}/take/${slug || 'demo'}.webm`,
|
|
258
|
+
still:
|
|
259
|
+
asText(output.still) ||
|
|
260
|
+
`demos/${slug || 'demo'}/take/${slug || 'demo'}.png`,
|
|
257
261
|
},
|
|
258
262
|
pointer: {
|
|
259
263
|
travelMs: asNumber(pointer.travelMs, POINTER_TRAVEL_MS),
|
package/src/demo/container.ts
CHANGED
|
@@ -135,7 +135,7 @@ export async function convertToGif(
|
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
137
|
* Written beside the video by default, so frames fall under the same
|
|
138
|
-
* `
|
|
138
|
+
* `take` folder gitignore entry the still already uses with no new rule
|
|
139
139
|
* needed. One frame a second by default, matched to the seconds-to-tens-of-
|
|
140
140
|
* seconds length a tuned recording runs, without a smarter sampling strategy
|
|
141
141
|
* nobody has asked for.
|
package/src/gov/renames.ts
CHANGED
|
@@ -34,8 +34,9 @@ export function loadRenames(root: string): Renames {
|
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Follows a declared rename to the name the toolkit ships the rule under now,
|
|
37
|
-
* through every later rename of that name. The ledger is
|
|
38
|
-
* target several releases behind still reaches the
|
|
37
|
+
* through every later rename of that name. The ledger is pruned only once a
|
|
38
|
+
* rule is retired, so a target several releases behind still reaches the
|
|
39
|
+
* current name of every rule that still ships.
|
|
39
40
|
*
|
|
40
41
|
* Returns nothing when the chain ends on a name with no source, or loops,
|
|
41
42
|
* since installing a guess is worse than the retire the caller falls back to.
|
package/src/help.ts
CHANGED
|
@@ -45,6 +45,7 @@ export const COMMAND_GROUPS: { title: string; rows: CommandRow[] }[] = [
|
|
|
45
45
|
['drive <url> <run>', 'Walk a page through interactions, measure each'],
|
|
46
46
|
['transcripts <url>', 'Fetch a YouTube transcript with frontmatter'],
|
|
47
47
|
['teach [cmd]', 'Learning workspaces (list, open, resource, glossary)'],
|
|
48
|
+
['canvas [cmd]', 'Local canvas of pages and frames (serve, list)'],
|
|
48
49
|
],
|
|
49
50
|
},
|
|
50
51
|
{
|
package/src/migrate/rename.ts
CHANGED
|
@@ -168,10 +168,11 @@ const AITK_ARTICLE = /\b([Aa])n(\s+`?)(canon|CANON|Canon)(?![A-Za-z])/g
|
|
|
168
168
|
/**
|
|
169
169
|
* The `aitk` to `canon` rename.
|
|
170
170
|
*
|
|
171
|
-
* `aitk-sandbox` is a
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
171
|
+
* `aitk-sandbox` is the retired spelling of a repository that was later renamed
|
|
172
|
+
* `canon-sandbox`, and GitHub still resolves the old name through a redirect.
|
|
173
|
+
* It has to win against the bare token, and it also has to win against the
|
|
174
|
+
* owner-scoped spelling, so a consumer's text naming `erclx/aitk-sandbox` keeps
|
|
175
|
+
* the form that resolves rather than being rewritten.
|
|
175
176
|
*
|
|
176
177
|
* Case is carried in the map rather than derived, because the uppercase form
|
|
177
178
|
* is an environment variable prefix and the title-case form is a heading word,
|