@smitdev/ai-skills 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 smitdev
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/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @smitdev/ai-skills
2
+
3
+ Reusable AI assistant **skills** you can install with one command — for
4
+ **Claude Code**, **GitHub Copilot**, **Cursor**, and **Windsurf**.
5
+
6
+ Skills are authored once in the Claude [Agent Skill](https://docs.claude.com/en/docs/claude-code/skills)
7
+ format (`SKILL.md`) and converted on install to whatever your assistant expects.
8
+
9
+ ## Install a skill
10
+
11
+ No global install needed — run it with `npx`:
12
+
13
+ ```bash
14
+ # See what's available
15
+ npx @smitdev/ai-skills list
16
+
17
+ # Interactive: pick assistant(s) and skill(s)
18
+ npx @smitdev/ai-skills install
19
+
20
+ # Non-interactive examples
21
+ npx @smitdev/ai-skills install --assistant claude --global
22
+ npx @smitdev/ai-skills install --assistant copilot,cursor,windsurf
23
+ npx @smitdev/ai-skills install --assistant all --skill contract --project
24
+ ```
25
+
26
+ ### Where files go
27
+
28
+ | Assistant | Scope | Destination |
29
+ |-----------------|------------------|-------------|
30
+ | Claude Code | `--global` | `~/.claude/skills/<name>/SKILL.md` |
31
+ | Claude Code | `--project` | `./.claude/skills/<name>/SKILL.md` |
32
+ | GitHub Copilot | project | `./.github/instructions/<name>.instructions.md` |
33
+ | Cursor | project | `./.cursor/rules/<name>.mdc` |
34
+ | Windsurf | project | `./.windsurf/rules/<name>.md` |
35
+
36
+ Copilot, Cursor, and Windsurf read their rules from inside a repo, so those
37
+ always install into a project directory (`--dir`, default: current folder).
38
+ Claude Code can install globally (available everywhere) or per-project.
39
+
40
+ Use `--dry-run` to preview without writing anything.
41
+
42
+ ## Available skills
43
+
44
+ - **contract** — Create a build-ready spec / PRD for a feature before any code
45
+ is written, so a coding assistant can build it without guessing.
46
+
47
+ ## Authoring your own skills
48
+
49
+ Each skill is a folder under [`skills/`](skills/) containing a `SKILL.md` with
50
+ YAML frontmatter:
51
+
52
+ ```markdown
53
+ ---
54
+ name: my-skill
55
+ description: When to use this skill, in one or two sentences. The assistant uses this to decide when to apply it.
56
+ ---
57
+
58
+ # My Skill
59
+
60
+ Instructions for the assistant…
61
+ ```
62
+
63
+ Drop the folder in `skills/`, bump the version, and publish. Any extra files in
64
+ the folder are copied verbatim for Claude Code.
65
+
66
+ ## License
67
+
68
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const readline = require('readline');
5
+ const path = require('path');
6
+ const { listSkills } = require('../lib/skills');
7
+ const { adapters } = require('../lib/adapters');
8
+ const { install } = require('../lib/install');
9
+
10
+ const ASSISTANT_IDS = Object.keys(adapters);
11
+
12
+ function parseArgs(argv) {
13
+ const args = { _: [], flags: {} };
14
+ for (let i = 0; i < argv.length; i++) {
15
+ const a = argv[i];
16
+ if (a.startsWith('--')) {
17
+ const key = a.slice(2);
18
+ const next = argv[i + 1];
19
+ if (next === undefined || next.startsWith('--')) {
20
+ args.flags[key] = true;
21
+ } else {
22
+ args.flags[key] = next;
23
+ i++;
24
+ }
25
+ } else if (a === '-y') {
26
+ args.flags.yes = true;
27
+ } else {
28
+ args._.push(a);
29
+ }
30
+ }
31
+ return args;
32
+ }
33
+
34
+ function ask(rl, question) {
35
+ return new Promise((resolve) => rl.question(question, (a) => resolve(a.trim())));
36
+ }
37
+
38
+ function parseList(val, valid) {
39
+ if (val === true || !val) return [];
40
+ const items = String(val)
41
+ .split(',')
42
+ .map((s) => s.trim())
43
+ .filter(Boolean);
44
+ if (val === 'all' || items.includes('all')) return valid.slice();
45
+ return items;
46
+ }
47
+
48
+ function printHelp() {
49
+ console.log(`
50
+ ai-skills — install reusable AI assistant skills
51
+
52
+ Usage:
53
+ npx @smitdev/ai-skills <command> [options]
54
+
55
+ Commands:
56
+ list List the skills bundled in this package
57
+ install Install skills for one or more assistants
58
+
59
+ Options for "install":
60
+ --assistant <ids> Comma-separated: ${ASSISTANT_IDS.join(', ')} (or "all")
61
+ --skill <names> Comma-separated skill names (or "all"; default: all)
62
+ --global Install to your home dir (Claude Code only)
63
+ --project Install into a project (default)
64
+ --dir <path> Project directory (default: current directory)
65
+ --dry-run Show what would change without writing
66
+ -y, --yes Skip interactive prompts (use flags / defaults)
67
+ -h, --help Show this help
68
+
69
+ Examples:
70
+ npx @smitdev/ai-skills list
71
+ npx @smitdev/ai-skills install --assistant claude --global
72
+ npx @smitdev/ai-skills install --assistant copilot,cursor --skill contract
73
+ `);
74
+ }
75
+
76
+ async function pickFromList(rl, label, options) {
77
+ console.log(`\n${label}`);
78
+ options.forEach((o, i) => console.log(` ${i + 1}. ${o.label}`));
79
+ console.log(` (comma-separated numbers, or "a" for all)`);
80
+ const ans = await ask(rl, '> ');
81
+ if (ans.toLowerCase() === 'a' || ans === '') return options.map((o) => o.id);
82
+ const picked = ans
83
+ .split(',')
84
+ .map((s) => parseInt(s.trim(), 10))
85
+ .filter((n) => n >= 1 && n <= options.length)
86
+ .map((n) => options[n - 1].id);
87
+ return [...new Set(picked)];
88
+ }
89
+
90
+ async function runInstall(args) {
91
+ const skills = listSkills();
92
+ if (skills.length === 0) {
93
+ console.error('No skills found in this package.');
94
+ process.exit(1);
95
+ }
96
+
97
+ const projectDir = path.resolve(
98
+ typeof args.flags.dir === 'string' ? args.flags.dir : process.cwd()
99
+ );
100
+ const dryRun = !!args.flags['dry-run'];
101
+ const scope = args.flags.global ? 'global' : 'project';
102
+
103
+ let assistants = parseList(args.flags.assistant, ASSISTANT_IDS);
104
+ let skillNames = parseList(args.flags.skill, skills.map((s) => s.name));
105
+
106
+ const nonInteractive =
107
+ args.flags.yes || (assistants.length > 0);
108
+
109
+ if (!nonInteractive && process.stdin.isTTY) {
110
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
111
+ if (assistants.length === 0) {
112
+ assistants = await pickFromList(
113
+ rl,
114
+ 'Which assistant(s)?',
115
+ ASSISTANT_IDS.map((id) => ({ id, label: adapters[id].label }))
116
+ );
117
+ }
118
+ if (skillNames.length === 0) {
119
+ skillNames = await pickFromList(
120
+ rl,
121
+ 'Which skill(s)?',
122
+ skills.map((s) => ({ id: s.name, label: `${s.name} — ${truncate(s.description, 60)}` }))
123
+ );
124
+ }
125
+ rl.close();
126
+ }
127
+
128
+ if (assistants.length === 0) assistants = ASSISTANT_IDS.slice();
129
+ if (skillNames.length === 0) skillNames = skills.map((s) => s.name);
130
+
131
+ const invalidA = assistants.filter((a) => !ASSISTANT_IDS.includes(a));
132
+ if (invalidA.length) {
133
+ console.error(`Unknown assistant(s): ${invalidA.join(', ')}`);
134
+ console.error(`Valid: ${ASSISTANT_IDS.join(', ')}`);
135
+ process.exit(1);
136
+ }
137
+ const selectedSkills = skills.filter((s) => skillNames.includes(s.name));
138
+ const invalidS = skillNames.filter((n) => !skills.some((s) => s.name === n));
139
+ if (invalidS.length) {
140
+ console.error(`Unknown skill(s): ${invalidS.join(', ')}`);
141
+ console.error(`Valid: ${skills.map((s) => s.name).join(', ')}`);
142
+ process.exit(1);
143
+ }
144
+
145
+ const results = install({
146
+ assistants,
147
+ skills: selectedSkills,
148
+ scope,
149
+ projectDir,
150
+ dryRun,
151
+ });
152
+
153
+ const verb = dryRun ? 'Would install' : 'Installed';
154
+ console.log(`\n${verb} ${selectedSkills.length} skill(s) for ${assistants.length} assistant(s):\n`);
155
+ for (const r of results) {
156
+ console.log(` [${r.assistant}] ${r.skill} -> ${r.path} (${r.action})`);
157
+ }
158
+ if (dryRun) console.log('\n(dry run — no files were written)');
159
+ }
160
+
161
+ function truncate(s, n) {
162
+ s = String(s || '');
163
+ return s.length > n ? s.slice(0, n - 1) + '…' : s;
164
+ }
165
+
166
+ async function main() {
167
+ const args = parseArgs(process.argv.slice(2));
168
+ const cmd = args._[0];
169
+
170
+ if (args.flags.help || args.flags.h || cmd === 'help' || !cmd) {
171
+ printHelp();
172
+ return;
173
+ }
174
+
175
+ if (cmd === 'list') {
176
+ const skills = listSkills();
177
+ console.log(`\n${skills.length} skill(s) available:\n`);
178
+ for (const s of skills) {
179
+ console.log(` ${s.name}`);
180
+ console.log(` ${truncate(s.description, 100)}\n`);
181
+ }
182
+ return;
183
+ }
184
+
185
+ if (cmd === 'install') {
186
+ await runInstall(args);
187
+ return;
188
+ }
189
+
190
+ console.error(`Unknown command: ${cmd}`);
191
+ printHelp();
192
+ process.exit(1);
193
+ }
194
+
195
+ main().catch((err) => {
196
+ console.error(err.message || err);
197
+ process.exit(1);
198
+ });
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * Each adapter turns a parsed skill into one or more output targets:
8
+ * { path, content } -> write a single file
9
+ * { path, copyDir } -> recursively copy a source folder (native format)
10
+ *
11
+ * `scope` is 'global' (user home), 'project' (a repo), or 'both'.
12
+ */
13
+
14
+ // YAML-safe scalar: JSON double-quoted strings are valid YAML double-quoted
15
+ // scalars, so this handles colons, quotes, and apostrophes in descriptions.
16
+ function yamlString(s) {
17
+ return JSON.stringify(String(s == null ? '' : s));
18
+ }
19
+
20
+ const claude = {
21
+ id: 'claude',
22
+ label: 'Claude Code',
23
+ scope: 'both',
24
+ // Native format — copy the whole skill folder verbatim.
25
+ resolve(skill, { scope, projectDir }) {
26
+ const base =
27
+ scope === 'global'
28
+ ? path.join(os.homedir(), '.claude', 'skills')
29
+ : path.join(projectDir, '.claude', 'skills');
30
+ return [{ path: path.join(base, skill.name), copyDir: skill.dir }];
31
+ },
32
+ };
33
+
34
+ const copilot = {
35
+ id: 'copilot',
36
+ label: 'GitHub Copilot',
37
+ scope: 'project',
38
+ resolve(skill, { projectDir }) {
39
+ const content =
40
+ `---\n` +
41
+ `description: ${yamlString(skill.description)}\n` +
42
+ `applyTo: "**"\n` +
43
+ `---\n\n` +
44
+ skill.body;
45
+ return [
46
+ {
47
+ path: path.join(
48
+ projectDir,
49
+ '.github',
50
+ 'instructions',
51
+ `${skill.name}.instructions.md`
52
+ ),
53
+ content,
54
+ },
55
+ ];
56
+ },
57
+ };
58
+
59
+ const cursor = {
60
+ id: 'cursor',
61
+ label: 'Cursor',
62
+ scope: 'project',
63
+ resolve(skill, { projectDir }) {
64
+ const content =
65
+ `---\n` +
66
+ `description: ${yamlString(skill.description)}\n` +
67
+ `globs:\n` +
68
+ `alwaysApply: false\n` +
69
+ `---\n\n` +
70
+ skill.body;
71
+ return [
72
+ {
73
+ path: path.join(projectDir, '.cursor', 'rules', `${skill.name}.mdc`),
74
+ content,
75
+ },
76
+ ];
77
+ },
78
+ };
79
+
80
+ const windsurf = {
81
+ id: 'windsurf',
82
+ label: 'Windsurf',
83
+ scope: 'project',
84
+ resolve(skill, { projectDir }) {
85
+ const content =
86
+ `---\n` +
87
+ `trigger: model_decision\n` +
88
+ `description: ${yamlString(skill.description)}\n` +
89
+ `---\n\n` +
90
+ skill.body;
91
+ return [
92
+ {
93
+ path: path.join(projectDir, '.windsurf', 'rules', `${skill.name}.md`),
94
+ content,
95
+ },
96
+ ];
97
+ },
98
+ };
99
+
100
+ const adapters = { claude, copilot, cursor, windsurf };
101
+
102
+ module.exports = { adapters, yamlString };
package/lib/install.js ADDED
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { adapters } = require('./adapters');
6
+
7
+ function copyDir(src, dest) {
8
+ fs.mkdirSync(dest, { recursive: true });
9
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
10
+ const s = path.join(src, entry.name);
11
+ const d = path.join(dest, entry.name);
12
+ if (entry.isDirectory()) copyDir(s, d);
13
+ else fs.copyFileSync(s, d);
14
+ }
15
+ }
16
+
17
+ function writeFile(file, content) {
18
+ fs.mkdirSync(path.dirname(file), { recursive: true });
19
+ fs.writeFileSync(file, content);
20
+ }
21
+
22
+ /**
23
+ * Install the given skills for the given assistants.
24
+ * opts: { assistants: string[], skills: Skill[], scope, projectDir, dryRun }
25
+ * Returns a list of { assistant, skill, path, action } records.
26
+ */
27
+ function install({ assistants, skills, scope, projectDir, dryRun }) {
28
+ const results = [];
29
+ for (const id of assistants) {
30
+ const adapter = adapters[id];
31
+ if (!adapter) throw new Error(`Unknown assistant: ${id}`);
32
+
33
+ // Project-only adapters ignore a 'global' scope request.
34
+ const effScope = adapter.scope === 'project' ? 'project' : scope;
35
+
36
+ for (const skill of skills) {
37
+ const targets = adapter.resolve(skill, { scope: effScope, projectDir });
38
+ for (const t of targets) {
39
+ const existed = fs.existsSync(t.path);
40
+ if (!dryRun) {
41
+ if (t.copyDir) copyDir(t.copyDir, t.path);
42
+ else writeFile(t.path, t.content);
43
+ }
44
+ results.push({
45
+ assistant: adapter.label,
46
+ skill: skill.name,
47
+ path: t.path,
48
+ action: existed ? 'updated' : 'created',
49
+ });
50
+ }
51
+ }
52
+ }
53
+ return results;
54
+ }
55
+
56
+ module.exports = { install, copyDir };
package/lib/skills.js ADDED
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const SKILLS_DIR = path.join(__dirname, '..', 'skills');
7
+
8
+ /**
9
+ * Minimal YAML frontmatter parser. Handles the flat `key: value` pairs we use
10
+ * in SKILL.md (name, description). Values run to end of line; surrounding
11
+ * quotes are stripped. This is intentionally tiny so the installer ships with
12
+ * zero dependencies.
13
+ */
14
+ function parseFrontmatter(raw) {
15
+ const fm = {};
16
+ const lines = raw.split(/\r?\n/);
17
+ let key = null;
18
+ for (const line of lines) {
19
+ const m = line.match(/^([A-Za-z0-9_-]+):\s?(.*)$/);
20
+ if (m) {
21
+ key = m[1];
22
+ fm[key] = stripQuotes(m[2].trim());
23
+ } else if (key && /^\s+\S/.test(line)) {
24
+ // continuation line for a folded value
25
+ fm[key] = (fm[key] + ' ' + line.trim()).trim();
26
+ }
27
+ }
28
+ return fm;
29
+ }
30
+
31
+ function stripQuotes(v) {
32
+ if (
33
+ (v.startsWith('"') && v.endsWith('"')) ||
34
+ (v.startsWith("'") && v.endsWith("'"))
35
+ ) {
36
+ return v.slice(1, -1);
37
+ }
38
+ return v;
39
+ }
40
+
41
+ /**
42
+ * Parse a single skill folder into { name, description, body, dir }.
43
+ * Only the FIRST frontmatter block (the top of SKILL.md) is stripped — any
44
+ * `---` inside fenced code blocks in the body is preserved.
45
+ */
46
+ function parseSkill(dir) {
47
+ const file = path.join(dir, 'SKILL.md');
48
+ const md = fs.readFileSync(file, 'utf8');
49
+ const m = md.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
50
+ let fm = {};
51
+ let body = md;
52
+ if (m) {
53
+ fm = parseFrontmatter(m[1]);
54
+ body = md.slice(m[0].length);
55
+ }
56
+ return {
57
+ name: fm.name || path.basename(dir),
58
+ description: fm.description || '',
59
+ body: body.replace(/^\s+/, ''),
60
+ dir,
61
+ };
62
+ }
63
+
64
+ /** Discover every skill folder under skills/. */
65
+ function listSkills() {
66
+ if (!fs.existsSync(SKILLS_DIR)) return [];
67
+ return fs
68
+ .readdirSync(SKILLS_DIR, { withFileTypes: true })
69
+ .filter((d) => d.isDirectory())
70
+ .filter((d) => fs.existsSync(path.join(SKILLS_DIR, d.name, 'SKILL.md')))
71
+ .map((d) => parseSkill(path.join(SKILLS_DIR, d.name)))
72
+ .sort((a, b) => a.name.localeCompare(b.name));
73
+ }
74
+
75
+ module.exports = { listSkills, parseSkill, parseFrontmatter, SKILLS_DIR };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@smitdev/ai-skills",
3
+ "version": "0.1.0",
4
+ "description": "Install reusable AI assistant skills (Claude Code, GitHub Copilot, Cursor, Windsurf) with one command.",
5
+ "bin": {
6
+ "ai-skills": "bin/cli.js"
7
+ },
8
+ "type": "commonjs",
9
+ "scripts": {
10
+ "test": "node --test"
11
+ },
12
+ "files": [
13
+ "bin/",
14
+ "lib/",
15
+ "skills/",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "keywords": [
20
+ "claude",
21
+ "claude-code",
22
+ "copilot",
23
+ "cursor",
24
+ "windsurf",
25
+ "ai",
26
+ "skills",
27
+ "agent",
28
+ "prompt"
29
+ ],
30
+ "author": "smitdev",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/smitcode/ai-skills.git"
35
+ },
36
+ "homepage": "https://github.com/smitcode/ai-skills#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/smitcode/ai-skills/issues"
39
+ },
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }
@@ -0,0 +1,150 @@
1
+ ---
2
+ name: contract
3
+ description: Create a contract - a spec or PRD that fully explains a feature before any code is written, so a coding assistant or engineer can build it without guessing. Use this whenever the user wants to plan or spec a feature before building - things like "spec this out", "write a PRD", "let's plan this feature first", "make an implementation doc", "write the contract for X", or "I want a plan before we touch the code". Trigger it even if the user doesn't say the word "contract" - any time they want a build-ready description of a feature instead of the code itself.
4
+ ---
5
+
6
+ # Contract
7
+
8
+ A contract is a plan that you and the user agree on before any code is written. It is the one document a coding assistant reads to build the feature without guessing or making things up. Your job here is to make that document - not to write the feature code.
9
+
10
+ Do these three steps in order: understand the codebase, then ask the user questions, then write the contract. Don't jump straight to writing - a contract built on guesses is worse than no contract.
11
+
12
+ ## Step 1 - Understand the codebase
13
+
14
+ Before you ask the user anything, read the parts of the codebase the feature will touch. You want real files, not a vague idea of the project.
15
+
16
+ Find and read the files that will likely change, or that the new code has to work with. For each one, note what it does now, the patterns it follows (naming, error handling, how state and APIs are done), and where the new feature would fit in.
17
+
18
+ By the end of this step you should have a short list of real file paths, what each one does today, and where the new feature connects. You'll use this directly in the contract, so write down the real paths - never write a spec that isn't connected to the real code.
19
+
20
+ ## Step 2 - Ask the user questions
21
+
22
+ Now fill in the gaps. The user knows what they want; you know the code. The questions bring the two together and find the decisions that shape the design.
23
+
24
+ Ask one question at a time - never a big list at once. After each answer, ask the next one. This keeps it easy for the user and lets each answer guide the next question.
25
+
26
+ For each question, give a short list of simple answer options to pick from, not an open question. Keep the options non-technical - describe what happens, not how it's built. Mark the option you recommend and say in one line why you'd pick it, then ask if they're okay with it. Always add an "other - let me explain" option so they're not stuck with your choices.
27
+
28
+ Across the questions, cover: what the feature should do for the user, what's not included (the limits of the work), edge cases and error states, and any spot where two reasonable choices exist. Stop once you can explain the whole feature start to finish with no "it depends" left.
29
+
30
+ **Example of how a question should look:**
31
+
32
+ > When a search returns no results, what should the user see?
33
+ >
34
+ > - **A - A short "nothing found" message** (recommended: simplest, and it matches how the rest of the app handles empty states)
35
+ > - **B - The empty state plus a few suggestions on what to try next**
36
+ > - **C - Keep the last results on screen and just show a small note**
37
+ > - **D - Something else - tell me what you have in mind**
38
+ >
39
+ > I'd go with A. Want to go with that, or pick another?
40
+
41
+ ## Step 3 - Check, then write the contract
42
+
43
+ Before you write the full document, say your understanding back in a few sentences and get a yes. This is a cheap way to avoid writing a long doc based on a wrong idea.
44
+
45
+ Then write the contract using the template below. Leave out any section that doesn't apply (for example, no "Data model" section for a simple styling change) instead of filling it with fluff - but keep the numbers on the sections you do use. Write it so that an engineer or coding assistant who never saw the conversation could build the feature correctly from this document alone.
46
+
47
+ ### Contract template
48
+
49
+ ```markdown
50
+ # Contract: <Feature name>
51
+
52
+ **Status:** Draft
53
+ **Date:** <YYYY-MM-DD>
54
+ **Area:** <which part of the product / module>
55
+
56
+ ## 1. Summary
57
+
58
+ One short paragraph, plain words: what we're building and why.
59
+
60
+ ## 2. Problem
61
+
62
+ The problem this solves. What happens now and why that's not good enough.
63
+
64
+ ## 3. Goals
65
+
66
+ Clear bullets - what success looks like.
67
+
68
+ ## 4. Not included (out of scope)
69
+
70
+ What this feature does NOT do. This is just as important as the goals - it
71
+ stops a coding assistant from adding extra work or features no one asked for.
72
+
73
+ ## 5. The code today (what's there now)
74
+
75
+ The real files involved and what they do now:
76
+
77
+ - `path/to/file.ts` - what it does today; what changes here
78
+ - `path/to/other.py` - ...
79
+ Patterns and conventions the new code should follow.
80
+
81
+ ## 6. The plan
82
+
83
+ The approach in a sentence or two, then broken down:
84
+
85
+ ### 6.1 UI and UX
86
+
87
+ What the user sees and does. Every state: normal, loading, empty, error,
88
+ success. Note accessibility where it matters.
89
+
90
+ ### 6.2 Data model
91
+
92
+ New or changed data: entities, fields, types, how they relate. (Skip if none.)
93
+
94
+ ### 6.3 API / interface
95
+
96
+ For each endpoint or function: the signature or method + path, what goes in,
97
+ what comes back, and the error cases. For frontend: component props, events,
98
+ and the state it gives back.
99
+
100
+ ### 6.4 Logic and edge cases
101
+
102
+ The step-by-step behavior. List the edge cases and say how each is handled -
103
+ this is where most of the confusion hides.
104
+
105
+ ## 7. Performance and optimization
106
+
107
+ How fast and how cheap this has to be, made concrete (real numbers where you
108
+ can). Cover what applies:
109
+
110
+ - Expected load / scale - how many requests, rows, items, or users, etc.
111
+ - The hot paths - the parts that run a lot or work on big data, and the target
112
+ (e.g. "the list should load in under X seconds with 10,000 rows").
113
+ - Caching, batching, rate limits, concurrency - what to reuse or throttle.
114
+ - Cost - any API quota or money limits, and how to stay under them.
115
+ Skip this only if the feature has no real performance concern.
116
+
117
+ ## 8. Other requirements
118
+
119
+ Only the ones that apply: security and access, logging/monitoring, easy to
120
+ maintain, reusable.
121
+
122
+ ## 9. Files to change (checklist)
123
+
124
+ A checklist the coding assistant can follow:
125
+
126
+ - [ ] `path/to/file.ts` - what to change
127
+ - [ ] `path/to/new_file.py` - new; what it holds
128
+
129
+ ## 10. Done when (acceptance criteria)
130
+
131
+ How we know it's done, written as things you can test:
132
+
133
+ - Given <situation>, when <action>, then <result>.
134
+
135
+ ## 11. Risks and open questions
136
+
137
+ Guesses you made, choices left for later, anything still unclear.
138
+ ```
139
+
140
+ ## Where to save it
141
+
142
+ Save the contract as a Markdown file. Use the project's existing folder if there is one - look for a `specs/`, `contracts/`, `docs/specs/`, or `.specify/` folder and put it there. If there isn't one, make a `specs/` folder.
143
+
144
+ Name the file after the feature, in kebab-case: `specs/<feature-slug>.md` (for example `specs/user-profile-page.md`). Tell the user the path when you're done.
145
+
146
+ ## Rules to keep in mind
147
+
148
+ - Make the contract, not the code. If the user also wants the code, finish and confirm the contract first - that's the thing they'll read and reuse.
149
+ - Keep the user-facing parts (Summary, Problem, Goals) in plain words, and the build-facing parts (API, done-when) exact. The coding assistant needs the exact parts to be exact.
150
+ - If you hit a decision while writing that you didn't settle in the questions, stop and ask instead of guessing - then write the answer into the doc.