@erclx/canon 5.26.0 → 5.28.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 +2 -2
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/SKILL.md +2 -0
- package/claude/skills/auto-ship/SKILL.md +3 -11
- package/claude/skills/auto-ship/references/review-findings.md +20 -0
- package/claude/skills/backlog-triage/SKILL.md +2 -0
- package/claude/skills/bash-cli-script/SKILL.md +2 -0
- package/claude/skills/build-in-slices/SKILL.md +2 -0
- package/claude/skills/canon-cli/SKILL.md +2 -0
- package/claude/skills/canon-feedback/SKILL.md +2 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +2 -0
- package/claude/skills/canon-operator/SKILL.md +2 -0
- package/claude/skills/canon-rollout/SKILL.md +3 -11
- package/claude/skills/canon-rollout/references/failure-recovery.md +20 -0
- package/claude/skills/canvas/SKILL.md +13 -0
- package/claude/skills/ci-workflow/SKILL.md +2 -0
- package/claude/skills/code-craft/SKILL.md +2 -0
- package/claude/skills/codebase-layout/SKILL.md +2 -0
- package/claude/skills/context-fold/SKILL.md +3 -17
- package/claude/skills/context-fold/references/completion-report.md +26 -0
- package/claude/skills/create-rule/SKILL.md +2 -0
- package/claude/skills/create-skill/SKILL.md +2 -0
- package/claude/skills/create-standard/SKILL.md +2 -0
- package/claude/skills/decision-escalate/SKILL.md +2 -0
- package/claude/skills/deploy-app/SKILL.md +2 -0
- package/claude/skills/deprecation-migration/SKILL.md +2 -0
- package/claude/skills/design-extract/SKILL.md +2 -0
- package/claude/skills/design-taste/SKILL.md +2 -0
- package/claude/skills/docs-sync/SKILL.md +2 -0
- package/claude/skills/document-health/SKILL.md +2 -0
- package/claude/skills/draft-and-pick/SKILL.md +2 -0
- package/claude/skills/draft-doc/SKILL.md +2 -0
- package/claude/skills/draft-doc/references/wiki.md +1 -1
- package/claude/skills/draft-figure/SKILL.md +2 -0
- package/claude/skills/draft-identity/SKILL.md +2 -0
- package/claude/skills/draft-ready/SKILL.md +5 -1
- package/claude/skills/draft-screencast/SKILL.md +2 -0
- package/claude/skills/draft-slides/SKILL.md +2 -0
- package/claude/skills/git-branch/SKILL.md +2 -0
- package/claude/skills/git-commit/SKILL.md +2 -0
- package/claude/skills/git-followup/SKILL.md +2 -0
- package/claude/skills/git-issue/SKILL.md +2 -0
- package/claude/skills/git-pr/SKILL.md +2 -0
- package/claude/skills/git-pr/references/evidence.md +7 -7
- package/claude/skills/git-ship/SKILL.md +2 -0
- package/claude/skills/git-split/SKILL.md +2 -0
- package/claude/skills/git-stage/SKILL.md +2 -0
- package/claude/skills/git-worktree/SKILL.md +2 -0
- package/claude/skills/index-lookup/SKILL.md +2 -0
- package/claude/skills/markdown-propose/SKILL.md +2 -0
- package/claude/skills/memory-capture/SKILL.md +2 -0
- package/claude/skills/memory-review/SKILL.md +2 -0
- package/claude/skills/plan-feature/SKILL.md +2 -0
- package/claude/skills/plan-groundwork/SKILL.md +2 -0
- package/claude/skills/plan-intake/SKILL.md +2 -0
- package/claude/skills/plan-intake-answer/SKILL.md +2 -0
- package/claude/skills/project-commands/SKILL.md +2 -0
- package/claude/skills/read-frames/SKILL.md +2 -0
- package/claude/skills/record-screencast/SKILL.md +2 -0
- package/claude/skills/repo-metadata/SKILL.md +2 -0
- package/claude/skills/restate-plainly/SKILL.md +2 -0
- package/claude/skills/review-address/REQUIREMENT.md +4 -2
- package/claude/skills/review-address/SKILL.md +19 -14
- package/claude/skills/review-address/references/post-review-findings.md +16 -0
- package/claude/skills/review-address/references/rebase-conflicts.md +4 -2
- package/claude/skills/review-branch/SKILL.md +2 -0
- package/claude/skills/review-craft/SKILL.md +2 -0
- package/claude/skills/review-craft/references/ui.md +6 -6
- package/claude/skills/review-pr/SKILL.md +4 -8
- package/claude/skills/review-pr/references/body-variants.md +52 -0
- package/claude/skills/review-pr/references/key-changes.md +16 -0
- package/claude/skills/review-pr/references/post.md +15 -43
- package/claude/skills/review-ui/REQUIREMENT.md +2 -2
- package/claude/skills/review-ui/SKILL.md +16 -12
- package/claude/skills/review-ui/references/driving.md +8 -8
- package/claude/skills/review-ui/references/post.md +13 -3
- package/claude/skills/review-ui/references/tick.md +22 -0
- package/claude/skills/role-orchestrator/REQUIREMENT.md +15 -15
- package/claude/skills/role-orchestrator/SKILL.md +31 -42
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +18 -16
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +13 -3
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +6 -34
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +4 -4
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +5 -5
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +12 -23
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +6 -6
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +38 -0
- package/claude/skills/role-orchestrator/references/orchestrator-sweep.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
- package/claude/skills/role-orchestrator/scripts/poll.ts +304 -10
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/role-planner/SKILL.md +4 -2
- package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
- package/claude/skills/role-reviewer/SKILL.md +3 -1
- package/claude/skills/role-worker/SKILL.md +5 -2
- package/claude/skills/search-craft/SKILL.md +2 -0
- package/claude/skills/seed-sync/SKILL.md +2 -0
- package/claude/skills/session-compact/SKILL.md +2 -0
- package/claude/skills/session-map/SKILL.md +2 -0
- package/claude/skills/session-relay/SKILL.md +2 -0
- package/claude/skills/session-resume/SKILL.md +2 -0
- package/claude/skills/session-worktree/SKILL.md +3 -11
- package/claude/skills/session-worktree/references/install-command.md +20 -0
- package/claude/skills/sketch-design/SKILL.md +2 -0
- package/claude/skills/systematic-debugging/SKILL.md +2 -0
- package/claude/skills/target-check/SKILL.md +2 -0
- package/claude/skills/target-setup/SKILL.md +2 -0
- package/claude/skills/task-board/SKILL.md +2 -0
- package/claude/skills/teach-workspace/SKILL.md +5 -15
- package/claude/skills/teach-workspace/references/lesson-build.md +75 -0
- package/claude/skills/teach-workspace/references/lesson-craft.md +13 -69
- package/claude/skills/teach-workspace/references/revisit.md +22 -0
- package/claude/skills/test-craft/SKILL.md +2 -0
- package/claude/skills/test-first/SKILL.md +2 -0
- package/claude/skills/ui-checklist/SKILL.md +3 -1
- package/claude/skills/ux-audit/SKILL.md +2 -0
- package/claude/skills/ux-measure/SKILL.md +2 -0
- package/claude/skills/ux-walkthrough/SKILL.md +2 -0
- package/claude/skills/video-craft/SKILL.md +2 -0
- package/claude/skills/write-human/SKILL.md +2 -0
- package/claude/skills/youtube-transcripts/SKILL.md +2 -0
- package/docs/agents/canvas.md +24 -8
- package/docs/agents/capture.md +5 -5
- package/docs/agents/commands.md +2 -2
- package/docs/agents/gate.md +3 -1
- package/docs/agents/index.md +5 -4
- package/docs/agents/install-and-sync.md +7 -0
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/pr-evidence.md +47 -23
- package/docs/agents/pr-local.md +19 -13
- package/docs/agents/pr-preview.md +44 -21
- package/docs/agents/pr-tick.md +72 -0
- package/docs/agents/sandbox.md +30 -6
- package/docs/agents/scripting.md +8 -1
- package/docs/agents/skills-audit.md +2 -1
- package/docs/agents/tasks-plans.md +1 -1
- package/docs/agents/tasks-validate.md +18 -18
- package/docs/agents/tasks.md +1 -1
- package/docs/workflow/operating-model.md +15 -21
- package/docs/workflow/parallel-features.md +24 -3
- package/docs/workflow/skill-map.md +3 -1
- package/docs/workflow/zshrc-aliases.md +1 -1
- package/package.json +1 -1
- package/scripts/core/regen-hero.ts +1 -1
- package/scripts/lib/sandbox-path.sh +6 -162
- package/src/audits/baseline.ts +1 -1
- package/src/audits/run.ts +2 -2
- package/src/canvas/address.ts +12 -3
- package/src/canvas/capture.ts +1 -1
- package/src/canvas/client/inspector/align-grid.tsx +162 -0
- package/src/canvas/client/inspector/color-field.tsx +201 -171
- package/src/canvas/client/inspector/color-inputs.tsx +139 -0
- package/src/canvas/client/inspector/color-picker.tsx +668 -0
- package/src/canvas/client/inspector/color-space.ts +240 -0
- package/src/canvas/client/inspector/field.tsx +66 -3
- package/src/canvas/client/inspector/fields.css +473 -6
- package/src/canvas/client/inspector/size-field.tsx +69 -0
- package/src/canvas/client/inspector/token-select.tsx +109 -0
- package/src/canvas/client/inspector/values.ts +117 -3
- package/src/canvas/client/inspector.tsx +311 -15
- package/src/canvas/client/keys.ts +54 -0
- package/src/canvas/client/selection.tsx +199 -0
- package/src/canvas/client/shell.css +210 -12
- package/src/canvas/client/state.ts +165 -14
- package/src/canvas/client/surface.tsx +341 -39
- package/src/canvas/client/tools.tsx +64 -0
- package/src/canvas/content.ts +51 -2
- package/src/canvas/edit.ts +49 -6
- package/src/canvas/editing.ts +203 -0
- package/src/canvas/global.d.ts +15 -0
- package/src/canvas/server.ts +149 -12
- package/src/canvas/tokens.ts +60 -18
- package/src/census/count.ts +1 -1
- package/src/claude/seeds.ts +3 -3
- package/src/claude/skills-audit.ts +28 -0
- package/src/claude/skills-drift.ts +1 -1
- package/src/claude/skills-families.ts +36 -0
- package/src/claude/skills-list.ts +76 -15
- package/src/claude/skills-reach.ts +1 -1
- package/src/commands/audits.ts +2 -2
- package/src/commands/canvas.ts +180 -3
- package/src/commands/claude.ts +70 -12
- package/src/commands/design.ts +145 -5
- package/src/commands/docs.ts +1 -1
- package/src/commands/feedback.ts +2 -2
- package/src/commands/gate.ts +9 -2
- package/src/commands/gov.ts +3 -3
- package/src/commands/hooks.ts +1 -1
- package/src/commands/indexes.ts +1 -1
- package/src/commands/init.ts +2 -2
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +2 -2
- package/src/commands/pr.ts +288 -65
- package/src/commands/records.ts +1 -1
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +64 -15
- package/src/commands/sessions.ts +1 -1
- package/src/commands/slides.ts +1 -1
- package/src/commands/sync.ts +2 -2
- package/src/commands/tasks.ts +21 -4
- package/src/commands/teach.ts +1 -1
- package/src/commands/tooling.ts +1 -1
- package/src/commands/transcripts.ts +2 -2
- package/src/commands/upgrade.ts +1 -1
- package/src/commands/wiki.ts +1 -1
- package/src/commands/worktrees.ts +1 -1
- package/src/comments/scan.ts +1 -1
- package/src/comments/trend.ts +1 -1
- package/src/context/architecture.ts +1 -1
- package/src/context/citations.ts +3 -3
- package/src/context/classify/extract.ts +3 -3
- package/src/context/folders.ts +2 -2
- package/src/context/requirements.ts +1 -1
- package/src/design/project-fonts.ts +392 -0
- package/src/docs/read.ts +1 -1
- package/src/exec.ts +1 -1
- package/src/feedback/github.ts +2 -2
- package/src/gate/measures.ts +48 -10
- package/src/gate/sequencer.ts +56 -4
- package/src/gate/stages.ts +17 -0
- package/src/gate/suite-lock.ts +214 -0
- package/src/{git-files.ts → git/files.ts} +2 -2
- package/src/{git-ignore.ts → git/ignore.ts} +1 -1
- package/src/{worktree.ts → git/worktree.ts} +1 -1
- package/src/gov/citations.ts +1 -1
- package/src/gov/install.ts +1 -1
- package/src/gov/superseded.ts +1 -1
- package/src/gov/test-order.ts +2 -2
- package/src/help.ts +1 -1
- package/src/hooks/post-merge.ts +1 -1
- package/src/indexes/walk.ts +1 -1
- package/src/intake/folder.ts +1 -1
- package/src/labels/audit.ts +1 -1
- package/src/labels/map.ts +1 -1
- package/src/labels/phase.ts +3 -3
- package/src/markdown/files.ts +1 -1
- package/src/migrate/apply.ts +1 -1
- package/src/migrate/record-layout.ts +1 -1
- package/src/migrate/records.ts +11 -11
- package/src/migrate/scratch-evidence.ts +1 -1
- package/src/migrate/surface-roots.ts +6 -6
- package/src/pr/bijection.ts +1 -1
- package/src/pr/evidence.ts +250 -59
- package/src/pr/local.ts +16 -10
- package/src/pr/preview.ts +78 -2
- package/src/pr/tick.ts +56 -0
- package/src/process/harness.ts +1 -1
- package/src/records/backup.ts +2 -2
- package/src/records/ordinal.ts +1 -1
- package/src/records/prune.ts +1 -1
- package/src/records/size.ts +1 -1
- package/src/records/stale-canonical.ts +2 -2
- package/src/records/stale.ts +1 -1
- package/src/records/validate.ts +2 -2
- package/src/{project-root.ts → roots/project.ts} +1 -1
- package/src/{record-root.ts → roots/record.ts} +1 -1
- package/src/sandbox/coverage.ts +11 -12
- package/src/sandbox/equivalence.ts +57 -26
- package/src/sandbox/headless/dispatch.ts +264 -0
- package/src/sandbox/headless/run.ts +653 -0
- package/src/sandbox/headless/watch.ts +145 -0
- package/src/sandbox/hooks.ts +1 -1
- package/src/sandbox/provision.ts +90 -29
- package/src/sandbox/scenario.ts +588 -0
- package/src/sandbox/tree.ts +4 -8
- package/src/secrets/scan.ts +1 -1
- package/src/sessions/claim.ts +1 -1
- package/src/sessions/live.ts +4 -1
- package/src/sessions/resolve.ts +1 -1
- package/src/sessions/transfer/bundle.ts +2 -2
- package/src/slides/decks.ts +1 -1
- package/src/slides/package/fonts.ts +6 -2
- package/src/standards/audit.ts +2 -2
- package/src/standards/read.ts +1 -1
- package/src/sync/check.ts +2 -2
- package/src/sync/engine.ts +3 -3
- package/src/sync/git.ts +1 -1
- package/src/sync/history.ts +1 -1
- package/src/sync/layout.ts +2 -2
- package/src/sync/reverse.ts +1 -1
- package/src/sync/stamp.ts +1 -1
- package/src/sync/target.ts +1 -1
- package/src/sync/workflow.ts +1 -1
- package/src/targets/pulls.ts +2 -2
- package/src/targets/sweep.ts +2 -2
- package/src/{target.ts → targets/validate.ts} +1 -1
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/tasks/label.ts +1 -1
- package/src/tasks/reach.ts +2 -2
- package/src/tasks/trunk.ts +1 -1
- package/src/tasks/validate.ts +38 -14
- package/src/teach/nav.ts +1 -1
- package/src/teach/workspace.ts +1 -1
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/read.ts +1 -1
- package/src/tooling/scan.ts +1 -1
- package/src/tooling/subfolder.ts +1 -1
- package/src/version/installed.ts +1 -1
- package/src/version/skew.ts +1 -1
- package/src/web/readme-citations.ts +1 -1
- package/src/worktrees/bare-flag.ts +1 -1
- package/src/worktrees/reclaim.ts +2 -2
- package/src/worktrees/remove.ts +1 -1
- package/standards/board.md +3 -3
- package/standards/canvas.md +2 -1
- package/standards/plan.md +13 -7
- package/standards/skill.md +1 -0
- package/standards/wiki.md +12 -3
- package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
- package/tsconfig.json +1 -1
- package/scripts/lib/sandbox-dispatch.sh +0 -190
- /package/src/{client-commands.ts → gate/client-commands.ts} +0 -0
- /package/src/{git-base.ts → git/base.ts} +0 -0
- /package/src/{git-env.ts → git/env.ts} +0 -0
- /package/src/{legacy-path.ts → roots/legacy.ts} +0 -0
- /package/src/{surface-root.ts → roots/surface.ts} +0 -0
- /package/src/{copy.ts → sync/copy.ts} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
|
+
import { isFamilyKey } from '@/claude/skills-families'
|
|
3
4
|
import { parseFrontmatter, readField } from '@/indexes/frontmatter'
|
|
4
5
|
import { bodyLines, maskDisplayed } from '@/markdown/scan'
|
|
5
6
|
|
|
@@ -128,6 +129,7 @@ export interface SkillsAudit {
|
|
|
128
129
|
readonly datedProvenance: readonly SkillFinding[]
|
|
129
130
|
readonly practiceShape: readonly SkillFinding[]
|
|
130
131
|
readonly referenceContents: readonly SkillFinding[]
|
|
132
|
+
readonly family: readonly SkillFinding[]
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
interface SkillSource {
|
|
@@ -136,6 +138,8 @@ interface SkillSource {
|
|
|
136
138
|
readonly hasReadme: boolean
|
|
137
139
|
readonly name: string | undefined
|
|
138
140
|
readonly description: string | undefined
|
|
141
|
+
/** `metadata.family`, undefined when absent, blank, or not a string. */
|
|
142
|
+
readonly family: string | undefined
|
|
139
143
|
/** Undefined when the folder carries no `REQUIREMENT.md` at all. */
|
|
140
144
|
readonly requirementHeadings: readonly string[] | undefined
|
|
141
145
|
/** Every H2 in `SKILL.md` outside a fence, for the practice shape. */
|
|
@@ -207,6 +211,7 @@ export async function auditSkills(root: string): Promise<SkillsAudit> {
|
|
|
207
211
|
referenceContents: sources.flatMap((source) =>
|
|
208
212
|
source.bodies.flatMap(contentsFindings),
|
|
209
213
|
),
|
|
214
|
+
family: sources.flatMap(familyFindings),
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
|
|
@@ -261,6 +266,7 @@ async function readSkill(
|
|
|
261
266
|
hasReadme: existsSync(join(skillDir, 'README.md')),
|
|
262
267
|
name: declared(readField(fields, 'name')),
|
|
263
268
|
description: declared(readField(fields, 'description')),
|
|
269
|
+
family: declaredFamily(fields?.fields.metadata),
|
|
264
270
|
requirementHeadings:
|
|
265
271
|
requirement === undefined ? undefined : headings(requirement),
|
|
266
272
|
sections: sectionsOf(body),
|
|
@@ -285,6 +291,13 @@ function declared(value: string | undefined): string | undefined {
|
|
|
285
291
|
return trimmed === undefined || trimmed === '' ? undefined : trimmed
|
|
286
292
|
}
|
|
287
293
|
|
|
294
|
+
function declaredFamily(metadata: unknown): string | undefined {
|
|
295
|
+
if (typeof metadata !== 'object' || metadata === null) return undefined
|
|
296
|
+
if (Array.isArray(metadata)) return undefined
|
|
297
|
+
const { family } = metadata as Record<string, unknown>
|
|
298
|
+
return typeof family === 'string' ? declared(family) : undefined
|
|
299
|
+
}
|
|
300
|
+
|
|
288
301
|
/** Every H2 outside a fence, the one level both exact-section checks read. */
|
|
289
302
|
function sectionsOf(source: string): string[] {
|
|
290
303
|
return bodyLines(source)
|
|
@@ -375,6 +388,21 @@ function practiceFindings(source: SkillSource): SkillFinding[] {
|
|
|
375
388
|
return [...sections, ...ledger]
|
|
376
389
|
}
|
|
377
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Asks only the shipped corpus for a family, since the family names the skill
|
|
393
|
+
* map group a skill's row sits under and an internal skill takes no row.
|
|
394
|
+
*/
|
|
395
|
+
function familyFindings(source: SkillSource): SkillFinding[] {
|
|
396
|
+
if (dirname(source.rel) !== SHIPPED_CORPUS) return []
|
|
397
|
+
if (source.family === undefined) {
|
|
398
|
+
return [
|
|
399
|
+
{ rel: source.rel, detail: 'frontmatter declares no metadata.family' },
|
|
400
|
+
]
|
|
401
|
+
}
|
|
402
|
+
if (isFamilyKey(source.family)) return []
|
|
403
|
+
return [{ rel: source.rel, detail: `unknown family: ${source.family}` }]
|
|
404
|
+
}
|
|
405
|
+
|
|
378
406
|
/**
|
|
379
407
|
* Reads references alone, since `SKILL.md` carries its own length checkpoint
|
|
380
408
|
* and no contents rule. Frontmatter counts toward the length, so the count a
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { execaSync } from 'execa'
|
|
4
|
-
import { gitEnv } from '@/git
|
|
4
|
+
import { gitEnv } from '@/git/env'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The shipped corpus alone. A session loads a body from the plugin root, which
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface SkillFamily {
|
|
2
|
+
/** The value a shipped `SKILL.md` declares as `metadata.family`. */
|
|
3
|
+
readonly key: string
|
|
4
|
+
/** The skill map heading the family's rows sit under, verbatim. */
|
|
5
|
+
readonly group: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The one vocabulary a shipped skill's family is drawn from, in the order the
|
|
10
|
+
* skill map reads across its two pages. A heading is the map's prose and a key
|
|
11
|
+
* is what frontmatter carries, so renaming a heading touches this constant and
|
|
12
|
+
* no skill.
|
|
13
|
+
*
|
|
14
|
+
* Import-free on purpose: `skills-list.ts` and `skills-audit.ts` both read it,
|
|
15
|
+
* and the first already imports the second.
|
|
16
|
+
*/
|
|
17
|
+
export const SKILL_FAMILIES: readonly SkillFamily[] = [
|
|
18
|
+
{ key: 'setup', group: 'Set up a project' },
|
|
19
|
+
{ key: 'decide', group: 'Decide what to build' },
|
|
20
|
+
{ key: 'build', group: 'Build the feature' },
|
|
21
|
+
{ key: 'check', group: 'Check the work before it leaves the branch' },
|
|
22
|
+
{ key: 'ship', group: 'Ship it' },
|
|
23
|
+
{ key: 'after-pr', group: 'After the pull request opens' },
|
|
24
|
+
{ key: 'parallel', group: 'Run several tracks at once' },
|
|
25
|
+
{ key: 'upkeep', group: 'Keep the project current with the toolkit' },
|
|
26
|
+
{ key: 'generate', group: 'Generate an artifact on demand' },
|
|
27
|
+
{ key: 'answer', group: 'Answer a question at any point' },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export function familyKeys(): string[] {
|
|
31
|
+
return SKILL_FAMILIES.map((family) => family.key)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isFamilyKey(value: string): boolean {
|
|
35
|
+
return SKILL_FAMILIES.some((family) => family.key === value)
|
|
36
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
3
|
import { CORPORA } from '@/claude/skills-audit'
|
|
4
|
+
import { isFamilyKey, SKILL_FAMILIES } from '@/claude/skills-families'
|
|
4
5
|
|
|
5
6
|
const FRONTMATTER = /^---\n([\s\S]*?)\n---/
|
|
6
7
|
|
|
@@ -8,6 +9,11 @@ export interface SkillListing {
|
|
|
8
9
|
readonly name: string
|
|
9
10
|
readonly description: string
|
|
10
11
|
readonly requirement: boolean
|
|
12
|
+
/**
|
|
13
|
+
* The `metadata.family` the frontmatter declares, read as written. Whether it
|
|
14
|
+
* names a vocabulary key is the audit's finding, not the listing's.
|
|
15
|
+
*/
|
|
16
|
+
readonly family: string | null
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
export interface SkillsCorpus {
|
|
@@ -72,28 +78,83 @@ export function listSkillsAt(skillsRoot: string): SkillListing[] {
|
|
|
72
78
|
}),
|
|
73
79
|
].sort()
|
|
74
80
|
|
|
75
|
-
return paths.map((path) =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
return paths.map((path) => {
|
|
82
|
+
const { description, family } = readListedFields(join(skillsRoot, path))
|
|
83
|
+
return {
|
|
84
|
+
name: dirname(path),
|
|
85
|
+
description,
|
|
86
|
+
requirement: existsSync(
|
|
87
|
+
join(skillsRoot, dirname(path), 'REQUIREMENT.md'),
|
|
88
|
+
),
|
|
89
|
+
family,
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface FamilyGroup {
|
|
95
|
+
readonly key: string
|
|
96
|
+
readonly group: string
|
|
97
|
+
readonly skills: readonly string[]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface FamilyGrouping {
|
|
101
|
+
/** Vocabulary order, holding only the families some listing declares. */
|
|
102
|
+
readonly groups: readonly FamilyGroup[]
|
|
103
|
+
/** Listings whose family is missing or names no vocabulary key. */
|
|
104
|
+
readonly unassigned: readonly string[]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function groupByFamily(
|
|
108
|
+
listings: readonly SkillListing[],
|
|
109
|
+
): FamilyGrouping {
|
|
110
|
+
const groups = SKILL_FAMILIES.map((family) => ({
|
|
111
|
+
...family,
|
|
112
|
+
skills: listings
|
|
113
|
+
.filter((listing) => listing.family === family.key)
|
|
114
|
+
.map((listing) => listing.name),
|
|
115
|
+
})).filter((group) => group.skills.length > 0)
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
groups,
|
|
119
|
+
unassigned: listings
|
|
120
|
+
.filter(
|
|
121
|
+
(listing) => listing.family === null || !isFamilyKey(listing.family),
|
|
122
|
+
)
|
|
123
|
+
.map((listing) => listing.name),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface ListedFields {
|
|
128
|
+
readonly description: string
|
|
129
|
+
readonly family: string | null
|
|
80
130
|
}
|
|
81
131
|
|
|
82
132
|
/**
|
|
83
|
-
* Returns an empty description rather than throwing on a
|
|
84
|
-
* frontmatter is missing or unparseable, so one malformed file
|
|
85
|
-
* the rest of the catalog from a caller counting it.
|
|
133
|
+
* Returns an empty description and a null family rather than throwing on a
|
|
134
|
+
* skill whose frontmatter is missing or unparseable, so one malformed file
|
|
135
|
+
* does not hide the rest of the catalog from a caller counting it.
|
|
86
136
|
*/
|
|
87
|
-
function
|
|
137
|
+
function readListedFields(path: string): ListedFields {
|
|
138
|
+
const empty = { description: '', family: null }
|
|
88
139
|
const match = FRONTMATTER.exec(readFileSync(path, 'utf8'))
|
|
89
|
-
if (!match) return
|
|
140
|
+
if (!match) return empty
|
|
90
141
|
|
|
142
|
+
let parsed: unknown
|
|
91
143
|
try {
|
|
92
|
-
|
|
93
|
-
if (typeof parsed !== 'object' || parsed === null) return ''
|
|
94
|
-
const { description } = parsed as Record<string, unknown>
|
|
95
|
-
return typeof description === 'string' ? description : ''
|
|
144
|
+
parsed = Bun.YAML.parse(match[1])
|
|
96
145
|
} catch {
|
|
97
|
-
return
|
|
146
|
+
return empty
|
|
98
147
|
}
|
|
148
|
+
if (!isRecord(parsed)) return empty
|
|
149
|
+
|
|
150
|
+
const { description, metadata } = parsed
|
|
151
|
+
const family = isRecord(metadata) ? metadata.family : undefined
|
|
152
|
+
return {
|
|
153
|
+
description: typeof description === 'string' ? description : '',
|
|
154
|
+
family: typeof family === 'string' ? family : null,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
159
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
99
160
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { resolveSkillsCorpus } from '@/claude/skills-list'
|
|
4
|
-
import { SURFACE_ROOTS } from '@/surface
|
|
4
|
+
import { SURFACE_ROOTS } from '@/roots/surface'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The authoring roots this repository owns and no install channel delivers.
|
package/src/commands/audits.ts
CHANGED
|
@@ -19,9 +19,9 @@ import {
|
|
|
19
19
|
type Summary,
|
|
20
20
|
summarize,
|
|
21
21
|
} from '@/audits/run'
|
|
22
|
-
import { gitEnv } from '@/git
|
|
22
|
+
import { gitEnv } from '@/git/env'
|
|
23
23
|
import { intro, logError, logInfo, logStep, logWarn, outro, plural } from '@/ui'
|
|
24
|
-
import { currentWorktreeRoot } from '@/worktree'
|
|
24
|
+
import { currentWorktreeRoot } from '@/git/worktree'
|
|
25
25
|
|
|
26
26
|
interface RunCommandOptions {
|
|
27
27
|
readonly json?: boolean
|
package/src/commands/canvas.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
readPage,
|
|
13
13
|
readSelection,
|
|
14
14
|
renamePage,
|
|
15
|
+
resizeFrame,
|
|
15
16
|
} from '@/canvas/content'
|
|
16
17
|
import { captureCanvas, captureComposite } from '@/canvas/capture'
|
|
17
18
|
import {
|
|
@@ -19,10 +20,22 @@ import {
|
|
|
19
20
|
editFrameAtIndex,
|
|
20
21
|
STYLE_PROPERTIES,
|
|
21
22
|
} from '@/canvas/edit'
|
|
23
|
+
import {
|
|
24
|
+
clearEditing,
|
|
25
|
+
EDITING_TTL_MS,
|
|
26
|
+
markEditing,
|
|
27
|
+
readEditing,
|
|
28
|
+
} from '@/canvas/editing'
|
|
22
29
|
import { CANVAS_PORT, startCanvas } from '@/canvas/server'
|
|
23
|
-
import { PROJECT_ROOT } from '@/project
|
|
30
|
+
import { PROJECT_ROOT } from '@/roots/project'
|
|
24
31
|
import { resolveFrameTokens } from '@/canvas/tokens'
|
|
25
32
|
import { displayPath, parsePort, waitForInterrupt } from '@/serve/report'
|
|
33
|
+
import {
|
|
34
|
+
callerIdentity,
|
|
35
|
+
type Located,
|
|
36
|
+
resolveSessions,
|
|
37
|
+
selfOf,
|
|
38
|
+
} from '@/sessions/resolve'
|
|
26
39
|
import {
|
|
27
40
|
intro,
|
|
28
41
|
logAdd,
|
|
@@ -33,7 +46,7 @@ import {
|
|
|
33
46
|
outro,
|
|
34
47
|
plural,
|
|
35
48
|
} from '@/ui'
|
|
36
|
-
import { mainWorktreeRoot } from '@/worktree'
|
|
49
|
+
import { mainWorktreeRoot } from '@/git/worktree'
|
|
37
50
|
|
|
38
51
|
const BANNER = 'canon canvas'
|
|
39
52
|
|
|
@@ -135,7 +148,7 @@ export function register(program: Command): void {
|
|
|
135
148
|
.command('canvas')
|
|
136
149
|
.helpOption('-h, --help', 'Show this help message')
|
|
137
150
|
.description(
|
|
138
|
-
'Local canvas of pages and HTML frames (serve, list, page, frame, selection, edit, capture)',
|
|
151
|
+
'Local canvas of pages and HTML frames (serve, list, page, frame, selection, edit, editing, capture)',
|
|
139
152
|
)
|
|
140
153
|
.addHelpText(
|
|
141
154
|
'after',
|
|
@@ -152,6 +165,7 @@ export function register(program: Command): void {
|
|
|
152
165
|
' canon canvas frame move drafts hero --x 200 --y 120',
|
|
153
166
|
' canon canvas selection --json',
|
|
154
167
|
" canon canvas edit drafts/hero --element 4 --set 'color=var(--color-accent)'",
|
|
168
|
+
' canon canvas editing drafts/hero',
|
|
155
169
|
' canon canvas capture drafts/hero',
|
|
156
170
|
' canon canvas list --json',
|
|
157
171
|
'',
|
|
@@ -341,6 +355,56 @@ export function register(program: Command): void {
|
|
|
341
355
|
},
|
|
342
356
|
)
|
|
343
357
|
|
|
358
|
+
withRoot(
|
|
359
|
+
frame
|
|
360
|
+
.command('resize')
|
|
361
|
+
.description('Resize a frame, and move it when given a position')
|
|
362
|
+
.argument('<page>', 'Page the frame is on')
|
|
363
|
+
.argument('<name>', 'Frame name')
|
|
364
|
+
.option('--width <px>', 'New width, defaulting to the frame width')
|
|
365
|
+
.option('--height <px>', 'New height, defaulting to the frame height')
|
|
366
|
+
.option('--x <px>', 'New x, defaulting to where the frame is')
|
|
367
|
+
.option('--y <px>', 'New y, defaulting to where the frame is'),
|
|
368
|
+
).action(
|
|
369
|
+
async (
|
|
370
|
+
pageName: string,
|
|
371
|
+
name: string,
|
|
372
|
+
opts: RootOptions & {
|
|
373
|
+
width?: string
|
|
374
|
+
height?: string
|
|
375
|
+
x?: string
|
|
376
|
+
y?: string
|
|
377
|
+
},
|
|
378
|
+
) => {
|
|
379
|
+
const resolved = await resolveRoot(opts)
|
|
380
|
+
if (!resolved.ok) {
|
|
381
|
+
process.exitCode = refuse(resolved, opts.json ?? false)
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
const { root } = resolved
|
|
385
|
+
const current = readPage(root, pageName)?.frames.find(
|
|
386
|
+
(candidate) => candidate.name === name,
|
|
387
|
+
)
|
|
388
|
+
const outcome = resizeFrame(root, pageName, name, {
|
|
389
|
+
x: parsePosition(opts.x, current?.x ?? 0),
|
|
390
|
+
y: parsePosition(opts.y, current?.y ?? 0),
|
|
391
|
+
width: parseSize(opts.width, current?.width ?? DEFAULT_FRAME.width),
|
|
392
|
+
height: parseSize(opts.height, current?.height ?? DEFAULT_FRAME.height),
|
|
393
|
+
})
|
|
394
|
+
if (!outcome.ok) {
|
|
395
|
+
process.exitCode = refuse(outcome, opts.json ?? false)
|
|
396
|
+
return
|
|
397
|
+
}
|
|
398
|
+
if (opts.json) writeJson(outcome)
|
|
399
|
+
intro(BANNER)
|
|
400
|
+
logAdd(`${outcome.page}/${outcome.file}`)
|
|
401
|
+
logInfo(
|
|
402
|
+
`${outcome.box.width} × ${outcome.box.height} at ${outcome.box.x}, ${outcome.box.y}`,
|
|
403
|
+
)
|
|
404
|
+
outro()
|
|
405
|
+
},
|
|
406
|
+
)
|
|
407
|
+
|
|
344
408
|
withRoot(
|
|
345
409
|
canvas
|
|
346
410
|
.command('selection')
|
|
@@ -487,6 +551,91 @@ export function register(program: Command): void {
|
|
|
487
551
|
},
|
|
488
552
|
)
|
|
489
553
|
|
|
554
|
+
withRoot(
|
|
555
|
+
canvas
|
|
556
|
+
.command('editing')
|
|
557
|
+
.description('Mark a frame as being edited, clear it, or list the marks')
|
|
558
|
+
.argument('[target]', '<page>/<frame>, or none to list the marks')
|
|
559
|
+
.option('--done', 'Clear the mark once the edits are written')
|
|
560
|
+
.option('--by <name>', 'The label the mark shows, over the session name'),
|
|
561
|
+
)
|
|
562
|
+
.addHelpText(
|
|
563
|
+
'after',
|
|
564
|
+
[
|
|
565
|
+
'',
|
|
566
|
+
'Mark a frame before writing it and clear it with --done after, so an',
|
|
567
|
+
`open canvas shows who is editing it. A mark lapses after ${EDITING_TTL_MS / 60_000} minutes`,
|
|
568
|
+
'unless marked again, so re-mark during a long edit. A mark gates no',
|
|
569
|
+
'write, and --done on a frame holding no mark succeeds.',
|
|
570
|
+
'',
|
|
571
|
+
].join('\n'),
|
|
572
|
+
)
|
|
573
|
+
.action(
|
|
574
|
+
async (
|
|
575
|
+
target: string | undefined,
|
|
576
|
+
opts: RootOptions & { done?: boolean; by?: string },
|
|
577
|
+
) => {
|
|
578
|
+
const emitJson = opts.json ?? false
|
|
579
|
+
const resolved = await resolveRoot(opts)
|
|
580
|
+
if (!resolved.ok) {
|
|
581
|
+
process.exitCode = refuse(resolved, emitJson)
|
|
582
|
+
return
|
|
583
|
+
}
|
|
584
|
+
const { root } = resolved
|
|
585
|
+
const now = new Date()
|
|
586
|
+
|
|
587
|
+
if (target !== undefined) {
|
|
588
|
+
const [pageName, frameName, ...rest] = target.split('/')
|
|
589
|
+
if (!pageName || !frameName || rest.length > 0) {
|
|
590
|
+
process.exitCode = refuse(
|
|
591
|
+
{
|
|
592
|
+
ok: false,
|
|
593
|
+
reason: 'invalid-name',
|
|
594
|
+
detail: `${target} is not <page>/<frame>`,
|
|
595
|
+
},
|
|
596
|
+
emitJson,
|
|
597
|
+
)
|
|
598
|
+
return
|
|
599
|
+
}
|
|
600
|
+
const outcome = opts.done
|
|
601
|
+
? clearEditing(root, pageName, frameName)
|
|
602
|
+
: markEditing(
|
|
603
|
+
root,
|
|
604
|
+
pageName,
|
|
605
|
+
frameName,
|
|
606
|
+
opts.by ?? (await sessionLabel()),
|
|
607
|
+
now,
|
|
608
|
+
)
|
|
609
|
+
if (!outcome.ok) {
|
|
610
|
+
process.exitCode = refuse(outcome, emitJson)
|
|
611
|
+
return
|
|
612
|
+
}
|
|
613
|
+
} else if (opts.done) {
|
|
614
|
+
process.exitCode = refuse(
|
|
615
|
+
{
|
|
616
|
+
ok: false,
|
|
617
|
+
reason: 'invalid-name',
|
|
618
|
+
detail: '--done needs the <page>/<frame> to clear',
|
|
619
|
+
},
|
|
620
|
+
emitJson,
|
|
621
|
+
)
|
|
622
|
+
return
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const marks = readEditing(root, now)
|
|
626
|
+
if (emitJson) writeJson({ ok: true, editing: marks })
|
|
627
|
+
intro(BANNER)
|
|
628
|
+
if (target !== undefined) {
|
|
629
|
+
logStep(`${target} ${opts.done ? 'cleared' : 'marked'}`)
|
|
630
|
+
}
|
|
631
|
+
if (marks.length === 0) logInfo('No frame is marked as being edited')
|
|
632
|
+
for (const mark of marks) {
|
|
633
|
+
logInfo(`${mark.page}/${mark.frame} ${mark.by} until ${mark.until}`)
|
|
634
|
+
}
|
|
635
|
+
outro()
|
|
636
|
+
},
|
|
637
|
+
)
|
|
638
|
+
|
|
490
639
|
withRoot(
|
|
491
640
|
canvas
|
|
492
641
|
.command('capture')
|
|
@@ -644,6 +793,29 @@ async function runComposite(
|
|
|
644
793
|
return 0
|
|
645
794
|
}
|
|
646
795
|
|
|
796
|
+
async function unlocated(): Promise<Located> {
|
|
797
|
+
return {
|
|
798
|
+
repository: null,
|
|
799
|
+
worktree: null,
|
|
800
|
+
branch: null,
|
|
801
|
+
unresolved: 'git-unavailable',
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* The caller's roster name, or a generic label when the environment names no
|
|
807
|
+
* session or the roster holds no row for it, as in a sandbox or a CI run.
|
|
808
|
+
*/
|
|
809
|
+
async function sessionLabel(): Promise<string> {
|
|
810
|
+
const identity = callerIdentity()
|
|
811
|
+
if (identity.sessionId === null && identity.pid === null) return 'a session'
|
|
812
|
+
/* The label needs the name alone, so skip the git probe each row's branch costs. */
|
|
813
|
+
const report = await resolveSessions({ locate: unlocated })
|
|
814
|
+
if (report.kind === 'absent') return 'a session'
|
|
815
|
+
const self = selfOf(report.sessions, identity)
|
|
816
|
+
return self.kind === 'self' ? self.session.name : 'a session'
|
|
817
|
+
}
|
|
818
|
+
|
|
647
819
|
/** NaN for a value that is not a number, which the content module refuses. */
|
|
648
820
|
function parsePosition(raw: string | undefined, fallback: number): number {
|
|
649
821
|
if (raw === undefined) return fallback
|
|
@@ -698,6 +870,11 @@ async function runServe(
|
|
|
698
870
|
if (tokens.source === 'none') logWarn(tokens.notice)
|
|
699
871
|
else if (tokens.source === 'installed') {
|
|
700
872
|
for (const file of tokens.files) logInfo(file)
|
|
873
|
+
for (const face of tokens.dropped ?? []) {
|
|
874
|
+
logWarn(
|
|
875
|
+
`${face.sheet}: dropped the face at ${face.file} (${face.reason})`,
|
|
876
|
+
)
|
|
877
|
+
}
|
|
701
878
|
} else logInfo('this toolkit’s own token module')
|
|
702
879
|
logStep('Stop')
|
|
703
880
|
logInfo('Ctrl-C')
|
package/src/commands/claude.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { join, resolve } from 'node:path'
|
|
|
5
5
|
import type { Command } from 'commander'
|
|
6
6
|
import { execa } from 'execa'
|
|
7
7
|
import { singleLine } from '@/commands/upgrade'
|
|
8
|
-
import { gitEnv } from '@/git
|
|
8
|
+
import { gitEnv } from '@/git/env'
|
|
9
9
|
import { claudeChain, pendingEntries, planGitignore } from '@/claude/gitignore'
|
|
10
10
|
import {
|
|
11
11
|
matchInstall,
|
|
@@ -44,7 +44,12 @@ import {
|
|
|
44
44
|
scanRouting,
|
|
45
45
|
} from '@/claude/routing'
|
|
46
46
|
import { type DriftReport, readDrift } from '@/claude/skills-drift'
|
|
47
|
-
import {
|
|
47
|
+
import { familyKeys, SKILL_FAMILIES } from '@/claude/skills-families'
|
|
48
|
+
import {
|
|
49
|
+
type FamilyGrouping,
|
|
50
|
+
groupByFamily,
|
|
51
|
+
listSkills,
|
|
52
|
+
} from '@/claude/skills-list'
|
|
48
53
|
import {
|
|
49
54
|
type ReachRefusal,
|
|
50
55
|
type ReachReport,
|
|
@@ -64,12 +69,12 @@ import {
|
|
|
64
69
|
serializeSettings,
|
|
65
70
|
writeSettings,
|
|
66
71
|
} from '@/claude/settings'
|
|
67
|
-
import { copyPreservingMode } from '@/copy'
|
|
72
|
+
import { copyPreservingMode } from '@/sync/copy'
|
|
68
73
|
import { execScript } from '@/exec'
|
|
69
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project
|
|
70
|
-
import { recordDir } from '@/record
|
|
71
|
-
import { SURFACE_ENTRIES, surfaceDir } from '@/surface
|
|
72
|
-
import { isDirectory, resolveTarget } from '@/
|
|
74
|
+
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
75
|
+
import { recordDir } from '@/roots/record'
|
|
76
|
+
import { SURFACE_ENTRIES, surfaceDir } from '@/roots/surface'
|
|
77
|
+
import { isDirectory, resolveTarget } from '@/targets/validate'
|
|
73
78
|
import { injectGitignore, pruneGitignore } from '@/tooling/inject'
|
|
74
79
|
import {
|
|
75
80
|
frameError,
|
|
@@ -96,6 +101,7 @@ interface SeedsListOptions {
|
|
|
96
101
|
interface SkillsListOptions {
|
|
97
102
|
readonly json?: boolean
|
|
98
103
|
readonly names?: boolean
|
|
104
|
+
readonly byFamily?: boolean
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
interface SkillsAuditOptions {
|
|
@@ -359,8 +365,12 @@ export function register(program: Command): void {
|
|
|
359
365
|
.command('list')
|
|
360
366
|
.description('List the plugin skills shipped under claude/skills/')
|
|
361
367
|
.helpOption('-h, --help', 'Show this help message')
|
|
362
|
-
.option(
|
|
368
|
+
.option(
|
|
369
|
+
'--json',
|
|
370
|
+
'Emit JSON with each skill and the ordered family vocabulary',
|
|
371
|
+
)
|
|
363
372
|
.option('--names', 'Only list skill names, one per line')
|
|
373
|
+
.option('--by-family', 'Group the listing under each family')
|
|
364
374
|
.addHelpText(
|
|
365
375
|
'after',
|
|
366
376
|
[
|
|
@@ -368,6 +378,8 @@ export function register(program: Command): void {
|
|
|
368
378
|
'Notes:',
|
|
369
379
|
' Internal skills under .claude/skills/ are excluded, since they',
|
|
370
380
|
' never install into a target project.',
|
|
381
|
+
' Each skill carries the family its frontmatter declares under',
|
|
382
|
+
' metadata.family, and --json lists the vocabulary as families.',
|
|
371
383
|
'',
|
|
372
384
|
].join('\n'),
|
|
373
385
|
)
|
|
@@ -770,7 +782,9 @@ function runSkillsList(opts: SkillsListOptions): number {
|
|
|
770
782
|
|
|
771
783
|
if (opts.json) {
|
|
772
784
|
if (mismatch !== undefined) logWarn(mismatch)
|
|
773
|
-
process.stdout.write(
|
|
785
|
+
process.stdout.write(
|
|
786
|
+
`${JSON.stringify({ skills: listings, families: SKILL_FAMILIES })}\n`,
|
|
787
|
+
)
|
|
774
788
|
return 0
|
|
775
789
|
}
|
|
776
790
|
|
|
@@ -784,14 +798,34 @@ function runSkillsList(opts: SkillsListOptions): number {
|
|
|
784
798
|
|
|
785
799
|
intro('canon claude')
|
|
786
800
|
if (mismatch !== undefined) logWarn(mismatch)
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
801
|
+
if (opts.byFamily) {
|
|
802
|
+
reportByFamily(groupByFamily(listings))
|
|
803
|
+
} else {
|
|
804
|
+
logStep('Plugin skills')
|
|
805
|
+
for (const listing of listings) {
|
|
806
|
+
logInfo(listing.name)
|
|
807
|
+
}
|
|
790
808
|
}
|
|
791
809
|
outro()
|
|
792
810
|
return 0
|
|
793
811
|
}
|
|
794
812
|
|
|
813
|
+
/**
|
|
814
|
+
* One step per family in vocabulary order. A skill with a missing or unknown
|
|
815
|
+
* family takes a trailing step rather than vanishing, so the view still
|
|
816
|
+
* accounts for every listed skill.
|
|
817
|
+
*/
|
|
818
|
+
function reportByFamily(grouping: FamilyGrouping): void {
|
|
819
|
+
for (const group of grouping.groups) {
|
|
820
|
+
logStep(`${group.group} (${group.key})`)
|
|
821
|
+
for (const name of group.skills) logInfo(name)
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (grouping.unassigned.length === 0) return
|
|
825
|
+
logStep('No known family')
|
|
826
|
+
for (const name of grouping.unassigned) logWarn(name)
|
|
827
|
+
}
|
|
828
|
+
|
|
795
829
|
/**
|
|
796
830
|
* Measures the cwd for the same reason the audit does, and takes the ref as a
|
|
797
831
|
* required argument with no default. `HEAD` would be the only defensible one and
|
|
@@ -1402,6 +1436,7 @@ async function runSkillsAudit(
|
|
|
1402
1436
|
reportProvenance(report)
|
|
1403
1437
|
reportPracticeShape(report)
|
|
1404
1438
|
reportReferenceContents(report)
|
|
1439
|
+
reportFamily(report)
|
|
1405
1440
|
reportUnmeasured()
|
|
1406
1441
|
outro()
|
|
1407
1442
|
}
|
|
@@ -1426,6 +1461,7 @@ async function runSkillsAudit(
|
|
|
1426
1461
|
datedProvenance: report.datedProvenance,
|
|
1427
1462
|
practiceShape: report.practiceShape,
|
|
1428
1463
|
referenceContents: report.referenceContents,
|
|
1464
|
+
family: report.family,
|
|
1429
1465
|
},
|
|
1430
1466
|
checkpoints: {
|
|
1431
1467
|
descriptionLimit: DESCRIPTION_LIMIT,
|
|
@@ -1435,6 +1471,7 @@ async function runSkillsAudit(
|
|
|
1435
1471
|
ledgerSkills: LEDGER_SKILLS,
|
|
1436
1472
|
practiceSections: PRACTICE_SECTIONS,
|
|
1437
1473
|
contentsThreshold: CONTENTS_THRESHOLD,
|
|
1474
|
+
families: familyKeys(),
|
|
1438
1475
|
},
|
|
1439
1476
|
})}\n`,
|
|
1440
1477
|
)
|
|
@@ -1668,6 +1705,27 @@ function reportReferenceContents(report: SkillsAudit): void {
|
|
|
1668
1705
|
reportFindings(report.referenceContents)
|
|
1669
1706
|
}
|
|
1670
1707
|
|
|
1708
|
+
/**
|
|
1709
|
+
* Reports without failing the verb, the split the practice shape takes. The
|
|
1710
|
+
* Skill family stage of `canon gate run` is what fails a push on it.
|
|
1711
|
+
*/
|
|
1712
|
+
function reportFamily(report: SkillsAudit): void {
|
|
1713
|
+
logStep('Family')
|
|
1714
|
+
logInfo(
|
|
1715
|
+
`A skill under ${SHIPPED_CORPUS} declares metadata.family, one of ${familyKeys().join(', ')}.`,
|
|
1716
|
+
)
|
|
1717
|
+
|
|
1718
|
+
if (report.family.length === 0) {
|
|
1719
|
+
logInfo('Every shipped skill declares a known family.')
|
|
1720
|
+
return
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
logWarn(
|
|
1724
|
+
`${plural(report.family.length, 'skill')} with a missing or unknown family`,
|
|
1725
|
+
)
|
|
1726
|
+
reportFindings(report.family)
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1671
1729
|
/**
|
|
1672
1730
|
* Stated on every run, including the run where everything above passed. A
|
|
1673
1731
|
* report that lists only what it measured reads as a verdict on the standard
|