@erclx/canon 4.6.1 → 4.7.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/canon-feedback-file/SKILL.md +3 -3
- package/claude/skills/canon-feedback-triage/SKILL.md +1 -1
- package/claude/skills/canon-rollout/SKILL.md +1 -1
- package/claude/skills/canon-screencast/SKILL.md +5 -5
- package/claude/skills/canon-slides-draft/SKILL.md +1 -1
- package/claude/skills/claude-address-review/SKILL.md +4 -4
- package/claude/skills/claude-autoship/SKILL.md +17 -17
- package/claude/skills/claude-design-extract/SKILL.md +2 -2
- package/claude/skills/claude-diagram/SKILL.md +13 -13
- package/claude/skills/claude-docs/SKILL.md +23 -23
- package/claude/skills/claude-feature/SKILL.md +6 -6
- package/claude/skills/claude-groundwork/SKILL.md +16 -16
- package/claude/skills/claude-intake/SKILL.md +8 -8
- package/claude/skills/claude-intake-answer/SKILL.md +1 -1
- package/claude/skills/claude-markdown-propose/REQUIREMENT.md +2 -2
- package/claude/skills/claude-markdown-propose/SKILL.md +8 -8
- package/claude/skills/claude-markdown-propose/references/format.md +3 -3
- package/claude/skills/claude-memory-capture/SKILL.md +13 -13
- package/claude/skills/claude-memory-review/SKILL.md +28 -28
- package/claude/skills/claude-memory-review/references/receipt-format.md +1 -1
- package/claude/skills/claude-orchestrate/SKILL.md +9 -9
- package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +1 -1
- package/claude/skills/claude-orchestrate/references/orchestrator-parked.md +1 -1
- package/claude/skills/claude-orchestrate/references/orchestrator-poll.md +1 -1
- package/claude/skills/claude-orchestrate/references/orchestrator-resume.md +1 -1
- package/claude/skills/claude-orchestrate/scripts/poll.sh +1 -1
- package/claude/skills/claude-planner/REQUIREMENT.md +1 -1
- package/claude/skills/claude-planner/SKILL.md +6 -6
- package/claude/skills/claude-pr-review/SKILL.md +5 -5
- package/claude/skills/claude-review/SKILL.md +4 -4
- package/claude/skills/claude-seed-sync/SKILL.md +2 -2
- package/claude/skills/claude-tasks/SKILL.md +16 -16
- package/claude/skills/claude-teach/SKILL.md +9 -9
- package/claude/skills/claude-ui-test/SKILL.md +3 -3
- package/claude/skills/claude-ux-audit/SKILL.md +5 -5
- package/claude/skills/claude-ux-measure/SKILL.md +3 -3
- package/claude/skills/claude-worker/SKILL.md +2 -2
- package/claude/skills/claude-worktree/SKILL.md +3 -3
- package/claude/skills/git-issue/SKILL.md +4 -4
- package/claude/skills/git-pr/SKILL.md +6 -6
- package/claude/skills/git-ship/SKILL.md +3 -3
- package/claude/skills/git-split/REQUIREMENT.md +1 -1
- package/claude/skills/git-split/SKILL.md +20 -20
- package/claude/skills/migration-superseded/SKILL.md +4 -4
- package/claude/skills/session-map/SKILL.md +2 -2
- package/claude/skills/session-resume/SKILL.md +8 -8
- package/docs/agents/capture.md +1 -1
- package/docs/agents/commands.md +5 -2
- package/docs/agents/demo.md +1 -1
- package/docs/agents/indexes.md +1 -1
- package/docs/agents/install-and-sync.md +1 -1
- package/docs/agents/intake.md +1 -1
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/records.md +12 -9
- package/docs/agents/restated.md +2 -2
- package/docs/agents/rule-citations.md +1 -1
- package/docs/agents/sandbox.md +1 -1
- package/docs/agents/tasks.md +6 -6
- package/docs/agents/teach.md +4 -4
- package/docs/ai-workflow.md +26 -19
- package/docs/operating-model.md +3 -3
- package/docs/target-projects.md +22 -2
- package/docs/visual-design-workflow.md +1 -1
- package/governance/rules/claude/555-tasks.md +3 -3
- package/governance/rules/claude/556-groundwork.md +2 -2
- package/governance/rules/claude/557-intake.md +2 -2
- package/governance/rules/claude/558-plan.md +3 -3
- package/governance/rules/claude/559-memory.md +2 -2
- package/governance/rules/claude/560-diagrams.md +3 -3
- package/governance/rules/claude/561-teach.md +2 -2
- package/governance/rules/claude/562-session.md +2 -2
- package/governance/rules/core/035-tasks.md +2 -2
- package/governance/rules/core/045-memory.md +3 -3
- package/governance/rules/core/055-scratch.md +2 -1
- package/governance/rules/core/085-worktrees.md +1 -1
- package/package.json +1 -1
- package/scripts/core/check-ignore-parity.sh +24 -51
- package/scripts/core/install-check.sh +4 -4
- package/scripts/core/snapshot.sh +1 -1
- package/scripts/manage-sandbox.sh +1 -1
- package/scripts/tooling/verify.sh +2 -2
- package/snippets/claude/decision-memo.md +1 -1
- package/snippets/claude/feature-recap.md +1 -1
- package/src/audits/catalog.ts +1 -1
- package/src/claude/cases/all.ts +1 -1
- package/src/claude/seeds.ts +13 -2
- package/src/claude/skills-rank.ts +1 -1
- package/src/cli.ts +4 -4
- package/src/commands/demo.ts +1 -1
- package/src/commands/design.ts +1 -1
- package/src/commands/feedback.ts +5 -5
- package/src/commands/gov.ts +17 -6
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +173 -2
- package/src/commands/pr.ts +1 -1
- package/src/commands/records.ts +1 -1
- package/src/commands/serve.ts +2 -2
- package/src/commands/slides.ts +1 -1
- package/src/commands/tasks.ts +5 -5
- package/src/commands/teach.ts +1 -1
- package/src/context/audit.ts +1 -1
- package/src/demo/compile.ts +1 -1
- package/src/gov/citations.ts +1 -1
- package/src/gov/restated.ts +3 -3
- package/src/migrate/apply.ts +67 -1
- package/src/migrate/records.ts +276 -0
- package/src/paths.ts +1 -1
- package/src/pr/paths.ts +1 -1
- package/src/record-root.ts +77 -27
- package/src/records/backup.ts +71 -17
- package/src/records/validate.ts +1 -1
- package/src/sync/layout.ts +11 -2
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +20 -3
- package/standards/diagrams.md +4 -4
- package/standards/glossary.md +1 -1
- package/standards/groundwork.md +3 -3
- package/standards/index.md +2 -2
- package/standards/intake.md +3 -3
- package/standards/memory.md +2 -2
- package/standards/plan.md +3 -3
- package/standards/session.md +2 -2
- package/standards/standard.md +1 -1
- package/standards/tasks.md +9 -9
- package/standards/teach.md +2 -2
- package/standards/versioning.md +1 -1
- package/tooling/base/configs/.husky/post-merge +1 -1
- package/tooling/base/reference.md +1 -1
- package/tooling/claude/manifest.toml +13 -3
- package/tooling/claude/reference.md +28 -15
- package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +8 -8
|
@@ -16,7 +16,7 @@ show_help() {
|
|
|
16
16
|
echo -e "${GREY}│${NC} --keep ${GREY}# Keep tmp dir after run for inspection${NC}"
|
|
17
17
|
echo -e "${GREY}│${NC} -h, --help ${GREY}# Show this help message${NC}"
|
|
18
18
|
echo -e "${GREY}│${NC}"
|
|
19
|
-
echo -e "${GREY}│${NC} Scaffolds <stack> into .
|
|
19
|
+
echo -e "${GREY}│${NC} Scaffolds <stack> into .canon/tmp/verify-<stack>/, syncs the full"
|
|
20
20
|
echo -e "${GREY}│${NC} layer chain, and runs check + test:e2e + screenshot."
|
|
21
21
|
echo -e "${GREY}└${NC}"
|
|
22
22
|
exit 0
|
|
@@ -100,7 +100,7 @@ main() {
|
|
|
100
100
|
open_timeline "canon tooling verify $stack"
|
|
101
101
|
trap close_timeline EXIT
|
|
102
102
|
|
|
103
|
-
local tmp_root="$PROJECT_ROOT/.
|
|
103
|
+
local tmp_root="$PROJECT_ROOT/.canon/tmp"
|
|
104
104
|
local tmp_dir="$tmp_root/verify-$stack"
|
|
105
105
|
|
|
106
106
|
mkdir -p "$tmp_root"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Produce a decision memo for a research or should-we question. This is the decision-heavy counterpart to `claude-feature`, which plans a build. Write one file per independent concern under `.
|
|
1
|
+
Produce a decision memo for a research or should-we question. This is the decision-heavy counterpart to `claude-feature`, which plans a build. Write one file per independent concern under `.canon/plans/feature-<slug>.md`, or answer inline when the plan is small. Use the same plans folder as `claude-feature`.
|
|
2
2
|
|
|
3
3
|
Read the current state from the code and docs before recommending. Do not assume it.
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Recap the implementation this session completed. Read the plan file referenced in the session (usually `.
|
|
1
|
+
Recap the implementation this session completed. Read the plan file referenced in the session (usually `.canon/plans/feature-<slug>.md`) and produce a verification block.
|
|
2
2
|
|
|
3
3
|
Output this shape, nothing else:
|
|
4
4
|
|
package/src/audits/catalog.ts
CHANGED
|
@@ -488,7 +488,7 @@ export const AUDITS: readonly AuditSpec[] = [
|
|
|
488
488
|
corpus: 'tracked',
|
|
489
489
|
// The one reason this verb refuses for that is an absence rather than a
|
|
490
490
|
// break, on the same test the reach check and the skill audit take: no
|
|
491
|
-
// target adopts `.claude/context/`, `.
|
|
491
|
+
// target adopts `.claude/context/`, `.canon/diagrams/`, and
|
|
492
492
|
// `.claude/wireframes/` all at once, so without the allowance every such
|
|
493
493
|
// project reports the verb unmeasured on every run and never changes.
|
|
494
494
|
absentReasons: ['no-folders'] satisfies ContextAuditRefusal[],
|
package/src/claude/cases/all.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { SETUP_CASES } from '@/claude/cases/setup'
|
|
|
12
12
|
* Bootstrapped from each skill's own quoted trigger phrases and then
|
|
13
13
|
* rephrased and supplemented rather than shipped verbatim, since a positive
|
|
14
14
|
* lifted unchanged from a trigger passes by construction and never tests a
|
|
15
|
-
* person's own words. See `.
|
|
15
|
+
* person's own words. See `.canon/groundwork/42-ai-blueprint/08-spikes.md`
|
|
16
16
|
* for the extraction arm this corpus replaces as the shipped measure.
|
|
17
17
|
*/
|
|
18
18
|
export const SKILL_CASES: readonly SkillCase[] = [
|
package/src/claude/seeds.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs'
|
|
|
2
2
|
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
3
|
import { dirname, join } from 'node:path'
|
|
4
4
|
import { copyPreservingMode } from '@/copy'
|
|
5
|
+
import { creationRel, isRecordEntry } from '@/record-root'
|
|
5
6
|
import { rewritesOnInstall, stripSeedMarker } from '@/seed-marker'
|
|
6
7
|
|
|
7
8
|
const SEEDS_DIR = join('tooling', 'claude', 'seeds')
|
|
@@ -81,13 +82,23 @@ export function planSeeds(root: string, target: string): SeedEntry[] {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
for (const subdir of SUBDIRS) {
|
|
85
|
+
// The seed tree authors every subdirectory under `.claude/`, and three of
|
|
86
|
+
// them are record folders that install under the record root instead. A
|
|
87
|
+
// target that has not migrated resolves back to `.claude/`, so the same seed
|
|
88
|
+
// lands beside the records already there rather than opening a second root.
|
|
89
|
+
// Scaffolding one under `.claude/` now would also land it outside the single
|
|
90
|
+
// `.canon/` ignore entry a target receives, which tracks the memory pen.
|
|
91
|
+
const installRel = isRecordEntry(subdir)
|
|
92
|
+
? creationRel(target, subdir)
|
|
93
|
+
: join(CLAUDE_DIR, subdir)
|
|
94
|
+
|
|
84
95
|
for (const name of listLevel(join(source, subdir))) {
|
|
85
96
|
const rel = `${subdir}/${name}`
|
|
86
97
|
seeds.push({
|
|
87
98
|
src: join(source, subdir, name),
|
|
88
|
-
dest: join(
|
|
99
|
+
dest: join(target, installRel, name),
|
|
89
100
|
scanLabel: rel,
|
|
90
|
-
applyLabel: join(
|
|
101
|
+
applyLabel: join(installRel, name),
|
|
91
102
|
scope: 'claude',
|
|
92
103
|
executable: subdir === HOOKS,
|
|
93
104
|
})
|
|
@@ -8,7 +8,7 @@ import { listSkillsAt, resolveSkillsCorpus } from '@/claude/skills-list'
|
|
|
8
8
|
* asks whether the descriptions are separable by the words they use, and
|
|
9
9
|
* Claude Code does not route this way.
|
|
10
10
|
*
|
|
11
|
-
* Ported from `.
|
|
11
|
+
* Ported from `.canon/groundwork/42-ai-blueprint/evidence/rank.ts`, which
|
|
12
12
|
* ran once against this catalog and named the collisions this measure now
|
|
13
13
|
* tracks on a cadence.
|
|
14
14
|
*/
|
package/src/cli.ts
CHANGED
|
@@ -72,11 +72,11 @@ function showHelp(): void {
|
|
|
72
72
|
`${GREY}│${NC} demo [cmd] ${GREY}# Record a running app (compile, run)${NC}`,
|
|
73
73
|
`${GREY}│${NC} inventory [subj] ${GREY}# Report one computed property across every route${NC}`,
|
|
74
74
|
`${GREY}│${NC} drive <url> <run> ${GREY}# Walk a page through named interactions and measure each state${NC}`,
|
|
75
|
-
`${GREY}│${NC} feedback ${GREY}# Write toolkit feedback from stdin to .
|
|
75
|
+
`${GREY}│${NC} feedback ${GREY}# Write toolkit feedback from stdin to .canon/review/feedback/${NC}`,
|
|
76
76
|
`${GREY}│${NC} transcripts <url> ${GREY}# Fetch a YouTube transcript with metadata frontmatter${NC}`,
|
|
77
77
|
`${GREY}│${NC} tasks [cmd] ${GREY}# Task board commands (archive)${NC}`,
|
|
78
|
-
`${GREY}│${NC} intake [cmd] ${GREY}# Intake folders under .
|
|
79
|
-
`${GREY}│${NC} teach [cmd] ${GREY}# Learning workspaces under .
|
|
78
|
+
`${GREY}│${NC} intake [cmd] ${GREY}# Intake folders under .canon/intake/ (list, answer)${NC}`,
|
|
79
|
+
`${GREY}│${NC} teach [cmd] ${GREY}# Learning workspaces under .canon/teach/ (list, open, resource, glossary)${NC}`,
|
|
80
80
|
`${GREY}│${NC} comments [cmd] ${GREY}# Measure comment density and trend (scan)${NC}`,
|
|
81
81
|
`${GREY}│${NC} context [cmd] ${GREY}# Report context folder health (audit)${NC}`,
|
|
82
82
|
`${GREY}│${NC} markdown [cmd] ${GREY}# Report markdown against the attribute standards (audit)${NC}`,
|
|
@@ -121,7 +121,7 @@ function showHelp(): void {
|
|
|
121
121
|
`${GREY}│${NC} canon slides render`,
|
|
122
122
|
`${GREY}│${NC} canon slides list --json`,
|
|
123
123
|
`${GREY}│${NC} canon capture assets/install.html`,
|
|
124
|
-
`${GREY}│${NC} canon serve .
|
|
124
|
+
`${GREY}│${NC} canon serve .canon/teach`,
|
|
125
125
|
`${GREY}│${NC} canon inventory focus --json`,
|
|
126
126
|
`${GREY}│${NC} canon drive http://localhost:4173 run.json --json`,
|
|
127
127
|
`${GREY}│${NC} pbpaste | canon feedback`,
|
package/src/commands/demo.ts
CHANGED
|
@@ -61,7 +61,7 @@ export function register(program: Command): void {
|
|
|
61
61
|
' 1 refused, with the reason on stderr',
|
|
62
62
|
'',
|
|
63
63
|
'Examples:',
|
|
64
|
-
' canon demo compile .
|
|
64
|
+
' canon demo compile .canon/tmp/screencast/inline-edit.md',
|
|
65
65
|
' canon demo compile draft.md --out demos --force',
|
|
66
66
|
'',
|
|
67
67
|
].join('\n'),
|
package/src/commands/design.ts
CHANGED
|
@@ -17,7 +17,7 @@ export function register(program: Command): void {
|
|
|
17
17
|
.option(
|
|
18
18
|
'-o, --out <path>',
|
|
19
19
|
'Output directory',
|
|
20
|
-
creationRel('review', 'design'),
|
|
20
|
+
creationRel(process.cwd(), 'review', 'design'),
|
|
21
21
|
)
|
|
22
22
|
.action((opts: { source: string; out: string }) => {
|
|
23
23
|
const sourcePath = resolve(process.cwd(), opts.source)
|
package/src/commands/feedback.ts
CHANGED
|
@@ -40,10 +40,10 @@ function isToolkitSource(): boolean {
|
|
|
40
40
|
* single ignore entry and the single backed-folder entry it already had.
|
|
41
41
|
*/
|
|
42
42
|
function writeLocal(body: string): string {
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
const relativeDir = creationRel('review', 'feedback')
|
|
43
|
+
// Resolved against the same root the write joins onto, so a checkout that has
|
|
44
|
+
// not migrated its records writes this beside the ones already there rather
|
|
45
|
+
// than opening a second root nothing reads.
|
|
46
|
+
const relativeDir = creationRel(PROJECT_ROOT, 'review', 'feedback')
|
|
47
47
|
const reviewDir = join(PROJECT_ROOT, relativeDir)
|
|
48
48
|
mkdirSync(reviewDir, { recursive: true })
|
|
49
49
|
const filename = `feedback-${deriveSlug(body)}-${timestamp()}.md`
|
|
@@ -57,7 +57,7 @@ export function register(program: Command): void {
|
|
|
57
57
|
program
|
|
58
58
|
.command('feedback')
|
|
59
59
|
.description(
|
|
60
|
-
'Write toolkit feedback from stdin to .
|
|
60
|
+
'Write toolkit feedback from stdin to .canon/review/feedback/, or open a GitHub issue with --github',
|
|
61
61
|
)
|
|
62
62
|
.option(
|
|
63
63
|
'--github',
|
package/src/commands/gov.ts
CHANGED
|
@@ -66,7 +66,17 @@ import {
|
|
|
66
66
|
select,
|
|
67
67
|
} from '@/ui'
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Where the rules payload lands, resolved per target rather than once at import.
|
|
71
|
+
*
|
|
72
|
+
* The record root a project uses is a fact about that project, and this command
|
|
73
|
+
* writes into a target the caller named, so a value computed here against this
|
|
74
|
+
* process's own tree would point every target at whichever root the toolkit
|
|
75
|
+
* happens to hold.
|
|
76
|
+
*/
|
|
77
|
+
function payloadRel(root: string): string {
|
|
78
|
+
return creationRel(root, SCRATCH, 'gov', 'rules.md')
|
|
79
|
+
}
|
|
70
80
|
const RULES_REL = join('.claude', 'rules')
|
|
71
81
|
|
|
72
82
|
interface InstallOptions {
|
|
@@ -170,7 +180,7 @@ export function register(program: Command): void {
|
|
|
170
180
|
|
|
171
181
|
gov
|
|
172
182
|
.command('build')
|
|
173
|
-
.description('Concatenate installed rules into .
|
|
183
|
+
.description('Concatenate installed rules into .canon/tmp/gov/rules.md')
|
|
174
184
|
.argument('[target]', 'Target directory', '.')
|
|
175
185
|
.helpOption('-h, --help', 'Show this help message')
|
|
176
186
|
.action(async (target: string) => {
|
|
@@ -1222,6 +1232,7 @@ async function runBuild(target: string): Promise<number> {
|
|
|
1222
1232
|
intro('canon gov build')
|
|
1223
1233
|
|
|
1224
1234
|
const resolved = resolve(target)
|
|
1235
|
+
const payload = payloadRel(resolved)
|
|
1225
1236
|
const rulesDir = join(resolved, RULES_REL)
|
|
1226
1237
|
|
|
1227
1238
|
if (!existsSync(rulesDir)) {
|
|
@@ -1237,7 +1248,7 @@ async function runBuild(target: string): Promise<number> {
|
|
|
1237
1248
|
}
|
|
1238
1249
|
|
|
1239
1250
|
const shouldBuild = await select({
|
|
1240
|
-
message: `Build ${files.length} rules to ${
|
|
1251
|
+
message: `Build ${files.length} rules to ${payload}?`,
|
|
1241
1252
|
options: [
|
|
1242
1253
|
{ value: true, label: 'Yes' },
|
|
1243
1254
|
{ value: false, label: 'No' },
|
|
@@ -1252,15 +1263,15 @@ async function runBuild(target: string): Promise<number> {
|
|
|
1252
1263
|
}
|
|
1253
1264
|
|
|
1254
1265
|
logStep('Building rules payload')
|
|
1255
|
-
const output = join(resolved,
|
|
1266
|
+
const output = join(resolved, payload)
|
|
1256
1267
|
await mkdir(dirname(output), { recursive: true })
|
|
1257
1268
|
await writeFile(output, buildRulesPayload(files))
|
|
1258
|
-
logAdd(
|
|
1269
|
+
logAdd(payload)
|
|
1259
1270
|
|
|
1260
1271
|
const { GREEN, NC } = palette(process.stderr)
|
|
1261
1272
|
outro()
|
|
1262
1273
|
process.stderr.write(
|
|
1263
|
-
`${GREEN}✓ Rules built (${files.length} rules → ${
|
|
1274
|
+
`${GREEN}✓ Rules built (${files.length} rules → ${payload})${NC}\n`,
|
|
1264
1275
|
)
|
|
1265
1276
|
return 0
|
|
1266
1277
|
}
|
package/src/commands/intake.ts
CHANGED
|
@@ -47,7 +47,7 @@ interface AnswerCommandOptions {
|
|
|
47
47
|
export function register(program: Command): void {
|
|
48
48
|
const intake = program
|
|
49
49
|
.command('intake')
|
|
50
|
-
.description('Read and answer intake folders in .
|
|
50
|
+
.description('Read and answer intake folders in .canon/intake/')
|
|
51
51
|
.helpOption('-h, --help', 'Show this help message')
|
|
52
52
|
|
|
53
53
|
intake
|
package/src/commands/migrate.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
1
3
|
import type { Command } from 'commander'
|
|
2
4
|
import { listRepositoryFiles } from '@/git-files'
|
|
3
|
-
import { applyRename, readSources } from '@/migrate/apply'
|
|
5
|
+
import { applyRecordsMove, applyRename, readSources } from '@/migrate/apply'
|
|
4
6
|
import { isToolkitOwned, planRename, type RenamePlan } from '@/migrate/plan'
|
|
7
|
+
import {
|
|
8
|
+
ignoresDestination,
|
|
9
|
+
planRecordsMove,
|
|
10
|
+
type RecordsPlan,
|
|
11
|
+
} from '@/migrate/records'
|
|
5
12
|
import { logError, logInfo, logStep, logWarn, pipeOutput, plural } from '@/ui'
|
|
6
13
|
|
|
7
14
|
interface RenameOptions {
|
|
@@ -128,12 +135,176 @@ function toRecord(
|
|
|
128
135
|
}
|
|
129
136
|
}
|
|
130
137
|
|
|
138
|
+
interface RecordsOptions {
|
|
139
|
+
readonly json?: boolean
|
|
140
|
+
readonly write?: boolean
|
|
141
|
+
readonly root?: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Moves a project's session records to `.canon/` and repoints what cites them.
|
|
146
|
+
*
|
|
147
|
+
* The ignore gate runs before anything is planned. Every folder being moved is
|
|
148
|
+
* ignored where it stands, so landing them under a root the project does not
|
|
149
|
+
* ignore publishes the memory pen into the next commit, and reporting a plan
|
|
150
|
+
* the caller cannot safely apply is worse than refusing to draw one.
|
|
151
|
+
*/
|
|
152
|
+
async function runRecords(opts: RecordsOptions): Promise<number> {
|
|
153
|
+
const root = opts.root ?? process.cwd()
|
|
154
|
+
|
|
155
|
+
const gitignore = readGitignore(root)
|
|
156
|
+
if (gitignore === undefined) {
|
|
157
|
+
logError(
|
|
158
|
+
`No .gitignore at ${root}, so the records cannot be moved somewhere they stay ignored.`,
|
|
159
|
+
)
|
|
160
|
+
return 1
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!ignoresDestination(gitignore)) {
|
|
164
|
+
logError(
|
|
165
|
+
'This project does not ignore .canon/, and every record folder being moved is ignored where it stands.',
|
|
166
|
+
)
|
|
167
|
+
logError(
|
|
168
|
+
'Run canon tooling sync --write to take the ignore entry, then run this again.',
|
|
169
|
+
)
|
|
170
|
+
return 1
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const files = await listRepositoryFiles(root)
|
|
174
|
+
if (files === undefined) {
|
|
175
|
+
logError(`Could not list files under ${root}. Is it a git repository?`)
|
|
176
|
+
return 1
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const plan = planRecordsMove(root, await readSources(root, files))
|
|
180
|
+
|
|
181
|
+
// stdout, so the record pipes clean. `pipeOutput` frames to stderr, which is
|
|
182
|
+
// where this command's report belongs and where a JSON record does not.
|
|
183
|
+
if (opts.json) {
|
|
184
|
+
process.stdout.write(
|
|
185
|
+
`${JSON.stringify(toRecordsRecord(plan, opts.write))}\n`,
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
reportRecords(plan)
|
|
190
|
+
|
|
191
|
+
if (plan.collisions.length > 0) {
|
|
192
|
+
logError(
|
|
193
|
+
`${plural(plan.collisions.length, 'destination')} already exist under .canon/. Merging two record folders is not a call this verb takes.`,
|
|
194
|
+
)
|
|
195
|
+
for (const path of plan.collisions) logError(` ${path}`)
|
|
196
|
+
return 1
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (plan.moves.length === 0 && plan.entries.length === 0) return 0
|
|
200
|
+
|
|
201
|
+
if (!opts.write) {
|
|
202
|
+
logWarn('Nothing was written. Pass --write to apply this plan.')
|
|
203
|
+
return 2
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const applied = await applyRecordsMove(root, plan)
|
|
207
|
+
logStep(
|
|
208
|
+
`Rewrote ${plural(applied.written, 'file')} and moved ${plural(applied.moved, 'folder')}.`,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
// `failed` carries a citation the write loop could not land as well as a
|
|
212
|
+
// folder the rename loop stopped on, so the line names a path rather than a
|
|
213
|
+
// folder. Reading a rejected write back as a folder that would not move sends
|
|
214
|
+
// the reader looking at the wrong half of the verb.
|
|
215
|
+
if (applied.failed.length > 0) {
|
|
216
|
+
logError(`Could not write ${plural(applied.failed.length, 'path')}.`)
|
|
217
|
+
for (const path of applied.failed) logError(` ${path}`)
|
|
218
|
+
return 1
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return 0
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function readGitignore(root: string): string | undefined {
|
|
225
|
+
try {
|
|
226
|
+
return readFileSync(join(root, '.gitignore'), 'utf8')
|
|
227
|
+
} catch {
|
|
228
|
+
return undefined
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function reportRecords(plan: RecordsPlan): void {
|
|
233
|
+
logInfo(`${plural(plan.moves.length, 'folder')} to move.`)
|
|
234
|
+
for (const move of plan.moves) logInfo(` ${move.from} -> ${move.to}`)
|
|
235
|
+
logInfo(
|
|
236
|
+
`${plural(plan.entries.length, 'file')} to change, ${plural(plan.rewritten, 'citation')} to rewrite.`,
|
|
237
|
+
)
|
|
238
|
+
logInfo(`${plural(plan.kept, 'citation')} marked to keep the old root.`)
|
|
239
|
+
|
|
240
|
+
if (plan.excluded.length > 0) {
|
|
241
|
+
logInfo(`${plural(plan.excluded.length, 'file')} excluded from the sweep.`)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function toRecordsRecord(
|
|
246
|
+
plan: RecordsPlan,
|
|
247
|
+
wrote: boolean | undefined,
|
|
248
|
+
): unknown {
|
|
249
|
+
return {
|
|
250
|
+
ok: plan.collisions.length === 0,
|
|
251
|
+
wrote: wrote === true,
|
|
252
|
+
moves: plan.moves,
|
|
253
|
+
collisions: plan.collisions,
|
|
254
|
+
files: plan.entries.length,
|
|
255
|
+
rewritten: plan.rewritten,
|
|
256
|
+
kept: plan.kept,
|
|
257
|
+
excluded: plan.excluded.length,
|
|
258
|
+
paths: plan.entries.map((entry) => ({
|
|
259
|
+
path: entry.path,
|
|
260
|
+
rewritten: entry.rewritten,
|
|
261
|
+
})),
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
131
265
|
export function register(program: Command): void {
|
|
132
266
|
const migrate = program
|
|
133
267
|
.command('migrate')
|
|
134
|
-
.description('Move a project off
|
|
268
|
+
.description('Move a project off a retired name or layout')
|
|
135
269
|
.helpOption('-h, --help', 'Show this help message')
|
|
136
270
|
|
|
271
|
+
migrate
|
|
272
|
+
.command('records')
|
|
273
|
+
.description('Move the gitignored session records to .canon/')
|
|
274
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
275
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
276
|
+
.option('--write', 'Apply the plan rather than reporting it')
|
|
277
|
+
.option(
|
|
278
|
+
'--root <path>',
|
|
279
|
+
'Project root, defaulting to the working directory',
|
|
280
|
+
)
|
|
281
|
+
.addHelpText(
|
|
282
|
+
'after',
|
|
283
|
+
[
|
|
284
|
+
'',
|
|
285
|
+
'Exit codes:',
|
|
286
|
+
' 0 nothing to move, or --write applied the whole plan',
|
|
287
|
+
' 1 refused, or a move failed',
|
|
288
|
+
' 2 a plan exists and --write was not passed',
|
|
289
|
+
'',
|
|
290
|
+
'The project has to ignore .canon/ before anything moves. Every folder',
|
|
291
|
+
'this relocates is ignored where it stands, and landing one under a root',
|
|
292
|
+
'the project tracks publishes the memory pen into the next commit.',
|
|
293
|
+
'',
|
|
294
|
+
'A line carrying canon-keep-record-root, or the line below it, keeps the',
|
|
295
|
+
'old root. Prose that dates a decision needs it; a live path does not.',
|
|
296
|
+
'',
|
|
297
|
+
'Examples:',
|
|
298
|
+
' canon migrate records',
|
|
299
|
+
' canon migrate records --write',
|
|
300
|
+
' canon migrate records --json',
|
|
301
|
+
'',
|
|
302
|
+
].join('\n'),
|
|
303
|
+
)
|
|
304
|
+
.action(async (opts: RecordsOptions) => {
|
|
305
|
+
process.exitCode = await runRecords(opts)
|
|
306
|
+
})
|
|
307
|
+
|
|
137
308
|
migrate
|
|
138
309
|
.command('rename')
|
|
139
310
|
.description('Rewrite every unprotected aitk token to canon')
|
package/src/commands/pr.ts
CHANGED
|
@@ -130,7 +130,7 @@ export function register(program: Command): void {
|
|
|
130
130
|
'Examples:',
|
|
131
131
|
' canon pr key-changes',
|
|
132
132
|
' canon pr key-changes 1265 --json',
|
|
133
|
-
' canon pr key-changes --body .
|
|
133
|
+
' canon pr key-changes --body .canon/tmp/body.md --base origin/main',
|
|
134
134
|
'',
|
|
135
135
|
].join('\n'),
|
|
136
136
|
)
|
package/src/commands/records.ts
CHANGED
|
@@ -226,7 +226,7 @@ function backupHelp(verb: 'push' | 'pull'): string {
|
|
|
226
226
|
'',
|
|
227
227
|
...direction,
|
|
228
228
|
'',
|
|
229
|
-
'The history lives in a second git directory at .
|
|
229
|
+
'The history lives in a second git directory at .canon/.records.git, pointed at a',
|
|
230
230
|
'private repository a person creates once. The verbs refuse with the setup command',
|
|
231
231
|
'when it is absent, and refuse when its origin is also a remote of this project.',
|
|
232
232
|
'',
|
package/src/commands/serve.ts
CHANGED
|
@@ -36,8 +36,8 @@ export function register(program: Command): void {
|
|
|
36
36
|
'starts it in the background and reads the record off stdout.',
|
|
37
37
|
'',
|
|
38
38
|
'Examples:',
|
|
39
|
-
' canon serve .
|
|
40
|
-
' canon serve .
|
|
39
|
+
' canon serve .canon/teach',
|
|
40
|
+
' canon serve .canon/teach --entry 03-fde-system-design/index.html',
|
|
41
41
|
' canon serve dist --port 4000 --json',
|
|
42
42
|
'',
|
|
43
43
|
].join('\n'),
|
package/src/commands/slides.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function register(program: Command): void {
|
|
|
20
20
|
.option(
|
|
21
21
|
'-o, --out <path>',
|
|
22
22
|
'Output directory',
|
|
23
|
-
creationRel('review', 'slides'),
|
|
23
|
+
creationRel(process.cwd(), 'review', 'slides'),
|
|
24
24
|
)
|
|
25
25
|
.option('-v, --variant <variant>', 'Override variant (light or dark)')
|
|
26
26
|
.option(
|
package/src/commands/tasks.ts
CHANGED
|
@@ -76,7 +76,7 @@ interface OutcomeCommandOptions {
|
|
|
76
76
|
export function register(program: Command): void {
|
|
77
77
|
const tasks = program
|
|
78
78
|
.command('tasks')
|
|
79
|
-
.description('Manage the task board in .
|
|
79
|
+
.description('Manage the task board in .canon/tasks/')
|
|
80
80
|
.helpOption('-h, --help', 'Show this help message')
|
|
81
81
|
|
|
82
82
|
tasks
|
|
@@ -157,8 +157,8 @@ export function register(program: Command): void {
|
|
|
157
157
|
'',
|
|
158
158
|
'Locations:',
|
|
159
159
|
' unstated the task carries no Plan: line',
|
|
160
|
-
' live the target resolves inside .
|
|
161
|
-
' archived the target resolves inside .
|
|
160
|
+
' live the target resolves inside .canon/plans/',
|
|
161
|
+
' archived the target resolves inside .canon/plans/archive/',
|
|
162
162
|
' outside the target resolves somewhere else',
|
|
163
163
|
'',
|
|
164
164
|
'Exit codes:',
|
|
@@ -201,11 +201,11 @@ export function register(program: Command): void {
|
|
|
201
201
|
'exit code, which a shell function wrapping canon can flatten to zero.',
|
|
202
202
|
'',
|
|
203
203
|
'A relative path resolves against the project root first and against',
|
|
204
|
-
'.
|
|
204
|
+
'.canon/tasks/ second, so the ../plans/ link a board row writes works.',
|
|
205
205
|
'',
|
|
206
206
|
'Examples:',
|
|
207
207
|
' canon tasks plan-answers dispatch-answer-gate',
|
|
208
|
-
' canon tasks plan-answers .
|
|
208
|
+
' canon tasks plan-answers .canon/plans/feature-dispatch-answer-gate.md',
|
|
209
209
|
' canon tasks plan-answers ../plans/feature-dispatch-answer-gate.md',
|
|
210
210
|
' canon tasks plan-answers dispatch-answer-gate --json',
|
|
211
211
|
'',
|
package/src/commands/teach.ts
CHANGED
|
@@ -76,7 +76,7 @@ interface GlossaryCommandOptions {
|
|
|
76
76
|
export function register(program: Command): void {
|
|
77
77
|
const teach = program
|
|
78
78
|
.command('teach')
|
|
79
|
-
.description('Manage learning workspaces in .
|
|
79
|
+
.description('Manage learning workspaces in .canon/teach/')
|
|
80
80
|
.helpOption('-h, --help', 'Show this help message')
|
|
81
81
|
|
|
82
82
|
teach
|
package/src/context/audit.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { isStubSeed } from '@/seed-marker'
|
|
|
19
19
|
* Every reason `canon context audit` refuses for.
|
|
20
20
|
*
|
|
21
21
|
* `no-folders` is the one ordinary absence: a project that never adopted
|
|
22
|
-
* `.claude/context/`, `.
|
|
22
|
+
* `.claude/context/`, `.canon/diagrams/`, or `.claude/wireframes/` names no
|
|
23
23
|
* corpus this audit can measure, the same state `no-skills` reads for the
|
|
24
24
|
* skill corpora. The other four are a malformed invocation or a checkout git
|
|
25
25
|
* cannot read, which stay a break rather than an absence.
|
package/src/demo/compile.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Beat, Draft } from '@/demo/beats'
|
|
|
5
5
|
* separate artifacts on purpose: a beat carries no target, no wait condition,
|
|
6
6
|
* and no timing, and putting those four fields on every beat would destroy the
|
|
7
7
|
* property the draft was designed around. See
|
|
8
|
-
* `.
|
|
8
|
+
* `.canon/groundwork/38-demo-recorder/06-decision.md`.
|
|
9
9
|
*
|
|
10
10
|
* A compiled plan is committed rather than scratch, because the timing below is
|
|
11
11
|
* a starting point the operator tunes and the draft cannot reproduce a tuned
|
package/src/gov/citations.ts
CHANGED
|
@@ -357,7 +357,7 @@ function globMatches(root: string, glob: string): boolean {
|
|
|
357
357
|
/**
|
|
358
358
|
* Which of `paths` git ignores, or nothing when git could not answer.
|
|
359
359
|
*
|
|
360
|
-
* Session scratch is the class this reaches. `.
|
|
360
|
+
* Session scratch is the class this reaches. `.canon/tasks/index.md` is real
|
|
361
361
|
* in a live project, absent from a fresh clone and from every linked worktree,
|
|
362
362
|
* and a rule naming it is right either way. Resolving against the filesystem
|
|
363
363
|
* alone would make the verdict depend on which tree the check ran in.
|
package/src/gov/restated.ts
CHANGED
|
@@ -43,7 +43,7 @@ const MIRRORS: readonly (readonly [string, string])[] = [
|
|
|
43
43
|
*
|
|
44
44
|
* Under one percent of the 2750 statements this repository offers. Tuned
|
|
45
45
|
* against that corpus rather than reasoned to, which is what the plan asked of
|
|
46
|
-
* the first run: `.
|
|
46
|
+
* the first run: `.canon/plans/` sits at 14 and is the anchor the motivating
|
|
47
47
|
* case turns on, while `file` sits at 371 and matches most of the tree.
|
|
48
48
|
*/
|
|
49
49
|
export const COMMON_CEILING = 20
|
|
@@ -55,7 +55,7 @@ export const ANCHOR_FLOOR = 3
|
|
|
55
55
|
* What a backticked token is worth against a plain word.
|
|
56
56
|
*
|
|
57
57
|
* An author marking a span as code named an identifier rather than describing
|
|
58
|
-
* one, so `.
|
|
58
|
+
* one, so `.canon/plans/archive/` says more about what a statement governs
|
|
59
59
|
* than any two prose words do. Weighting it is what lets the floor rise high
|
|
60
60
|
* enough to drop a coincidental word pair without losing a rule two surfaces
|
|
61
61
|
* spelled entirely differently around one shared path.
|
|
@@ -267,7 +267,7 @@ interface Indexed<T extends Statement> {
|
|
|
267
267
|
/**
|
|
268
268
|
* Splits text into the tokens an anchor can be drawn from.
|
|
269
269
|
*
|
|
270
|
-
* A code span keeps its inner text whole, since `.
|
|
270
|
+
* A code span keeps its inner text whole, since `.canon/plans/archive/` is the
|
|
271
271
|
* strongest anchor this corpus offers and splitting it on the punctuation would
|
|
272
272
|
* leave three words every second bullet also carries.
|
|
273
273
|
*/
|