ai-dev-setup 1.0.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +338 -0
  3. package/bin/ai-dev-setup.js +7 -0
  4. package/package.json +35 -0
  5. package/src/cli/index.js +142 -0
  6. package/src/cli/logger.js +29 -0
  7. package/src/cli/prompts.js +267 -0
  8. package/src/commands/init.js +321 -0
  9. package/src/commands/update.js +6 -0
  10. package/src/constants.js +34 -0
  11. package/src/core/detector.js +164 -0
  12. package/src/core/git-ref.js +28 -0
  13. package/src/core/gitignore-vendor.js +126 -0
  14. package/src/core/renderer.js +97 -0
  15. package/src/core/vendors.js +354 -0
  16. package/src/core/writer.js +67 -0
  17. package/src/platforms/claude-code.js +33 -0
  18. package/src/platforms/cursor.js +33 -0
  19. package/src/platforms/platform.js +18 -0
  20. package/src/platforms/registry.js +56 -0
  21. package/src/templates/claude-code/claude.md.tmpl +58 -0
  22. package/src/templates/claude-code/commands/kickoff.md.tmpl +18 -0
  23. package/src/templates/claude-code/commands/review.md.tmpl +20 -0
  24. package/src/templates/claude-code/commands/ship.md.tmpl +19 -0
  25. package/src/templates/claude-code/settings.json.tmpl +17 -0
  26. package/src/templates/cursor/cursorrules.tmpl +36 -0
  27. package/src/templates/cursor/rules/agents.mdc.tmpl +12 -0
  28. package/src/templates/cursor/rules/core-rules.mdc.tmpl +14 -0
  29. package/src/templates/cursor/rules/review.mdc.tmpl +13 -0
  30. package/src/templates/cursor/rules/workflow.mdc.tmpl +12 -0
  31. package/src/templates/ignore/claudeignore.tmpl +25 -0
  32. package/src/templates/ignore/cursorignore.tmpl +25 -0
  33. package/src/templates/shared/agents.md.tmpl +41 -0
  34. package/src/templates/shared/docs/api-patterns.md.tmpl +39 -0
  35. package/src/templates/shared/docs/architecture.md.tmpl +41 -0
  36. package/src/templates/shared/docs/conventions.md.tmpl +50 -0
  37. package/src/templates/shared/docs/error-handling.md.tmpl +32 -0
  38. package/src/templates/shared/docs/security.md.tmpl +37 -0
  39. package/src/templates/shared/docs/testing.md.tmpl +34 -0
  40. package/src/templates/shared/rules.md.tmpl +65 -0
  41. package/src/templates/shared/workflow.md.tmpl +42 -0
