@erclx/canon 4.127.0 → 5.1.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 +5 -7
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/references/verb-absent.md +0 -1
- package/claude/skills/canon-feedback/REQUIREMENT.md +1 -1
- package/claude/skills/canon-feedback/SKILL.md +2 -2
- package/claude/skills/canon-operator/REQUIREMENT.md +2 -2
- package/claude/skills/canon-operator/SKILL.md +3 -3
- package/claude/skills/codebase-layout/REQUIREMENT.md +1 -0
- package/claude/skills/codebase-layout/SKILL.md +6 -0
- package/claude/skills/create-rule/REQUIREMENT.md +1 -1
- package/claude/skills/create-standard/REQUIREMENT.md +0 -2
- package/claude/skills/decision-escalate/REQUIREMENT.md +1 -2
- package/claude/skills/draft-context/REQUIREMENT.md +1 -1
- package/claude/skills/draft-docs/REQUIREMENT.md +1 -1
- package/claude/skills/draft-wireframes/REQUIREMENT.md +1 -1
- package/claude/skills/plan-intake/SKILL.md +1 -1
- package/claude/skills/target-setup/REQUIREMENT.md +2 -2
- package/claude/skills/target-setup/SKILL.md +1 -1
- package/claude/skills/target-setup/references/detect.md +1 -1
- package/claude/skills/target-setup/references/indexes.md +1 -5
- package/docs/agents/commands.md +1 -3
- package/docs/agents/counts.md +1 -2
- package/docs/agents/docs.md +1 -1
- package/docs/agents/drift-surfaces.md +6 -4
- package/docs/agents/index.md +1 -1
- package/docs/agents/indexes.md +1 -1
- package/docs/agents/install-and-sync.md +1 -8
- package/docs/agents/markdown-audit.md +2 -2
- package/docs/agents/restated.md +7 -5
- package/docs/agents/review-classification.md +1 -2
- package/docs/agents/scripting.md +2 -3
- package/docs/agents/skills-reach.md +3 -3
- package/docs/agents/state-scoped-risk.md +1 -1
- package/docs/target/index.md +4 -4
- package/docs/target/{target-migrations.md → migrations.md} +17 -1
- package/docs/target/{target-projects.md → projects.md} +5 -7
- package/docs/target/{target-scenarios.md → scenarios.md} +1 -1
- package/docs/target/{target-sync.md → sync.md} +4 -2
- package/docs/workflow/ai-workflow.md +3 -7
- package/docs/workflow/operating-model.md +4 -4
- package/docs/workflow/parallel-features.md +1 -1
- package/docs/workflow/skill-map.md +1 -2
- package/docs/workflow/visual-design-workflow.md +5 -5
- package/docs/workflow/zshrc-aliases.md +1 -1
- package/package.json +1 -2
- package/scripts/core/check-plugin-boundary.sh +1 -1
- package/scripts/core/install-check.sh +3 -4
- package/scripts/core/regen-hero.sh +2 -9
- package/src/autoship/paths.ts +3 -5
- package/src/claude/cases/authoring.ts +1 -5
- package/src/claude/skills-reach.ts +1 -2
- package/src/cli.ts +0 -2
- package/src/commands/feedback-format.ts +0 -1
- package/src/commands/gov.ts +8 -7
- package/src/commands/markdown.ts +2 -2
- package/src/counts/catalogs.ts +0 -16
- package/src/counts/scan.ts +1 -1
- package/src/docs/read.ts +25 -25
- package/src/gate/measures.ts +52 -0
- package/src/gate/stages.ts +9 -0
- package/src/gov/restated.ts +60 -22
- package/src/help.ts +0 -2
- package/src/init/steps.ts +1 -1
- package/src/migrate/skill-names.ts +4 -5
- package/src/secrets/shipped.ts +2 -2
- package/src/serve/static.ts +1 -2
- package/src/shipped/references.ts +7 -8
- package/src/sync/engine.ts +2 -2
- package/standards/context.md +2 -0
- package/standards/docs.md +1 -0
- package/standards/index.md +0 -1
- package/standards/skill.md +1 -2
- package/standards/wiki.md +2 -1
- package/tooling/base/reference.md +2 -2
- package/tooling/claude/reference.md +1 -1
- package/claude/skills/create-snippet/REQUIREMENT.md +0 -39
- package/claude/skills/create-snippet/SKILL.md +0 -30
- package/scripts/snippets/create.sh +0 -156
- package/snippets/align.md +0 -12
- package/snippets/claude/decision-memo.md +0 -39
- package/snippets/claude/feature-recap.md +0 -19
- package/snippets/claude/figma-steps.md +0 -24
- package/snippets/compact-summary.md +0 -5
- package/snippets/decision-help.md +0 -6
- package/snippets/meta-prompt.md +0 -14
- package/snippets/research-prompt.md +0 -7
- package/snippets/session-notes.md +0 -11
- package/snippets/snippets.toml +0 -5
- package/snippets/step-by-step.md +0 -10
- package/snippets/web-research.md +0 -21
- package/src/commands/snippets.ts +0 -90
- package/src/snippets/categories.ts +0 -66
- package/src/snippets/list.ts +0 -32
- package/src/snippets/presets.ts +0 -50
- package/standards/snippets.md +0 -76
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
PROJECT_ROOT="${PROJECT_ROOT:-$(dirname "$(dirname "$SCRIPT_DIR")")}"
|
|
7
|
-
|
|
8
|
-
source "$PROJECT_ROOT/scripts/lib/ui.sh"
|
|
9
|
-
trap close_timeline EXIT
|
|
10
|
-
|
|
11
|
-
SNIPPETS_SOURCE="$PROJECT_ROOT/snippets"
|
|
12
|
-
|
|
13
|
-
show_help() {
|
|
14
|
-
echo -e "${GREY}┌${NC}"
|
|
15
|
-
echo -e "${GREY}├${NC} ${WHITE}Usage:${NC} canon snippets create"
|
|
16
|
-
echo -e "${GREY}│${NC}"
|
|
17
|
-
echo -e "${GREY}│${NC} Creates a new snippet: prompts for category and name, writes the file."
|
|
18
|
-
echo -e "${GREY}│${NC}"
|
|
19
|
-
echo -e "${GREY}│${NC} ${WHITE}Options:${NC}"
|
|
20
|
-
echo -e "${GREY}│${NC} -h, --help ${GREY}# Show this help message${NC}"
|
|
21
|
-
echo -e "${GREY}└${NC}"
|
|
22
|
-
exit 0
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
list_categories() {
|
|
26
|
-
echo "base (root, no prefix)"
|
|
27
|
-
find "$SNIPPETS_SOURCE" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort
|
|
28
|
-
echo "new category"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
slug_error() {
|
|
32
|
-
local slug="$1"
|
|
33
|
-
local path="$2"
|
|
34
|
-
if [ -z "$slug" ]; then
|
|
35
|
-
echo "name cannot be empty"
|
|
36
|
-
return
|
|
37
|
-
fi
|
|
38
|
-
if [[ ! "$slug" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
|
|
39
|
-
echo "kebab-case only (e.g. my-snippet)"
|
|
40
|
-
return
|
|
41
|
-
fi
|
|
42
|
-
if [ -f "$path" ]; then
|
|
43
|
-
echo "file already exists: $path"
|
|
44
|
-
return
|
|
45
|
-
fi
|
|
46
|
-
echo ""
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
ask_new_category() {
|
|
50
|
-
while true; do
|
|
51
|
-
ask "Category name?" "CATEGORY_INPUT"
|
|
52
|
-
if [ -z "$CATEGORY_INPUT" ]; then
|
|
53
|
-
log_warn "Category name cannot be empty"
|
|
54
|
-
continue
|
|
55
|
-
fi
|
|
56
|
-
if [[ ! "$CATEGORY_INPUT" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
|
|
57
|
-
log_warn "Invalid category: kebab-case only (e.g. my-category)"
|
|
58
|
-
continue
|
|
59
|
-
fi
|
|
60
|
-
if [ -d "$SNIPPETS_SOURCE/$CATEGORY_INPUT" ]; then
|
|
61
|
-
log_warn "Category '$CATEGORY_INPUT' already exists"
|
|
62
|
-
continue
|
|
63
|
-
fi
|
|
64
|
-
CATEGORY_NAME="$CATEGORY_INPUT"
|
|
65
|
-
break
|
|
66
|
-
done
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
ask_name() {
|
|
70
|
-
local category="$1"
|
|
71
|
-
local is_base="$2"
|
|
72
|
-
local label
|
|
73
|
-
local error
|
|
74
|
-
|
|
75
|
-
label="Snippet name?"
|
|
76
|
-
|
|
77
|
-
while true; do
|
|
78
|
-
ask "$label" "NAME_INPUT"
|
|
79
|
-
local dest_path
|
|
80
|
-
if [ "$is_base" = "true" ]; then
|
|
81
|
-
dest_path="$SNIPPETS_SOURCE/$NAME_INPUT.md"
|
|
82
|
-
else
|
|
83
|
-
dest_path="$SNIPPETS_SOURCE/$category/$NAME_INPUT.md"
|
|
84
|
-
fi
|
|
85
|
-
error=$(slug_error "$NAME_INPUT" "$dest_path")
|
|
86
|
-
if [ -z "$error" ]; then
|
|
87
|
-
NAME="$NAME_INPUT"
|
|
88
|
-
break
|
|
89
|
-
fi
|
|
90
|
-
log_warn "Invalid name: $error"
|
|
91
|
-
done
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
cmd_create() {
|
|
95
|
-
local categories=()
|
|
96
|
-
mapfile -t categories < <(list_categories)
|
|
97
|
-
|
|
98
|
-
select_option "Category?" "${categories[@]}"
|
|
99
|
-
local selected="$SELECTED_OPTION"
|
|
100
|
-
|
|
101
|
-
local category
|
|
102
|
-
local is_base="false"
|
|
103
|
-
|
|
104
|
-
if [ "$selected" = "base (root, no prefix)" ]; then
|
|
105
|
-
is_base="true"
|
|
106
|
-
category="base"
|
|
107
|
-
elif [ "$selected" = "new category" ]; then
|
|
108
|
-
ask_new_category
|
|
109
|
-
category="$CATEGORY_NAME"
|
|
110
|
-
else
|
|
111
|
-
category="$selected"
|
|
112
|
-
fi
|
|
113
|
-
|
|
114
|
-
ask_name "$category" "$is_base"
|
|
115
|
-
local name="$NAME"
|
|
116
|
-
|
|
117
|
-
local dest_path
|
|
118
|
-
local install_path
|
|
119
|
-
if [ "$is_base" = "true" ]; then
|
|
120
|
-
dest_path="$SNIPPETS_SOURCE/$name.md"
|
|
121
|
-
install_path="snippets/$name"
|
|
122
|
-
else
|
|
123
|
-
dest_path="$SNIPPETS_SOURCE/$category/$name.md"
|
|
124
|
-
install_path="snippets/$category/$name"
|
|
125
|
-
fi
|
|
126
|
-
|
|
127
|
-
log_info "Installs as: @${install_path}"
|
|
128
|
-
|
|
129
|
-
log_step "Creating file"
|
|
130
|
-
|
|
131
|
-
if [ "$is_base" = "false" ]; then
|
|
132
|
-
mkdir -p "$SNIPPETS_SOURCE/$category"
|
|
133
|
-
fi
|
|
134
|
-
|
|
135
|
-
printf '<!-- TODO: write %s prompt -->\n' "$slug" >"$dest_path"
|
|
136
|
-
|
|
137
|
-
if [ "$is_base" = "true" ]; then
|
|
138
|
-
log_add "snippets/$name.md"
|
|
139
|
-
else
|
|
140
|
-
log_add "snippets/$category/$name.md"
|
|
141
|
-
fi
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
main() {
|
|
145
|
-
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
|
146
|
-
show_help
|
|
147
|
-
fi
|
|
148
|
-
|
|
149
|
-
cmd_create
|
|
150
|
-
|
|
151
|
-
trap - EXIT
|
|
152
|
-
echo -e "${GREY}└${NC}\n"
|
|
153
|
-
echo -e "${GREEN}✓ Snippet created${NC}"
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
main "$@"
|
package/snippets/align.md
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
Check alignment before acting, not after. State three lines, then proceed.
|
|
2
|
-
|
|
3
|
-
- Outcome: what will be true when this is done
|
|
4
|
-
- Scope: what this deliberately does not touch
|
|
5
|
-
- Assumption: the one inference that would change the work if it is wrong
|
|
6
|
-
|
|
7
|
-
Act without waiting, unless being wrong about the assumption wastes the work rather than costing an edit.
|
|
8
|
-
|
|
9
|
-
- Three lines, not three paragraphs
|
|
10
|
-
- Name the inference you are least sure of, not the safest one
|
|
11
|
-
- Do not ask what the request already answers
|
|
12
|
-
- Silence is agreement
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
Produce a decision memo for a research or should-we question. This is the decision-heavy counterpart to `plan-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 `plan-feature`.
|
|
2
|
-
|
|
3
|
-
Read the current state from the code and docs before recommending. Do not assume it.
|
|
4
|
-
|
|
5
|
-
Output this shape per memo:
|
|
6
|
-
|
|
7
|
-
```markdown
|
|
8
|
-
# <slug title>
|
|
9
|
-
|
|
10
|
-
<the question in one or two lines>
|
|
11
|
-
|
|
12
|
-
## Summary
|
|
13
|
-
|
|
14
|
-
- <goal in one line>
|
|
15
|
-
- <deliverables in one line>
|
|
16
|
-
- <key tradeoff in one line>
|
|
17
|
-
|
|
18
|
-
## Finding
|
|
19
|
-
|
|
20
|
-
<what the current state actually is, from reading the code and docs>
|
|
21
|
-
|
|
22
|
-
## Item A: <decision>
|
|
23
|
-
|
|
24
|
-
**Current state.** <what exists today>
|
|
25
|
-
|
|
26
|
-
**Recommendation.** <the pick, with the tradeoff in one sentence>
|
|
27
|
-
|
|
28
|
-
**Options.** <the real alternatives, only when the choice is the user's to make>
|
|
29
|
-
|
|
30
|
-
**Feasibility.** <what is not buildable as asked and why, omit when all options are buildable>
|
|
31
|
-
|
|
32
|
-
**Questions.**
|
|
33
|
-
|
|
34
|
-
1. <question>
|
|
35
|
-
- Suggested: <best-practice default derived from context>
|
|
36
|
-
- Answer:
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Repeat the `## Item` block per decision. Keep chat to the file pointer plus the questions. Recommend, do not enumerate, unless the user's preference is the deciding factor.
|
|
@@ -1,19 +0,0 @@
|
|
|
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
|
-
|
|
3
|
-
Output this shape, nothing else:
|
|
4
|
-
|
|
5
|
-
```markdown
|
|
6
|
-
**Deliverable 1**: <name from the plan>
|
|
7
|
-
|
|
8
|
-
- `path/to/file`: one-line reason
|
|
9
|
-
|
|
10
|
-
**Deliverable 2**: <name from the plan>
|
|
11
|
-
|
|
12
|
-
- `path/to/file`: one-line reason
|
|
13
|
-
|
|
14
|
-
**Verified**: <what was smoke-tested or `Nothing tested in session.`>
|
|
15
|
-
|
|
16
|
-
**Open**: <unresolved questions, deferred items, or `None.`>
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Group files by deliverable, not by directory. Keep each line scannable. Do not restate the plan body.
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
Read the relevant spec from `canon/DESIGN.md` and any other design files needed. Write step-by-step Figma instructions to produce the component exactly.
|
|
2
|
-
|
|
3
|
-
For each step, specify:
|
|
4
|
-
|
|
5
|
-
- The exact tool or keyboard shortcut to activate
|
|
6
|
-
- Which panel or field to click
|
|
7
|
-
- The exact value to enter
|
|
8
|
-
|
|
9
|
-
When a step has multiple sub-actions or lists several items, use nested bullets instead of stacking them in one sentence.
|
|
10
|
-
|
|
11
|
-
Cover in order:
|
|
12
|
-
|
|
13
|
-
- Frame or group setup (dimensions, corner radius, fill)
|
|
14
|
-
- Each layer or shape (type, size, position, fill, stroke)
|
|
15
|
-
- Alignment and spacing rules
|
|
16
|
-
- Export settings
|
|
17
|
-
|
|
18
|
-
Throughout:
|
|
19
|
-
|
|
20
|
-
- Use exact values from the spec
|
|
21
|
-
- Do not restate design rationale
|
|
22
|
-
- Use Title Case for all layer and frame names
|
|
23
|
-
- Use Figma terminology throughout, not code or CSS equivalents
|
|
24
|
-
- Reuse existing frames or components from the same Figma document by name instead of rebuilding from scratch
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
Apply senior-level judgment. Pick the best option from the discussion so far. State the pick in one sentence, then the reason and the main tradeoff in one or two sentences.
|
|
2
|
-
|
|
3
|
-
- Minimal and necessary only
|
|
4
|
-
- No nice-to-haves
|
|
5
|
-
- No padding
|
|
6
|
-
- No alternatives unless they change the recommendation
|
package/snippets/meta-prompt.md
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
Turn the idea below into a production-grade system prompt.
|
|
2
|
-
|
|
3
|
-
Give it a role of one to three lines, a constraints section, and an output format carrying a template plus one worked example. Add a validation checklist when the prompt involves multi-step logic, edge cases, or error handling. Omit that checklist for a single-purpose prompt.
|
|
4
|
-
|
|
5
|
-
Group constraints under domain headings when they span two or more domains and total five or more. Use flat must-do and must-not-do lists otherwise. Keep dos and don'ts together under the topic they belong to.
|
|
6
|
-
|
|
7
|
-
Use `{{DOUBLE_BRACES}}` for values the end user fills in at runtime.
|
|
8
|
-
|
|
9
|
-
Constraints:
|
|
10
|
-
|
|
11
|
-
- Use imperative voice. Write `Do X`, never `You should` or `Try to`.
|
|
12
|
-
- Make every constraint verifiable from the output. Reject subjective qualifiers like `appropriate`, `concise`, or `well-structured`.
|
|
13
|
-
- Match prompt length to task complexity. A single-purpose task gets a minimal prompt.
|
|
14
|
-
- Never write `maybe`, `consider`, or `depending on`. Be definitive.
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
Generate a self-contained research prompt for another AI chat. Include:
|
|
2
|
-
|
|
3
|
-
- State what I am trying to do and why
|
|
4
|
-
- List what I have already tried and the results
|
|
5
|
-
- Specify questions that need answers
|
|
6
|
-
|
|
7
|
-
Write it in first person. Assume the reader has no context from this session.
|
package/snippets/snippets.toml
DELETED
package/snippets/step-by-step.md
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
Give me step-by-step instructions.
|
|
2
|
-
|
|
3
|
-
For each step:
|
|
4
|
-
|
|
5
|
-
- State what to do in one sentence.
|
|
6
|
-
- Include the exact value, command, or input where precision matters.
|
|
7
|
-
- Note any output to save or verify before moving on.
|
|
8
|
-
- When the step has multiple sub-actions or lists several items, use nested bullets instead of stacking them in one sentence.
|
|
9
|
-
|
|
10
|
-
Group related steps under a heading if the process has distinct phases. Flag any step that is irreversible or that blocks later steps.
|
package/snippets/web-research.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
Search the web for the topic or question added after this snippet. Fetch the most relevant pages. Synthesize findings into a decision-ready block using this format:
|
|
2
|
-
|
|
3
|
-
```markdown
|
|
4
|
-
Topic: [what was researched]
|
|
5
|
-
Recommendation: [one clear answer or choice]
|
|
6
|
-
|
|
7
|
-
Options considered:
|
|
8
|
-
|
|
9
|
-
- [Option A]: why considered, why chosen or rejected
|
|
10
|
-
- [Option B]: why considered, why chosen or rejected
|
|
11
|
-
|
|
12
|
-
Key constraints:
|
|
13
|
-
|
|
14
|
-
- [constraint that shaped the decision]
|
|
15
|
-
|
|
16
|
-
Sources:
|
|
17
|
-
|
|
18
|
-
- [URL]: what it confirmed
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Output only the block. Do not add commentary before or after it.
|
package/src/commands/snippets.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander'
|
|
2
|
-
import { registerPassThroughVerbs } from '@/commands/pass-through'
|
|
3
|
-
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project-root'
|
|
4
|
-
import { BASE_CATEGORY } from '@/snippets/categories'
|
|
5
|
-
import { buildSnippetsCatalog } from '@/snippets/list'
|
|
6
|
-
import { intro, logInfo, logStep, logWarn, outro } from '@/ui'
|
|
7
|
-
|
|
8
|
-
const PASS_THROUGH_VERBS = ['create'] as const
|
|
9
|
-
|
|
10
|
-
interface ListOptions {
|
|
11
|
-
readonly categories?: boolean
|
|
12
|
-
readonly entries?: boolean
|
|
13
|
-
readonly json?: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function register(program: Command): void {
|
|
17
|
-
const snippets = program
|
|
18
|
-
.command('snippets')
|
|
19
|
-
.description('Snippets commands (create, list)')
|
|
20
|
-
.helpOption('-h, --help', 'Show this help message')
|
|
21
|
-
|
|
22
|
-
snippets
|
|
23
|
-
.command('list')
|
|
24
|
-
.description('List snippet presets, categories, and entries')
|
|
25
|
-
.helpOption('-h, --help', 'Show this help message')
|
|
26
|
-
.option('--categories', 'Only list presets and category names')
|
|
27
|
-
.option('--entries', 'Only list entry slugs grouped by category')
|
|
28
|
-
.option('--json', 'Emit machine-readable JSON')
|
|
29
|
-
.action((opts: ListOptions) => {
|
|
30
|
-
process.exitCode = runList(opts)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
registerPassThroughVerbs(snippets, 'snippets', PASS_THROUGH_VERBS)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* `JSON.stringify` replaces a `printf` template that interpolated names into a
|
|
38
|
-
* JSON string literal unescaped, so a slug or category carrying a quote emitted
|
|
39
|
-
* output no consuming skill could parse.
|
|
40
|
-
*
|
|
41
|
-
* The frame opens after the `--json` return, since the bash verb only ever
|
|
42
|
-
* emitted section headers and the pass-through above it owned the `┌`.
|
|
43
|
-
*/
|
|
44
|
-
function runList(opts: ListOptions): number {
|
|
45
|
-
// The warning is a frame-interior line, so it goes out after `intro` on the
|
|
46
|
-
// path that opens one and bare on the `--json` path, which returns first and
|
|
47
|
-
// opens none. One position cannot serve both.
|
|
48
|
-
const mismatch = checkoutMismatchWarning(process.cwd())
|
|
49
|
-
const catalog = buildSnippetsCatalog(PROJECT_ROOT)
|
|
50
|
-
|
|
51
|
-
if (opts.json) {
|
|
52
|
-
if (mismatch !== undefined) logWarn(mismatch)
|
|
53
|
-
process.stdout.write(`${JSON.stringify(catalog)}\n`)
|
|
54
|
-
return 0
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
intro('canon snippets list')
|
|
58
|
-
if (mismatch !== undefined) logWarn(mismatch)
|
|
59
|
-
|
|
60
|
-
const showCategories = opts.entries !== true
|
|
61
|
-
const showEntries = opts.categories !== true
|
|
62
|
-
|
|
63
|
-
if (showCategories) {
|
|
64
|
-
if (catalog.presets.length > 0) {
|
|
65
|
-
logStep('Presets')
|
|
66
|
-
for (const preset of catalog.presets) {
|
|
67
|
-
logInfo(`${preset.name} (${preset.slugs.length} entries)`)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
logStep('Categories')
|
|
72
|
-
for (const category of catalog.categories) {
|
|
73
|
-
logInfo(`${category.name} (${category.entries.length} entries)`)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (showEntries) {
|
|
78
|
-
logStep('Entries')
|
|
79
|
-
for (const category of catalog.categories) {
|
|
80
|
-
for (const entry of category.entries) {
|
|
81
|
-
logInfo(
|
|
82
|
-
category.name === BASE_CATEGORY ? entry : `${category.name}/${entry}`,
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
outro()
|
|
89
|
-
return 0
|
|
90
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { readdirSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import { isDirectory } from '@/target'
|
|
4
|
-
|
|
5
|
-
export const BASE_CATEGORY = 'base'
|
|
6
|
-
/** A real category resolving to zero entries, for a caller declining on purpose. */
|
|
7
|
-
export const NONE_CATEGORY = 'none'
|
|
8
|
-
|
|
9
|
-
export function snippetsSourceDir(root: string): string {
|
|
10
|
-
return join(root, 'snippets')
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Lists the folder categories. `base` is the synthetic name for snippets
|
|
15
|
-
* sitting directly under `snippets/`, so it is not a directory and callers
|
|
16
|
-
* prepend it themselves.
|
|
17
|
-
*
|
|
18
|
-
* Nothing is filtered: toolkit-internal snippets are authored under `internal/`,
|
|
19
|
-
* which this never reads, so every category found here is publishable.
|
|
20
|
-
*/
|
|
21
|
-
export function listFolderCategories(root: string): string[] {
|
|
22
|
-
const dir = snippetsSourceDir(root)
|
|
23
|
-
if (!isDirectory(dir)) return []
|
|
24
|
-
|
|
25
|
-
return readdirSync(dir, { withFileTypes: true })
|
|
26
|
-
.filter((entry) => entry.isDirectory())
|
|
27
|
-
.map((entry) => entry.name)
|
|
28
|
-
.sort()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function listCategories(root: string): string[] {
|
|
32
|
-
return [BASE_CATEGORY, ...listFolderCategories(root)]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function categoryDir(root: string, category: string): string {
|
|
36
|
-
const source = snippetsSourceDir(root)
|
|
37
|
-
return category === BASE_CATEGORY ? source : join(source, category)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Tests for a directory rather than mere existence. `snippets/` holds files
|
|
42
|
-
* alongside its category folders, so an argument naming one of them resolves
|
|
43
|
-
* to a real path that cannot be scanned as a category.
|
|
44
|
-
*
|
|
45
|
-
* An empty category refuses rather than resolving, since `join(source, '')`
|
|
46
|
-
* is `source` and would otherwise report the base directory as the category,
|
|
47
|
-
* widening an unset value to every base entry instead of naming none.
|
|
48
|
-
*/
|
|
49
|
-
export function categoryExists(root: string, category: string): boolean {
|
|
50
|
-
if (category === '') return false
|
|
51
|
-
return isDirectory(categoryDir(root, category))
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Lists one category's entry slugs. Only the immediate level is read, matching
|
|
56
|
-
* the `find -maxdepth 1` the bash used, so a nested folder is its own category
|
|
57
|
-
* rather than part of its parent.
|
|
58
|
-
*/
|
|
59
|
-
export function listEntries(root: string, category: string): string[] {
|
|
60
|
-
const dir = categoryDir(root, category)
|
|
61
|
-
if (!isDirectory(dir)) return []
|
|
62
|
-
|
|
63
|
-
return [...new Bun.Glob('*.md').scanSync({ cwd: dir, onlyFiles: true })]
|
|
64
|
-
.map((name) => name.slice(0, -'.md'.length))
|
|
65
|
-
.sort()
|
|
66
|
-
}
|
package/src/snippets/list.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { listCategories, listEntries } from '@/snippets/categories'
|
|
2
|
-
import { loadPresets } from '@/snippets/presets'
|
|
3
|
-
|
|
4
|
-
export interface CategoryCatalogEntry {
|
|
5
|
-
readonly name: string
|
|
6
|
-
readonly entries: readonly string[]
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface SnippetsCatalog {
|
|
10
|
-
readonly presets: readonly {
|
|
11
|
-
readonly name: string
|
|
12
|
-
readonly slugs: readonly string[]
|
|
13
|
-
}[]
|
|
14
|
-
readonly categories: readonly CategoryCatalogEntry[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Builds the catalog both output modes read, so the human list and the `--json`
|
|
19
|
-
* one cannot describe different corpora.
|
|
20
|
-
*/
|
|
21
|
-
export function buildSnippetsCatalog(root: string): SnippetsCatalog {
|
|
22
|
-
return {
|
|
23
|
-
presets: loadPresets(root).map((preset) => ({
|
|
24
|
-
name: preset.name,
|
|
25
|
-
slugs: preset.slugs,
|
|
26
|
-
})),
|
|
27
|
-
categories: listCategories(root).map((name) => ({
|
|
28
|
-
name,
|
|
29
|
-
entries: listEntries(root, name),
|
|
30
|
-
})),
|
|
31
|
-
}
|
|
32
|
-
}
|
package/src/snippets/presets.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import { snippetsSourceDir } from '@/snippets/categories'
|
|
4
|
-
|
|
5
|
-
export interface Preset {
|
|
6
|
-
readonly name: string
|
|
7
|
-
readonly slugs: readonly string[]
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function presetsPath(root: string): string {
|
|
11
|
-
return join(snippetsSourceDir(root), 'snippets.toml')
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Reads every preset from `snippets.toml`. The bash read section headers with
|
|
16
|
-
* `grep '^\['` and then re-walked the file per preset with a `BASH_REMATCH`
|
|
17
|
-
* loop, so a name carrying a bracket or a slug split across lines parsed by
|
|
18
|
-
* accident rather than by rule.
|
|
19
|
-
*/
|
|
20
|
-
export function loadPresets(root: string): Preset[] {
|
|
21
|
-
const path = presetsPath(root)
|
|
22
|
-
if (!existsSync(path)) return []
|
|
23
|
-
|
|
24
|
-
const parsed = Bun.TOML.parse(readFileSync(path, 'utf8')) as Record<
|
|
25
|
-
string,
|
|
26
|
-
unknown
|
|
27
|
-
>
|
|
28
|
-
const presets: Preset[] = []
|
|
29
|
-
|
|
30
|
-
for (const [name, table] of Object.entries(parsed)) {
|
|
31
|
-
if (typeof table !== 'object' || table === null || Array.isArray(table)) {
|
|
32
|
-
continue
|
|
33
|
-
}
|
|
34
|
-
const names = (table as Record<string, unknown>).names
|
|
35
|
-
presets.push({
|
|
36
|
-
name,
|
|
37
|
-
slugs: Array.isArray(names)
|
|
38
|
-
? names.filter(
|
|
39
|
-
(slug): slug is string => typeof slug === 'string' && slug !== '',
|
|
40
|
-
)
|
|
41
|
-
: [],
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return presets.sort((left, right) => left.name.localeCompare(right.name))
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function findPreset(root: string, name: string): Preset | undefined {
|
|
49
|
-
return loadPresets(root).find((preset) => preset.name === name)
|
|
50
|
-
}
|
package/standards/snippets.md
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Snippet reference
|
|
3
|
-
description: Snippet reference and authoring conventions
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Snippet reference
|
|
7
|
-
|
|
8
|
-
## Scope
|
|
9
|
-
|
|
10
|
-
Governs a snippet file: what one is for, whether a prompt qualifies as one, how it is invoked, and the structure of its body.
|
|
11
|
-
|
|
12
|
-
Does not govern:
|
|
13
|
-
|
|
14
|
-
- Skill folders, which carry frontmatter, references, and scripts a snippet has none of: `skill.md`
|
|
15
|
-
- Voice, rhythm, and sentence construction in snippet prose: the `write-human` skill
|
|
16
|
-
- Punctuation, formatting, and word choice in snippet prose: `markdown.md`
|
|
17
|
-
|
|
18
|
-
## What a snippet is
|
|
19
|
-
|
|
20
|
-
A snippet is a short, focused prompt stored as a plain markdown file. Invoke one to insert a prepared instruction into any AI chat without retyping it. Each snippet covers one purpose. If a prompt needs headers or multiple goals, use a system prompt instead.
|
|
21
|
-
|
|
22
|
-
## Admission
|
|
23
|
-
|
|
24
|
-
Two tests decide whether a prompt becomes a snippet, and both have to pass. Apply them when adding one and when auditing the catalog.
|
|
25
|
-
|
|
26
|
-
- Cadence: a prompt invoked many times across sessions qualifies. A one-shot audit, migration, or bootstrap prompt does not, and belongs in notes outside the catalog.
|
|
27
|
-
- Audience: a prompt the consuming project would invoke ships in `snippets/`. One only the authoring repository can run stays outside every installable folder, which is a rule for a repository that authors snippets for others rather than for one that only consumes them.
|
|
28
|
-
|
|
29
|
-
A subfolder under `snippets/` names where a prompt is invoked rather than what it is about. A prompt that reads or writes the project's own files goes in a folder, and one carrying its whole context in the message goes at the root.
|
|
30
|
-
|
|
31
|
-
Overlapping a skill that does the same job is not disqualifying on its own. A snippet fires when a person asks for it by name and a skill fires on a description match, so the two coexist when those paths differ and the outputs do. Record the reason where the project keeps its decisions, or drop the snippet.
|
|
32
|
-
|
|
33
|
-
## Invocation channels
|
|
34
|
-
|
|
35
|
-
- Chrome extension: type `>slug` in a supported chat UI (claude.ai, gemini.google.com) to insert the snippet text inline
|
|
36
|
-
- Claude Code terminal: prefix the plugin path with `@` (e.g. `@claude/snippets/claude/feature-recap`). `claude/snippets` is a symlink to the authoring root, so the folder a snippet is written in is the folder it is invoked from
|
|
37
|
-
|
|
38
|
-
## Use patterns
|
|
39
|
-
|
|
40
|
-
- Run-as-is: invoke and send immediately. The snippet is self-contained and needs no extra context.
|
|
41
|
-
- Invoke-then-add-context: invoke the snippet, then append specifics in the same message (e.g. invoke `plan-feature`, then add the feature name or extra constraints)
|
|
42
|
-
- Invoke-on-history: invoke after a discussion. The snippet uses prior conversation as implicit context with no additional input needed (e.g. invoke `claude-figma` after discussing a design).
|
|
43
|
-
|
|
44
|
-
## Authoring
|
|
45
|
-
|
|
46
|
-
- One focused purpose per snippet. If it needs headers or multiple goals, use a system prompt instead.
|
|
47
|
-
- Self-contained. No references to external files or assumed prior context.
|
|
48
|
-
- No user fill-in placeholders. If a value depends on context, the user adds it after invocation.
|
|
49
|
-
- Plain markdown only. No YAML frontmatter, no headers, no nested structure.
|
|
50
|
-
- Filename is the slug: kebab-case, no capitals, no underscores
|
|
51
|
-
|
|
52
|
-
## Structure
|
|
53
|
-
|
|
54
|
-
- Lead with a verb. Open with an imperative that states the job immediately.
|
|
55
|
-
- One instruction per sentence. Do not stack multiple actions into one sentence.
|
|
56
|
-
- For sequential steps, use a numbered list with one action per item.
|
|
57
|
-
- When the output has a fixed shape, show it in a fenced code block with a language identifier.
|
|
58
|
-
- Put constraints and exclusions last, not inline with the main instructions.
|
|
59
|
-
|
|
60
|
-
## Examples
|
|
61
|
-
|
|
62
|
-
### Correct
|
|
63
|
-
|
|
64
|
-
```markdown
|
|
65
|
-
I want to implement the following. Scan relevant files and list conflicts. Do not implement. # user adds feature after invocation
|
|
66
|
-
Scan relevant files and list conflicts. Do not implement. # run-as-is, no context needed
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Incorrect
|
|
70
|
-
|
|
71
|
-
```markdown
|
|
72
|
-
I want to implement: <feature or task name> # redundant fill-in, add context after invocation instead
|
|
73
|
-
See ARCHITECTURE.md before starting. # external dependency, not self-contained
|
|
74
|
-
|
|
75
|
-
## Overview\n## Steps # headers belong in a system prompt, not a snippet
|
|
76
|
-
```
|