@cronus-ui/stack 0.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/LICENSE +21 -0
- package/README.md +13 -0
- package/dist/catalog.d.ts +39 -0
- package/dist/catalog.js +826 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +78 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/engine.d.ts +48 -0
- package/dist/engine.js +528 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/kickoff.d.ts +48 -0
- package/dist/kickoff.js +437 -0
- package/dist/schema.d.ts +68 -0
- package/dist/schema.js +133 -0
- package/dist/types.d.ts +128 -0
- package/dist/types.js +14 -0
- package/package.json +84 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kickoff artifacts for a resolved Cronus stack.
|
|
3
|
+
*
|
|
4
|
+
* Turns a {@link StackConfig} into the three things a user leaves the builder
|
|
5
|
+
* with:
|
|
6
|
+
* - `generateCommand` → the `bun create cronus-stack@latest …` shell command.
|
|
7
|
+
* - `generateKickoff` → a KICKOFF.md briefing (the resolved stack is the
|
|
8
|
+
* single source of truth) for the coding agent.
|
|
9
|
+
* - `generateStackJson` → a machine-readable snapshot of the stack.
|
|
10
|
+
*
|
|
11
|
+
* SECURITY: the project name is the only user-controlled free text that reaches
|
|
12
|
+
* a shell command. {@link sanitizeProjectName} reduces it to a strict
|
|
13
|
+
* `[a-z0-9-]` slug so it can never break out of the argument or inject flags.
|
|
14
|
+
*/
|
|
15
|
+
import type { Catalog, StackConfig } from "./types.js";
|
|
16
|
+
/**
|
|
17
|
+
* Reduce an arbitrary string to a safe project slug: lowercase, ASCII
|
|
18
|
+
* `[a-z0-9-]` only, no leading/trailing/duplicate dashes, capped length. Any
|
|
19
|
+
* shell metacharacters, spaces, quotes, slashes, `..`, or flags collapse away.
|
|
20
|
+
* Falls back to "my-cronus-app" when nothing usable remains.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sanitizeProjectName(raw: string): string;
|
|
23
|
+
/** Map an option id to the short CLI flag value (strips the category prefix). */
|
|
24
|
+
export declare function flagValue(optionId: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* The categories emitted as `--flag value` pairs, in stable order. Toggles map
|
|
27
|
+
* to presence/absence flags; multis to comma-separated lists.
|
|
28
|
+
*/
|
|
29
|
+
export declare const CLI_FLAGS: {
|
|
30
|
+
catId: string;
|
|
31
|
+
flag: string;
|
|
32
|
+
kind: "single" | "multi";
|
|
33
|
+
}[];
|
|
34
|
+
/**
|
|
35
|
+
* Build the scaffolding command. One flag per selected category (multi → CSV).
|
|
36
|
+
* The project name is sanitized to a safe slug. Output is a single string with
|
|
37
|
+
* line continuations for readability.
|
|
38
|
+
*/
|
|
39
|
+
export declare function generateCommand(config: StackConfig, projectName?: string): string;
|
|
40
|
+
/** A machine-readable snapshot of the resolved stack. */
|
|
41
|
+
export declare function generateStackJson(config: StackConfig, projectName?: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Render the KICKOFF.md briefing. The resolved stack is the SINGLE SOURCE OF
|
|
44
|
+
* TRUTH: mission, repo map, commands, configured AI capabilities, the Cronus UI
|
|
45
|
+
* contract (only when UI = Cronus UI), guardrails, and a Definition of Done.
|
|
46
|
+
*/
|
|
47
|
+
export declare function generateKickoff(config: StackConfig, projectName?: string, catalog?: Catalog): string;
|
|
48
|
+
//# sourceMappingURL=kickoff.d.ts.map
|
package/dist/kickoff.js
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kickoff artifacts for a resolved Cronus stack.
|
|
3
|
+
*
|
|
4
|
+
* Turns a {@link StackConfig} into the three things a user leaves the builder
|
|
5
|
+
* with:
|
|
6
|
+
* - `generateCommand` → the `bun create cronus-stack@latest …` shell command.
|
|
7
|
+
* - `generateKickoff` → a KICKOFF.md briefing (the resolved stack is the
|
|
8
|
+
* single source of truth) for the coding agent.
|
|
9
|
+
* - `generateStackJson` → a machine-readable snapshot of the stack.
|
|
10
|
+
*
|
|
11
|
+
* SECURITY: the project name is the only user-controlled free text that reaches
|
|
12
|
+
* a shell command. {@link sanitizeProjectName} reduces it to a strict
|
|
13
|
+
* `[a-z0-9-]` slug so it can never break out of the argument or inject flags.
|
|
14
|
+
*/
|
|
15
|
+
import { catalog as defaultCatalog, MULTI_DEFAULTS } from "./catalog.js";
|
|
16
|
+
import { STACK_BUILDER_GENERATOR, STACK_CREATE_COMMAND } from "./constants.js";
|
|
17
|
+
// --------------------------------------------------------------------------
|
|
18
|
+
// Project-name hardening
|
|
19
|
+
// --------------------------------------------------------------------------
|
|
20
|
+
/**
|
|
21
|
+
* Reduce an arbitrary string to a safe project slug: lowercase, ASCII
|
|
22
|
+
* `[a-z0-9-]` only, no leading/trailing/duplicate dashes, capped length. Any
|
|
23
|
+
* shell metacharacters, spaces, quotes, slashes, `..`, or flags collapse away.
|
|
24
|
+
* Falls back to "my-cronus-app" when nothing usable remains.
|
|
25
|
+
*/
|
|
26
|
+
export function sanitizeProjectName(raw) {
|
|
27
|
+
const slug = (raw ?? "")
|
|
28
|
+
.normalize("NFKD")
|
|
29
|
+
.replace(/[̀-ͯ]/g, "") // strip diacritics
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.replace(/[^a-z0-9]+/g, "-") // any non-alnum run -> single dash
|
|
32
|
+
.replace(/^-+|-+$/g, "") // trim dashes
|
|
33
|
+
.slice(0, 64)
|
|
34
|
+
.replace(/-+$/g, ""); // re-trim after slice
|
|
35
|
+
return slug.length > 0 ? slug : "my-cronus-app";
|
|
36
|
+
}
|
|
37
|
+
// --------------------------------------------------------------------------
|
|
38
|
+
// Catalog/selection lookup helpers
|
|
39
|
+
// --------------------------------------------------------------------------
|
|
40
|
+
function indexCatalog(catalog) {
|
|
41
|
+
const catById = new Map();
|
|
42
|
+
const optById = new Map();
|
|
43
|
+
for (const cat of catalog) {
|
|
44
|
+
catById.set(cat.id, cat);
|
|
45
|
+
for (const opt of cat.options)
|
|
46
|
+
optById.set(opt.id, opt);
|
|
47
|
+
}
|
|
48
|
+
return { catById, optById };
|
|
49
|
+
}
|
|
50
|
+
function singleId(config, catId) {
|
|
51
|
+
const v = config[catId];
|
|
52
|
+
return typeof v === "string" ? v : undefined;
|
|
53
|
+
}
|
|
54
|
+
function multiIds(config, catId) {
|
|
55
|
+
const v = config[catId];
|
|
56
|
+
return Array.isArray(v) ? v : [];
|
|
57
|
+
}
|
|
58
|
+
function toggleOn(config, catId) {
|
|
59
|
+
return config[catId] === true;
|
|
60
|
+
}
|
|
61
|
+
/** Map an option id to the short CLI flag value (strips the category prefix). */
|
|
62
|
+
export function flagValue(optionId) {
|
|
63
|
+
const dash = optionId.indexOf("-");
|
|
64
|
+
return dash === -1 ? optionId : optionId.slice(dash + 1);
|
|
65
|
+
}
|
|
66
|
+
function optName(optById, id) {
|
|
67
|
+
return optById.get(id)?.name ?? id;
|
|
68
|
+
}
|
|
69
|
+
// --------------------------------------------------------------------------
|
|
70
|
+
// CLI command
|
|
71
|
+
// --------------------------------------------------------------------------
|
|
72
|
+
/**
|
|
73
|
+
* The categories emitted as `--flag value` pairs, in stable order. Toggles map
|
|
74
|
+
* to presence/absence flags; multis to comma-separated lists.
|
|
75
|
+
*/
|
|
76
|
+
export const CLI_FLAGS = [
|
|
77
|
+
{ catId: "web", flag: "web", kind: "single" },
|
|
78
|
+
{ catId: "backend", flag: "backend", kind: "single" },
|
|
79
|
+
{ catId: "runtime", flag: "runtime", kind: "single" },
|
|
80
|
+
{ catId: "api", flag: "api", kind: "single" },
|
|
81
|
+
{ catId: "database", flag: "database", kind: "single" },
|
|
82
|
+
{ catId: "orm", flag: "orm", kind: "single" },
|
|
83
|
+
{ catId: "dbSetup", flag: "db-setup", kind: "single" },
|
|
84
|
+
{ catId: "auth", flag: "auth", kind: "single" },
|
|
85
|
+
{ catId: "payments", flag: "payments", kind: "single" },
|
|
86
|
+
{ catId: "ui", flag: "ui", kind: "single" },
|
|
87
|
+
{ catId: "assistants", flag: "ai", kind: "multi" },
|
|
88
|
+
{ catId: "mcp", flag: "mcp", kind: "multi" },
|
|
89
|
+
{ catId: "skills", flag: "skills", kind: "multi" },
|
|
90
|
+
{ catId: "deploy", flag: "deploy", kind: "single" },
|
|
91
|
+
{ catId: "packageManager", flag: "package-manager", kind: "single" },
|
|
92
|
+
{ catId: "addons", flag: "addons", kind: "multi" },
|
|
93
|
+
// Conventions — project standards baked into the scaffold.
|
|
94
|
+
{ catId: "naming", flag: "naming", kind: "single" },
|
|
95
|
+
{ catId: "structure", flag: "structure", kind: "single" },
|
|
96
|
+
{ catId: "importAlias", flag: "import-alias", kind: "single" },
|
|
97
|
+
{ catId: "commitStyle", flag: "commits", kind: "single" },
|
|
98
|
+
{ catId: "tsStrict", flag: "ts", kind: "single" },
|
|
99
|
+
];
|
|
100
|
+
/**
|
|
101
|
+
* Build the scaffolding command. One flag per selected category (multi → CSV).
|
|
102
|
+
* The project name is sanitized to a safe slug. Output is a single string with
|
|
103
|
+
* line continuations for readability.
|
|
104
|
+
*/
|
|
105
|
+
export function generateCommand(config, projectName = "my-cronus-app") {
|
|
106
|
+
const slug = sanitizeProjectName(projectName);
|
|
107
|
+
const parts = [`${STACK_CREATE_COMMAND} ${slug} --yes`];
|
|
108
|
+
for (const { catId, flag, kind } of CLI_FLAGS) {
|
|
109
|
+
if (kind === "single") {
|
|
110
|
+
const id = singleId(config, catId);
|
|
111
|
+
if (id)
|
|
112
|
+
parts.push(`--${flag} ${flagValue(id)}`);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const ids = multiIds(config, catId);
|
|
116
|
+
if (ids.length > 0)
|
|
117
|
+
parts.push(`--${flag} ${ids.map(flagValue).join(",")}`);
|
|
118
|
+
else if ((MULTI_DEFAULTS[catId]?.length ?? 0) > 0)
|
|
119
|
+
parts.push(`--${flag} none`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Boolean toggles -> explicit on/off flags so the command is unambiguous.
|
|
123
|
+
if (config.vibe === true)
|
|
124
|
+
parts.push("--vibe");
|
|
125
|
+
parts.push(toggleOn(config, "git") ? "--git" : "--no-git");
|
|
126
|
+
parts.push(toggleOn(config, "install") ? "--install" : "--no-install");
|
|
127
|
+
return parts.join(" \\\n ");
|
|
128
|
+
}
|
|
129
|
+
// --------------------------------------------------------------------------
|
|
130
|
+
// stack.json
|
|
131
|
+
// --------------------------------------------------------------------------
|
|
132
|
+
/** A machine-readable snapshot of the resolved stack. */
|
|
133
|
+
export function generateStackJson(config, projectName = "my-cronus-app") {
|
|
134
|
+
const snapshot = {
|
|
135
|
+
$schema: "https://cronus.dev/schema/stack-1.json",
|
|
136
|
+
version: 1,
|
|
137
|
+
name: sanitizeProjectName(projectName),
|
|
138
|
+
generator: STACK_BUILDER_GENERATOR,
|
|
139
|
+
stack: config,
|
|
140
|
+
};
|
|
141
|
+
return JSON.stringify(snapshot, null, 2);
|
|
142
|
+
}
|
|
143
|
+
// --------------------------------------------------------------------------
|
|
144
|
+
// Conventions & standards
|
|
145
|
+
// --------------------------------------------------------------------------
|
|
146
|
+
/**
|
|
147
|
+
* Turn the chosen convention options into concrete, imperative rules for the
|
|
148
|
+
* KICKOFF brief. These are what make generated projects follow consistent best
|
|
149
|
+
* practices — the resolver guarantees each convention category always has a
|
|
150
|
+
* value (its default), so the list is never empty.
|
|
151
|
+
*/
|
|
152
|
+
function conventionRules(config) {
|
|
153
|
+
const rules = [];
|
|
154
|
+
switch (singleId(config, "naming")) {
|
|
155
|
+
case "naming-kebab":
|
|
156
|
+
rules.push("**Naming:** kebab-case for file and folder names (`user-profile.tsx`); keep React components PascalCase in code.");
|
|
157
|
+
break;
|
|
158
|
+
case "naming-camel":
|
|
159
|
+
rules.push("**Naming:** camelCase for file names and identifiers (`userProfile.tsx`).");
|
|
160
|
+
break;
|
|
161
|
+
case "naming-pascal":
|
|
162
|
+
rules.push("**Naming:** PascalCase for files and exported components/types (`UserProfile.tsx`).");
|
|
163
|
+
break;
|
|
164
|
+
case "naming-snake":
|
|
165
|
+
rules.push("**Naming:** snake_case for file and folder names (`user_profile.tsx`).");
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
switch (singleId(config, "structure")) {
|
|
169
|
+
case "structure-src":
|
|
170
|
+
rules.push("**Structure:** all application code lives under `src/`; keep config at the root.");
|
|
171
|
+
break;
|
|
172
|
+
case "structure-root":
|
|
173
|
+
rules.push("**Structure:** application code lives at the repo root (e.g. `app/`) — no `src/` directory.");
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
switch (singleId(config, "importAlias")) {
|
|
177
|
+
case "import-alias":
|
|
178
|
+
rules.push("**Imports:** use the `@/…` path alias for absolute imports; avoid deep relative paths (`../../../`).");
|
|
179
|
+
break;
|
|
180
|
+
case "import-relative":
|
|
181
|
+
rules.push("**Imports:** use relative import paths (`./`, `../`).");
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
switch (singleId(config, "commitStyle")) {
|
|
185
|
+
case "commit-conventional":
|
|
186
|
+
rules.push("**Commits:** Conventional Commits — `type(scope): subject` (`feat`, `fix`, `chore`, …).");
|
|
187
|
+
break;
|
|
188
|
+
case "commit-plain":
|
|
189
|
+
rules.push("**Commits:** short, imperative commit subjects.");
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
switch (singleId(config, "tsStrict")) {
|
|
193
|
+
case "ts-strict":
|
|
194
|
+
rules.push("**TypeScript:** strict mode — no implicit `any`, no `!` non-null assertions to silence the compiler.");
|
|
195
|
+
break;
|
|
196
|
+
case "ts-standard":
|
|
197
|
+
rules.push("**TypeScript:** the framework's default tsconfig strictness.");
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
return rules;
|
|
201
|
+
}
|
|
202
|
+
// --------------------------------------------------------------------------
|
|
203
|
+
// KICKOFF.md
|
|
204
|
+
// --------------------------------------------------------------------------
|
|
205
|
+
function selectedSingleName(config, optById, catId) {
|
|
206
|
+
const id = singleId(config, catId);
|
|
207
|
+
if (!id)
|
|
208
|
+
return undefined;
|
|
209
|
+
// Hide the empty "None" choices from the prose.
|
|
210
|
+
if (id.endsWith("-none"))
|
|
211
|
+
return undefined;
|
|
212
|
+
return optName(optById, id);
|
|
213
|
+
}
|
|
214
|
+
function scriptCommand(pmBin, script) {
|
|
215
|
+
return pmBin === "npm" ? `npm run ${script}` : `${pmBin} ${script}`;
|
|
216
|
+
}
|
|
217
|
+
function kickoffAppDir(config) {
|
|
218
|
+
const web = singleId(config, "web");
|
|
219
|
+
if (web !== "web-next")
|
|
220
|
+
return "src";
|
|
221
|
+
return singleId(config, "structure") === "structure-root" ? "app" : "src/app";
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Render the KICKOFF.md briefing. The resolved stack is the SINGLE SOURCE OF
|
|
225
|
+
* TRUTH: mission, repo map, commands, configured AI capabilities, the Cronus UI
|
|
226
|
+
* contract (only when UI = Cronus UI), guardrails, and a Definition of Done.
|
|
227
|
+
*/
|
|
228
|
+
export function generateKickoff(config, projectName = "my-cronus-app", catalog = defaultCatalog) {
|
|
229
|
+
const { optById } = indexCatalog(catalog);
|
|
230
|
+
const slug = sanitizeProjectName(projectName);
|
|
231
|
+
const web = selectedSingleName(config, optById, "web");
|
|
232
|
+
const backend = selectedSingleName(config, optById, "backend");
|
|
233
|
+
const runtime = selectedSingleName(config, optById, "runtime");
|
|
234
|
+
const api = selectedSingleName(config, optById, "api");
|
|
235
|
+
const database = selectedSingleName(config, optById, "database");
|
|
236
|
+
const orm = selectedSingleName(config, optById, "orm");
|
|
237
|
+
const dbSetup = selectedSingleName(config, optById, "dbSetup");
|
|
238
|
+
const auth = selectedSingleName(config, optById, "auth");
|
|
239
|
+
const payments = selectedSingleName(config, optById, "payments");
|
|
240
|
+
const ui = singleId(config, "ui");
|
|
241
|
+
const uiName = ui ? optName(optById, ui) : undefined;
|
|
242
|
+
const deploy = selectedSingleName(config, optById, "deploy");
|
|
243
|
+
const pm = singleId(config, "packageManager");
|
|
244
|
+
const pmName = pm ? optName(optById, pm) : "bun";
|
|
245
|
+
const pmBin = pm ? flagValue(pm) : "bun";
|
|
246
|
+
const assistants = multiIds(config, "assistants").map((id) => optName(optById, id));
|
|
247
|
+
const mcp = multiIds(config, "mcp").map((id) => optName(optById, id));
|
|
248
|
+
const skills = multiIds(config, "skills").map((id) => optName(optById, id));
|
|
249
|
+
const addons = multiIds(config, "addons").map((id) => optName(optById, id));
|
|
250
|
+
const vibe = config.vibe === true;
|
|
251
|
+
const isCronusUi = ui === "ui-cronus";
|
|
252
|
+
const appDir = kickoffAppDir(config);
|
|
253
|
+
const webId = singleId(config, "web");
|
|
254
|
+
const backendId = singleId(config, "backend");
|
|
255
|
+
const apiId = singleId(config, "api");
|
|
256
|
+
const databaseId = singleId(config, "database");
|
|
257
|
+
const authId = singleId(config, "auth");
|
|
258
|
+
const paymentsId = singleId(config, "payments");
|
|
259
|
+
const isNext = webId === "web-next";
|
|
260
|
+
const hasDatabase = databaseId !== undefined && databaseId !== "db-none";
|
|
261
|
+
const hasApi = apiId !== undefined && apiId !== "api-none";
|
|
262
|
+
const hasDedicatedBackend = backendId !== undefined &&
|
|
263
|
+
backendId !== "backend-none" &&
|
|
264
|
+
backendId !== "backend-fullstack-next" &&
|
|
265
|
+
backendId !== "backend-fullstack-tanstack";
|
|
266
|
+
const hasAuth = authId !== undefined && authId !== "auth-none";
|
|
267
|
+
const hasPayments = paymentsId !== undefined && paymentsId !== "pay-none";
|
|
268
|
+
const hasEnv = hasDatabase || hasAuth || hasPayments;
|
|
269
|
+
const hasLint = multiIds(config, "addons").includes("addon-biome");
|
|
270
|
+
const stackRows = [
|
|
271
|
+
["Web", web],
|
|
272
|
+
["Backend", backend],
|
|
273
|
+
["Runtime", runtime],
|
|
274
|
+
["API", api],
|
|
275
|
+
["Database", database],
|
|
276
|
+
["ORM", orm],
|
|
277
|
+
["DB Setup", dbSetup],
|
|
278
|
+
["Auth", auth],
|
|
279
|
+
["Payments", payments],
|
|
280
|
+
["UI", uiName],
|
|
281
|
+
["Deploy", deploy],
|
|
282
|
+
["Package Manager", pmName],
|
|
283
|
+
];
|
|
284
|
+
const lines = [];
|
|
285
|
+
lines.push(`# KICKOFF — ${slug}`);
|
|
286
|
+
lines.push("");
|
|
287
|
+
lines.push("> Generated by the Cronus Stack Builder. This file is the **single source of truth** for the project's stack, conventions, and mission. Read it fully before writing any code.");
|
|
288
|
+
lines.push("");
|
|
289
|
+
// --- Mission ---
|
|
290
|
+
lines.push("## Mission");
|
|
291
|
+
lines.push("");
|
|
292
|
+
lines.push(`Build **${slug}** on the stack below. Keep choices consistent with this stack — do not introduce frameworks, databases, or tools not listed here without explicit approval.`);
|
|
293
|
+
lines.push("");
|
|
294
|
+
// --- Resolved stack ---
|
|
295
|
+
lines.push("## Stack (resolved — the truth)");
|
|
296
|
+
lines.push("");
|
|
297
|
+
lines.push("| Layer | Choice |");
|
|
298
|
+
lines.push("| --- | --- |");
|
|
299
|
+
for (const [label, value] of stackRows) {
|
|
300
|
+
if (value)
|
|
301
|
+
lines.push(`| ${label} | ${value} |`);
|
|
302
|
+
}
|
|
303
|
+
lines.push("");
|
|
304
|
+
// --- Conventions & standards ---
|
|
305
|
+
const conventions = conventionRules(config);
|
|
306
|
+
if (conventions.length > 0) {
|
|
307
|
+
lines.push("## Conventions & standards");
|
|
308
|
+
lines.push("");
|
|
309
|
+
lines.push("Project standards — apply them everywhere, from the first commit, so the codebase stays consistent no matter who writes the next file.");
|
|
310
|
+
lines.push("");
|
|
311
|
+
for (const rule of conventions)
|
|
312
|
+
lines.push(`- ${rule}`);
|
|
313
|
+
lines.push("");
|
|
314
|
+
}
|
|
315
|
+
// --- Repo map ---
|
|
316
|
+
lines.push("## Repo map");
|
|
317
|
+
lines.push("");
|
|
318
|
+
lines.push("```");
|
|
319
|
+
lines.push(`${slug}/`);
|
|
320
|
+
lines.push(" package.json # scripts and selected dependencies");
|
|
321
|
+
if (isNext) {
|
|
322
|
+
lines.push(` ${appDir}/ # Next.js App Router starter`);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
lines.push(" src/index.ts # placeholder entry for the selected web framework");
|
|
326
|
+
}
|
|
327
|
+
if (isNext && isCronusUi)
|
|
328
|
+
lines.push(" cronus-ui.json # Cronus UI registry paths and pinned registry URL");
|
|
329
|
+
if (hasEnv)
|
|
330
|
+
lines.push(" .env.example # required environment variables to fill before wiring services");
|
|
331
|
+
lines.push(" KICKOFF.md # this briefing");
|
|
332
|
+
lines.push(" stack.json # machine-readable stack snapshot");
|
|
333
|
+
lines.push("```");
|
|
334
|
+
lines.push("");
|
|
335
|
+
const followUps = [];
|
|
336
|
+
if (!isNext && web) {
|
|
337
|
+
followUps.push("Wire the selected web framework; the generator wrote a placeholder entry only.");
|
|
338
|
+
}
|
|
339
|
+
if (hasDedicatedBackend && backend) {
|
|
340
|
+
followUps.push(`Add the dedicated backend service for **${backend}**.`);
|
|
341
|
+
}
|
|
342
|
+
if (hasApi && api) {
|
|
343
|
+
followUps.push(`Add the typed API contract for **${api}**.`);
|
|
344
|
+
}
|
|
345
|
+
if (hasDatabase && database) {
|
|
346
|
+
followUps.push(`Wire **${database}**${orm ? ` with **${orm}**` : ""} before syncing schema.`);
|
|
347
|
+
}
|
|
348
|
+
if (auth)
|
|
349
|
+
followUps.push(`Implement **${auth}** and protect mutating routes.`);
|
|
350
|
+
if (payments) {
|
|
351
|
+
followUps.push(`Implement **${payments}** webhooks server-side; never trust client-side amounts.`);
|
|
352
|
+
}
|
|
353
|
+
if (followUps.length > 0) {
|
|
354
|
+
lines.push("## Manual follow-up from selected stack");
|
|
355
|
+
lines.push("");
|
|
356
|
+
for (const item of followUps)
|
|
357
|
+
lines.push(`- ${item}`);
|
|
358
|
+
lines.push("");
|
|
359
|
+
}
|
|
360
|
+
// --- Commands ---
|
|
361
|
+
lines.push("## Commands");
|
|
362
|
+
lines.push("");
|
|
363
|
+
lines.push("```bash");
|
|
364
|
+
lines.push(`${pmBin} install`);
|
|
365
|
+
lines.push(scriptCommand(pmBin, "dev"));
|
|
366
|
+
lines.push(scriptCommand(pmBin, "typecheck"));
|
|
367
|
+
if (hasLint)
|
|
368
|
+
lines.push(scriptCommand(pmBin, "lint"));
|
|
369
|
+
lines.push(scriptCommand(pmBin, "build"));
|
|
370
|
+
if (hasDatabase)
|
|
371
|
+
lines.push(`${scriptCommand(pmBin, "db:push")} # sync schema after DB is wired`);
|
|
372
|
+
lines.push("```");
|
|
373
|
+
lines.push("");
|
|
374
|
+
// --- AI capabilities ---
|
|
375
|
+
lines.push("## AI capabilities selected");
|
|
376
|
+
lines.push("");
|
|
377
|
+
lines.push(`- **Assistants:** ${assistants.length ? assistants.join(", ") : "none"}`);
|
|
378
|
+
lines.push(`- **MCP servers:** ${mcp.length ? mcp.join(", ") : "none"}`);
|
|
379
|
+
lines.push(`- **Skills:** ${skills.length ? skills.join(", ") : "none"}`);
|
|
380
|
+
lines.push("- Generated files cover the supported assistants/templates; any unsupported MCP server or skill pack remains manual follow-up.");
|
|
381
|
+
if (vibe) {
|
|
382
|
+
lines.push("- **Vibe Mode: ON** — looser guardrails for fast prototyping. Prefer momentum over ceremony, but never skip the security/DoD checks below.");
|
|
383
|
+
}
|
|
384
|
+
lines.push("");
|
|
385
|
+
// --- Cronus UI contract (only when relevant) ---
|
|
386
|
+
if (isCronusUi) {
|
|
387
|
+
lines.push("## Cronus UI contract");
|
|
388
|
+
lines.push("");
|
|
389
|
+
lines.push("This project uses **Cronus UI**. Treat these as hard rules:");
|
|
390
|
+
lines.push("");
|
|
391
|
+
lines.push("- Use **only** components from `@cronus-ui/ui` — never hand-roll a primitive that already exists.");
|
|
392
|
+
lines.push("- Use **only** semantic design-system tokens (e.g. `bg-surface-raised`, `text-fg`, `border-border`). Never hardcode hex/rgb colors or raw Tailwind palette classes.");
|
|
393
|
+
lines.push("- Compose class names with `cn` from `@cronus-ui/ui`.");
|
|
394
|
+
lines.push('- Add `"use client"` to any component that holds state or effects.');
|
|
395
|
+
lines.push("- Every interactive element must have a visible `focus-visible` ring and pass the axe a11y gate.");
|
|
396
|
+
lines.push("- Tag composite elements with `data-slot` for styling hooks.");
|
|
397
|
+
if (multiIds(config, "mcp").includes("mcp-cronus-ui")) {
|
|
398
|
+
lines.push("- The **cronus-ui MCP server** is selected. Use it only after your assistant has loaded the generated `.mcp.json` or an equivalent MCP config.");
|
|
399
|
+
}
|
|
400
|
+
lines.push("");
|
|
401
|
+
}
|
|
402
|
+
// --- Addons ---
|
|
403
|
+
if (addons.length > 0) {
|
|
404
|
+
lines.push("## Addons selected");
|
|
405
|
+
lines.push("");
|
|
406
|
+
for (const a of addons)
|
|
407
|
+
lines.push(`- ${a}`);
|
|
408
|
+
lines.push("- Generated files include only addons implemented by the generator; anything else is tracked as manual follow-up in the scaffold README.");
|
|
409
|
+
lines.push("");
|
|
410
|
+
}
|
|
411
|
+
// --- Guardrails ---
|
|
412
|
+
lines.push("## Guardrails");
|
|
413
|
+
lines.push("");
|
|
414
|
+
lines.push("- Stay on the stack above; ask before adding new top-level dependencies.");
|
|
415
|
+
lines.push("- No secrets in the repo — use `.env` and document required vars in `.env.example`.");
|
|
416
|
+
lines.push("- Keep changes typed; do not introduce `any` or suppress type errors to ship.");
|
|
417
|
+
if (auth)
|
|
418
|
+
lines.push(`- Auth is **${auth}** — protect every mutation/route that needs a signed-in user.`);
|
|
419
|
+
if (payments)
|
|
420
|
+
lines.push(`- Payments via **${payments}** — verify webhooks and never trust client-side amounts.`);
|
|
421
|
+
lines.push("");
|
|
422
|
+
// --- Definition of Done ---
|
|
423
|
+
lines.push("## Definition of Done");
|
|
424
|
+
lines.push("");
|
|
425
|
+
lines.push("- [ ] Type checks pass (no errors, no new `any`).");
|
|
426
|
+
if (hasLint)
|
|
427
|
+
lines.push("- [ ] Lint/format pass.");
|
|
428
|
+
lines.push("- [ ] Tests pass (and new behavior is covered).");
|
|
429
|
+
if (isCronusUi)
|
|
430
|
+
lines.push("- [ ] UI uses only `@cronus-ui/ui` + semantic tokens; axe a11y gate is green.");
|
|
431
|
+
lines.push("- [ ] App runs locally end-to-end against the configured stack.");
|
|
432
|
+
if (deploy)
|
|
433
|
+
lines.push(`- [ ] Deploy config for **${deploy}** is present and documented.`);
|
|
434
|
+
lines.push("");
|
|
435
|
+
return lines.join("\n");
|
|
436
|
+
}
|
|
437
|
+
//# sourceMappingURL=kickoff.js.map
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
type JsonSchema = Record<string, unknown>;
|
|
2
|
+
export declare const STACK_SCHEMA_VERSION = 1;
|
|
3
|
+
export declare const STACK_SCHEMA_ID = "https://cronus.dev/schema/stack-1.json";
|
|
4
|
+
export declare const STACK_GENERATOR_PACKAGE = "create-cronus-stack";
|
|
5
|
+
export declare const STACK_BUILDER_METADATA: {
|
|
6
|
+
readonly name: "Cronus Stack Builder";
|
|
7
|
+
readonly statusLabel: "Stable";
|
|
8
|
+
readonly route: "/stack";
|
|
9
|
+
readonly docsRoute: "/docs/stack-builder";
|
|
10
|
+
readonly schemaId: "https://cronus.dev/schema/stack-1.json";
|
|
11
|
+
readonly schemaVersion: 1;
|
|
12
|
+
readonly generatorPackage: "create-cronus-stack";
|
|
13
|
+
readonly generatorCreateAlias: "cronus-stack";
|
|
14
|
+
readonly generatorPackageExistsInRepo: true;
|
|
15
|
+
readonly generatorTruth: "This repository ships create-cronus-stack; bun create cronus-stack@latest resolves to that generator package after publish.";
|
|
16
|
+
readonly artifacts: readonly ["Scaffolding command", "KICKOFF.md", "stack.json"];
|
|
17
|
+
};
|
|
18
|
+
export declare const STACK_SCHEMA: JsonSchema;
|
|
19
|
+
export declare const STACK_SCHEMA_EXAMPLE: {
|
|
20
|
+
readonly $schema: "https://cronus.dev/schema/stack-1.json";
|
|
21
|
+
readonly version: 1;
|
|
22
|
+
readonly name: "my-cronus-app";
|
|
23
|
+
readonly generator: "cronus-stack-builder";
|
|
24
|
+
readonly stack: {
|
|
25
|
+
readonly web: "web-next";
|
|
26
|
+
readonly backend: "backend-none";
|
|
27
|
+
readonly runtime: "runtime-bun";
|
|
28
|
+
readonly api: "api-none";
|
|
29
|
+
readonly database: "db-none";
|
|
30
|
+
readonly orm: "orm-none";
|
|
31
|
+
readonly dbSetup: "dbsetup-basic";
|
|
32
|
+
readonly auth: "auth-none";
|
|
33
|
+
readonly payments: "pay-none";
|
|
34
|
+
readonly ui: "ui-cronus";
|
|
35
|
+
readonly assistants: readonly ["ai-claude-code"];
|
|
36
|
+
readonly mcp: readonly [];
|
|
37
|
+
readonly skills: readonly [];
|
|
38
|
+
readonly vibe: false;
|
|
39
|
+
readonly deploy: "deploy-none";
|
|
40
|
+
readonly packageManager: "pm-bun";
|
|
41
|
+
readonly addons: readonly [];
|
|
42
|
+
readonly naming: "naming-kebab";
|
|
43
|
+
readonly structure: "structure-src";
|
|
44
|
+
readonly importAlias: "import-alias";
|
|
45
|
+
readonly commitStyle: "commit-conventional";
|
|
46
|
+
readonly tsStrict: "ts-strict";
|
|
47
|
+
readonly git: true;
|
|
48
|
+
readonly install: true;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export declare const STACK_SCHEMA_FIELD_SUMMARY: readonly [{
|
|
52
|
+
readonly field: "$schema";
|
|
53
|
+
readonly description: "Pins the snapshot to the current Stack Builder schema.";
|
|
54
|
+
}, {
|
|
55
|
+
readonly field: "version";
|
|
56
|
+
readonly description: "Version number for migrations when the snapshot contract changes.";
|
|
57
|
+
}, {
|
|
58
|
+
readonly field: "name";
|
|
59
|
+
readonly description: "Sanitized project slug used by the preview command and kickoff docs.";
|
|
60
|
+
}, {
|
|
61
|
+
readonly field: "generator";
|
|
62
|
+
readonly description: "Always cronus-stack-builder for artifacts emitted by this builder.";
|
|
63
|
+
}, {
|
|
64
|
+
readonly field: "stack";
|
|
65
|
+
readonly description: "Resolved category selections after all requires, conflicts, and defaults apply.";
|
|
66
|
+
}];
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=schema.d.ts.map
|