@@ -0,0 +1,354 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { assertSafeGitRef } from './git-ref.js';
5
+ import { createReadStream } from 'node:fs';
6
+ import { createInterface } from 'node:readline';
7
+
8
+ export const SUPERPOWERS_GIT_URL = 'https://github.com/obra/superpowers.git';
9
+ export const AGENCY_GIT_URL = 'https://github.com/msitarzewski/agency-agents.git';
10
+
11
+ export const VENDOR_SUPERPOWERS = path.join('vendor', 'superpowers');
12
+ export const VENDOR_AGENCY = path.join('vendor', 'agency-agents');
13
+
14
+ /** Same division roots as agency-agents `scripts/install.sh` install_claude_code */
15
+ export const AGENCY_CLAUDE_DIVISIONS = [
16
+ 'academic',
17
+ 'design',
18
+ 'engineering',
19
+ 'game-development',
20
+ 'marketing',
21
+ 'paid-media',
22
+ 'sales',
23
+ 'product',
24
+ 'project-management',
25
+ 'testing',
26
+ 'support',
27
+ 'spatial-computing',
28
+ 'specialized',
29
+ ];
30
+
31
+ /**
32
+ * @param {string} command
33
+ * @param {string[]} args
34
+ * @param {{ cwd?: string }} [opts]
35
+ */
36
+ export function runProcess(command, args, opts = {}) {
37
+ return new Promise((resolve, reject) => {
38
+ const child = spawn(command, args, {
39
+ cwd: opts.cwd,
40
+ stdio: ['ignore', 'pipe', 'pipe'],
41
+ });
42
+ let stdout = '';
43
+ let stderr = '';
44
+ child.stdout?.on('data', (c) => {
45
+ stdout += c;
46
+ });
47
+ child.stderr?.on('data', (c) => {
48
+ stderr += c;
49
+ });
50
+ child.on('error', reject);
51
+ child.on('close', (code) => {
52
+ if (code === 0) {
53
+ resolve({ stdout, stderr });
54
+ } else {
55
+ reject(
56
+ new Error(
57
+ `${command} ${args.join(' ')} exited with code ${code}\n${stderr || stdout}`.trim(),
58
+ ),
59
+ );
60
+ }
61
+ });
62
+ });
63
+ }
64
+
65
+ async function assertGitAvailable() {
66
+ try {
67
+ await runProcess('git', ['--version']);
68
+ } catch {
69
+ throw new Error('git is required on PATH to vendor Superpowers and Agency Agents.');
70
+ }
71
+ }
72
+
73
+ async function assertBashAvailable() {
74
+ try {
75
+ await runProcess('bash', ['--version']);
76
+ } catch {
77
+ throw new Error('bash is required on PATH to run vendor/agency-agents/scripts/convert.sh.');
78
+ }
79
+ }
80
+
81
+ /**
82
+ * @param {string} cwd
83
+ * @param {string} relDir
84
+ * @param {string} url
85
+ * @param {string} ref
86
+ * @param {boolean} force
87
+ */
88
+ async function gitShallowClone(cwd, relDir, url, ref, force) {
89
+ const target = path.join(cwd, relDir);
90
+ try {
91
+ await fs.access(target);
92
+ if (force) {
93
+ await fs.rm(target, { recursive: true, force: true });
94
+ } else {
95
+ return { skipped: true, path: relDir };
96
+ }
97
+ } catch {
98
+ /* missing */
99
+ }
100
+ await fs.mkdir(path.dirname(target), { recursive: true });
101
+ await runProcess('git', ['clone', '--depth', '1', '--branch', ref, url, target], { cwd });
102
+ return { skipped: false, path: relDir };
103
+ }
104
+
105
+ /**
106
+ * Rewrite Superpowers .cursor-plugin paths for workspace-root install.
107
+ * @param {Record<string, unknown>} plugin
108
+ * @param {string} posixPrefix e.g. ./vendor/superpowers/
109
+ */
110
+ export function rewriteSuperpowersCursorPlugin(plugin, posixPrefix) {
111
+ const out = { ...plugin };
112
+ for (const key of ['skills', 'agents', 'commands', 'hooks']) {
113
+ const v = out[key];
114
+ if (typeof v === 'string' && v.startsWith('./')) {
115
+ const rest = v.slice(2);
116
+ out[key] = `${posixPrefix}${rest}`.replace(/\/{2,}/g, '/');
117
+ }
118
+ }
119
+ return out;
120
+ }
121
+
122
+ /**
123
+ * @param {string} filePath
124
+ */
125
+ export async function markdownStartsWithFrontmatter(filePath) {
126
+ const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });
127
+ try {
128
+ for await (const line of rl) {
129
+ return line.trimStart().startsWith('---');
130
+ }
131
+ } finally {
132
+ rl.close();
133
+ }
134
+ return false;
135
+ }
136
+
137
+ /**
138
+ * @param {string} agencyRepoRoot vendor/agency-agents absolute
139
+ * @param {string} projectRoot
140
+ * @param {boolean} force
141
+ */
142
+ export async function copyAgencyClaudeAgents(agencyRepoRoot, projectRoot, force) {
143
+ const dest = path.join(projectRoot, '.claude', 'agents');
144
+ await fs.mkdir(dest, { recursive: true });
145
+ let count = 0;
146
+ for (const division of AGENCY_CLAUDE_DIVISIONS) {
147
+ const divPath = path.join(agencyRepoRoot, division);
148
+ let stat;
149
+ try {
150
+ stat = await fs.stat(divPath);
151
+ } catch {
152
+ continue;
153
+ }
154
+ if (!stat.isDirectory()) continue;
155
+ count += await copyMarkdownAgentsFromTree(divPath, dest, force);
156
+ }
157
+ return count;
158
+ }
159
+
160
+ /**
161
+ * @param {string} dir
162
+ * @param {string} destDir
163
+ * @param {boolean} force
164
+ */
165
+ async function copyMarkdownAgentsFromTree(dir, destDir, force) {
166
+ let n = 0;
167
+ const entries = await fs.readdir(dir, { withFileTypes: true });
168
+ for (const ent of entries) {
169
+ const full = path.join(dir, ent.name);
170
+ if (ent.isDirectory()) {
171
+ n += await copyMarkdownAgentsFromTree(full, destDir, force);
172
+ } else if (ent.isFile() && ent.name.endsWith('.md')) {
173
+ if (!(await markdownStartsWithFrontmatter(full))) continue;
174
+ const destFile = path.join(destDir, ent.name);
175
+ if (!force) {
176
+ try {
177
+ await fs.access(destFile);
178
+ continue;
179
+ } catch {
180
+ /* ok */
181
+ }
182
+ }
183
+ await fs.copyFile(full, destFile);
184
+ n++;
185
+ }
186
+ }
187
+ return n;
188
+ }
189
+
190
+ /**
191
+ * @param {string} integrationsRulesDir
192
+ * @param {string} projectRulesDir .cursor/rules
193
+ * @param {boolean} force overwrite agency-*.mdc
194
+ */
195
+ export async function copyAgencyCursorRules(integrationsRulesDir, projectRulesDir, force) {
196
+ let count = 0;
197
+ try {
198
+ await fs.access(integrationsRulesDir);
199
+ } catch {
200
+ return 0;
201
+ }
202
+ const files = await fs.readdir(integrationsRulesDir);
203
+ for (const name of files) {
204
+ if (!name.endsWith('.mdc')) continue;
205
+ const prefixed = name.startsWith('agency-') ? name : `agency-${name}`;
206
+ const src = path.join(integrationsRulesDir, name);
207
+ const dest = path.join(projectRulesDir, prefixed);
208
+ if (!force) {
209
+ try {
210
+ await fs.access(dest);
211
+ continue;
212
+ } catch {
213
+ /* copy */
214
+ }
215
+ }
216
+ await fs.mkdir(projectRulesDir, { recursive: true });
217
+ await fs.copyFile(src, dest);
218
+ count++;
219
+ }
220
+ return count;
221
+ }
222
+
223
+ /**
224
+ * @param {string} skillsRoot vendor/superpowers/skills
225
+ * @param {string} destRoot .claude/skills
226
+ * @param {boolean} force
227
+ */
228
+ export async function copySuperpowersSkills(skillsRoot, destRoot, force) {
229
+ let count = 0;
230
+ let rootStat;
231
+ try {
232
+ rootStat = await fs.stat(skillsRoot);
233
+ } catch {
234
+ throw new Error(`Superpowers skills directory missing: ${skillsRoot}`);
235
+ }
236
+ if (!rootStat.isDirectory()) {
237
+ throw new Error(`Superpowers skills path is not a directory: ${skillsRoot}`);
238
+ }
239
+ const skillDirs = await fs.readdir(skillsRoot, { withFileTypes: true });
240
+ for (const ent of skillDirs) {
241
+ if (!ent.isDirectory()) continue;
242
+ const src = path.join(skillsRoot, ent.name);
243
+ const dest = path.join(destRoot, ent.name);
244
+ if (!force) {
245
+ try {
246
+ await fs.access(dest);
247
+ continue;
248
+ } catch {
249
+ /* copy */
250
+ }
251
+ } else {
252
+ await fs.rm(dest, { recursive: true, force: true });
253
+ }
254
+ await fs.cp(src, dest, { recursive: true });
255
+ count++;
256
+ }
257
+ return count;
258
+ }
259
+
260
+ /**
261
+ * @param {string} projectRoot
262
+ * @param {string} superpowersRoot absolute vendor/superpowers
263
+ */
264
+ export async function writeSuperpowersCursorPluginFile(projectRoot, superpowersRoot) {
265
+ const pluginSrc = path.join(superpowersRoot, '.cursor-plugin', 'plugin.json');
266
+ const raw = await fs.readFile(pluginSrc, 'utf8');
267
+ const plugin = JSON.parse(raw);
268
+ const rewritten = rewriteSuperpowersCursorPlugin(plugin, './vendor/superpowers/');
269
+ const hooksRel = rewritten.hooks;
270
+ if (typeof hooksRel === 'string') {
271
+ const rel = hooksRel.replace(/^\.\//, '');
272
+ const hooksAbs = path.join(projectRoot, rel);
273
+ try {
274
+ await fs.access(hooksAbs);
275
+ } catch {
276
+ throw new Error(`Superpowers Cursor hooks file missing after clone: ${hooksRel}`);
277
+ }
278
+ }
279
+ const outDir = path.join(projectRoot, '.cursor-plugin');
280
+ await fs.mkdir(outDir, { recursive: true });
281
+ await fs.writeFile(path.join(outDir, 'plugin.json'), `${JSON.stringify(rewritten, null, 2)}\n`, 'utf8');
282
+ }
283
+
284
+ /**
285
+ * @param {object} options
286
+ * @param {string[]} options.platformKeys
287
+ * @param {boolean} options.force
288
+ * @param {string} options.superpowersRef
289
+ * @param {string} options.agencyRef
290
+ * @returns {Promise<string[]>} log lines
291
+ */
292
+ export async function installVendors(projectRoot, options) {
293
+ const { platformKeys, force } = options;
294
+ const superpowersRef = assertSafeGitRef(
295
+ options.superpowersRef != null && options.superpowersRef !== ''
296
+ ? String(options.superpowersRef)
297
+ : 'main',
298
+ );
299
+ const agencyRef = assertSafeGitRef(
300
+ options.agencyRef != null && options.agencyRef !== '' ? String(options.agencyRef) : 'main',
301
+ );
302
+ const wantClaude = platformKeys.includes('claude');
303
+ const wantCursor = platformKeys.includes('cursor');
304
+ const lines = [];
305
+
306
+ await assertGitAvailable();
307
+
308
+ const spRel = VENDOR_SUPERPOWERS;
309
+ const agRel = VENDOR_AGENCY;
310
+
311
+ const sp = await gitShallowClone(projectRoot, spRel, SUPERPOWERS_GIT_URL, superpowersRef, force);
312
+ lines.push(sp.skipped ? `${sp.path} (already present, skipped clone — use --force to refresh)` : `+ ${sp.path} (cloned)`);
313
+
314
+ const ag = await gitShallowClone(projectRoot, agRel, AGENCY_GIT_URL, agencyRef, force);
315
+ lines.push(ag.skipped ? `${ag.path} (already present, skipped clone — use --force to refresh)` : `+ ${ag.path} (cloned)`);
316
+
317
+ const superAbs = path.join(projectRoot, spRel);
318
+ const agencyAbs = path.join(projectRoot, agRel);
319
+
320
+ if (wantCursor) {
321
+ await writeSuperpowersCursorPluginFile(projectRoot, superAbs);
322
+ lines.push('+ .cursor-plugin/plugin.json (Superpowers paths → vendor/superpowers)');
323
+ }
324
+
325
+ if (wantClaude) {
326
+ const n = await copySuperpowersSkills(path.join(superAbs, 'skills'), path.join(projectRoot, '.claude', 'skills'), force);
327
+ lines.push(`+ .claude/skills (${n} skill trees from Superpowers)`);
328
+ }
329
+
330
+ if (wantCursor) {
331
+ await assertBashAvailable();
332
+ lines.push('… running vendor/agency-agents/scripts/convert.sh (may take a minute)');
333
+ await runProcess('bash', ['scripts/convert.sh'], { cwd: agencyAbs });
334
+ const rulesSrc = path.join(agencyAbs, 'integrations', 'cursor', 'rules');
335
+ const rulesDest = path.join(projectRoot, '.cursor', 'rules');
336
+ const n = await copyAgencyCursorRules(rulesSrc, rulesDest, force);
337
+ if (n === 0) {
338
+ throw new Error(
339
+ 'Agency Cursor rules not found after convert.sh. Expected integrations/cursor/rules/*.mdc',
340
+ );
341
+ }
342
+ lines.push(`+ .cursor/rules (agency-*.mdc × ${n})`);
343
+ }
344
+
345
+ if (wantClaude) {
346
+ const n = await copyAgencyClaudeAgents(agencyAbs, projectRoot, force);
347
+ if (n === 0) {
348
+ throw new Error('No Agency agent markdown files were copied to .claude/agents/. Check vendor/agency-agents layout.');
349
+ }
350
+ lines.push(`+ .claude/agents (${n} agent files from Agency)`);
351
+ }
352
+
353
+ return lines;
354
+ }
@@ -0,0 +1,67 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+
5
+ function isPathInsideRoot(root, target) {
6
+ const r = path.resolve(root);
7
+ const t = path.resolve(target);
8
+ const prefix = r.endsWith(path.sep) ? r : r + path.sep;
9
+ return t === r || t.startsWith(prefix);
10
+ }
11
+
12
+ /**
13
+ * @param {Array<{ path: string, content: string }>} files
14
+ * @param {{ cwd?: string, force?: boolean }} [options]
15
+ */
16
+ export async function writeFiles(files, { cwd = process.cwd(), force = false } = {}) {
17
+ const root = path.resolve(cwd);
18
+ /** @type {Array<{ path: string, status: 'written'|'skipped'|'error', error?: string }>} */
19
+ const results = [];
20
+
21
+ for (const file of files) {
22
+ const target = path.resolve(root, file.path);
23
+ if (!isPathInsideRoot(root, target)) {
24
+ results.push({
25
+ path: file.path,
26
+ status: 'error',
27
+ error: 'Path escapes working directory',
28
+ });
29
+ continue;
30
+ }
31
+
32
+ try {
33
+ await fs.mkdir(path.dirname(target), { recursive: true });
34
+ } catch (err) {
35
+ results.push({
36
+ path: file.path,
37
+ status: 'error',
38
+ error: err instanceof Error ? err.message : String(err),
39
+ });
40
+ continue;
41
+ }
42
+
43
+ try {
44
+ let exists = false;
45
+ try {
46
+ await fs.access(target);
47
+ exists = true;
48
+ } catch {
49
+ exists = false;
50
+ }
51
+ if (exists && !force) {
52
+ results.push({ path: file.path, status: 'skipped' });
53
+ continue;
54
+ }
55
+ await fs.writeFile(target, file.content, 'utf8');
56
+ results.push({ path: file.path, status: 'written' });
57
+ } catch (err) {
58
+ results.push({
59
+ path: file.path,
60
+ status: 'error',
61
+ error: err instanceof Error ? err.message : String(err),
62
+ });
63
+ }
64
+ }
65
+
66
+ return results;
67
+ }
@@ -0,0 +1,33 @@
1
+ import path from 'node:path';
2
+ import { renderFile } from '../core/renderer.js';
3
+ import { Platform } from './platform.js';
4
+ import { register, TEMPLATES_DIR } from './registry.js';
5
+
6
+ function tpl(rel) {
7
+ return path.join(TEMPLATES_DIR, rel);
8
+ }
9
+
10
+ export class ClaudeCodePlatform extends Platform {
11
+ constructor() {
12
+ super('claude', 'Claude Code');
13
+ }
14
+
15
+ /** @param {Record<string, unknown>} config */
16
+ async getFiles(config) {
17
+ const pairs = [
18
+ ['claude-code/claude.md.tmpl', 'CLAUDE.md'],
19
+ ['claude-code/settings.json.tmpl', '.claude/settings.json'],
20
+ ['claude-code/commands/kickoff.md.tmpl', '.claude/commands/kickoff.md'],
21
+ ['claude-code/commands/review.md.tmpl', '.claude/commands/review.md'],
22
+ ['claude-code/commands/ship.md.tmpl', '.claude/commands/ship.md'],
23
+ ];
24
+ const out = [];
25
+ for (const [rel, dest] of pairs) {
26
+ const content = await renderFile(tpl(rel), config);
27
+ out.push({ path: dest, content });
28
+ }
29
+ return out;
30
+ }
31
+ }
32
+
33
+ register(new ClaudeCodePlatform());
@@ -0,0 +1,33 @@
1
+ import path from 'node:path';
2
+ import { renderFile } from '../core/renderer.js';
3
+ import { Platform } from './platform.js';
4
+ import { register, TEMPLATES_DIR } from './registry.js';
5
+
6
+ function tpl(rel) {
7
+ return path.join(TEMPLATES_DIR, rel);
8
+ }
9
+
10
+ export class CursorPlatform extends Platform {
11
+ constructor() {
12
+ super('cursor', 'Cursor');
13
+ }
14
+
15
+ /** @param {Record<string, unknown>} config */
16
+ async getFiles(config) {
17
+ const pairs = [
18
+ ['cursor/cursorrules.tmpl', '.cursorrules'],
19
+ ['cursor/rules/core-rules.mdc.tmpl', '.cursor/rules/core-rules.mdc'],
20
+ ['cursor/rules/workflow.mdc.tmpl', '.cursor/rules/workflow.mdc'],
21
+ ['cursor/rules/review.mdc.tmpl', '.cursor/rules/review.mdc'],
22
+ ['cursor/rules/agents.mdc.tmpl', '.cursor/rules/agents.mdc'],
23
+ ];
24
+ const out = [];
25
+ for (const [rel, dest] of pairs) {
26
+ const content = await renderFile(tpl(rel), config);
27
+ out.push({ path: dest, content });
28
+ }
29
+ return out;
30
+ }
31
+ }
32
+
33
+ register(new CursorPlatform());
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Strategy base: each assistant platform implements `getFiles(config)`.
3
+ */
4
+ export class Platform {
5
+ /**
6
+ * @param {string} key
7
+ * @param {string} label
8
+ */
9
+ constructor(key, label) {
10
+ this.key = key;
11
+ this.label = label;
12
+ }
13
+
14
+ /** @param {Record<string, unknown>} _config */
15
+ async getFiles(_config) {
16
+ throw new Error(`${this.key}: getFiles() not implemented`);
17
+ }
18
+ }
@@ -0,0 +1,56 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { renderFile } from '../core/renderer.js';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ export const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
7
+
8
+ /** @type {Map<string, import('./platform.js').Platform>} */
9
+ const platforms = new Map();
10
+
11
+ /** @param {import('./platform.js').Platform} platform */
12
+ export function register(platform) {
13
+ platforms.set(platform.key, platform);
14
+ }
15
+
16
+ /** @param {string} key */
17
+ export function getPlatform(key) {
18
+ return platforms.get(key);
19
+ }
20
+
21
+ export function getAllPlatforms() {
22
+ return [...platforms.values()];
23
+ }
24
+
25
+ /** @param {string} rel */
26
+ function tpl(rel) {
27
+ return path.join(TEMPLATES_DIR, rel);
28
+ }
29
+
30
+ /**
31
+ * Shared artifacts for every run (.ai/, docs/, ignore files).
32
+ * @param {Record<string, unknown>} config
33
+ */
34
+ export async function getSharedFiles(config) {
35
+ /** @type {Array<{ template: string, out: string }>} */
36
+ const map = [
37
+ { template: 'shared/rules.md.tmpl', out: '.ai/rules.md' },
38
+ { template: 'shared/workflow.md.tmpl', out: '.ai/workflow.md' },
39
+ { template: 'shared/agents.md.tmpl', out: '.ai/agents.md' },
40
+ { template: 'shared/docs/architecture.md.tmpl', out: 'docs/ARCHITECTURE.md' },
41
+ { template: 'shared/docs/conventions.md.tmpl', out: 'docs/CONVENTIONS.md' },
42
+ { template: 'shared/docs/testing.md.tmpl', out: 'docs/TESTING-STRATEGY.md' },
43
+ { template: 'shared/docs/api-patterns.md.tmpl', out: 'docs/API-PATTERNS.md' },
44
+ { template: 'shared/docs/error-handling.md.tmpl', out: 'docs/ERROR-HANDLING.md' },
45
+ { template: 'shared/docs/security.md.tmpl', out: 'docs/SECURITY.md' },
46
+ { template: 'ignore/claudeignore.tmpl', out: '.claudeignore' },
47
+ { template: 'ignore/cursorignore.tmpl', out: '.cursorignore' },
48
+ ];
49
+
50
+ const out = [];
51
+ for (const { template, out: dest } of map) {
52
+ const content = await renderFile(tpl(template), config);
53
+ out.push({ path: dest, content });
54
+ }
55
+ return out;
56
+ }
@@ -0,0 +1,58 @@
1
+ # {{PROJECT_NAME}} — Claude Code
2
+
3
+ ## Identity
4
+
5
+ | Field | Value |
6
+ |-------|-------|
7
+ | Language | {{LANGUAGE}} |
8
+ | Framework | {{FRAMEWORK}} |
9
+ | Database | {{DATABASE}} |
10
+
11
+ ## Core stack (required)
12
+
13
+ | System | Location | Role |
14
+ |--------|----------|------|
15
+ | Superpowers (obra) | `vendor/superpowers/`, skills in `.claude/skills/` | Workflow engine: brainstorming → design → plans → TDD → review — use these skills for every non-trivial task |
16
+ | Agency Agents | `vendor/agency-agents/`, agents in `.claude/agents/` | Specialist execution: pick the agent that matches the task from `.ai/agents.md` |
17
+
18
+ Do not treat Superpowers or Agency as optional add-ons. If `vendor/` is missing, run `npx ai-dev-setup init` without `--skip-vendor`.
19
+
20
+ ## Read first (onboarding)
21
+
22
+ | Order | File |
23
+ |-------|------|
24
+ | 1 | `.ai/rules.md` |
25
+ | 2 | `.ai/workflow.md` |
26
+ | 3 | `docs/ARCHITECTURE.md` (fill if empty) |
27
+
28
+ Load other `docs/*` only when the task touches that area.
29
+
30
+ ## Operating rules
31
+
32
+ - Prefer repo scripts: test `{{TEST_CMD}}`, lint `{{LINT_CMD}}`, build `{{BUILD_CMD}}`
33
+ - Keep replies short; point to paths instead of pasting large files
34
+ - After structural change, update `docs/ARCHITECTURE.md` in the same PR when behavior crosses modules
35
+ - Let Superpowers skills drive phase gates; align with `.ai/workflow.md` without duplicating long prose here
36
+
37
+ ## Slash commands
38
+
39
+ | Command | Use |
40
+ |---------|-----|
41
+ | `/kickoff` | New feature: scope, risks, plan |
42
+ | `/review` | Pre-merge review checklist |
43
+ | `/ship` | Release/merge readiness |
44
+
45
+ ## Docs index
46
+
47
+ | Topic | Path |
48
+ |-------|------|
49
+ | Conventions | `docs/CONVENTIONS.md` |
50
+ | Testing | `docs/TESTING-STRATEGY.md` |
51
+ | API | `docs/API-PATTERNS.md` |
52
+ | Errors | `docs/ERROR-HANDLING.md` |
53
+ | Security | `docs/SECURITY.md` |
54
+
55
+ ## Out of scope for this file
56
+
57
+ - Do not duplicate `.ai/rules.md`
58
+ - Do not paste long examples—add them under `docs/` and link
@@ -0,0 +1,18 @@
1
+ # /kickoff — {{PROJECT_NAME}}
2
+
3
+ ## Goal
4
+
5
+ Turn a feature request into a bounded plan aligned with `.ai/workflow.md`.
6
+
7
+ ## Steps
8
+
9
+ 1. Restate objective and **non-goals** (3–7 bullets)
10
+ 2. List unknowns; ask **one** blocking question if needed
11
+ 3. Propose architecture touchpoints with links to `docs/ARCHITECTURE.md` sections to update
12
+ 4. Emit a task list: each item has **path**, **change**, **verify** (`{{TEST_CMD}}` / manual check)
13
+
14
+ ## Output format
15
+
16
+ - ### Summary
17
+ - ### Risks
18
+ - ### Tasks (numbered)
@@ -0,0 +1,20 @@
1
+ # /review — {{PROJECT_NAME}}
2
+
3
+ ## Goal
4
+
5
+ Catch correctness, safety, and maintainability issues before merge.
6
+
7
+ ## Checklist
8
+
9
+ - [ ] Matches stated acceptance checks
10
+ - [ ] Errors handled at boundaries per `docs/ERROR-HANDLING.md`
11
+ - [ ] Tests updated or gap documented (`{{TEST_CMD}}`)
12
+ - [ ] No secrets / PII in logs
13
+ - [ ] Public API or behavior change reflected in docs
14
+ - [ ] `{{LINT_CMD}}` clean or waivers explained inline
15
+
16
+ ## Output format
17
+
18
+ - ### Findings (severity: high/med/low)
19
+ - ### Suggested patches (file-scoped)
20
+ - ### Merge verdict: approve | changes requested
@@ -0,0 +1,19 @@
1
+ # /ship — {{PROJECT_NAME}}
2
+
3
+ ## Goal
4
+
5
+ Confirm release/merge readiness.
6
+
7
+ ## Verify
8
+
9
+ - [ ] `{{LINT_CMD}}` passes
10
+ - [ ] `{{TEST_CMD}}` passes
11
+ - [ ] `{{BUILD_CMD}}` passes (if applicable)
12
+ - [ ] Changelog / release notes updated when user-visible
13
+ - [ ] Migrations or feature flags documented
14
+
15
+ ## Output format
16
+
17
+ - ### Ship status: ready | blocked
18
+ - ### Blockers (if any)
19
+ - ### Rollback notes