ship-create 1.5.0 → 1.6.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/create.mjs +256 -124
- package/package.json +1 -1
- package/templates/.claude/skills/ship-method/SKILL.md +26 -53
- package/templates/.cursorrules +6 -4
- package/templates/.windsurfrules +6 -4
- package/templates/AGENTS.md +6 -4
- package/templates/CLAUDE.md +6 -4
package/create.mjs
CHANGED
|
@@ -10,18 +10,6 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Usage:
|
|
12
12
|
* npx ship-create
|
|
13
|
-
*
|
|
14
|
-
* All templates (starter-kit app, agent rule files, Claude skill) are
|
|
15
|
-
* bundled inside this package's own templates/ folder — nothing is read
|
|
16
|
-
* from outside, so it works standalone via npx.
|
|
17
|
-
*
|
|
18
|
-
* What changed from v1.3.1 → v1.4.0:
|
|
19
|
-
* - Removed the AI-tool picker (no longer needed)
|
|
20
|
-
* - Added 4 idea questions so PROJECT.md + HUMAN_FLOW.md are pre-filled
|
|
21
|
-
* with real content (no [bracket placeholders] in the core sections)
|
|
22
|
-
* - Runs `npm install` automatically so the project is ready to open
|
|
23
|
-
* - End message tells the user exactly how to start building in their
|
|
24
|
-
* AI coding tool — no intermediate manual steps
|
|
25
13
|
*/
|
|
26
14
|
|
|
27
15
|
import fs from "node:fs";
|
|
@@ -34,19 +22,105 @@ import { spawnSync } from "node:child_process";
|
|
|
34
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
35
23
|
const TEMPLATES_DIR = path.join(__dirname, "templates");
|
|
36
24
|
|
|
25
|
+
// ─── ANSI helpers ────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const C = {
|
|
28
|
+
reset: "\x1b[0m",
|
|
29
|
+
bold: "\x1b[1m",
|
|
30
|
+
dim: "\x1b[2m",
|
|
31
|
+
cyan: "\x1b[36m",
|
|
32
|
+
green: "\x1b[32m",
|
|
33
|
+
white: "\x1b[37m",
|
|
34
|
+
hideCursor: "\x1b[?25l",
|
|
35
|
+
showCursor: "\x1b[?25h",
|
|
36
|
+
clearLine: "\x1b[2K",
|
|
37
|
+
up: (n) => `\x1b[${n}A`,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ─── Data ─────────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
37
42
|
const PRODUCT_TYPES = [
|
|
38
|
-
{ key: "SAAS",
|
|
39
|
-
{ key: "CRM",
|
|
40
|
-
{ key: "MEMBERSHIP",
|
|
41
|
-
{ key: "LEADGEN",
|
|
42
|
-
{ key: "DIRECTORY",
|
|
43
|
-
{ key: "DASHBOARD",
|
|
44
|
-
{ key: "INTERNAL_TOOL",
|
|
45
|
-
{ key: "MARKETPLACE",
|
|
43
|
+
{ key: "SAAS", name: "Web app / SaaS", desc: "users sign up and use a tool online", file: "SAAS_TEMPLATE.md" },
|
|
44
|
+
{ key: "CRM", name: "CRM / Sales pipeline", desc: "track leads, deals, and customers", file: "CRM_TEMPLATE.md" },
|
|
45
|
+
{ key: "MEMBERSHIP", name: "Membership / community", desc: "paid access to content or a community", file: "MEMBERSHIP_TEMPLATE.md" },
|
|
46
|
+
{ key: "LEADGEN", name: "Landing page / lead capture", desc: "get visitors to fill a form or book a call", file: "LEADGEN_TEMPLATE.md" },
|
|
47
|
+
{ key: "DIRECTORY", name: "Directory / listing site", desc: "browse and find providers, products, or places", file: "DIRECTORY_TEMPLATE.md" },
|
|
48
|
+
{ key: "DASHBOARD", name: "Dashboard / analytics", desc: "visualise data and KPIs for a team or business", file: "DASHBOARD_TEMPLATE.md" },
|
|
49
|
+
{ key: "INTERNAL_TOOL", name: "Internal tool", desc: "replaces a manual or spreadsheet-based process inside a company", file: "INTERNAL_TOOL_TEMPLATE.md" },
|
|
50
|
+
{ key: "MARKETPLACE", name: "Marketplace", desc: "connects buyers and sellers; transactions happen on the platform", file: "MARKETPLACE_TEMPLATE.md" },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const LANGUAGES = [
|
|
54
|
+
{ code: "en", name: "English", desc: "UI text, labels, and copy in English" },
|
|
55
|
+
{ code: "th", name: "Thai (ภาษาไทย)", desc: "ข้อความ label และ copy ในระบบเป็นภาษาไทย" },
|
|
46
56
|
];
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
const I18N = {
|
|
59
|
+
en: {
|
|
60
|
+
sectionIdea: "── About your idea ────────────────────────────",
|
|
61
|
+
qName: "Project name?",
|
|
62
|
+
qNameDefault: "My Product",
|
|
63
|
+
qIdea: "Describe it in 1–3 sentences (who it's for and what it does)",
|
|
64
|
+
qUser: "Who is your primary user? (e.g. 'freelance designers', 'gym owners')",
|
|
65
|
+
qUserDefault: "small business owners",
|
|
66
|
+
qProblem: "What is the #1 problem they face today?",
|
|
67
|
+
qValue: "What makes them say 'I need this'? (the aha moment)",
|
|
68
|
+
scaffolding: (slug) => `Scaffolding ./${slug} ...`,
|
|
69
|
+
installing: "Installing packages (this takes ~30 seconds)...",
|
|
70
|
+
installFail: (slug) => `npm install failed. Run it manually:\n cd ${slug} && npm install`,
|
|
71
|
+
folderExists: (slug) => `Folder ./${slug} already exists — pick a different name and run again.`,
|
|
72
|
+
templatesMissing:"Bundled templates missing. Try: npx ship-create@latest",
|
|
73
|
+
done: (slug) => `
|
|
74
|
+
✔ Done! Your project is at ./${slug}/
|
|
75
|
+
|
|
76
|
+
docs/PROJECT.md — product spec (pre-filled)
|
|
77
|
+
docs/HUMAN_FLOW.md — UX flow (pre-filled)
|
|
78
|
+
docs/DESIGN_SYSTEM.md — design tokens (filled by /build)
|
|
79
|
+
|
|
80
|
+
──────────────────────────────────────────
|
|
81
|
+
Open in your AI coding tool and type /build
|
|
82
|
+
|
|
83
|
+
Claude Code → claude ./${slug}
|
|
84
|
+
Cursor → cursor ./${slug}
|
|
85
|
+
Windsurf → windsurf ./${slug}
|
|
86
|
+
|
|
87
|
+
Then type: /build
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
th: {
|
|
91
|
+
sectionIdea: "── เกี่ยวกับ idea ของคุณ ─────────────────────",
|
|
92
|
+
qName: "ชื่อโปรเจค?",
|
|
93
|
+
qNameDefault: "สินค้าของฉัน",
|
|
94
|
+
qIdea: "อธิบายในไม่เกิน 1–3 ประโยค (สำหรับใคร และทำอะไร)",
|
|
95
|
+
qUser: "ผู้ใช้หลักของคุณคือใคร? (เช่น 'ฟรีแลนซ์ดีไซเนอร์', 'เจ้าของยิม')",
|
|
96
|
+
qUserDefault: "เจ้าของธุรกิจขนาดเล็ก",
|
|
97
|
+
qProblem: "ปัญหาอันดับ 1 ที่พวกเขาเจออยู่ทุกวันคืออะไร?",
|
|
98
|
+
qValue: "อะไรทำให้พวกเขาบอกว่า 'ฉันต้องการสิ่งนี้!'? (จุด aha moment)",
|
|
99
|
+
scaffolding: (slug) => `กำลัง scaffold ./${slug} ...`,
|
|
100
|
+
installing: "กำลังติดตั้ง packages (ใช้เวลาประมาณ 30 วินาที)...",
|
|
101
|
+
installFail: (slug) => `npm install ล้มเหลว รันเองได้ที่:\n cd ${slug} && npm install`,
|
|
102
|
+
folderExists: (slug) => `โฟลเดอร์ ./${slug} มีอยู่แล้ว — เลือกชื่ออื่นแล้วรันใหม่`,
|
|
103
|
+
templatesMissing:"ไม่พบ template ที่ bundle ไว้ ลองรัน: npx ship-create@latest",
|
|
104
|
+
done: (slug) => `
|
|
105
|
+
✔ เสร็จแล้ว! โปรเจคของคุณอยู่ที่ ./${slug}/
|
|
106
|
+
|
|
107
|
+
docs/PROJECT.md — spec สินค้า (กรอกข้อมูลแล้ว)
|
|
108
|
+
docs/HUMAN_FLOW.md — UX flow (กรอกข้อมูลแล้ว)
|
|
109
|
+
docs/DESIGN_SYSTEM.md — design tokens (เติมโดย /build)
|
|
110
|
+
|
|
111
|
+
──────────────────────────────────────────
|
|
112
|
+
เปิดใน AI coding tool แล้วพิมพ์ /build
|
|
113
|
+
|
|
114
|
+
Claude Code → claude ./${slug}
|
|
115
|
+
Cursor → cursor ./${slug}
|
|
116
|
+
Windsurf → windsurf ./${slug}
|
|
117
|
+
|
|
118
|
+
จากนั้นพิมพ์: /build
|
|
119
|
+
`,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Pre-seeded journey stages per product type so HUMAN_FLOW.md has real content.
|
|
50
124
|
const JOURNEY_SEEDS = {
|
|
51
125
|
SAAS: {
|
|
52
126
|
discovery: "Googles a pain they have, or sees a post about the tool from someone they follow",
|
|
@@ -98,19 +172,87 @@ const JOURNEY_SEEDS = {
|
|
|
98
172
|
},
|
|
99
173
|
};
|
|
100
174
|
|
|
101
|
-
// ───
|
|
175
|
+
// ─── Interactive selector (arrow keys) ───────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
function selectInteractive(title, items) {
|
|
178
|
+
return new Promise((resolve) => {
|
|
179
|
+
let idx = 0;
|
|
180
|
+
|
|
181
|
+
const printItems = () => {
|
|
182
|
+
items.forEach((item, i) => {
|
|
183
|
+
const selected = i === idx;
|
|
184
|
+
process.stdout.write(C.clearLine + "\r");
|
|
185
|
+
if (selected) {
|
|
186
|
+
process.stdout.write(
|
|
187
|
+
` ${C.cyan}${C.bold}❯ ${item.name}${C.reset}` +
|
|
188
|
+
` ${C.dim}${item.desc}${C.reset}\n`
|
|
189
|
+
);
|
|
190
|
+
} else {
|
|
191
|
+
process.stdout.write(` ${C.dim} ${item.name}${C.reset}\n`);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
// hint line (no trailing newline so cursor stays here)
|
|
195
|
+
process.stdout.write(
|
|
196
|
+
C.clearLine + `\r ${C.dim}↑↓ navigate · Enter to select${C.reset}`
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// initial render
|
|
201
|
+
process.stdout.write(`\n ${C.bold}${title}${C.reset}\n\n`);
|
|
202
|
+
process.stdout.write(C.hideCursor);
|
|
203
|
+
printItems();
|
|
204
|
+
|
|
205
|
+
process.stdin.setRawMode(true);
|
|
206
|
+
process.stdin.resume();
|
|
207
|
+
process.stdin.setEncoding("utf8");
|
|
208
|
+
|
|
209
|
+
const onData = (key) => {
|
|
210
|
+
if (key === "\x03") { // Ctrl+C
|
|
211
|
+
process.stdout.write(C.showCursor + "\n");
|
|
212
|
+
process.exit(0);
|
|
213
|
+
}
|
|
214
|
+
if (key === "\r" || key === "\n") { // Enter
|
|
215
|
+
process.stdin.removeListener("data", onData);
|
|
216
|
+
process.stdin.setRawMode(false);
|
|
217
|
+
process.stdin.pause();
|
|
218
|
+
// replace hint line with confirmed selection
|
|
219
|
+
process.stdout.write(
|
|
220
|
+
"\r" + C.clearLine +
|
|
221
|
+
` ${C.green}${C.bold}✔ ${items[idx].name}${C.reset}\n`
|
|
222
|
+
);
|
|
223
|
+
process.stdout.write(C.showCursor);
|
|
224
|
+
resolve(items[idx]);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const prev = idx;
|
|
228
|
+
if (key === "\x1b[A") idx = Math.max(0, idx - 1); // up
|
|
229
|
+
if (key === "\x1b[B") idx = Math.min(items.length - 1, idx + 1); // down
|
|
230
|
+
if (idx !== prev) {
|
|
231
|
+
process.stdout.write(C.up(items.length));
|
|
232
|
+
printItems();
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
process.stdin.on("data", onData);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
102
239
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.
|
|
108
|
-
|
|
240
|
+
// Fallback for non-TTY environments (CI, piped input)
|
|
241
|
+
async function selectFallback(title, items, nextLine) {
|
|
242
|
+
console.log(`\n ${title}`);
|
|
243
|
+
items.forEach((item, i) =>
|
|
244
|
+
console.log(` ${i + 1}. ${item.name} — ${item.desc}`)
|
|
245
|
+
);
|
|
246
|
+
while (true) {
|
|
247
|
+
const raw = await nextLine(` Pick a number (1-${items.length}): `);
|
|
248
|
+
const n = parseInt(raw, 10) - 1;
|
|
249
|
+
if (n >= 0 && n < items.length) return items[n];
|
|
250
|
+
console.log(" Not a valid number, try again.");
|
|
251
|
+
}
|
|
109
252
|
}
|
|
110
253
|
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
// lines after the first question() call resolves.
|
|
254
|
+
// ─── Text question helper ─────────────────────────────────────────────────────
|
|
255
|
+
|
|
114
256
|
function makeLineReader(rl) {
|
|
115
257
|
const it = rl[Symbol.asyncIterator]();
|
|
116
258
|
return async function nextLine(promptText) {
|
|
@@ -122,22 +264,12 @@ function makeLineReader(rl) {
|
|
|
122
264
|
}
|
|
123
265
|
|
|
124
266
|
async function ask(nextLine, question, defaultValue) {
|
|
125
|
-
const suffix = defaultValue ? `
|
|
126
|
-
|
|
267
|
+
const suffix = defaultValue ? ` ${C.dim}(${defaultValue})${C.reset}` : "";
|
|
268
|
+
output.write(` ${question}${suffix}\n ${C.cyan}›${C.reset} `);
|
|
269
|
+
const answer = await nextLine("");
|
|
127
270
|
return answer || defaultValue || "";
|
|
128
271
|
}
|
|
129
272
|
|
|
130
|
-
async function pickFromList(nextLine, title, items, labelFn) {
|
|
131
|
-
console.log(`\n${title}`);
|
|
132
|
-
items.forEach((item, i) => console.log(` ${i + 1}. ${labelFn(item)}`));
|
|
133
|
-
while (true) {
|
|
134
|
-
const raw = await nextLine(`Pick a number (1-${items.length}): `);
|
|
135
|
-
const idx = parseInt(raw, 10) - 1;
|
|
136
|
-
if (idx >= 0 && idx < items.length) return items[idx];
|
|
137
|
-
console.log(" Not a valid number, try again.");
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
273
|
function copyRecursiveExcluding(src, dest, excludeNames) {
|
|
142
274
|
fs.mkdirSync(dest, { recursive: true });
|
|
143
275
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -152,9 +284,17 @@ function copyRecursiveExcluding(src, dest, excludeNames) {
|
|
|
152
284
|
}
|
|
153
285
|
}
|
|
154
286
|
|
|
155
|
-
|
|
287
|
+
function toKebabCase(str) {
|
|
288
|
+
return str
|
|
289
|
+
.trim()
|
|
290
|
+
.toLowerCase()
|
|
291
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
292
|
+
.replace(/(^-|-$)/g, "") || "my-product";
|
|
293
|
+
}
|
|
156
294
|
|
|
157
|
-
|
|
295
|
+
// ─── Doc builders ─────────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
function buildProjectMd(projectName, productTypeName, { targetUser, problem, valueProp, idea, uiLanguage }) {
|
|
158
298
|
return `# PROJECT.md
|
|
159
299
|
|
|
160
300
|
**Phase:** S — Structure
|
|
@@ -164,7 +304,7 @@ function buildProjectMd(projectName, productTypeLabel, { targetUser, problem, va
|
|
|
164
304
|
|
|
165
305
|
## 1. Product Vision
|
|
166
306
|
|
|
167
|
-
${projectName} is a ${
|
|
307
|
+
${projectName} is a ${productTypeName.toLowerCase()} that helps **${targetUser}** ${valueProp}.
|
|
168
308
|
|
|
169
309
|
> *Expand this into a fuller paragraph before moving to HUMAN_FLOW.md — what does this product become in 2 years if it succeeds?*
|
|
170
310
|
|
|
@@ -242,7 +382,15 @@ ${projectName} is a ${productTypeLabel.toLowerCase()} that helps **${targetUser}
|
|
|
242
382
|
|
|
243
383
|
---
|
|
244
384
|
|
|
245
|
-
## 9.
|
|
385
|
+
## 9. Technical Decisions
|
|
386
|
+
|
|
387
|
+
- **UI language:** ${uiLanguage || "English"}
|
|
388
|
+
- **Auth method:** [magic link / OAuth / password — fill in]
|
|
389
|
+
- **Hosting:** [Vercel / Railway / other — fill in]
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## 10. Original Idea (keep for reference)
|
|
246
394
|
|
|
247
395
|
> *Your words from when you scaffolded this project.*
|
|
248
396
|
|
|
@@ -336,94 +484,94 @@ function buildHumanFlowMd(projectName, productType, { targetUser, problem, value
|
|
|
336
484
|
`;
|
|
337
485
|
}
|
|
338
486
|
|
|
339
|
-
// ───
|
|
487
|
+
// ─── Main ──────────────────────────────────────────────────────────────────────
|
|
340
488
|
|
|
341
489
|
async function main() {
|
|
490
|
+
const isTTY = process.stdin.isTTY;
|
|
491
|
+
|
|
492
|
+
// ── Banner ─────────────────────────────────────────────────────────────────
|
|
493
|
+
console.log("");
|
|
494
|
+
console.log(`${C.bold}${C.cyan} ███████╗██╗ ██╗██╗██████╗ ${C.reset}`);
|
|
495
|
+
console.log(`${C.bold}${C.cyan} ██╔════╝██║ ██║██║██╔══██╗${C.reset}`);
|
|
496
|
+
console.log(`${C.bold}${C.cyan} ███████╗███████║██║██████╔╝${C.reset}`);
|
|
497
|
+
console.log(`${C.bold}${C.cyan} ╚════██║██╔══██║██║██╔═══╝ ${C.reset}`);
|
|
498
|
+
console.log(`${C.bold}${C.cyan} ███████║██║ ██║██║██║ ${C.reset}`);
|
|
499
|
+
console.log(`${C.bold}${C.cyan} ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ${C.reset}`);
|
|
500
|
+
console.log("");
|
|
501
|
+
console.log(` ${C.bold}The SHIP Method${C.reset} ${C.dim}· scaffold your project${C.reset}`);
|
|
502
|
+
console.log(` ${C.dim}──────────────────────────────────────────${C.reset}`);
|
|
503
|
+
console.log("");
|
|
504
|
+
|
|
505
|
+
// ── Interactive selections (no readline needed) ────────────────────────────
|
|
506
|
+
let productType, uiLanguage;
|
|
507
|
+
|
|
508
|
+
if (isTTY) {
|
|
509
|
+
productType = await selectInteractive("What type of system are you building?", PRODUCT_TYPES);
|
|
510
|
+
uiLanguage = await selectInteractive("UI language — what will your app display to users?", LANGUAGES);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ── Text questions (readline) — switch language after uiLanguage is known ──
|
|
342
514
|
const rl = readline.createInterface({ input, terminal: false });
|
|
343
515
|
const nextLine = makeLineReader(rl);
|
|
344
516
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// ── 1. Core questions ──────────────────────────────────────────────────
|
|
350
|
-
const projectNameRaw = await ask(nextLine, "Project name?", "My Product");
|
|
351
|
-
const projectSlug = toKebabCase(projectNameRaw);
|
|
517
|
+
if (!isTTY) {
|
|
518
|
+
productType = await selectFallback("What type of system are you building?", PRODUCT_TYPES, nextLine);
|
|
519
|
+
uiLanguage = await selectFallback("UI language — what will your app display to users?", LANGUAGES, nextLine);
|
|
520
|
+
}
|
|
352
521
|
|
|
353
|
-
const
|
|
354
|
-
nextLine,
|
|
355
|
-
"What are you building?",
|
|
356
|
-
PRODUCT_TYPES,
|
|
357
|
-
(t) => t.label
|
|
358
|
-
);
|
|
522
|
+
const t = I18N[uiLanguage.code] ?? I18N.en;
|
|
359
523
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
""
|
|
366
|
-
);
|
|
367
|
-
const targetUser = await ask(
|
|
368
|
-
nextLine,
|
|
369
|
-
"Who is your primary user? (e.g. 'freelance designers', 'gym owners')",
|
|
370
|
-
"small business owners"
|
|
371
|
-
);
|
|
372
|
-
const problem = await ask(
|
|
373
|
-
nextLine,
|
|
374
|
-
"What is the #1 problem they face today?",
|
|
375
|
-
""
|
|
376
|
-
);
|
|
377
|
-
const valueProp = await ask(
|
|
378
|
-
nextLine,
|
|
379
|
-
"What makes them say 'I need this'? (the aha moment)",
|
|
380
|
-
""
|
|
381
|
-
);
|
|
524
|
+
console.log(`\n ${C.dim}${t.sectionIdea}${C.reset}`);
|
|
525
|
+
const projectNameRaw = await ask(nextLine, t.qName, t.qNameDefault);
|
|
526
|
+
const projectSlug = toKebabCase(projectNameRaw);
|
|
527
|
+
const idea = await ask(nextLine, t.qIdea, "");
|
|
528
|
+
const targetUser = await ask(nextLine, t.qUser, t.qUserDefault);
|
|
529
|
+
const problem = await ask(nextLine, t.qProblem, "");
|
|
530
|
+
const valueProp = await ask(nextLine, t.qValue, "");
|
|
382
531
|
|
|
383
|
-
// Close readline before any child processes touch stdin
|
|
384
532
|
rl.close();
|
|
385
533
|
|
|
386
|
-
// ──
|
|
534
|
+
// ── Guard: don't overwrite an existing folder ──────────────────────────────
|
|
387
535
|
const outDir = path.join(process.cwd(), projectSlug);
|
|
388
536
|
if (fs.existsSync(outDir)) {
|
|
389
|
-
console.log(`\n
|
|
537
|
+
console.log(`\n ${C.dim}${t.folderExists(projectSlug)}${C.reset}`);
|
|
390
538
|
process.exit(1);
|
|
391
539
|
}
|
|
392
540
|
|
|
393
|
-
console.log(`\
|
|
541
|
+
console.log(`\n ${C.dim}${t.scaffolding(projectSlug)}${C.reset}\n`);
|
|
394
542
|
|
|
395
543
|
const starterKitSrc = path.join(TEMPLATES_DIR, "starter-kit");
|
|
396
544
|
if (!fs.existsSync(starterKitSrc) || !fs.existsSync(path.join(TEMPLATES_DIR, "docs"))) {
|
|
397
|
-
console.log(
|
|
545
|
+
console.log(`\n ${t.templatesMissing}\n`);
|
|
398
546
|
process.exit(1);
|
|
399
547
|
}
|
|
400
548
|
|
|
401
|
-
// ──
|
|
549
|
+
// ── Copy app shell ─────────────────────────────────────────────────────────
|
|
402
550
|
copyRecursiveExcluding(
|
|
403
551
|
starterKitSrc,
|
|
404
552
|
outDir,
|
|
405
553
|
new Set(["node_modules", ".next", "package-lock.json", "next-env.d.ts", "tsconfig.tsbuildinfo"])
|
|
406
554
|
);
|
|
407
555
|
|
|
408
|
-
// ──
|
|
556
|
+
// ── Copy agent rule files ──────────────────────────────────────────────────
|
|
409
557
|
for (const ruleFile of ["AGENTS.md", "CLAUDE.md", ".cursorrules", ".windsurfrules"]) {
|
|
410
558
|
const src = path.join(TEMPLATES_DIR, ruleFile);
|
|
411
559
|
if (fs.existsSync(src)) fs.copyFileSync(src, path.join(outDir, ruleFile));
|
|
412
560
|
}
|
|
413
561
|
|
|
414
|
-
// ──
|
|
562
|
+
// ── Copy Claude Code skill ─────────────────────────────────────────────────
|
|
415
563
|
const claudeSkillSrc = path.join(TEMPLATES_DIR, ".claude");
|
|
416
564
|
if (fs.existsSync(claudeSkillSrc)) {
|
|
417
565
|
copyRecursiveExcluding(claudeSkillSrc, path.join(outDir, ".claude"), new Set());
|
|
418
566
|
}
|
|
419
567
|
|
|
420
|
-
// ──
|
|
568
|
+
// ── Write pre-filled docs ──────────────────────────────────────────────────
|
|
421
569
|
const docsDir = path.join(outDir, "docs");
|
|
422
570
|
fs.mkdirSync(docsDir, { recursive: true });
|
|
423
571
|
|
|
424
572
|
fs.writeFileSync(
|
|
425
573
|
path.join(docsDir, "PROJECT.md"),
|
|
426
|
-
buildProjectMd(projectNameRaw, productType.
|
|
574
|
+
buildProjectMd(projectNameRaw, productType.name, { targetUser, problem, valueProp, idea, uiLanguage: uiLanguage.name })
|
|
427
575
|
);
|
|
428
576
|
|
|
429
577
|
fs.writeFileSync(
|
|
@@ -431,65 +579,49 @@ async function main() {
|
|
|
431
579
|
buildHumanFlowMd(projectNameRaw, productType, { targetUser, problem, valueProp })
|
|
432
580
|
);
|
|
433
581
|
|
|
434
|
-
// Product-type feature checklist
|
|
435
582
|
const templateSrc = path.join(TEMPLATES_DIR, "docs", "product-types", productType.file);
|
|
436
583
|
if (fs.existsSync(templateSrc)) {
|
|
437
584
|
fs.copyFileSync(templateSrc, path.join(docsDir, productType.file));
|
|
438
585
|
}
|
|
439
586
|
|
|
440
|
-
// Full prompt chain (Stages 3-6) for users who want it
|
|
441
587
|
const promptsSrc = path.join(TEMPLATES_DIR, "docs", "PROMPTS.md");
|
|
442
588
|
if (fs.existsSync(promptsSrc)) {
|
|
443
589
|
fs.copyFileSync(promptsSrc, path.join(docsDir, "PROMPTS.md"));
|
|
444
590
|
}
|
|
445
591
|
|
|
446
|
-
// Tech-stack reference — agent reads this when making stack decisions.
|
|
447
592
|
const techStackSrc = path.join(TEMPLATES_DIR, "docs", "tech-stack");
|
|
448
593
|
if (fs.existsSync(techStackSrc)) {
|
|
449
594
|
copyRecursiveExcluding(techStackSrc, path.join(docsDir, "tech-stack"), new Set());
|
|
450
595
|
}
|
|
451
596
|
|
|
452
|
-
// Design system + spec templates — filled by /build after user picks theme/font/trend.
|
|
453
597
|
for (const f of ["DESIGN_SYSTEM.md", "DESIGN_SPEC.md"]) {
|
|
454
598
|
const src = path.join(TEMPLATES_DIR, "docs", f);
|
|
455
599
|
if (fs.existsSync(src)) fs.copyFileSync(src, path.join(docsDir, f));
|
|
456
600
|
}
|
|
457
601
|
|
|
458
|
-
// ──
|
|
459
|
-
console.log(
|
|
602
|
+
// ── npm install ────────────────────────────────────────────────────────────
|
|
603
|
+
console.log(` ${C.dim}${t.installing}${C.reset}\n`);
|
|
460
604
|
const install = spawnSync("npm", ["install"], {
|
|
461
605
|
cwd: outDir,
|
|
462
606
|
stdio: "inherit",
|
|
463
607
|
shell: true,
|
|
464
608
|
});
|
|
465
609
|
if (install.status !== 0) {
|
|
466
|
-
console.log(`\n
|
|
610
|
+
console.log(`\n ${t.installFail(projectSlug)}`);
|
|
467
611
|
}
|
|
468
612
|
|
|
469
|
-
// ──
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
docs/tech-stack/STACK_DECISION_MATRIX.md — stack choices reference
|
|
478
|
-
|
|
479
|
-
── Open in your AI coding tool and type /build ─────────────────
|
|
480
|
-
|
|
481
|
-
Claude Code → claude ./${projectSlug}
|
|
482
|
-
Cursor → cursor ./${projectSlug}
|
|
483
|
-
Windsurf → windsurf ./${projectSlug}
|
|
484
|
-
|
|
485
|
-
Then type: /build
|
|
486
|
-
|
|
487
|
-
The agent will read your docs, create the build spec, pick a theme,
|
|
488
|
-
and start coding the MVP — no copy-paste, no extra setup.
|
|
489
|
-
`);
|
|
613
|
+
// ── Done ───────────────────────────────────────────────────────────────────
|
|
614
|
+
const doneLines = t.done(projectSlug).split("\n");
|
|
615
|
+
const styledDone = doneLines.map((line, i) => {
|
|
616
|
+
if (i === 1) return ` ${C.green}${C.bold}${line.trim()}${C.reset}`;
|
|
617
|
+
if (line.includes("/build")) return line.replace("/build", `${C.cyan}${C.bold}/build${C.reset}`);
|
|
618
|
+
return `${C.dim}${line}${C.reset}`;
|
|
619
|
+
}).join("\n");
|
|
620
|
+
console.log(styledDone);
|
|
490
621
|
}
|
|
491
622
|
|
|
492
623
|
main().catch((err) => {
|
|
493
|
-
|
|
624
|
+
process.stdout.write(C.showCursor);
|
|
625
|
+
console.error(`\n ${C.dim}Something went wrong:${C.reset}`, err.message);
|
|
494
626
|
process.exit(1);
|
|
495
627
|
});
|
package/package.json
CHANGED
|
@@ -1,75 +1,48 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ship-method
|
|
3
|
-
description: Use when starting any new product, feature, or "build me an app" request — before writing any application code. Walks through Structure,
|
|
3
|
+
description: Use when starting any new product, feature, or "build me an app" request — before writing any application code. Walks through Structure, Theme, Instruction, Publish in order, using this repo's templates. Also use when asked to review whether a project is "ready to build" or "ready to ship."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# The SHIP Method
|
|
7
7
|
|
|
8
|
-
You are operating inside a project built with **The SHIP Method OS**. The method has four phases, run strictly in order:
|
|
9
|
-
|
|
10
8
|
```
|
|
11
|
-
S — STRUCTURE
|
|
12
|
-
(what & why) (the experience) (AI-ready specs) (ship it)
|
|
9
|
+
S — STRUCTURE → H — HUMAN FLOW → I — INSTRUCTION → P — PUBLISH
|
|
13
10
|
```
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## When to Use This Skill
|
|
18
|
-
|
|
19
|
-
- The user describes a new product/feature idea and wants something built
|
|
20
|
-
- The user asks you to generate code for a feature that doesn't have a filled spec yet
|
|
21
|
-
- The user asks "is this ready to build?" or "is this ready to ship?"
|
|
22
|
-
- You're about to scaffold, design a schema, or write business logic and no `PROJECT.md` / `HUMAN_FLOW.md` exists yet for it
|
|
23
|
-
- The user runs the `/ship` shortcut — drive them through the gates below one phase at a time, drafting their answers into the docs
|
|
12
|
+
## When `/build` is invoked — do this immediately, in this order
|
|
24
13
|
|
|
25
|
-
|
|
14
|
+
**Do not create a task list. Do not plan. Act.**
|
|
26
15
|
|
|
27
|
-
|
|
16
|
+
### 1. Read (no output, no tasks)
|
|
17
|
+
Silently read `docs/PROJECT.md` and `docs/HUMAN_FLOW.md`. If core sections are empty, ask the user one question: *"Describe your idea in one sentence."* Fill the docs from their answer, then continue.
|
|
28
18
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- [ ] **Gate 3 — Instruction exists.** Functional requirements, data model, and API contract are written somewhere concrete (`AI_BUILD_SPEC.md` / `docs/PROJECT.md`'s feature section) before you generate feature code.
|
|
32
|
-
- [ ] **Gate 4 — Theme & First Screen.** Once Gates 1-3 pass and before final polish/ship: derive 2-3 business-appropriate themes from `PROJECT.md`, let the user pick one, apply it (`app/globals.css`, `app/layout.tsx`), and record it in the design system; then read `HUMAN_FLOW.md` and replace the starter's generic `app/page.tsx` ("Pick your area") with the real entry point. See `theme-guide.md` in this skill folder.
|
|
33
|
-
- [ ] **Gate 5 — Publish readiness.** Before telling the user something is "done," check it against the relevant checklist (`QA_CHECKLIST.md`, `LAUNCH_CHECKLIST.md`) rather than declaring success from a clean build alone.
|
|
19
|
+
### 2. Theme — first visible output
|
|
20
|
+
Present 2–3 theme options (palette name + font, one line each). Wait for the user to pick. Apply the chosen theme to `app/globals.css` and `app/layout.tsx`. Record in `docs/DESIGN_SYSTEM.md`. **This is the first thing the user sees — it must happen before any feature code.**
|
|
34
21
|
|
|
35
|
-
|
|
22
|
+
### 3. Home screen — second visible output
|
|
23
|
+
Read `HUMAN_FLOW.md`. Replace `app/page.tsx` with the real entry point for this product. The user should be able to run `npm run dev` and see their product, not the starter template.
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
3. **If all gates are filled:** proceed normally — generate code straight from the spec, and flag if a code request contradicts what's already written in `PROJECT.md` or `HUMAN_FLOW.md` rather than silently overriding it.
|
|
40
|
-
4. **When asked "is this ready to build/ship?":** walk the checklist above explicitly and report which gates pass/fail, rather than giving a vague yes/no.
|
|
25
|
+
### 4. Ask one question
|
|
26
|
+
*"Which feature do you want to build first?"* Then build just that one.
|
|
41
27
|
|
|
42
|
-
|
|
28
|
+
---
|
|
43
29
|
|
|
44
|
-
|
|
45
|
-
knows the business from `PROJECT.md`, so it can theme and build the front door
|
|
46
|
-
accurately.
|
|
30
|
+
**That's the entire flow.** Steps 1–3 are one session. Features are added one at a time after that.
|
|
47
31
|
|
|
48
|
-
|
|
49
|
-
(palette as HSL token values + a font pairing). Present them and let the user
|
|
50
|
-
pick — never pick silently, never require brand assets the user didn't give.
|
|
51
|
-
2. **Apply it.** Write the chosen tokens into `app/globals.css` (`:root` and
|
|
52
|
-
`.dark`), set fonts in `app/layout.tsx`, and record the choice in
|
|
53
|
-
`12-DESIGN-SYSTEM/DESIGN_SYSTEM.md` (or `docs/DESIGN_SYSTEM.md`).
|
|
54
|
-
3. **Build the first screen.** Read `HUMAN_FLOW.md`, decide the real entry point
|
|
55
|
-
for this business, and replace the starter's `app/page.tsx` ("Pick your
|
|
56
|
-
area") with it — landing/sale for marketing-led products, app home/dashboard
|
|
57
|
-
redirect for tools. Adjust each area's main page to match.
|
|
32
|
+
## Hard rules
|
|
58
33
|
|
|
59
|
-
|
|
60
|
-
|
|
34
|
+
- **No task list at start.** Create tasks only when building a specific feature, and only 1–2 at a time.
|
|
35
|
+
- **No spec before theme.** `docs/AI_BUILD_SPEC.md` is written after the home screen exists, not before.
|
|
36
|
+
- **No gates that block visible output.** The only thing that can pause the flow is a missing answer to a direct question.
|
|
37
|
+
- **If the user says "skip docs":** comply. Say once what's skipped. Never repeat.
|
|
61
38
|
|
|
62
|
-
## Reference
|
|
39
|
+
## Reference files
|
|
63
40
|
|
|
64
41
|
| Need | File |
|
|
65
42
|
|---|---|
|
|
66
|
-
|
|
|
67
|
-
| User
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
| Business-type → palette/font theme guide | `theme-guide.md` (this skill folder) |
|
|
73
|
-
| Stack decisions | `13-TECH-STACK/STACK_DECISION_MATRIX.md` |
|
|
74
|
-
|
|
75
|
-
If this is a project generated by `ship-create`, the same files exist under `docs/` instead of the numbered folders above.
|
|
43
|
+
| Vision, audience, MVP | `docs/PROJECT.md` |
|
|
44
|
+
| User flow, screens | `docs/HUMAN_FLOW.md` |
|
|
45
|
+
| Functional spec | `docs/AI_BUILD_SPEC.md` (written after home screen) |
|
|
46
|
+
| Stack choices | `docs/tech-stack/STACK_DECISION_MATRIX.md` |
|
|
47
|
+
| UI guidance | invoke `ui-ux-pro-max` skill |
|
|
48
|
+
| Theme palette table | `theme-guide.md` (this skill folder) |
|
package/templates/.cursorrules
CHANGED
|
@@ -51,10 +51,12 @@ All docs live under `docs/`:
|
|
|
51
51
|
|
|
52
52
|
6. **Never invent business facts** (market size, pricing, real metrics, real user quotes). Draft clearly-labeled `[TBD: ...]` placeholders or ask the user.
|
|
53
53
|
|
|
54
|
-
7. **
|
|
55
|
-
|
|
56
|
-
- **
|
|
57
|
-
- **
|
|
54
|
+
7. **When `/build` is invoked: act immediately, no planning, no task list.**
|
|
55
|
+
- **First:** read `docs/PROJECT.md` + `docs/HUMAN_FLOW.md` silently. If empty, ask one question: *"Describe your idea in one sentence."* Fill docs, then continue.
|
|
56
|
+
- **Second:** propose 2–3 theme options (one line each), let the user pick, apply to `app/globals.css` + `app/layout.tsx`. This is the first visible output.
|
|
57
|
+
- **Third:** replace `app/page.tsx` with the real home screen from `HUMAN_FLOW.md`. User runs `npm run dev` and sees their product.
|
|
58
|
+
- **Then:** ask "which feature first?" and build just that one.
|
|
59
|
+
- **No task list at start.** No spec before theme. No gate that blocks visible output.
|
|
58
60
|
- See `.claude/skills/ship-method/theme-guide.md` for product-type → palette reference.
|
|
59
61
|
|
|
60
62
|
## Quick Orientation for a New Agent Session
|
package/templates/.windsurfrules
CHANGED
|
@@ -51,10 +51,12 @@ All docs live under `docs/`:
|
|
|
51
51
|
|
|
52
52
|
6. **Never invent business facts** (market size, pricing, real metrics, real user quotes). Draft clearly-labeled `[TBD: ...]` placeholders or ask the user.
|
|
53
53
|
|
|
54
|
-
7. **
|
|
55
|
-
|
|
56
|
-
- **
|
|
57
|
-
- **
|
|
54
|
+
7. **When `/build` is invoked: act immediately, no planning, no task list.**
|
|
55
|
+
- **First:** read `docs/PROJECT.md` + `docs/HUMAN_FLOW.md` silently. If empty, ask one question: *"Describe your idea in one sentence."* Fill docs, then continue.
|
|
56
|
+
- **Second:** propose 2–3 theme options (one line each), let the user pick, apply to `app/globals.css` + `app/layout.tsx`. This is the first visible output.
|
|
57
|
+
- **Third:** replace `app/page.tsx` with the real home screen from `HUMAN_FLOW.md`. User runs `npm run dev` and sees their product.
|
|
58
|
+
- **Then:** ask "which feature first?" and build just that one.
|
|
59
|
+
- **No task list at start.** No spec before theme. No gate that blocks visible output.
|
|
58
60
|
- See `.claude/skills/ship-method/theme-guide.md` for product-type → palette reference.
|
|
59
61
|
|
|
60
62
|
## Quick Orientation for a New Agent Session
|
package/templates/AGENTS.md
CHANGED
|
@@ -51,10 +51,12 @@ All docs live under `docs/`:
|
|
|
51
51
|
|
|
52
52
|
6. **Never invent business facts** (market size, pricing, real metrics, real user quotes). Draft clearly-labeled `[TBD: ...]` placeholders or ask the user.
|
|
53
53
|
|
|
54
|
-
7. **
|
|
55
|
-
|
|
56
|
-
- **
|
|
57
|
-
- **
|
|
54
|
+
7. **When `/build` is invoked: act immediately, no planning, no task list.**
|
|
55
|
+
- **First:** read `docs/PROJECT.md` + `docs/HUMAN_FLOW.md` silently. If empty, ask one question: *"Describe your idea in one sentence."* Fill docs, then continue.
|
|
56
|
+
- **Second:** propose 2–3 theme options (one line each), let the user pick, apply to `app/globals.css` + `app/layout.tsx`. This is the first visible output.
|
|
57
|
+
- **Third:** replace `app/page.tsx` with the real home screen from `HUMAN_FLOW.md`. User runs `npm run dev` and sees their product.
|
|
58
|
+
- **Then:** ask "which feature first?" and build just that one.
|
|
59
|
+
- **No task list at start.** No spec before theme. No gate that blocks visible output.
|
|
58
60
|
- See `.claude/skills/ship-method/theme-guide.md` for product-type → palette reference.
|
|
59
61
|
|
|
60
62
|
## Quick Orientation for a New Agent Session
|
package/templates/CLAUDE.md
CHANGED
|
@@ -51,10 +51,12 @@ All docs live under `docs/`:
|
|
|
51
51
|
|
|
52
52
|
6. **Never invent business facts** (market size, pricing, real metrics, real user quotes). Draft clearly-labeled `[TBD: ...]` placeholders or ask the user.
|
|
53
53
|
|
|
54
|
-
7. **
|
|
55
|
-
|
|
56
|
-
- **
|
|
57
|
-
- **
|
|
54
|
+
7. **When `/build` is invoked: act immediately, no planning, no task list.**
|
|
55
|
+
- **First:** read `docs/PROJECT.md` + `docs/HUMAN_FLOW.md` silently. If empty, ask one question: *"Describe your idea in one sentence."* Fill docs, then continue.
|
|
56
|
+
- **Second:** propose 2–3 theme options (one line each), let the user pick, apply to `app/globals.css` + `app/layout.tsx`. This is the first visible output.
|
|
57
|
+
- **Third:** replace `app/page.tsx` with the real home screen from `HUMAN_FLOW.md`. User runs `npm run dev` and sees their product.
|
|
58
|
+
- **Then:** ask "which feature first?" and build just that one.
|
|
59
|
+
- **No task list at start.** No spec before theme. No gate that blocks visible output.
|
|
58
60
|
- See `.claude/skills/ship-method/theme-guide.md` for product-type → palette reference.
|
|
59
61
|
|
|
60
62
|
## Quick Orientation for a New Agent Session
|