@jeremiewerner/motto 0.0.3
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/LICENSE +21 -0
- package/bin/motto.js +78 -0
- package/dist/public/.claude-plugin/plugin.json +5 -0
- package/dist/public/author-skill/SKILL.md +117 -0
- package/dist/public/author-skill/references/skill-schema.md +163 -0
- package/dist/public/setup-project/SKILL.md +133 -0
- package/dist/public/setup-project/references/skill-schema.md +163 -0
- package/package.json +31 -0
- package/src/build.js +214 -0
- package/src/config.js +105 -0
- package/src/frontmatter.js +134 -0
- package/src/lint.js +207 -0
- package/src/schema.js +170 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jérémie Werner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/bin/motto.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* motto CLI entry point — thin shell.
|
|
4
|
+
*
|
|
5
|
+
* Parses the subcommand with node:util parseArgs (allowPositionals, strict:true
|
|
6
|
+
* so unknown flags throw and are caught to print usage). Dispatches to the lint
|
|
7
|
+
* orchestrator or reserves the build slot for Phase 3.
|
|
8
|
+
*
|
|
9
|
+
* Output format (D2-11):
|
|
10
|
+
* Clean: "✓ N skills OK" to stdout, exit 0.
|
|
11
|
+
* Errors: "✗ <skill>: <message>" per error to stdout, exit 1.
|
|
12
|
+
* Usage: "usage: motto <lint|build>" to stderr, exit 1.
|
|
13
|
+
*
|
|
14
|
+
* Exit codes: always set via process.exitCode (never process.exit(1)) to
|
|
15
|
+
* avoid truncating buffered stdout (Pitfall 7). The only exception is
|
|
16
|
+
* process.exit() (no arg) in the parseArgs catch block, which is safe because
|
|
17
|
+
* we have already written all output to stderr before calling it.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { parseArgs } from 'node:util';
|
|
21
|
+
import { lintProject } from '../src/lint.js';
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Parse argv — strict:true throws on any unknown flag (e.g. --help)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = parseArgs({
|
|
30
|
+
args: process.argv.slice(2),
|
|
31
|
+
options: {},
|
|
32
|
+
allowPositionals: true,
|
|
33
|
+
strict: true,
|
|
34
|
+
});
|
|
35
|
+
} catch {
|
|
36
|
+
// Unknown flag (strict:true) — print usage and exit (Pitfall 4, D2-16)
|
|
37
|
+
process.stderr.write('usage: motto <lint|build>\n');
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
process.exit(); // exits with exitCode 1; safe: all output written before this call
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sub = parsed.positionals[0];
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Subcommand dispatch
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
if (sub === 'lint') {
|
|
49
|
+
// cwd-only, v1 (D2-15)
|
|
50
|
+
const result = await lintProject(process.cwd());
|
|
51
|
+
if (result.ok) {
|
|
52
|
+
process.stdout.write(`✓ ${result.count} skills OK\n`);
|
|
53
|
+
} else {
|
|
54
|
+
for (const e of result.errors) {
|
|
55
|
+
process.stdout.write(`✗ ${e.skill}: ${e.message}\n`);
|
|
56
|
+
}
|
|
57
|
+
process.exitCode = 1; // D2-11; process.exit(1) NOT used (Pitfall 7)
|
|
58
|
+
}
|
|
59
|
+
} else if (sub === 'build') {
|
|
60
|
+
// cwd-only, mirrors lint branch (D3-15)
|
|
61
|
+
const { buildProject } = await import('../src/build.js');
|
|
62
|
+
const result = await buildProject(process.cwd());
|
|
63
|
+
if (result.ok) {
|
|
64
|
+
// D3-16: output dir + one-line summary
|
|
65
|
+
process.stdout.write(
|
|
66
|
+
`✓ built ${result.outDir} — ${result.skillCount} skills, ${result.bucketCount} plugin(s)\n`,
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
for (const e of result.errors) {
|
|
70
|
+
process.stdout.write(`✗ ${e.skill}: ${e.message}\n`);
|
|
71
|
+
}
|
|
72
|
+
process.exitCode = 1; // never process.exit(1) — preserves stdout flush (Pitfall 7)
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
// Unknown subcommand or no subcommand at all (D2-16)
|
|
76
|
+
process.stderr.write('usage: motto <lint|build>\n');
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: author-skill
|
|
3
|
+
description: Teaches how to write a conforming SKILL.md for the Motto framework. Use this skill when authoring a new Motto skill or investigating a lint error.
|
|
4
|
+
audience: public
|
|
5
|
+
shared_references:
|
|
6
|
+
- skill-schema
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Authoring a Motto Skill
|
|
10
|
+
|
|
11
|
+
**Role:** You are a hands-on guide for writing Motto SKILL.md files. Walk the author through each required frontmatter field, the body spine contract, and shared references step by step, citing the exact lint error messages they will see if something is wrong.
|
|
12
|
+
|
|
13
|
+
A Motto skill is a `SKILL.md` file that Motto validates and packages into a Claude Code Agent Skill plugin. You write the source; Motto handles distribution.
|
|
14
|
+
|
|
15
|
+
## Required Frontmatter Fields
|
|
16
|
+
|
|
17
|
+
Every SKILL.md must open with a frontmatter block — a `---`-delimited YAML section — containing three required fields.
|
|
18
|
+
|
|
19
|
+
### `name`
|
|
20
|
+
|
|
21
|
+
The skill's identifier. Must be a **letter-start kebab-case** string matching `^[a-z][a-z0-9]*(-[a-z0-9]+)*$` and must equal the skill's folder name exactly. See the bundled `skill-schema` reference for the full regex, reserved-word list, and all four lint error messages in the name validation cascade.
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
name: my-skill
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### `description`
|
|
28
|
+
|
|
29
|
+
A short, non-empty string stating what the skill does and when an agent should trigger it. Keep it under 1024 characters and avoid XML-like tags (`<`, `>`).
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
description: Explains how to configure my-skill. Use this skill when setting up a new my-skill configuration.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Lint error if missing:** `description is required`
|
|
36
|
+
|
|
37
|
+
### `audience`
|
|
38
|
+
|
|
39
|
+
Must be exactly `public` or `private`.
|
|
40
|
+
|
|
41
|
+
- `public` — skill is shipped in the public plugin bucket (`dist/public/`)
|
|
42
|
+
- `private` — skill is shipped in the private plugin bucket (`dist/private/`) only
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
audience: public
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Lint error if wrong:** `audience must be public|private`
|
|
49
|
+
|
|
50
|
+
## The Body Spine
|
|
51
|
+
|
|
52
|
+
After the closing `---` delimiter, the skill body must satisfy two independent checks. If both fail, both errors are reported in a single lint run.
|
|
53
|
+
|
|
54
|
+
**H1 title:** The first non-blank line must be an H1 heading — `# Title` with at least one non-space character after `# `.
|
|
55
|
+
|
|
56
|
+
**Role line:** The body must contain at least one line beginning with `**Role:`. Write a complete, behavioral sentence — "You are a..." — not just a label. An empty `**Role:**` passes the validator but produces unusable agent instruction content.
|
|
57
|
+
|
|
58
|
+
```markdown
|
|
59
|
+
# My Skill Title
|
|
60
|
+
|
|
61
|
+
**Role:** You are a guide who walks the author through every step of X.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Optional Frontmatter
|
|
65
|
+
|
|
66
|
+
### `shared_references`
|
|
67
|
+
|
|
68
|
+
A YAML array of bare basenames — no `.md` extension, no path separators. Each entry must correspond to an existing `shared/references/<entry>.md` file.
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
shared_references:
|
|
72
|
+
- skill-schema
|
|
73
|
+
- another-reference
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`motto build` copies each referenced file into the skill's `references/` directory in the output, making the skill self-contained when distributed. If a skill already has a local `references/<entry>.md`, build will report a collision and stop.
|
|
77
|
+
|
|
78
|
+
**Lint error if file is missing:** `shared_references entry "X" not found in shared/references/`
|
|
79
|
+
|
|
80
|
+
### `template` and `dependencies`
|
|
81
|
+
|
|
82
|
+
Accepted and passed through verbatim; not validated in v0.0.2.
|
|
83
|
+
|
|
84
|
+
## Annotated Minimal Example
|
|
85
|
+
|
|
86
|
+
The following is a minimal, correct SKILL.md showing every required field and the mandatory body spine:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
---
|
|
90
|
+
name: my-skill
|
|
91
|
+
description: Shows how to configure my-skill. Use this skill when setting up my-skill for the first time.
|
|
92
|
+
audience: public
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
# My Skill
|
|
96
|
+
|
|
97
|
+
**Role:** You are a guide who walks the author through configuring my-skill step by step.
|
|
98
|
+
|
|
99
|
+
Main body content goes here.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The folder containing this file must be named `my-skill` to match the `name` field.
|
|
103
|
+
|
|
104
|
+
## Common Lint Errors
|
|
105
|
+
|
|
106
|
+
| Error message | Cause | Fix |
|
|
107
|
+
|---------------|-------|-----|
|
|
108
|
+
| `name is required` | `name` field is missing | Add `name: your-skill-name` |
|
|
109
|
+
| `name must be letter-start kebab-case (...)` | Uppercase, underscores, leading digit, or double hyphen | Use lowercase letters, hyphens only, starting with a letter |
|
|
110
|
+
| `name must not contain the reserved substrings "anthropic" or "claude": "X"` | Skill name contains a reserved substring | Rename the skill folder and `name` field |
|
|
111
|
+
| `name "X" must equal its folder name "Y"` | `name` field does not match the folder name | Rename the folder or fix the `name` field |
|
|
112
|
+
| `description is required` | `description` field is missing or empty | Add a non-empty `description` |
|
|
113
|
+
| `audience must be public\|private` | `audience` is missing or has an unexpected value | Set `audience: public` or `audience: private` |
|
|
114
|
+
| `body must begin with an H1 title line (# Title) as its first non-blank line` | Body does not start with `# Title` | Make the first non-blank line after `---` an H1 |
|
|
115
|
+
| `body must contain a **Role:** line` | No line starting with `**Role:` in the body | Add `**Role:** You are...` to the body |
|
|
116
|
+
| `shared_references entry "X" is an unsafe basename (must not contain "/" or ".")` | Entry contains a path separator or extension | Use a bare basename like `skill-schema`, not `./skill-schema.md` |
|
|
117
|
+
| `shared_references entry "X" not found in shared/references/` | Declared shared ref file does not exist | Create `shared/references/X.md` or fix the basename |
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Motto Skill Schema Reference
|
|
2
|
+
|
|
3
|
+
This file is the canonical rule source for the Motto skill schema (v0.0.2).
|
|
4
|
+
It is bundled verbatim by `motto build` into each public skill's `references/` directory.
|
|
5
|
+
All claims are derived from `src/schema.js`, `src/config.js`, `src/frontmatter.js`, and `src/lint.js`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. `name` Field
|
|
10
|
+
|
|
11
|
+
**Rule:** A skill's `name` must be a letter-start kebab-case identifier.
|
|
12
|
+
|
|
13
|
+
Pattern: `^[a-z][a-z0-9]*(-[a-z0-9]+)*$`
|
|
14
|
+
- Starts with exactly one lowercase letter (not a digit, not a hyphen)
|
|
15
|
+
- Followed by zero or more lowercase letters or digits
|
|
16
|
+
- Then zero or more groups of (one hyphen + one or more lowercase letters/digits)
|
|
17
|
+
|
|
18
|
+
**Additional constraints:**
|
|
19
|
+
- Must equal the skill's folder name exactly. If the folder is `my-skill`, the `name` field must be `my-skill`.
|
|
20
|
+
- Max 64 characters (Claude Code platform limit; enforced by the Motto validator as a cascade step).
|
|
21
|
+
- Must not contain the substrings `anthropic` or `claude` (substring match, not word boundary).
|
|
22
|
+
|
|
23
|
+
**Name validation cascades — it stops at the first failure:**
|
|
24
|
+
|
|
25
|
+
| Step | Check | Lint error emitted |
|
|
26
|
+
|------|-------|--------------------|
|
|
27
|
+
| 1 | `name` is missing, empty, or falsy (also covers non-string falsy values) | `name is required` |
|
|
28
|
+
| 2 | `name` is a truthy non-string (boolean, number, array, object) | `name must be a string (got <typeof>)` |
|
|
29
|
+
| 3 | `name` fails the kebab regex | `name must be letter-start kebab-case (/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/): "X"` |
|
|
30
|
+
| 4 | `name` exceeds 64 characters | `name must not exceed 64 characters (got N): "X"` |
|
|
31
|
+
| 5 | `name` contains a reserved substring | `name must not contain the reserved substrings "anthropic" or "claude": "X"` |
|
|
32
|
+
| 6 | `name` does not match the folder | `name "X" must equal its folder name "Y"` |
|
|
33
|
+
|
|
34
|
+
Valid examples: `my-skill`, `abc`, `a1`, `abc-def-123`
|
|
35
|
+
Invalid examples: `0bad` (leading digit), `My_Skill` (uppercase/underscore), `my--skill` (double dash), `-bad` (leading dash)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. `description` Field
|
|
40
|
+
|
|
41
|
+
**Rule:** Must be present and non-empty.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
description: A concise description of what this skill does and when to use it.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Authoring guidance:**
|
|
48
|
+
- State both what the skill does AND when an agent should trigger it.
|
|
49
|
+
- Max 1024 characters (enforced by the Motto validator).
|
|
50
|
+
- Must not contain XML-tag shapes (e.g. `<example>`, `</p>`, `<br/>`). Plain comparison/math
|
|
51
|
+
text such as `a<b` is not flagged; only strings matching the tag-shape regex are rejected.
|
|
52
|
+
|
|
53
|
+
**Lint errors:**
|
|
54
|
+
- `description is required`
|
|
55
|
+
- `description must not exceed 1024 characters (got N)`
|
|
56
|
+
- `description must not contain XML tags (e.g. <example>)`
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 3. `audience` Field
|
|
61
|
+
|
|
62
|
+
**Rule:** Must be exactly the string `public` or `private`. Any other value (including missing) is an error.
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
audience: public # shipped in dist/public/ bucket
|
|
66
|
+
audience: private # shipped in dist/private/ bucket only
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Lint error if wrong:** `audience must be public|private`
|
|
70
|
+
|
|
71
|
+
**Build behavior:**
|
|
72
|
+
- `public` skills are copied to `dist/public/<skill-name>/`
|
|
73
|
+
- `private` skills are copied to `dist/private/<skill-name>/`
|
|
74
|
+
- `plugins.private` must be set in `motto.yaml` before any private skill can be built
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 4. Body Spine — Two Independent Checks
|
|
79
|
+
|
|
80
|
+
The skill body (the Markdown after the closing `---` delimiter) must satisfy two checks. Both run independently — both errors are reported even if both fail at once.
|
|
81
|
+
|
|
82
|
+
**Check 1 — H1 title:** The first non-blank line of the body must match `^# \S` (an H1 heading with at least one non-space character after `# `).
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
# My Skill Title
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Lint error if missing:** `body must begin with an H1 title line (# Title) as its first non-blank line`
|
|
89
|
+
|
|
90
|
+
**Check 2 — Role line:** The body must contain at least one line that starts with `**Role:` (multiline match).
|
|
91
|
+
|
|
92
|
+
```markdown
|
|
93
|
+
**Role:** You are a hands-on guide who walks the author through...
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The Role line content after `:` is not validated. An empty `**Role:**` passes the regex but produces unusable agent instruction content. Write Role lines as complete, behavioral sentences.
|
|
97
|
+
|
|
98
|
+
**Lint error if missing:** `body must contain a **Role:** line`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 5. `shared_references` Field
|
|
103
|
+
|
|
104
|
+
**Rule:** Optional. When present, must be a YAML array of strings. Each entry must be a safe basename.
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
shared_references:
|
|
108
|
+
- skill-schema
|
|
109
|
+
- another-reference
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Per-entry rules:**
|
|
113
|
+
- Must NOT contain `/` or `.` — bare basenames only, no path separators or extensions
|
|
114
|
+
- Must correspond to an existing `shared/references/<entry>.md` file
|
|
115
|
+
|
|
116
|
+
**Per-entry lint errors:**
|
|
117
|
+
- `shared_references entry "X" is an unsafe basename (must not contain "/" or ".")`
|
|
118
|
+
- `shared_references entry "X" not found in shared/references/`
|
|
119
|
+
|
|
120
|
+
**Build behavior:** `motto build` copies `shared/references/<entry>.md` into each skill's `dist/<audience>/<skill-name>/references/` directory, making the skill self-contained.
|
|
121
|
+
|
|
122
|
+
**Collision guard:** If a skill already has a local `references/<entry>.md`, build will report a collision and stop.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 6. `template` and `dependencies` Fields
|
|
127
|
+
|
|
128
|
+
These fields are accepted and passed through verbatim. They are NOT validated in Motto v0.0.2.
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
template: some-template
|
|
132
|
+
dependencies:
|
|
133
|
+
- another-skill
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 7. Frontmatter Envelope
|
|
139
|
+
|
|
140
|
+
Every SKILL.md must open with a bare `---` on line 1 and close with a matching bare `---`.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
---
|
|
144
|
+
name: my-skill
|
|
145
|
+
description: What this skill does and when to use it.
|
|
146
|
+
audience: public
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
# My Skill Title
|
|
150
|
+
|
|
151
|
+
**Role:** You are...
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Rules:**
|
|
155
|
+
- The opening `---` must be the very first line (no blank lines, no BOM — BOM is stripped automatically).
|
|
156
|
+
- A matching closing `---` is required.
|
|
157
|
+
- YAML between the delimiters is parsed with the `yaml` v2 library (YAML 1.2 core schema) — `yes`/`no`/`on`/`off` are NOT treated as booleans.
|
|
158
|
+
- A stray `---` inside the body is flagged only if the content between it and the body start parses as a clean, non-empty YAML mapping. Wrapping example SKILL.md blocks in fenced code blocks avoids this edge case.
|
|
159
|
+
|
|
160
|
+
**Lint errors:**
|
|
161
|
+
- `missing frontmatter: file must begin with a '--- ... ---' block`
|
|
162
|
+
- `unterminated frontmatter: no closing '---' delimiter found`
|
|
163
|
+
- `stray '---' delimiter in frontmatter: the block must contain exactly one closing '---'`
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup-project
|
|
3
|
+
description: Walks through initializing a Motto project — directory layout, motto.yaml configuration, and running lint and build. Use this skill when starting a new Motto project.
|
|
4
|
+
audience: public
|
|
5
|
+
shared_references:
|
|
6
|
+
- skill-schema
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Setting Up a Motto Project
|
|
10
|
+
|
|
11
|
+
**Role:** You are a Motto project setup guide. Walk the author through the source directory layout, every motto.yaml field, the lint and build commands, and what the dist/ output contains.
|
|
12
|
+
|
|
13
|
+
## Directory Layout
|
|
14
|
+
|
|
15
|
+
A Motto project uses a fixed directory structure:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
my-project/
|
|
19
|
+
skills/
|
|
20
|
+
my-skill/
|
|
21
|
+
SKILL.md ← one skill per folder
|
|
22
|
+
references/ ← optional local references (skill-specific)
|
|
23
|
+
another-skill/
|
|
24
|
+
SKILL.md
|
|
25
|
+
shared/
|
|
26
|
+
references/ ← shared references bundled into multiple skills at build time
|
|
27
|
+
skill-schema.md
|
|
28
|
+
motto.yaml ← project configuration
|
|
29
|
+
dist/ ← generated output; never edit manually
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Rules:
|
|
33
|
+
- Each skill lives in its own folder under `skills/`. The folder name must match the `name` field in the skill's `SKILL.md`.
|
|
34
|
+
- `shared/references/` holds Markdown reference files that can be declared by multiple skills and bundled by `motto build`.
|
|
35
|
+
- `dist/` is created (and wiped) by `motto build`. Do not commit it.
|
|
36
|
+
|
|
37
|
+
## `motto.yaml` Fields
|
|
38
|
+
|
|
39
|
+
The root `motto.yaml` configures the project and both plugin buckets.
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
name: my-project
|
|
43
|
+
version: "0.1.0"
|
|
44
|
+
description: A short description of what this project's skills do.
|
|
45
|
+
plugins:
|
|
46
|
+
public: my-project-skills
|
|
47
|
+
private: my-project-private # only required when private-audience skills exist
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Field | Required | Rule | Example |
|
|
51
|
+
|-------|----------|------|---------|
|
|
52
|
+
| `name` | Yes | Truthy string; used as project identifier | `motto` |
|
|
53
|
+
| `version` | Yes | Truthy string; recommend semver; quote it to ensure a YAML string | `"0.1.0"` |
|
|
54
|
+
| `description` | Yes | Truthy string; feeds into each built plugin.json | `"A framework for..."` |
|
|
55
|
+
| `plugins.public` | Yes | Letter-start kebab-case (`^[a-z][a-z0-9]*(-[a-z0-9]+)*$`); cannot contain `anthropic` or `claude` | `my-project-skills` |
|
|
56
|
+
| `plugins.private` | Only when private skills exist | Same kebab-case rule as `plugins.public` | `my-project-private` |
|
|
57
|
+
|
|
58
|
+
See the `skill-schema` reference for the full kebab regex and name constraint rules.
|
|
59
|
+
|
|
60
|
+
## Running Lint
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
node bin/motto.js lint
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
or, once installed globally:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
motto lint
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Clean output:**
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
✓ 3 skills OK
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Error output (example):**
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
✗ my-skill: name must be letter-start kebab-case ...
|
|
82
|
+
✗ my-skill: body must contain a **Role:** line
|
|
83
|
+
✗ (project): missing plugins.public
|
|
84
|
+
|
|
85
|
+
3 errors
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Lint validates the config (`motto.yaml`), loads shared references, then validates every skill in `skills/`. All errors from all skills are reported in a single run.
|
|
89
|
+
|
|
90
|
+
## Running Build
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
node bin/motto.js build
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
or:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
motto build
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Build runs lint first. If lint fails, no files are written to `dist/`. On success, `dist/` is wiped and rebuilt.
|
|
103
|
+
|
|
104
|
+
### What `dist/` Contains
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
dist/
|
|
108
|
+
public/
|
|
109
|
+
.claude-plugin/
|
|
110
|
+
plugin.json ← { "name": "my-project-skills", "version": "...", "description": "..." }
|
|
111
|
+
my-skill/
|
|
112
|
+
SKILL.md ← verbatim copy from skills/my-skill/SKILL.md
|
|
113
|
+
references/
|
|
114
|
+
skill-schema.md ← copied from shared/references/ (if declared)
|
|
115
|
+
private/
|
|
116
|
+
.claude-plugin/
|
|
117
|
+
plugin.json ← { "name": "my-project-private", "version": "...", "description": "..." }
|
|
118
|
+
secret-skill/
|
|
119
|
+
SKILL.md
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- `plugin.json` is generated from `motto.yaml` fields (`name`, `version`, `description`).
|
|
123
|
+
- Shared references declared in a skill's `shared_references` field are bundled into that skill's `references/` directory.
|
|
124
|
+
- Private skills appear only in `dist/private/`. The public plugin never contains private skill content.
|
|
125
|
+
|
|
126
|
+
## Installing the Built Plugin in Claude Code
|
|
127
|
+
|
|
128
|
+
After building, point Claude Code at the output directory for the bucket you want to install:
|
|
129
|
+
|
|
130
|
+
- **Public plugin:** install from `dist/public/`
|
|
131
|
+
- **Private plugin:** install from `dist/private/`
|
|
132
|
+
|
|
133
|
+
Use the `claude plugin add` command (or the Claude Code UI) with the path to the bucket directory. Each bucket contains a valid `.claude-plugin/plugin.json` manifest.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Motto Skill Schema Reference
|
|
2
|
+
|
|
3
|
+
This file is the canonical rule source for the Motto skill schema (v0.0.2).
|
|
4
|
+
It is bundled verbatim by `motto build` into each public skill's `references/` directory.
|
|
5
|
+
All claims are derived from `src/schema.js`, `src/config.js`, `src/frontmatter.js`, and `src/lint.js`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. `name` Field
|
|
10
|
+
|
|
11
|
+
**Rule:** A skill's `name` must be a letter-start kebab-case identifier.
|
|
12
|
+
|
|
13
|
+
Pattern: `^[a-z][a-z0-9]*(-[a-z0-9]+)*$`
|
|
14
|
+
- Starts with exactly one lowercase letter (not a digit, not a hyphen)
|
|
15
|
+
- Followed by zero or more lowercase letters or digits
|
|
16
|
+
- Then zero or more groups of (one hyphen + one or more lowercase letters/digits)
|
|
17
|
+
|
|
18
|
+
**Additional constraints:**
|
|
19
|
+
- Must equal the skill's folder name exactly. If the folder is `my-skill`, the `name` field must be `my-skill`.
|
|
20
|
+
- Max 64 characters (Claude Code platform limit; enforced by the Motto validator as a cascade step).
|
|
21
|
+
- Must not contain the substrings `anthropic` or `claude` (substring match, not word boundary).
|
|
22
|
+
|
|
23
|
+
**Name validation cascades — it stops at the first failure:**
|
|
24
|
+
|
|
25
|
+
| Step | Check | Lint error emitted |
|
|
26
|
+
|------|-------|--------------------|
|
|
27
|
+
| 1 | `name` is missing, empty, or falsy (also covers non-string falsy values) | `name is required` |
|
|
28
|
+
| 2 | `name` is a truthy non-string (boolean, number, array, object) | `name must be a string (got <typeof>)` |
|
|
29
|
+
| 3 | `name` fails the kebab regex | `name must be letter-start kebab-case (/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/): "X"` |
|
|
30
|
+
| 4 | `name` exceeds 64 characters | `name must not exceed 64 characters (got N): "X"` |
|
|
31
|
+
| 5 | `name` contains a reserved substring | `name must not contain the reserved substrings "anthropic" or "claude": "X"` |
|
|
32
|
+
| 6 | `name` does not match the folder | `name "X" must equal its folder name "Y"` |
|
|
33
|
+
|
|
34
|
+
Valid examples: `my-skill`, `abc`, `a1`, `abc-def-123`
|
|
35
|
+
Invalid examples: `0bad` (leading digit), `My_Skill` (uppercase/underscore), `my--skill` (double dash), `-bad` (leading dash)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. `description` Field
|
|
40
|
+
|
|
41
|
+
**Rule:** Must be present and non-empty.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
description: A concise description of what this skill does and when to use it.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Authoring guidance:**
|
|
48
|
+
- State both what the skill does AND when an agent should trigger it.
|
|
49
|
+
- Max 1024 characters (enforced by the Motto validator).
|
|
50
|
+
- Must not contain XML-tag shapes (e.g. `<example>`, `</p>`, `<br/>`). Plain comparison/math
|
|
51
|
+
text such as `a<b` is not flagged; only strings matching the tag-shape regex are rejected.
|
|
52
|
+
|
|
53
|
+
**Lint errors:**
|
|
54
|
+
- `description is required`
|
|
55
|
+
- `description must not exceed 1024 characters (got N)`
|
|
56
|
+
- `description must not contain XML tags (e.g. <example>)`
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 3. `audience` Field
|
|
61
|
+
|
|
62
|
+
**Rule:** Must be exactly the string `public` or `private`. Any other value (including missing) is an error.
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
audience: public # shipped in dist/public/ bucket
|
|
66
|
+
audience: private # shipped in dist/private/ bucket only
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Lint error if wrong:** `audience must be public|private`
|
|
70
|
+
|
|
71
|
+
**Build behavior:**
|
|
72
|
+
- `public` skills are copied to `dist/public/<skill-name>/`
|
|
73
|
+
- `private` skills are copied to `dist/private/<skill-name>/`
|
|
74
|
+
- `plugins.private` must be set in `motto.yaml` before any private skill can be built
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 4. Body Spine — Two Independent Checks
|
|
79
|
+
|
|
80
|
+
The skill body (the Markdown after the closing `---` delimiter) must satisfy two checks. Both run independently — both errors are reported even if both fail at once.
|
|
81
|
+
|
|
82
|
+
**Check 1 — H1 title:** The first non-blank line of the body must match `^# \S` (an H1 heading with at least one non-space character after `# `).
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
# My Skill Title
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Lint error if missing:** `body must begin with an H1 title line (# Title) as its first non-blank line`
|
|
89
|
+
|
|
90
|
+
**Check 2 — Role line:** The body must contain at least one line that starts with `**Role:` (multiline match).
|
|
91
|
+
|
|
92
|
+
```markdown
|
|
93
|
+
**Role:** You are a hands-on guide who walks the author through...
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The Role line content after `:` is not validated. An empty `**Role:**` passes the regex but produces unusable agent instruction content. Write Role lines as complete, behavioral sentences.
|
|
97
|
+
|
|
98
|
+
**Lint error if missing:** `body must contain a **Role:** line`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 5. `shared_references` Field
|
|
103
|
+
|
|
104
|
+
**Rule:** Optional. When present, must be a YAML array of strings. Each entry must be a safe basename.
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
shared_references:
|
|
108
|
+
- skill-schema
|
|
109
|
+
- another-reference
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Per-entry rules:**
|
|
113
|
+
- Must NOT contain `/` or `.` — bare basenames only, no path separators or extensions
|
|
114
|
+
- Must correspond to an existing `shared/references/<entry>.md` file
|
|
115
|
+
|
|
116
|
+
**Per-entry lint errors:**
|
|
117
|
+
- `shared_references entry "X" is an unsafe basename (must not contain "/" or ".")`
|
|
118
|
+
- `shared_references entry "X" not found in shared/references/`
|
|
119
|
+
|
|
120
|
+
**Build behavior:** `motto build` copies `shared/references/<entry>.md` into each skill's `dist/<audience>/<skill-name>/references/` directory, making the skill self-contained.
|
|
121
|
+
|
|
122
|
+
**Collision guard:** If a skill already has a local `references/<entry>.md`, build will report a collision and stop.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 6. `template` and `dependencies` Fields
|
|
127
|
+
|
|
128
|
+
These fields are accepted and passed through verbatim. They are NOT validated in Motto v0.0.2.
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
template: some-template
|
|
132
|
+
dependencies:
|
|
133
|
+
- another-skill
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 7. Frontmatter Envelope
|
|
139
|
+
|
|
140
|
+
Every SKILL.md must open with a bare `---` on line 1 and close with a matching bare `---`.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
---
|
|
144
|
+
name: my-skill
|
|
145
|
+
description: What this skill does and when to use it.
|
|
146
|
+
audience: public
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
# My Skill Title
|
|
150
|
+
|
|
151
|
+
**Role:** You are...
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Rules:**
|
|
155
|
+
- The opening `---` must be the very first line (no blank lines, no BOM — BOM is stripped automatically).
|
|
156
|
+
- A matching closing `---` is required.
|
|
157
|
+
- YAML between the delimiters is parsed with the `yaml` v2 library (YAML 1.2 core schema) — `yes`/`no`/`on`/`off` are NOT treated as booleans.
|
|
158
|
+
- A stray `---` inside the body is flagged only if the content between it and the body start parses as a clean, non-empty YAML mapping. Wrapping example SKILL.md blocks in fenced code blocks avoids this edge case.
|
|
159
|
+
|
|
160
|
+
**Lint errors:**
|
|
161
|
+
- `missing frontmatter: file must begin with a '--- ... ---' block`
|
|
162
|
+
- `unterminated frontmatter: no closing '---' delimiter found`
|
|
163
|
+
- `stray '---' delimiter in frontmatter: the block must contain exactly one closing '---'`
|