create-venture 0.2.0 → 0.4.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/index.mjs +58 -20
- package/package.json +2 -2
package/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// [--source <function>=<system>[:<address>],... ...] [--rho-fixture]
|
|
11
11
|
// [--layers a,b] [--legal-form <form>] [--dump <file>]
|
|
12
12
|
// [--use-npm | --use-pnpm | --use-yarn | --use-bun] [--skip-install] [--no-git]
|
|
13
|
-
// [--framework <spec>] [--reset-preferences]
|
|
13
|
+
// [--framework <spec>] [--reset-preferences] [--venture-name "<name>"] [--no-workspace]
|
|
14
14
|
//
|
|
15
15
|
// --source says where a function keeps its truth (schema/systems-catalog.json), recorded as pointers in
|
|
16
16
|
// knowledge/sources.json: --source products=local-repo:../app,github:acme/app,vercel:acme/app
|
|
@@ -47,7 +47,7 @@ const FORMS = JSON.parse(readFileSync(join(framework, "schema/venture.schema.jso
|
|
|
47
47
|
const argv = process.argv.slice(2);
|
|
48
48
|
const opt = (n) => { const i = argv.indexOf(`--${n}`); return i >= 0 ? argv[i + 1] : undefined; };
|
|
49
49
|
const flag = (n) => argv.includes(`--${n}`);
|
|
50
|
-
const valued = new Set(["phase", "layers", "legal-form", "dump", "framework", "type", "describe", "role", "name", "team"]);
|
|
50
|
+
const valued = new Set(["phase", "layers", "legal-form", "dump", "framework", "type", "describe", "role", "name", "team", "layout", "venture-name"]);
|
|
51
51
|
const positional = argv.filter((a, i) => !a.startsWith("--") && !(i > 0 && valued.has(argv[i - 1].slice(2))));
|
|
52
52
|
const bold = (s) => (process.stdout.isTTY ? `\x1b[1m${s}\x1b[22m` : s);
|
|
53
53
|
const cyan = (s) => (process.stdout.isTTY ? `\x1b[36m${s}\x1b[39m` : s);
|
|
@@ -75,13 +75,18 @@ const choose = async (q, options, fallback) => {
|
|
|
75
75
|
return a;
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
// 1. The name and the directory.
|
|
78
|
+
// 1. The name and the directory. The defaults are "My first venture" in my-first-venture/, and either can
|
|
79
|
+
// be renamed later; a directory given on the command line names the venture unless --venture-name does.
|
|
80
|
+
const FIRST = "My first venture";
|
|
81
|
+
const WORKSPACE_FILE = "naive-workspace.json";
|
|
79
82
|
let target = positional[0];
|
|
83
|
+
let ventureName = opt("venture-name");
|
|
80
84
|
if (!target) {
|
|
81
|
-
|
|
82
|
-
target =
|
|
85
|
+
ventureName ??= interactive ? await ask("What is your venture named?", FIRST) : FIRST;
|
|
86
|
+
target = slug(ventureName);
|
|
83
87
|
}
|
|
84
88
|
const dir = resolve(target);
|
|
89
|
+
ventureName ??= basename(dir);
|
|
85
90
|
const id = slug(basename(dir));
|
|
86
91
|
if (!id) fail(`${basename(dir)} has no letters or digits to make a venture id from`);
|
|
87
92
|
if (existsSync(dir) && readdirSync(dir).length) fail(`${dir} already exists and is not empty. create-venture only creates new ventures; to bring an existing project in, create one here and run naive port or naive migrate.`);
|
|
@@ -149,13 +154,23 @@ let answers = { ...recommended };
|
|
|
149
154
|
const given = opt("phase") || opt("layers") || opt("legal-form") || opt("dump");
|
|
150
155
|
if (given) answers = { phase: opt("phase") ?? "discovery", layers: opt("layers") ?? null, legalForm: opt("legal-form") ?? null, dump: opt("dump") ?? null };
|
|
151
156
|
else if (interactive) answers.phase = await choose("Which phase is the venture in?", PHASES, prefs?.phase ?? "discovery");
|
|
152
|
-
//
|
|
157
|
+
// The layout (EE-086): structured (knowledge/, meta/, the org chart in venture.json) unless --layout flat.
|
|
158
|
+
const layout = opt("layout") ?? "structured";
|
|
159
|
+
if (!["flat", "structured"].includes(layout)) fail(`unknown layout ${layout}: structured or flat`);
|
|
160
|
+
const structured = layout === "structured";
|
|
161
|
+
// A solo founder owns every function; on a team, each function is asked once. In the structured layout the
|
|
162
|
+
// functions are the org chart's (one per package, flat) and ownership goes in venture.json; in the flat one,
|
|
163
|
+
// the catalog's departments, worn as hats in people/.
|
|
164
|
+
const fnIds = structured ? vtype.packages : vtype.departments;
|
|
165
|
+
const fnLabel = (f) => (structured ? f : deptLabel(f));
|
|
153
166
|
const owners = people.filter((p) => roleOf(p.role)?.owns_when_solo);
|
|
167
|
+
const ownerOf = {};
|
|
154
168
|
for (const p of people) p.hats = [];
|
|
155
|
-
if (owners.length === 1 || !interactive) for (const
|
|
156
|
-
else for (const
|
|
157
|
-
const who = await choose(`Who owns ${
|
|
158
|
-
|
|
169
|
+
if (owners.length === 1 || !interactive) for (const f of fnIds) { ownerOf[f] = owners[0]?.id ?? null; owners[0]?.hats.push(f); }
|
|
170
|
+
else for (const f of fnIds) {
|
|
171
|
+
const who = await choose(`Who owns ${fnLabel(f)}?`, owners.map((p) => p.id), owners[0].id);
|
|
172
|
+
ownerOf[f] = who;
|
|
173
|
+
owners.find((p) => p.id === who).hats.push(f);
|
|
159
174
|
}
|
|
160
175
|
// 6. Where each function keeps its truth (EE-083): pointers to its systems of record, in the founder's words when none fits.
|
|
161
176
|
const CATALOG = JSON.parse(readFileSync(join(framework, "schema/systems-catalog.json"), "utf8"));
|
|
@@ -207,30 +222,34 @@ rl?.close();
|
|
|
207
222
|
|
|
208
223
|
// 4. The venture itself, by the framework's own scaffold.
|
|
209
224
|
console.log(`\nCreating a new ${vtype.label.toLowerCase()} in ${bold(dir)}, with ${mode === "solo" ? `${me} as its ${role}, wearing every hat` : `${people.length} founders`}.\n`);
|
|
210
|
-
const init = spawnSync("node", [join(framework, "schema/init-venture.mjs"), dir, id, "--phase", answers.phase, "--type", vtype.id,
|
|
225
|
+
const init = spawnSync("node", [join(framework, "schema/init-venture.mjs"), dir, id, "--phase", answers.phase, "--type", vtype.id, "--layout", layout,
|
|
211
226
|
...(answers.layers ? ["--layers", answers.layers] : []), ...(answers.legalForm ? ["--legal-form", answers.legalForm] : [])], { encoding: "utf8" });
|
|
212
227
|
if (init.status !== 0) fail((init.stderr || init.stdout).trim());
|
|
213
228
|
const vfile = join(dir, "venture.json");
|
|
214
229
|
const venture = JSON.parse(readFileSync(vfile, "utf8"));
|
|
215
|
-
venture.name =
|
|
230
|
+
venture.name = ventureName;
|
|
216
231
|
if (mode === "team") venture.members = people.slice(1).map((p) => ({ role: p.role === "general-partner" ? "partner" : p.role, kind: "person" }));
|
|
232
|
+
// The org chart (EE-086): each function's owner. Hats are read from here, never stored twice.
|
|
233
|
+
if (structured) for (const f of Object.keys(venture.functions ?? {})) venture.functions[f].owner = ownerOf[f] ?? null;
|
|
217
234
|
writeFileSync(vfile, JSON.stringify(venture, null, 2) + "\n");
|
|
235
|
+
// Where each logical file sits in this venture's layout (schema/venture-paths.mjs).
|
|
236
|
+
const { ventureFile: vf, physical } = await import(join(framework, "schema/venture-paths.mjs"));
|
|
218
237
|
|
|
219
238
|
// people/: the roster and one file per founder, the venture.json of human capital (EE-082).
|
|
220
239
|
const { agentFromEnv, record } = await import(join(framework, "interface/provenance.mjs"));
|
|
221
240
|
const today = new Date().toISOString().slice(0, 10);
|
|
222
|
-
mkdirSync(
|
|
241
|
+
mkdirSync(vf(dir, "people/founders"), { recursive: true });
|
|
223
242
|
const asPerson = agentFromEnv("person", "cli", { id: people[0].id });
|
|
224
243
|
for (const p of people.filter((x) => roleOf(x.role)?.founder)) {
|
|
225
244
|
p.founder_file = `people/founders/${p.id}.json`;
|
|
226
|
-
writeFileSync(
|
|
245
|
+
writeFileSync(vf(dir, p.founder_file), JSON.stringify({ id: p.id, name: p.name, role: p.role, ...(structured ? {} : { hats: p.hats }), joined: today }, null, 2) + "\n");
|
|
227
246
|
record(dir, { entity: p.founder_file, route: `people/founders/${p.id}`, activity: "write", agent: asPerson, note: `${p.name}, ${p.role}, as answered at create-venture` });
|
|
228
247
|
}
|
|
229
|
-
writeFileSync(
|
|
248
|
+
writeFileSync(vf(dir, "people/people.json"), JSON.stringify({ venture: id, mode, people: people.map(({ id: pid, name, role: r, hats, founder_file }) => ({ id: pid, name, role: r, ...(structured ? {} : { hats }), ...(founder_file ? { founder_file } : {}) })), updated: today }, null, 2) + "\n");
|
|
230
249
|
record(dir, { entity: "people/people.json", route: "people", activity: "write", agent: asPerson, note: `${mode}: ${people.map((p) => `${p.name} (${p.role})`).join(", ")}` });
|
|
231
250
|
if (answers.dump) {
|
|
232
|
-
mkdirSync(
|
|
233
|
-
copyFileSync(resolve(answers.dump),
|
|
251
|
+
mkdirSync(vf(dir, "raw"), { recursive: true });
|
|
252
|
+
copyFileSync(resolve(answers.dump), vf(dir, "raw/dump.md"));
|
|
234
253
|
record(dir, { entity: "raw/dump.md", route: "raw/dump", activity: "write", agent: agentFromEnv("person", "cli"), note: `the founder's idea dump, verbatim, from ${resolve(answers.dump)}` });
|
|
235
254
|
}
|
|
236
255
|
|
|
@@ -250,7 +269,7 @@ for (const f of Object.values(functions)) for (const s of f.systems) {
|
|
|
250
269
|
} else rhoNote = `Rho: could not sync (${(r.stderr || r.stdout).trim().split("\n").at(-1)}); recorded as pointed`;
|
|
251
270
|
}
|
|
252
271
|
if (Object.keys(functions).length) {
|
|
253
|
-
const map =
|
|
272
|
+
const map = vf(dir, "knowledge/sources.json");
|
|
254
273
|
mkdirSync(dirname(map), { recursive: true });
|
|
255
274
|
writeFileSync(map, JSON.stringify({ sources: [], records: {}, truth: "local", functions }, null, 2) + "\n");
|
|
256
275
|
record(dir, { entity: "knowledge/sources.json", route: "knowledge/knowledge-sources", activity: "write", agent: asPerson,
|
|
@@ -266,12 +285,12 @@ writeFileSync(join(dir, "package.json"), JSON.stringify({
|
|
|
266
285
|
scripts: { dev: "naive dev", build: "naive build", start: "naive start", check: "naive check" },
|
|
267
286
|
devDependencies: { [fw.name]: spec },
|
|
268
287
|
}, null, 2) + "\n");
|
|
269
|
-
writeFileSync(join(dir, ".gitignore"), "node_modules/\n.naive/\n.DS_Store\n# Founder-supplied originals stay out of git (EE-070).\n**/raw/**/original.*\n# A data stream's activity (bank transactions, CRM events) stays out of git; only its stream.json and summary.md are versioned.\n/*/_streams/*/*\n!/*/_streams/*/stream.json\n!/*/_streams/*/summary.md\n");
|
|
288
|
+
writeFileSync(join(dir, ".gitignore"), "node_modules/\n.naive/\n.DS_Store\n# Founder-supplied originals stay out of git (EE-070).\n**/raw/**/original.*\n# A data stream's activity (bank transactions, CRM events) stays out of git; only its stream.json and summary.md are versioned.\n/*/_streams/*/*\n!/*/_streams/*/stream.json\n!/*/_streams/*/summary.md\n/k-base/*/_streams/*/*\n!/k-base/*/_streams/*/stream.json\n!/k-base/*/_streams/*/summary.md\n");
|
|
270
289
|
|
|
271
290
|
// Editor completion for records (I-26): every record file is mapped to its JSON Schema in the installed framework.
|
|
272
291
|
const { routes } = await import(join(framework, "interface/router.mjs"));
|
|
273
292
|
const schemas = new Map();
|
|
274
|
-
for (const r of routes()) if (r.kind === "record" && r.schema && r.form !== "md") for (const f of r.files ?? []) if (f.endsWith(".json")) schemas.set(r.schema, [...(schemas.get(r.schema) ?? []), `/${f}`]);
|
|
293
|
+
for (const r of routes()) if (r.kind === "record" && r.schema && r.form !== "md") for (const f of r.files ?? []) if (f.endsWith(".json")) schemas.set(r.schema, [...(schemas.get(r.schema) ?? []), `/${physical(dir, f)}`]);
|
|
275
294
|
mkdirSync(join(dir, ".vscode"), { recursive: true });
|
|
276
295
|
writeFileSync(join(dir, ".vscode/settings.json"), JSON.stringify({
|
|
277
296
|
"json.schemas": [...schemas].map(([schema, fileMatch]) => ({ fileMatch: [...new Set(fileMatch)], url: `./node_modules/${fw.name}/${schema}` })),
|
|
@@ -291,6 +310,24 @@ if (!flag("no-git") && spawnSync("git", ["--version"]).status === 0 && spawnSync
|
|
|
291
310
|
git = c.status === 0 ? "initialized, with a first commit" : "initialized; the first commit was skipped because git has no user.name and user.email";
|
|
292
311
|
} else if (!flag("no-git")) git = "skipped (already inside a git repository)";
|
|
293
312
|
|
|
313
|
+
// The workspace (the folder holding the founder's ventures, like a monorepo root): the nearest
|
|
314
|
+
// naive-workspace.json above the venture lists it; with none, the venture's parent folder becomes the
|
|
315
|
+
// workspace, named "Naive workspace" until the founder renames it, with an AGENTS.md for rules that hold
|
|
316
|
+
// across every venture. --no-workspace leaves the parent folder as it is.
|
|
317
|
+
let workspace = null;
|
|
318
|
+
if (!flag("no-workspace")) {
|
|
319
|
+
let up = dirname(dir);
|
|
320
|
+
while (!existsSync(join(up, WORKSPACE_FILE)) && dirname(up) !== up) up = dirname(up);
|
|
321
|
+
const root = existsSync(join(up, WORKSPACE_FILE)) ? up : dirname(dir);
|
|
322
|
+
const wf = join(root, WORKSPACE_FILE);
|
|
323
|
+
const ws = existsSync(wf) ? JSON.parse(readFileSync(wf, "utf8")) : { schema: "everything-ecosystem", schema_version: readFileSync(join(framework, "schema/VERSION"), "utf8").trim(), name: "Naive workspace", ventures: [] };
|
|
324
|
+
const rel = relative(root, dir);
|
|
325
|
+
if (!ws.ventures.includes(rel)) ws.ventures.push(rel);
|
|
326
|
+
writeFileSync(wf, JSON.stringify(ws, null, 2) + "\n");
|
|
327
|
+
if (!existsSync(join(root, "AGENTS.md"))) writeFileSync(join(root, "AGENTS.md"), `# ${ws.name}\n\nRules for agents across every venture in this workspace: who you are, how you work, what is off limits. A venture's own AGENTS.md adds to these, and the nearer file wins when two say the same thing.\n`);
|
|
328
|
+
workspace = { root, name: ws.name };
|
|
329
|
+
}
|
|
330
|
+
|
|
294
331
|
try { mkdirSync(dirname(prefsFile), { recursive: true }); writeFileSync(prefsFile, JSON.stringify({ type: vtype.id, role, mode, phase: answers.phase, layers: answers.layers, legalForm: answers.legalForm }, null, 2) + "\n"); } catch {}
|
|
295
332
|
|
|
296
333
|
const runScript = (s) => (manager === "npm" ? `npm run ${s}` : `${manager} ${s}`);
|
|
@@ -298,6 +335,7 @@ const cd = relative(process.cwd(), dir) || ".";
|
|
|
298
335
|
const legal = functions.legal?.systems.length ? null : "Legal documents: none connected yet. Point Naive at them later with --source legal=google-drive:<folder> (or docusign, local-folder), and draft the founder agreement with the founding-agreement kit.";
|
|
299
336
|
console.log(`
|
|
300
337
|
${bold("Success!")} Created ${basename(dir)} at ${dir}
|
|
338
|
+
Workspace: ${workspace ? `${workspace.name} at ${workspace.root} (${WORKSPACE_FILE})` : "none (--no-workspace)"}
|
|
301
339
|
Git: ${git}${rhoNote ? `\n${rhoNote}` : ""}${legal ? `\n${legal}` : ""}
|
|
302
340
|
|
|
303
341
|
Inside that directory, you can run:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-venture",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Create a new Naive venture: npx create-venture my-venture",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,6 +20,6 @@
|
|
|
20
20
|
"LICENSE"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"naive-framework": "^0.
|
|
23
|
+
"naive-framework": "^0.4.0"
|
|
24
24
|
}
|
|
25
25
|
}
|