create-ai-scaffold 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.
Files changed (3) hide show
  1. package/dist/index.js +467 -0
  2. package/package.json +52 -0
  3. package/src/index.ts +13 -0
package/dist/index.js ADDED
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import path3 from "path";
8
+ import { input, select, confirm } from "@inquirer/prompts";
9
+
10
+ // src/scaffolder/scaffold.ts
11
+ import path2 from "path";
12
+
13
+ // src/utils/fs.ts
14
+ import fs from "fs";
15
+ import path from "path";
16
+ function ensureDir(dirPath) {
17
+ fs.mkdirSync(dirPath, { recursive: true });
18
+ }
19
+ function writeFileSafe(filePath, content) {
20
+ if (fs.existsSync(filePath)) {
21
+ return "skipped";
22
+ }
23
+ ensureDir(path.dirname(filePath));
24
+ fs.writeFileSync(filePath, content, "utf-8");
25
+ return "created";
26
+ }
27
+ var MERGE_HEADER = "# --- Added by create-ai-scaffold ---";
28
+ function mergeGitignore(filePath, newEntries) {
29
+ if (!fs.existsSync(filePath)) {
30
+ ensureDir(path.dirname(filePath));
31
+ fs.writeFileSync(filePath, newEntries.join("\n") + "\n", "utf-8");
32
+ return "created";
33
+ }
34
+ const existing = fs.readFileSync(filePath, "utf-8");
35
+ const existingLines = new Set(
36
+ existing.split("\n").map((l) => l.trim()).filter(Boolean)
37
+ );
38
+ const missing = newEntries.filter((entry) => !existingLines.has(entry.trim()));
39
+ if (missing.length === 0) {
40
+ return "skipped";
41
+ }
42
+ const append = `
43
+ ${MERGE_HEADER}
44
+ ${missing.join("\n")}
45
+ `;
46
+ fs.appendFileSync(filePath, append, "utf-8");
47
+ return "merged";
48
+ }
49
+
50
+ // src/scaffolder/templates/claude-md.ts
51
+ function claudeMd(_opts) {
52
+ return `# CLAUDE.md
53
+
54
+ This file is the entry point for Claude Code (claude.ai/code).
55
+
56
+ **All instructions live in \`.ai/assistant.md\`** \u2014 this file just points there.
57
+
58
+ Read \`.ai/assistant.md\` before starting any task.
59
+ `;
60
+ }
61
+
62
+ // src/scaffolder/templates/agents-md.ts
63
+ function agentsMd(_opts) {
64
+ return `# AGENTS.md
65
+
66
+ This file is the entry point for Codex and other agent-based tools.
67
+
68
+ **All instructions live in \`.ai/assistant.md\`** \u2014 this file just points there.
69
+
70
+ Read \`.ai/assistant.md\` before starting any task.
71
+ `;
72
+ }
73
+
74
+ // src/scaffolder/templates/cursorrules.ts
75
+ function cursorrules(_opts) {
76
+ return `# .cursorrules
77
+
78
+ This file is the entry point for Cursor.
79
+
80
+ **All instructions live in \`.ai/assistant.md\`** \u2014 this file just points there.
81
+
82
+ Read \`.ai/assistant.md\` before starting any task.
83
+ `;
84
+ }
85
+
86
+ // src/scaffolder/templates/windsurfrules.ts
87
+ function windsurfrules(_opts) {
88
+ return `# .windsurfrules
89
+
90
+ This file is the entry point for Windsurf.
91
+
92
+ **All instructions live in \`.ai/assistant.md\`** \u2014 this file just points there.
93
+
94
+ Read \`.ai/assistant.md\` before starting any task.
95
+ `;
96
+ }
97
+
98
+ // src/scaffolder/templates/assistant-md.ts
99
+ function assistantMd(opts) {
100
+ const { projectName, tier } = opts;
101
+ const header = `# AI Assistant Instructions \u2014 ${projectName}
102
+
103
+ ## Getting Started
104
+
105
+ - Read this file before starting any task.
106
+ - Check \`.ai/buildplan.md\` for the current implementation plan.
107
+ - Check \`.ai/conventions.md\` for project conventions and patterns.
108
+ - Drop relevant context files in \`.ai/context/\` for reference.
109
+ `;
110
+ const workflow = `
111
+ ## Workflow
112
+
113
+ 1. **Read** the relevant plan section or issue before writing code.
114
+ 2. **Implement** in small, focused commits.
115
+ 3. **Test** your changes before marking work as done.
116
+ 4. **Update** the buildplan when completing a step.
117
+ `;
118
+ const conventions = `
119
+ ## Conventions Reference
120
+
121
+ See \`.ai/conventions.md\` for:
122
+ - Code style and formatting rules
123
+ - Naming conventions
124
+ - File organization patterns
125
+ `;
126
+ let tierSpecific = "";
127
+ if (tier === "team" || tier === "production") {
128
+ tierSpecific += `
129
+ ## Architecture Decisions
130
+
131
+ Record significant decisions in \`.ai/decisions/\` using the ADR template.
132
+ When facing a non-trivial design choice, create a decision record before implementing.
133
+ `;
134
+ }
135
+ if (tier === "production") {
136
+ tierSpecific += `
137
+ ## Quality Requirements
138
+
139
+ - **Tests**: All new features and bug fixes require tests.
140
+ - **Documentation**: Update relevant docs when changing public APIs.
141
+ - **Security**: Never commit secrets. Validate all external input.
142
+ - **Review**: All changes should be reviewed before merging.
143
+ `;
144
+ }
145
+ return header + workflow + conventions + tierSpecific;
146
+ }
147
+
148
+ // src/scaffolder/templates/buildplan-md.ts
149
+ function buildplanMd(opts) {
150
+ return `# Build Plan \u2014 ${opts.projectName}
151
+
152
+ <!-- This is your implementation roadmap. Break work into phases and steps. -->
153
+ <!-- AI assistants will read this to understand what to work on next. -->
154
+
155
+ ## Phase 1 \u2014 MVP
156
+
157
+ <!-- Describe the minimum viable version of your project. -->
158
+
159
+ ### Step 1: ...
160
+
161
+ <!-- What needs to happen first? -->
162
+
163
+ ### Step 2: ...
164
+
165
+ <!-- What comes next? -->
166
+
167
+ ---
168
+
169
+ ## Phase 2 \u2014 Enhancements
170
+
171
+ <!-- What comes after the MVP? -->
172
+
173
+ ---
174
+
175
+ ## Notes
176
+
177
+ <!-- Any additional context, open questions, or constraints. -->
178
+ `;
179
+ }
180
+
181
+ // src/scaffolder/templates/conventions-md.ts
182
+ function conventionsMd(opts) {
183
+ const { tier } = opts;
184
+ if (tier === "solo") {
185
+ return `# Conventions
186
+
187
+ <!-- Define your project's conventions here so AI assistants follow them consistently. -->
188
+
189
+ ## Code Style
190
+
191
+ <!-- e.g., "Use single quotes", "Prefer const over let", "Max line length: 100" -->
192
+
193
+ ## File Organization
194
+
195
+ <!-- e.g., "Components in src/components/", "Tests next to source files" -->
196
+
197
+ ## Naming
198
+
199
+ <!-- e.g., "camelCase for variables", "PascalCase for components" -->
200
+ `;
201
+ }
202
+ return `# Conventions
203
+
204
+ <!-- Define your project's conventions here so AI assistants follow them consistently. -->
205
+
206
+ ## Code Style
207
+
208
+ <!-- e.g., "Use single quotes", "Prefer const over let", "Max line length: 100" -->
209
+
210
+ ## File Organization
211
+
212
+ <!-- e.g., "Components in src/components/", "Tests next to source files" -->
213
+
214
+ ## Naming
215
+
216
+ <!-- e.g., "camelCase for variables", "PascalCase for components", "kebab-case for files" -->
217
+
218
+ ## Git & Branching
219
+
220
+ <!-- e.g., "Conventional commits", "Feature branches off main", "Squash merges" -->
221
+
222
+ ## Testing
223
+
224
+ <!-- e.g., "Unit tests for all utils", "Integration tests for API routes" -->
225
+
226
+ ## Dependencies
227
+
228
+ <!-- e.g., "Prefer stdlib over deps", "All deps must be MIT/Apache licensed" -->
229
+
230
+ ## Documentation
231
+
232
+ <!-- e.g., "JSDoc for public APIs", "README per package" -->
233
+ `;
234
+ }
235
+
236
+ // src/scaffolder/templates/decision-template-md.ts
237
+ function decisionTemplateMd(_opts) {
238
+ return `# ADR-000: [Title]
239
+
240
+ ## Status
241
+
242
+ <!-- Proposed | Accepted | Deprecated | Superseded by ADR-XXX -->
243
+
244
+ Proposed
245
+
246
+ ## Context
247
+
248
+ <!-- What is the issue that we're seeing that motivates this decision? -->
249
+
250
+ ## Decision
251
+
252
+ <!-- What is the change that we're proposing and/or doing? -->
253
+
254
+ ## Consequences
255
+
256
+ <!-- What becomes easier or harder because of this change? -->
257
+ `;
258
+ }
259
+
260
+ // src/scaffolder/templates/context-readme-md.ts
261
+ function contextReadmeMd(_opts) {
262
+ return `# Context
263
+
264
+ Drop files here that you want AI assistants to reference during tasks.
265
+
266
+ ## Examples
267
+
268
+ - API specs (OpenAPI/Swagger docs)
269
+ - Design documents or wireframes
270
+ - Architecture diagrams
271
+ - Relevant RFCs or technical specs
272
+ - Example code from other projects
273
+
274
+ ## Tips
275
+
276
+ - Keep files focused \u2014 one topic per file.
277
+ - Use descriptive filenames (e.g., \`auth-api-spec.md\`, not \`notes.md\`).
278
+ - Remove files when they're no longer relevant.
279
+ - AI assistants won't read these automatically \u2014 reference them in your prompts.
280
+ `;
281
+ }
282
+
283
+ // src/scaffolder/templates/gitignore.ts
284
+ var GITIGNORE_ENTRIES = [
285
+ "node_modules/",
286
+ "dist/",
287
+ ".env",
288
+ ".env.*",
289
+ "!.env.example",
290
+ ".DS_Store",
291
+ "Thumbs.db",
292
+ "*.swp",
293
+ "*.swo",
294
+ "*~",
295
+ ".idea/",
296
+ ".vscode/",
297
+ "*.log"
298
+ ];
299
+ function gitignore(_opts) {
300
+ return GITIGNORE_ENTRIES.join("\n") + "\n";
301
+ }
302
+
303
+ // src/scaffolder/scaffold.ts
304
+ function buildManifest(options) {
305
+ const opts = { projectName: options.projectName, tier: options.tier };
306
+ const entries = [
307
+ // Root pointer files
308
+ { relativePath: "CLAUDE.md", content: claudeMd(opts), mode: "create" },
309
+ { relativePath: "AGENTS.md", content: agentsMd(opts), mode: "create" },
310
+ { relativePath: ".cursorrules", content: cursorrules(opts), mode: "create" },
311
+ { relativePath: ".windsurfrules", content: windsurfrules(opts), mode: "create" },
312
+ // .ai/ directory
313
+ { relativePath: ".ai/assistant.md", content: assistantMd(opts), mode: "create" },
314
+ { relativePath: ".ai/buildplan.md", content: buildplanMd(opts), mode: "create" },
315
+ { relativePath: ".ai/conventions.md", content: conventionsMd(opts), mode: "create" },
316
+ { relativePath: ".ai/context/README.md", content: contextReadmeMd(opts), mode: "create" },
317
+ // .gitignore
318
+ { relativePath: ".gitignore", content: gitignore(opts), mode: "merge" }
319
+ ];
320
+ if (options.tier === "team" || options.tier === "production") {
321
+ entries.push({
322
+ relativePath: ".ai/decisions/000-template.md",
323
+ content: decisionTemplateMd(opts),
324
+ mode: "create"
325
+ });
326
+ }
327
+ return entries;
328
+ }
329
+ function getPlannedFiles(options) {
330
+ return buildManifest(options).map((e) => e.relativePath);
331
+ }
332
+ function executeManifest(targetDir, manifest) {
333
+ const result = { created: [], skipped: [], merged: [] };
334
+ for (const entry of manifest) {
335
+ const fullPath = path2.join(targetDir, entry.relativePath);
336
+ if (entry.mode === "merge" && entry.relativePath === ".gitignore") {
337
+ const status = mergeGitignore(fullPath, GITIGNORE_ENTRIES);
338
+ result[status === "created" ? "created" : status === "merged" ? "merged" : "skipped"].push(
339
+ entry.relativePath
340
+ );
341
+ } else {
342
+ const status = writeFileSafe(fullPath, entry.content);
343
+ result[status].push(entry.relativePath);
344
+ }
345
+ }
346
+ return result;
347
+ }
348
+ function scaffold(options) {
349
+ const manifest = buildManifest(options);
350
+ return executeManifest(options.targetDir, manifest);
351
+ }
352
+
353
+ // src/utils/logger.ts
354
+ var isColorEnabled = !process.env["NO_COLOR"] && process.stdout.isTTY !== false;
355
+ var color = (code, text) => isColorEnabled ? `\x1B[${code}m${text}\x1B[0m` : text;
356
+ var green = (t) => color("32", t);
357
+ var yellow = (t) => color("33", t);
358
+ var red = (t) => color("31", t);
359
+ var cyan = (t) => color("36", t);
360
+ var bold = (t) => color("1", t);
361
+ var dim = (t) => color("2", t);
362
+ function info(msg) {
363
+ console.log(`${cyan("\u2139")} ${msg}`);
364
+ }
365
+ function warn(msg) {
366
+ console.log(`${yellow("\u26A0")} ${msg}`);
367
+ }
368
+ function error(msg) {
369
+ console.error(`${red("\u2716")} ${msg}`);
370
+ }
371
+ function banner() {
372
+ console.log();
373
+ console.log(bold(" create-ai-scaffold"));
374
+ console.log(dim(" Scaffold the AI-collaboration layer for any project"));
375
+ console.log();
376
+ }
377
+ function nextSteps(items) {
378
+ console.log();
379
+ console.log(bold("Next steps:"));
380
+ items.forEach((item, i) => {
381
+ console.log(` ${dim(`${i + 1}.`)} ${item}`);
382
+ });
383
+ console.log();
384
+ }
385
+ function fileResult(status, filePath) {
386
+ const labels = {
387
+ created: green("CREATE"),
388
+ skipped: yellow(" SKIP "),
389
+ merged: cyan("MERGE ")
390
+ };
391
+ console.log(` ${labels[status]} ${filePath}`);
392
+ }
393
+
394
+ // src/commands/init.ts
395
+ var VALID_TIERS = ["solo", "team", "production"];
396
+ function registerInitCommand(program2) {
397
+ program2.argument("[project-name]", 'Project name (creates subdirectory unless ".")').option("-y, --yes", "Skip prompts and use defaults", false).option("-t, --tier <tier>", "Collaboration tier: solo, team, or production").action(async (projectNameArg, opts) => {
398
+ banner();
399
+ let projectName;
400
+ if (projectNameArg) {
401
+ projectName = projectNameArg;
402
+ } else if (opts.yes) {
403
+ projectName = path3.basename(process.cwd());
404
+ } else {
405
+ projectName = await input({
406
+ message: "Project name:",
407
+ default: path3.basename(process.cwd())
408
+ });
409
+ }
410
+ let tier;
411
+ if (opts.tier) {
412
+ if (!VALID_TIERS.includes(opts.tier)) {
413
+ error(`Invalid tier "${opts.tier}". Must be one of: ${VALID_TIERS.join(", ")}`);
414
+ process.exit(1);
415
+ }
416
+ tier = opts.tier;
417
+ } else if (opts.yes) {
418
+ tier = "solo";
419
+ } else {
420
+ tier = await select({
421
+ message: "Collaboration tier:",
422
+ choices: [
423
+ { value: "solo", name: "solo \u2014 Just you and AI assistants" },
424
+ { value: "team", name: "team \u2014 Multiple contributors, ADRs, shared conventions" },
425
+ { value: "production", name: "production \u2014 Full quality gates: tests, docs, security" }
426
+ ],
427
+ default: "solo"
428
+ });
429
+ }
430
+ const isCurrentDir = !projectNameArg || projectNameArg === ".";
431
+ const targetDir = isCurrentDir ? process.cwd() : path3.resolve(process.cwd(), projectNameArg);
432
+ const plannedFiles = getPlannedFiles({ projectName, tier, targetDir });
433
+ info(`Scaffolding "${projectName}" (${tier} tier) in ${targetDir}`);
434
+ console.log();
435
+ plannedFiles.forEach((f) => console.log(` ${f}`));
436
+ console.log();
437
+ if (!opts.yes) {
438
+ const proceed = await confirm({
439
+ message: "Create these files?",
440
+ default: true
441
+ });
442
+ if (!proceed) {
443
+ warn("Aborted.");
444
+ return;
445
+ }
446
+ }
447
+ ensureDir(targetDir);
448
+ const result = scaffold({ projectName, tier, targetDir });
449
+ console.log();
450
+ for (const f of result.created) fileResult("created", f);
451
+ for (const f of result.merged) fileResult("merged", f);
452
+ for (const f of result.skipped) fileResult("skipped", f);
453
+ const steps = [
454
+ `Edit ${isCurrentDir ? "" : `${projectName}/`}.ai/assistant.md with your project-specific instructions`,
455
+ `Edit ${isCurrentDir ? "" : `${projectName}/`}.ai/conventions.md with your code style rules`,
456
+ `Write your implementation plan in ${isCurrentDir ? "" : `${projectName}/`}.ai/buildplan.md`,
457
+ "Start a conversation with your AI assistant!"
458
+ ];
459
+ nextSteps(steps);
460
+ });
461
+ }
462
+
463
+ // src/index.ts
464
+ var program = new Command();
465
+ program.name("create-ai-scaffold").description("Scaffold the AI-collaboration layer for any project").version("0.1.0");
466
+ registerInitCommand(program);
467
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "create-ai-scaffold",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold the AI-collaboration layer for any project",
5
+ "homepage": "https://github.com/kylewebdev/create-ai-scaffold#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/kylewebdev/create-ai-scaffold/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/kylewebdev/create-ai-scaffold.git"
12
+ },
13
+ "license": "ISC",
14
+ "author": "Kyle Garcia",
15
+ "type": "module",
16
+ "main": "src/index.ts",
17
+ "bin": {
18
+ "create-ai-scaffold": "dist/index.js"
19
+ },
20
+ "directories": {
21
+ "test": "tests"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "dev": "tsup --watch",
29
+ "test": "vitest run",
30
+ "lint": "tsc --noEmit"
31
+ },
32
+ "dependencies": {
33
+ "@inquirer/prompts": "^7.0.0",
34
+ "commander": "^13.0.0",
35
+ "diff": "^8.0.3",
36
+ "ignore": "^7.0.5"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.0.0",
40
+ "tsup": "^8.0.0",
41
+ "typescript": "^5.7.0",
42
+ "vitest": "^3.0.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ },
47
+ "pnpm": {
48
+ "onlyBuiltDependencies": [
49
+ "esbuild"
50
+ ]
51
+ }
52
+ }
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { Command } from 'commander';
2
+ import { registerInitCommand } from './commands/init.js';
3
+
4
+ const program = new Command();
5
+
6
+ program
7
+ .name('create-ai-scaffold')
8
+ .description('Scaffold the AI-collaboration layer for any project')
9
+ .version('0.1.0');
10
+
11
+ registerInitCommand(program);
12
+
13
+ program.parse();