ship-create 1.5.0 → 1.6.1
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 +263 -125
- 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: (pm) => `Installing packages with ${pm} ...`,
|
|
70
|
+
installFail: (slug, pm) => `${pm} install failed. Run it manually:\n cd ${slug} && ${pm} 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: (pm) => `กำลังติดตั้ง packages ด้วย ${pm} ...`,
|
|
101
|
+
installFail: (slug, pm) => `${pm} install ล้มเหลว รันเองได้ที่:\n cd ${slug} && ${pm} 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,22 @@ function copyRecursiveExcluding(src, dest, excludeNames) {
|
|
|
152
284
|
}
|
|
153
285
|
}
|
|
154
286
|
|
|
155
|
-
|
|
287
|
+
function detectPackageManager() {
|
|
288
|
+
const result = spawnSync("pnpm", ["--version"], { shell: true, encoding: "utf8" });
|
|
289
|
+
return result.status === 0 ? "pnpm" : "npm";
|
|
290
|
+
}
|
|
156
291
|
|
|
157
|
-
function
|
|
292
|
+
function toKebabCase(str) {
|
|
293
|
+
return str
|
|
294
|
+
.trim()
|
|
295
|
+
.toLowerCase()
|
|
296
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
297
|
+
.replace(/(^-|-$)/g, "") || "my-product";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ─── Doc builders ─────────────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
function buildProjectMd(projectName, productTypeName, { targetUser, problem, valueProp, idea, uiLanguage }) {
|
|
158
303
|
return `# PROJECT.md
|
|
159
304
|
|
|
160
305
|
**Phase:** S — Structure
|
|
@@ -164,7 +309,7 @@ function buildProjectMd(projectName, productTypeLabel, { targetUser, problem, va
|
|
|
164
309
|
|
|
165
310
|
## 1. Product Vision
|
|
166
311
|
|
|
167
|
-
${projectName} is a ${
|
|
312
|
+
${projectName} is a ${productTypeName.toLowerCase()} that helps **${targetUser}** ${valueProp}.
|
|
168
313
|
|
|
169
314
|
> *Expand this into a fuller paragraph before moving to HUMAN_FLOW.md — what does this product become in 2 years if it succeeds?*
|
|
170
315
|
|
|
@@ -242,7 +387,15 @@ ${projectName} is a ${productTypeLabel.toLowerCase()} that helps **${targetUser}
|
|
|
242
387
|
|
|
243
388
|
---
|
|
244
389
|
|
|
245
|
-
## 9.
|
|
390
|
+
## 9. Technical Decisions
|
|
391
|
+
|
|
392
|
+
- **UI language:** ${uiLanguage || "English"}
|
|
393
|
+
- **Auth method:** [magic link / OAuth / password — fill in]
|
|
394
|
+
- **Hosting:** [Vercel / Railway / other — fill in]
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## 10. Original Idea (keep for reference)
|
|
246
399
|
|
|
247
400
|
> *Your words from when you scaffolded this project.*
|
|
248
401
|
|
|
@@ -336,94 +489,94 @@ function buildHumanFlowMd(projectName, productType, { targetUser, problem, value
|
|
|
336
489
|
`;
|
|
337
490
|
}
|
|
338
491
|
|
|
339
|
-
// ───
|
|
492
|
+
// ─── Main ──────────────────────────────────────────────────────────────────────
|
|
340
493
|
|
|
341
494
|
async function main() {
|
|
495
|
+
const isTTY = process.stdin.isTTY;
|
|
496
|
+
|
|
497
|
+
// ── Banner ─────────────────────────────────────────────────────────────────
|
|
498
|
+
console.log("");
|
|
499
|
+
console.log(`${C.bold}${C.cyan} ███████╗██╗ ██╗██╗██████╗ ${C.reset}`);
|
|
500
|
+
console.log(`${C.bold}${C.cyan} ██╔════╝██║ ██║██║██╔══██╗${C.reset}`);
|
|
501
|
+
console.log(`${C.bold}${C.cyan} ███████╗███████║██║██████╔╝${C.reset}`);
|
|
502
|
+
console.log(`${C.bold}${C.cyan} ╚════██║██╔══██║██║██╔═══╝ ${C.reset}`);
|
|
503
|
+
console.log(`${C.bold}${C.cyan} ███████║██║ ██║██║██║ ${C.reset}`);
|
|
504
|
+
console.log(`${C.bold}${C.cyan} ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ${C.reset}`);
|
|
505
|
+
console.log("");
|
|
506
|
+
console.log(` ${C.bold}The SHIP Method${C.reset} ${C.dim}· scaffold your project${C.reset}`);
|
|
507
|
+
console.log(` ${C.dim}──────────────────────────────────────────${C.reset}`);
|
|
508
|
+
console.log("");
|
|
509
|
+
|
|
510
|
+
// ── Interactive selections (no readline needed) ────────────────────────────
|
|
511
|
+
let productType, uiLanguage;
|
|
512
|
+
|
|
513
|
+
if (isTTY) {
|
|
514
|
+
productType = await selectInteractive("What type of system are you building?", PRODUCT_TYPES);
|
|
515
|
+
uiLanguage = await selectInteractive("UI language — what will your app display to users?", LANGUAGES);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// ── Text questions (readline) — switch language after uiLanguage is known ──
|
|
342
519
|
const rl = readline.createInterface({ input, terminal: false });
|
|
343
520
|
const nextLine = makeLineReader(rl);
|
|
344
521
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// ── 1. Core questions ──────────────────────────────────────────────────
|
|
350
|
-
const projectNameRaw = await ask(nextLine, "Project name?", "My Product");
|
|
351
|
-
const projectSlug = toKebabCase(projectNameRaw);
|
|
522
|
+
if (!isTTY) {
|
|
523
|
+
productType = await selectFallback("What type of system are you building?", PRODUCT_TYPES, nextLine);
|
|
524
|
+
uiLanguage = await selectFallback("UI language — what will your app display to users?", LANGUAGES, nextLine);
|
|
525
|
+
}
|
|
352
526
|
|
|
353
|
-
const
|
|
354
|
-
nextLine,
|
|
355
|
-
"What are you building?",
|
|
356
|
-
PRODUCT_TYPES,
|
|
357
|
-
(t) => t.label
|
|
358
|
-
);
|
|
527
|
+
const t = I18N[uiLanguage.code] ?? I18N.en;
|
|
359
528
|
|
|
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
|
-
);
|
|
529
|
+
console.log(`\n ${C.dim}${t.sectionIdea}${C.reset}`);
|
|
530
|
+
const projectNameRaw = await ask(nextLine, t.qName, t.qNameDefault);
|
|
531
|
+
const projectSlug = toKebabCase(projectNameRaw);
|
|
532
|
+
const idea = await ask(nextLine, t.qIdea, "");
|
|
533
|
+
const targetUser = await ask(nextLine, t.qUser, t.qUserDefault);
|
|
534
|
+
const problem = await ask(nextLine, t.qProblem, "");
|
|
535
|
+
const valueProp = await ask(nextLine, t.qValue, "");
|
|
382
536
|
|
|
383
|
-
// Close readline before any child processes touch stdin
|
|
384
537
|
rl.close();
|
|
385
538
|
|
|
386
|
-
// ──
|
|
539
|
+
// ── Guard: don't overwrite an existing folder ──────────────────────────────
|
|
387
540
|
const outDir = path.join(process.cwd(), projectSlug);
|
|
388
541
|
if (fs.existsSync(outDir)) {
|
|
389
|
-
console.log(`\n
|
|
542
|
+
console.log(`\n ${C.dim}${t.folderExists(projectSlug)}${C.reset}`);
|
|
390
543
|
process.exit(1);
|
|
391
544
|
}
|
|
392
545
|
|
|
393
|
-
console.log(`\
|
|
546
|
+
console.log(`\n ${C.dim}${t.scaffolding(projectSlug)}${C.reset}\n`);
|
|
394
547
|
|
|
395
548
|
const starterKitSrc = path.join(TEMPLATES_DIR, "starter-kit");
|
|
396
549
|
if (!fs.existsSync(starterKitSrc) || !fs.existsSync(path.join(TEMPLATES_DIR, "docs"))) {
|
|
397
|
-
console.log(
|
|
550
|
+
console.log(`\n ${t.templatesMissing}\n`);
|
|
398
551
|
process.exit(1);
|
|
399
552
|
}
|
|
400
553
|
|
|
401
|
-
// ──
|
|
554
|
+
// ── Copy app shell ─────────────────────────────────────────────────────────
|
|
402
555
|
copyRecursiveExcluding(
|
|
403
556
|
starterKitSrc,
|
|
404
557
|
outDir,
|
|
405
558
|
new Set(["node_modules", ".next", "package-lock.json", "next-env.d.ts", "tsconfig.tsbuildinfo"])
|
|
406
559
|
);
|
|
407
560
|
|
|
408
|
-
// ──
|
|
561
|
+
// ── Copy agent rule files ──────────────────────────────────────────────────
|
|
409
562
|
for (const ruleFile of ["AGENTS.md", "CLAUDE.md", ".cursorrules", ".windsurfrules"]) {
|
|
410
563
|
const src = path.join(TEMPLATES_DIR, ruleFile);
|
|
411
564
|
if (fs.existsSync(src)) fs.copyFileSync(src, path.join(outDir, ruleFile));
|
|
412
565
|
}
|
|
413
566
|
|
|
414
|
-
// ──
|
|
567
|
+
// ── Copy Claude Code skill ─────────────────────────────────────────────────
|
|
415
568
|
const claudeSkillSrc = path.join(TEMPLATES_DIR, ".claude");
|
|
416
569
|
if (fs.existsSync(claudeSkillSrc)) {
|
|
417
570
|
copyRecursiveExcluding(claudeSkillSrc, path.join(outDir, ".claude"), new Set());
|
|
418
571
|
}
|
|
419
572
|
|
|
420
|
-
// ──
|
|
573
|
+
// ── Write pre-filled docs ──────────────────────────────────────────────────
|
|
421
574
|
const docsDir = path.join(outDir, "docs");
|
|
422
575
|
fs.mkdirSync(docsDir, { recursive: true });
|
|
423
576
|
|
|
424
577
|
fs.writeFileSync(
|
|
425
578
|
path.join(docsDir, "PROJECT.md"),
|
|
426
|
-
buildProjectMd(projectNameRaw, productType.
|
|
579
|
+
buildProjectMd(projectNameRaw, productType.name, { targetUser, problem, valueProp, idea, uiLanguage: uiLanguage.name })
|
|
427
580
|
);
|
|
428
581
|
|
|
429
582
|
fs.writeFileSync(
|
|
@@ -431,65 +584,50 @@ async function main() {
|
|
|
431
584
|
buildHumanFlowMd(projectNameRaw, productType, { targetUser, problem, valueProp })
|
|
432
585
|
);
|
|
433
586
|
|
|
434
|
-
// Product-type feature checklist
|
|
435
587
|
const templateSrc = path.join(TEMPLATES_DIR, "docs", "product-types", productType.file);
|
|
436
588
|
if (fs.existsSync(templateSrc)) {
|
|
437
589
|
fs.copyFileSync(templateSrc, path.join(docsDir, productType.file));
|
|
438
590
|
}
|
|
439
591
|
|
|
440
|
-
// Full prompt chain (Stages 3-6) for users who want it
|
|
441
592
|
const promptsSrc = path.join(TEMPLATES_DIR, "docs", "PROMPTS.md");
|
|
442
593
|
if (fs.existsSync(promptsSrc)) {
|
|
443
594
|
fs.copyFileSync(promptsSrc, path.join(docsDir, "PROMPTS.md"));
|
|
444
595
|
}
|
|
445
596
|
|
|
446
|
-
// Tech-stack reference — agent reads this when making stack decisions.
|
|
447
597
|
const techStackSrc = path.join(TEMPLATES_DIR, "docs", "tech-stack");
|
|
448
598
|
if (fs.existsSync(techStackSrc)) {
|
|
449
599
|
copyRecursiveExcluding(techStackSrc, path.join(docsDir, "tech-stack"), new Set());
|
|
450
600
|
}
|
|
451
601
|
|
|
452
|
-
// Design system + spec templates — filled by /build after user picks theme/font/trend.
|
|
453
602
|
for (const f of ["DESIGN_SYSTEM.md", "DESIGN_SPEC.md"]) {
|
|
454
603
|
const src = path.join(TEMPLATES_DIR, "docs", f);
|
|
455
604
|
if (fs.existsSync(src)) fs.copyFileSync(src, path.join(docsDir, f));
|
|
456
605
|
}
|
|
457
606
|
|
|
458
|
-
// ──
|
|
459
|
-
|
|
460
|
-
|
|
607
|
+
// ── install ────────────────────────────────────────────────────────────────
|
|
608
|
+
const pm = detectPackageManager();
|
|
609
|
+
console.log(` ${C.dim}${t.installing(pm)}${C.reset}\n`);
|
|
610
|
+
const install = spawnSync(pm, ["install"], {
|
|
461
611
|
cwd: outDir,
|
|
462
612
|
stdio: "inherit",
|
|
463
613
|
shell: true,
|
|
464
614
|
});
|
|
465
615
|
if (install.status !== 0) {
|
|
466
|
-
console.log(`\n
|
|
616
|
+
console.log(`\n ${t.installFail(projectSlug, pm)}`);
|
|
467
617
|
}
|
|
468
618
|
|
|
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
|
-
`);
|
|
619
|
+
// ── Done ───────────────────────────────────────────────────────────────────
|
|
620
|
+
const doneLines = t.done(projectSlug).split("\n");
|
|
621
|
+
const styledDone = doneLines.map((line, i) => {
|
|
622
|
+
if (i === 1) return ` ${C.green}${C.bold}${line.trim()}${C.reset}`;
|
|
623
|
+
if (line.includes("/build")) return line.replace("/build", `${C.cyan}${C.bold}/build${C.reset}`);
|
|
624
|
+
return `${C.dim}${line}${C.reset}`;
|
|
625
|
+
}).join("\n");
|
|
626
|
+
console.log(styledDone);
|
|
490
627
|
}
|
|
491
628
|
|
|
492
629
|
main().catch((err) => {
|
|
493
|
-
|
|
630
|
+
process.stdout.write(C.showCursor);
|
|
631
|
+
console.error(`\n ${C.dim}Something went wrong:${C.reset}`, err.message);
|
|
494
632
|
process.exit(1);
|
|
495
633
|
});
|
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
|