naive-framework 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/AGENTS.md +1 -1
- package/docs/decisions/decisions.json +15 -0
- package/evidence/skills/fidelity-ladder-audit/ladder-census.mjs +2 -1
- package/interface/adapters.mjs +7 -6
- package/interface/boundaries.mjs +5 -4
- package/interface/brand.mjs +6 -5
- package/interface/cli/mentura.mjs +4 -3
- package/interface/cli/naive.mjs +11 -6
- package/interface/context.mjs +7 -6
- package/interface/demo/board.mjs +9 -8
- package/interface/demo/decision-harness.mjs +4 -3
- package/interface/gate-hooks.mjs +2 -1
- package/interface/harness/evals.mjs +8 -7
- package/interface/harness/run.mjs +14 -13
- package/interface/instrumentation.mjs +4 -3
- package/interface/layer.json +4 -0
- package/interface/package-routes.mjs +4 -3
- package/interface/provenance.mjs +5 -4
- package/interface/reads.mjs +4 -3
- package/interface/release-card.mjs +2 -1
- package/interface/router.mjs +10 -9
- package/interface/skills/choosing-artifacts/SKILL.md +113 -0
- package/interface/vitals.mjs +3 -2
- package/knowledge/connectors/catalog.mjs +3 -2
- package/knowledge/connectors/filesystem.mjs +10 -5
- package/knowledge/connectors/index.mjs +3 -2
- package/operations/marketing/check-public.mjs +7 -6
- package/operations/marketing/site/plan.mjs +4 -3
- package/operations/marketing/site/presence.mjs +4 -3
- package/package.json +1 -1
- package/platform/integrations/rho.mjs +2 -1
- package/purpose/frameworks/idea-map/route.mjs +5 -4
- package/purpose/frameworks/product-anatomy/anatomy.mjs +2 -1
- package/schema/access.mjs +2 -1
- package/schema/add-department.mjs +3 -2
- package/schema/assess-problem.mjs +3 -2
- package/schema/assets.mjs +6 -5
- package/schema/brand.mjs +11 -10
- package/schema/caching.mjs +5 -4
- package/schema/check-config.mjs +4 -3
- package/schema/check-data-model.mjs +2 -1
- package/schema/check-data-packages.mjs +6 -5
- package/schema/check-execution.mjs +4 -3
- package/schema/check-people.mjs +22 -8
- package/schema/check-private.mjs +2 -1
- package/schema/check-ready.mjs +6 -5
- package/schema/check-sources.mjs +2 -1
- package/schema/check-where-used.mjs +4 -3
- package/schema/decision-analytics.mjs +2 -1
- package/schema/decisions.mjs +14 -11
- package/schema/foundation.mjs +8 -7
- package/schema/founder.schema.json +2 -2
- package/schema/frames.mjs +4 -3
- package/schema/freshness.mjs +13 -12
- package/schema/gate-drafts.mjs +8 -7
- package/schema/init-venture.mjs +48 -20
- package/schema/migrate-venture.mjs +8 -7
- package/schema/migrations/kit-to-package.mjs +4 -3
- package/schema/migrations/package-layout.mjs +9 -5
- package/schema/model-steps.mjs +8 -7
- package/schema/naive-config.mjs +4 -3
- package/schema/need-now.mjs +5 -4
- package/schema/packages.mjs +11 -10
- package/schema/payouts.mjs +3 -2
- package/schema/people.schema.json +2 -3
- package/schema/port-venture.mjs +206 -132
- package/schema/release-read.mjs +5 -4
- package/schema/releases.mjs +21 -20
- package/schema/run-handoff.mjs +5 -4
- package/schema/setup.mjs +5 -4
- package/schema/stale.mjs +6 -5
- package/schema/structure.mjs +5 -4
- package/schema/time-to-ready.mjs +7 -6
- package/schema/triggers.mjs +7 -6
- package/schema/upgrades.mjs +31 -30
- package/schema/venture-paths.mjs +97 -0
- package/schema/venture.schema.json +25 -17
- package/schema/workspace.schema.json +5 -1
|
@@ -21,6 +21,7 @@ import { fileURLToPath } from "node:url";
|
|
|
21
21
|
import { call, routes } from "../interface/router.mjs";
|
|
22
22
|
import { agentFromEnv, record as prov } from "../interface/provenance.mjs";
|
|
23
23
|
import { propose } from "./decisions.mjs";
|
|
24
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
24
25
|
|
|
25
26
|
export const PLAN = ".release/migrate-plan.json";
|
|
26
27
|
const MAX_FILE = 512 * 1024;
|
|
@@ -117,7 +118,7 @@ Files: ${JSON.stringify(Object.fromEntries(given.map((f) => [f.path, f.text])))}
|
|
|
117
118
|
/** Writes the plan into the venture and proposes one open decision per function it touches. */
|
|
118
119
|
export function proposeDecisions(dir, p) {
|
|
119
120
|
mkdirSync(join(dir, dirname(PLAN)), { recursive: true });
|
|
120
|
-
writeFileSync(
|
|
121
|
+
writeFileSync(ventureFile(dir, PLAN), JSON.stringify(p, null, 2) + "\n");
|
|
121
122
|
prov(dir, { entity: PLAN, activity: "draft", agent: agentFromEnv("agent", "migrate", { id: "naive-migrate" }), note: `migrate plan from ${p.from}: ${p.items.length} records, ${p.left_behind.length} left behind` });
|
|
122
123
|
const groups = new Map();
|
|
123
124
|
for (const it of p.items) groups.set(functionOf(it.route), [...(groups.get(functionOf(it.route)) ?? []), it]);
|
|
@@ -139,8 +140,8 @@ const answerOf = (d) => { const a = String(d?.answer ?? "").toLowerCase(); retur
|
|
|
139
140
|
|
|
140
141
|
/** Writes what a person decided, through the router. Open decisions wait. */
|
|
141
142
|
export async function apply(dir) {
|
|
142
|
-
const p = load(
|
|
143
|
-
const decisionOf = (fn) => { const f = join(dir, "decisions", `migrate-${fn}.decision.json`); return existsSync(f) ? load(f) : null; };
|
|
143
|
+
const p = load(ventureFile(dir, PLAN));
|
|
144
|
+
const decisionOf = (fn) => { const f = join(ventureFile(dir, "decisions"), `migrate-${fn}.decision.json`); return existsSync(f) ? load(f) : null; };
|
|
144
145
|
const done = { brought: 0, drafted: 0, pointed: 0, left: 0, waiting: 0, failed: 0 };
|
|
145
146
|
let pointed = false;
|
|
146
147
|
for (const it of p.items.filter((x) => x.status === "planned")) {
|
|
@@ -157,7 +158,7 @@ export async function apply(dir) {
|
|
|
157
158
|
done[it.status]++;
|
|
158
159
|
}
|
|
159
160
|
if (pointed) {
|
|
160
|
-
const f = join(dir, "knowledge/sources.json");
|
|
161
|
+
const f = join(ventureFile(dir, "knowledge/sources.json"));
|
|
161
162
|
const s = existsSync(f) ? load(f) : { sources: [], records: {} };
|
|
162
163
|
const id = slug(basename(p.from));
|
|
163
164
|
if (!s.sources.some((x) => x.id === id)) s.sources.push({ id, connector: "filesystem", writes: "none", root: p.from, note: "migrate: a person chose to point to these files rather than rewrite them" });
|
|
@@ -165,7 +166,7 @@ export async function apply(dir) {
|
|
|
165
166
|
writeFileSync(f, JSON.stringify(s, null, 2) + "\n");
|
|
166
167
|
prov(dir, { entity: "knowledge/sources.json", activity: "write", agent: agentFromEnv("agent", "migrate", { id: "naive-migrate" }), note: `points to ${p.from}, read-only` });
|
|
167
168
|
}
|
|
168
|
-
writeFileSync(
|
|
169
|
+
writeFileSync(ventureFile(dir, PLAN), JSON.stringify(p, null, 2) + "\n");
|
|
169
170
|
return done;
|
|
170
171
|
}
|
|
171
172
|
|
|
@@ -176,7 +177,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
176
177
|
if (!dir || !existsSync(join(dir, "venture.json"))) { console.error("usage: node schema/migrate-venture.mjs --from <project> --venture <dir> | --venture <dir> --apply"); process.exit(2); }
|
|
177
178
|
try {
|
|
178
179
|
if (args.includes("--apply")) {
|
|
179
|
-
if (!existsSync(
|
|
180
|
+
if (!existsSync(ventureFile(dir, PLAN))) { console.error(`no plan at ${ventureFile(dir, PLAN)}: run with --from first`); process.exit(1); }
|
|
180
181
|
const d = await apply(dir);
|
|
181
182
|
console.log(Object.entries(d).filter(([, n]) => n).map(([k, n]) => `${n} ${k}`).join(", ") || "nothing to do");
|
|
182
183
|
} else {
|
|
@@ -186,7 +187,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
186
187
|
for (const i of p.items) console.log(`${i.id} ${i.from.join(", ")} -> ${i.route} (${i.reshaping})`);
|
|
187
188
|
for (const l of p.left_behind) console.log(`left ${l.from} (${l.why})`);
|
|
188
189
|
console.log(`\n${p.items.length} records planned, ${p.left_behind.length} left behind. Nothing is written yet.`);
|
|
189
|
-
if (decisions.length) console.log(`Read ${relative(process.cwd(),
|
|
190
|
+
if (decisions.length) console.log(`Read ${relative(process.cwd(), ventureFile(dir, PLAN))}, then decide ${decisions.join(", ")} with mentura decide (answer "bring in", "point to them" or "leave them"), then run with --apply.`);
|
|
190
191
|
}
|
|
191
192
|
} catch (e) { console.error(e.message); process.exit(1); }
|
|
192
193
|
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
|
+
import { ventureFile } from "../venture-paths.mjs";
|
|
17
18
|
|
|
18
19
|
export const id = "kit-to-package";
|
|
19
20
|
export const describe = "Moves Naive packages out of naive.config.json kits into packages, and rewrites layer and kit routes inside records (decision unlocks, team-plan access, where-used parents) to package routes";
|
|
@@ -67,15 +68,15 @@ export function apply(dir, ctx) {
|
|
|
67
68
|
return r.to;
|
|
68
69
|
};
|
|
69
70
|
const rewriteFile = (file, fn) => {
|
|
70
|
-
if (!existsSync(
|
|
71
|
-
const before = readFileSync(
|
|
71
|
+
if (!existsSync(ventureFile(dir, file))) return;
|
|
72
|
+
const before = readFileSync(ventureFile(dir, file), "utf8");
|
|
72
73
|
const rec = JSON.parse(before);
|
|
73
74
|
fn(rec);
|
|
74
75
|
const after = JSON.stringify(rec, null, 2) + "\n";
|
|
75
76
|
if (after.trim() !== before.trim()) ctx.write(file, after, { note: "routes renamed from layer and kit routes to package routes (EE-071)" });
|
|
76
77
|
};
|
|
77
78
|
|
|
78
|
-
const decisionFiles = (sub = "") => readdirSync(
|
|
79
|
+
const decisionFiles = (sub = "") => readdirSync(ventureFile(dir, sub), { withFileTypes: true }).flatMap((e) => {
|
|
79
80
|
const rel = sub ? `${sub}/${e.name}` : e.name;
|
|
80
81
|
if (e.isDirectory()) return e.name.startsWith(".") || e.name.startsWith("_") || existsSync(join(dir, rel, "venture.json")) ? [] : decisionFiles(rel);
|
|
81
82
|
return e.name.endsWith(".decision.json") ? [rel] : [];
|
|
@@ -8,16 +8,20 @@
|
|
|
8
8
|
|
|
9
9
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
+
import { ventureFile } from "../venture-paths.mjs";
|
|
12
|
+
import { layoutOf } from "../venture-paths.mjs";
|
|
11
13
|
|
|
12
14
|
export const id = "package-layout";
|
|
13
15
|
export const describe = "Moves records from the layer folders (purpose/, pillars/, products/, knowledge/) into package folders (foundation/, validation/, product/, marketing/, people/, decisions/)";
|
|
14
16
|
|
|
15
17
|
export function apply(dir, ctx) {
|
|
18
|
+
// A venture in the structured layout (EE-086) is already past this layout: nothing to move.
|
|
19
|
+
if (layoutOf(dir) === "structured") return;
|
|
16
20
|
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
17
21
|
// A decision file infers its target from the folder it sits in. Stamp that path before the file
|
|
18
22
|
// moves, so the decision still points at the same record.
|
|
19
23
|
const stampDecisions = (folder) => {
|
|
20
|
-
const d =
|
|
24
|
+
const d = ventureFile(dir, folder);
|
|
21
25
|
if (!existsSync(d)) return;
|
|
22
26
|
for (const f of readdirSync(d).filter((x) => x.endsWith(".decision.json"))) {
|
|
23
27
|
const rec = load(join(d, f));
|
|
@@ -45,7 +49,7 @@ export function apply(dir, ctx) {
|
|
|
45
49
|
];
|
|
46
50
|
for (const [a, b] of pairs) ctx.mv(a, b);
|
|
47
51
|
|
|
48
|
-
const list = (rel, ext) => existsSync(
|
|
52
|
+
const list = (rel, ext) => existsSync(ventureFile(dir, rel)) ? readdirSync(ventureFile(dir, rel)).filter((f) => f.endsWith(ext)) : [];
|
|
49
53
|
for (const f of list("products", ".json")) ctx.mv(`products/${f}`, `product/${f}`);
|
|
50
54
|
for (const f of list("purpose/anatomy", ".json")) ctx.mv(`purpose/anatomy/${f}`, `product/anatomy/${f}`);
|
|
51
55
|
|
|
@@ -55,7 +59,7 @@ export function apply(dir, ctx) {
|
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
for (const f of ["product.md", "business.md"]) {
|
|
58
|
-
if (existsSync(join(dir, "pillars", f))) {
|
|
62
|
+
if (existsSync(join(ventureFile(dir, "pillars"), f))) {
|
|
59
63
|
ctx.mv(`pillars/${f}`, `_private/pillars/${f}`);
|
|
60
64
|
ctx.escalate(`${f.replace(".md", "")} pillar: the brief names a home only for brand (marketing/). Archived at _private/pillars/${f} rather than guessed.`);
|
|
61
65
|
}
|
|
@@ -68,8 +72,8 @@ export function apply(dir, ctx) {
|
|
|
68
72
|
for (const gone of ["purpose", "pillars", "knowledge", "products"]) ctx.removeEmpty(gone);
|
|
69
73
|
|
|
70
74
|
const policy = "governance/publication-policy.json";
|
|
71
|
-
if (existsSync(
|
|
72
|
-
const text = readFileSync(
|
|
75
|
+
if (existsSync(ventureFile(dir, policy))) {
|
|
76
|
+
const text = readFileSync(ventureFile(dir, policy), "utf8");
|
|
73
77
|
if (text.includes("pillars/brand.md")) ctx.write(policy, text.replaceAll("pillars/brand.md", "marketing/brand.md"), { note: "the brand pillar moved to marketing/brand.md (EE-070)" });
|
|
74
78
|
}
|
|
75
79
|
|
package/schema/model-steps.mjs
CHANGED
|
@@ -47,6 +47,7 @@ import { call as routerCall, files as ventureFiles, glob, routes } from "../inte
|
|
|
47
47
|
import { agentFromEnv, entries as provEntries, record as prov } from "../interface/provenance.mjs";
|
|
48
48
|
import { decisions as decisionsOf, propose } from "./decisions.mjs";
|
|
49
49
|
import { loadPackages } from "./load-packages.mjs";
|
|
50
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
50
51
|
|
|
51
52
|
const repo = new URL("..", import.meta.url).pathname;
|
|
52
53
|
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
@@ -264,7 +265,7 @@ async function context(dir, { call = routerCall, workspace } = {}) {
|
|
|
264
265
|
let byId = null;
|
|
265
266
|
const recordById = (id) => {
|
|
266
267
|
byId ??= new Map(ventureFiles(dir).filter((f) => f.endsWith(".json") && !f.startsWith("knowledge/judgments/")).flatMap((f) => {
|
|
267
|
-
try { const r = load(
|
|
268
|
+
try { const r = load(ventureFile(dir, f)); return r && typeof r.id === "string" ? [[r.id, { file: f, record: r }]] : []; } catch { return []; }
|
|
268
269
|
}));
|
|
269
270
|
return byId.get(id) ?? null;
|
|
270
271
|
};
|
|
@@ -583,8 +584,8 @@ function confirmedBy(dir, file) {
|
|
|
583
584
|
return c ? c.agent.id ?? "a person" : null;
|
|
584
585
|
}
|
|
585
586
|
const isModelJudgment = (dir, f) => {
|
|
586
|
-
if (!glob(`${JUDGMENTS}/*.json`).test(f) || !existsSync(
|
|
587
|
-
try { return load(
|
|
587
|
+
if (!glob(`${JUDGMENTS}/*.json`).test(f) || !existsSync(ventureFile(dir, f))) return false;
|
|
588
|
+
try { return load(ventureFile(dir, f)).by?.kind === "model"; } catch { return false; }
|
|
588
589
|
};
|
|
589
590
|
|
|
590
591
|
/**
|
|
@@ -603,12 +604,12 @@ export async function provenanceOf(dir, file, { checks = false, call = routerCal
|
|
|
603
604
|
if (last.agent.kind === "person" || confirmed) return { entity: file, status: "person", model: false, downstream_of_model: [],
|
|
604
605
|
why: confirmed ? `confirmed by ${confirmed.agent.id ?? "a person"}` : `${last.activity === "accept" ? "accepted" : "written"} by ${last.agent.id ?? "a person"}` };
|
|
605
606
|
const upstream = [];
|
|
606
|
-
for (const d of (last.derived_from ?? []).map((x) => x.split("#")[0]).filter((x) => !seen.has(x) && existsSync(
|
|
607
|
+
for (const d of (last.derived_from ?? []).map((x) => x.split("#")[0]).filter((x) => !seen.has(x) && existsSync(ventureFile(dir, x)))) {
|
|
607
608
|
const u = await provenanceOf(dir, d, { seen });
|
|
608
609
|
if (u.model) upstream.push(d); else upstream.push(...u.downstream_of_model);
|
|
609
610
|
}
|
|
610
611
|
const res = { entity: file, status: "agent", model, downstream_of_model: [...new Set(upstream)], why: model ? `a model's judgment (${last.note ?? "use model"}); a person confirms it` : upstream.length ? "an agent wrote it from a model's output" : "an agent wrote it" };
|
|
611
|
-
if (checks && !(model && load(
|
|
612
|
+
if (checks && !(model && load(ventureFile(dir, file)).kind === "draft") && last.route) {
|
|
612
613
|
const venture = load(join(dir, "venture.json")).id;
|
|
613
614
|
const r = await call({ venture, workspace: workspace ?? dirname(dir), route: last.route, action: "check" });
|
|
614
615
|
if (r.ok && r.result?.length) return { ...res, status: "clear", why: `${res.why}; its checks pass (${r.result.map((x) => x.check).join(", ")})` };
|
|
@@ -620,8 +621,8 @@ export async function provenanceOf(dir, file, { checks = false, call = routerCal
|
|
|
620
621
|
export function confirm(dir, id, { by, note } = {}) {
|
|
621
622
|
if (!by) throw new Expected("confirming needs the person's name (--by)", "refused");
|
|
622
623
|
const file = `${JUDGMENTS}/${id}.json`;
|
|
623
|
-
if (!existsSync(
|
|
624
|
-
const j = load(
|
|
624
|
+
if (!existsSync(ventureFile(dir, file))) throw new Expected(`no judgment ${id}`, "missing record");
|
|
625
|
+
const j = load(ventureFile(dir, file));
|
|
625
626
|
if (j.outcome === "uncertain") throw new Expected(`${id} is uncertain: answer its decision ${j.decision} instead`, "refused");
|
|
626
627
|
const e = prov(dir, { entity: file, route: `knowledge/judgment/${id}`, activity: "confirm", agent: agentFromEnv("person", "cli", { id: by }), note: note ?? `confirmed ${j.package}/${j.step}${j.item ? ` for ${j.item}` : ""}` });
|
|
627
628
|
return { confirmed: id, by, provenance: e.hash };
|
package/schema/naive-config.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
13
14
|
|
|
14
15
|
const repo = new URL("..", import.meta.url).pathname;
|
|
15
16
|
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
@@ -36,7 +37,7 @@ function merge(base, over) {
|
|
|
36
37
|
|
|
37
38
|
/** The venture's naive.config.json as written, or {} when it has none. A file that is not JSON throws, naming the file. */
|
|
38
39
|
export function rawConfig(dir) {
|
|
39
|
-
const f = dir &&
|
|
40
|
+
const f = dir && ventureFile(dir, CONFIG);
|
|
40
41
|
if (!f || !existsSync(f)) return {};
|
|
41
42
|
try { return load(f); } catch (e) { throw new Error(`${CONFIG} is not valid JSON: ${e.message}`); }
|
|
42
43
|
}
|
|
@@ -139,7 +140,7 @@ export function pinsBlocking(config, recordType) {
|
|
|
139
140
|
|
|
140
141
|
/** The venture's naive.config.json, or {} when it has none. A file that is not JSON throws. */
|
|
141
142
|
export function readConfig(dir) {
|
|
142
|
-
const f =
|
|
143
|
+
const f = ventureFile(dir, CONFIG);
|
|
143
144
|
if (!existsSync(f)) return {};
|
|
144
145
|
try { return JSON.parse(readFileSync(f, "utf8")); }
|
|
145
146
|
catch (e) { throw new Error(`${CONFIG} is not valid JSON: ${e.message}`); }
|
|
@@ -149,7 +150,7 @@ export function readConfig(dir) {
|
|
|
149
150
|
export function writeConfig(dir, cfg) {
|
|
150
151
|
const order = ["naive", "packages", "kits", "triggers", "gate"];
|
|
151
152
|
const out = Object.fromEntries([...order.filter((k) => cfg[k] !== undefined).map((k) => [k, cfg[k]]), ...Object.entries(cfg).filter(([k]) => !order.includes(k))]);
|
|
152
|
-
writeFileSync(
|
|
153
|
+
writeFileSync(ventureFile(dir, CONFIG), JSON.stringify(out, null, 2) + "\n");
|
|
153
154
|
return out;
|
|
154
155
|
}
|
|
155
156
|
|
package/schema/need-now.mjs
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { catalog } from "./departments.mjs";
|
|
18
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
18
19
|
|
|
19
20
|
const root = new URL("..", import.meta.url).pathname;
|
|
20
21
|
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
@@ -27,14 +28,14 @@ const glob = (g) => new RegExp("^" + g.replace(/[.+^${}()|[\]\\]/g, "\\$&").repl
|
|
|
27
28
|
/** Facts about a venture that the need-now conditions test. */
|
|
28
29
|
export function factsOf(dir) {
|
|
29
30
|
const v = load(join(dir, "venture.json"));
|
|
30
|
-
const json = (p) => existsSync(
|
|
31
|
-
const products = existsSync(join(dir, "products")) ? readdirSync(join(dir, "products")).filter((f) => f.endsWith(".json")).map((f) => load(join(dir, "products", f))) : [];
|
|
31
|
+
const json = (p) => existsSync(ventureFile(dir, p)) ? load(ventureFile(dir, p)) : null;
|
|
32
|
+
const products = existsSync(join(ventureFile(dir, "products"))) ? readdirSync(join(ventureFile(dir, "products"))).filter((f) => f.endsWith(".json")).map((f) => load(join(ventureFile(dir, "products"), f))) : [];
|
|
32
33
|
const members = (v.members ?? []).map((m) => typeof m === "string" ? { kind: "person", role: m } : m);
|
|
33
34
|
const own = json("governance/ownership.json");
|
|
34
|
-
const hDir = join(dir, "operations/handoffs");
|
|
35
|
+
const hDir = join(ventureFile(dir, "operations/handoffs"));
|
|
35
36
|
const handoffs = existsSync(hDir) ? readdirSync(hDir).filter((f) => f.endsWith(".json")).map((f) => load(join(hDir, f))) : [];
|
|
36
37
|
// The founding team, as the founder answered Naive's team question (EE-060): solo, cofounders or looking.
|
|
37
|
-
const teamFile = join(dir, "decisions/venture.team.decision.json");
|
|
38
|
+
const teamFile = join(ventureFile(dir, "decisions/venture.team.decision.json"));
|
|
38
39
|
const td = existsSync(teamFile) ? load(teamFile) : null;
|
|
39
40
|
const team = td?.status === "decided" ? ["solo", "cofounders", "looking"].find((t) => (td.answer ?? "").toLowerCase().startsWith(t)) ?? null : null;
|
|
40
41
|
return {
|
package/schema/packages.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import { route } from "../purpose/frameworks/idea-map/route.mjs";
|
|
|
31
31
|
import { loadPackages } from "./load-packages.mjs";
|
|
32
32
|
// Upgrades hook (U-5): packages a venture adopted early in naive.config.json open now.
|
|
33
33
|
import { adopted } from "./naive-config.mjs";
|
|
34
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
34
35
|
|
|
35
36
|
const repo = new URL("..", import.meta.url).pathname;
|
|
36
37
|
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
@@ -39,7 +40,7 @@ export const OUT = "package";
|
|
|
39
40
|
const MODEL = load(join(repo, "schema/data-model.json"));
|
|
40
41
|
const PHASES = ["discovery", "validation", "beachhead", "scale", "platform"];
|
|
41
42
|
const r2 = (x) => Math.round(x * 100) / 100;
|
|
42
|
-
const json = (dir, f) => existsSync(
|
|
43
|
+
const json = (dir, f) => existsSync(ventureFile(dir, f)) ? load(ventureFile(dir, f)) : null;
|
|
43
44
|
|
|
44
45
|
/** The facts the packages test: need-now's, the founder's flags, and the question matrix's answers. */
|
|
45
46
|
export function situation(dir, { flags = {}, answers = null } = {}) {
|
|
@@ -52,7 +53,7 @@ export function situation(dir, { flags = {}, answers = null } = {}) {
|
|
|
52
53
|
const settled = {
|
|
53
54
|
raising: flags.raising != null ? !!flags.raising : f.funding === "bootstrapped" ? false : undefined,
|
|
54
55
|
// A site read by site presence; a planned sitemap does not count, since the pipeline plans one for every venture.
|
|
55
|
-
site: existsSync(join(dir, "operations/marketing/site-presence.json")) || undefined,
|
|
56
|
+
site: existsSync(join(ventureFile(dir, "operations/marketing/site-presence.json"))) || undefined,
|
|
56
57
|
partner_led: profiles.some((p) => p.reached_through?.length) || products.some((p) => (p.offered_to ?? []).some((id) => profiles.some((y) => y.reached_through?.includes(id)))) || undefined,
|
|
57
58
|
hiring: flags.hiring != null ? !!flags.hiring : f.team === "looking" ? true : undefined,
|
|
58
59
|
regulated: undefined,
|
|
@@ -60,7 +61,7 @@ export function situation(dir, { flags = {}, answers = null } = {}) {
|
|
|
60
61
|
};
|
|
61
62
|
const Q = PACKAGES.questions;
|
|
62
63
|
// Jev's answers from the last written package stand, unless new answers replace them.
|
|
63
|
-
const last = existsSync(join(dir, OUT, "venture-package.json")) ? Object.fromEntries(load(join(dir, OUT, "venture-package.json")).situation.questions.filter((q) => q.p != null).map((q) => [`q:${q.id}`, { noul: q.p }])) : {};
|
|
64
|
+
const last = existsSync(join(ventureFile(dir, OUT), "venture-package.json")) ? Object.fromEntries(load(join(ventureFile(dir, OUT), "venture-package.json")).situation.questions.filter((q) => q.p != null).map((q) => [`q:${q.id}`, { noul: q.p }])) : {};
|
|
64
65
|
answers = { ...last, ...(answers ?? {}) };
|
|
65
66
|
const questions = Q.items.filter((q) => q.phases.includes(phase)).map((q) => {
|
|
66
67
|
if (settled[q.id] !== undefined) return { ...q, value: settled[q.id], by: "records" };
|
|
@@ -77,8 +78,8 @@ export function request(dir) {
|
|
|
77
78
|
const { questions } = situation(dir);
|
|
78
79
|
const v = load(join(dir, "venture.json"));
|
|
79
80
|
const say = (o) => o ? JSON.stringify(o).slice(0, 1200) : null;
|
|
80
|
-
const dumpFile = ["raw/dump.md", "purpose/ideas/dump.md"].find((f) => existsSync(
|
|
81
|
-
const dump = dumpFile ? readFileSync(
|
|
81
|
+
const dumpFile = ["raw/dump.md", "purpose/ideas/dump.md"].find((f) => existsSync(ventureFile(dir, f)));
|
|
82
|
+
const dump = dumpFile ? readFileSync(ventureFile(dir, dumpFile), "utf8").slice(0, 2500) : null;
|
|
82
83
|
const problem = json(dir, "foundation/problem.json") ?? json(dir, "purpose/validation/problem.json");
|
|
83
84
|
const icpRec = json(dir, "foundation/icp.json") ?? json(dir, "purpose/icp.json");
|
|
84
85
|
return { state: { venture: say({ name: v.name, phase: v.phase, ambition: v.ambition, form: v.form, core: v.core }), problem: say(problem), icp: say(icpRec),
|
|
@@ -89,9 +90,9 @@ export function request(dir) {
|
|
|
89
90
|
|
|
90
91
|
/** A record's state in this venture: confirmed by a person, written by an agent, waiting as a draft, or missing. */
|
|
91
92
|
function stateOf(dir, type) {
|
|
92
|
-
const files = filesOf(dir, type).filter((f) => existsSync(
|
|
93
|
+
const files = filesOf(dir, type).filter((f) => existsSync(ventureFile(dir, f)) && !(f.endsWith(".json") && ["retired", "archived"].includes(json(dir, f)?.status)));
|
|
93
94
|
const drafts = (filesOf(dir, type).length ? filesOf(dir, type) : [MODEL.records.find((r) => r.id === type)?.paths?.find((p) => p.startsWith("instance/**/"))?.slice(12)].filter(Boolean))
|
|
94
|
-
.filter((f) => !f.includes("*") && existsSync(
|
|
95
|
+
.filter((f) => !f.includes("*") && existsSync(ventureFile(dir, `_drafts/${f.replace(/\//g, "__")}.draft`)));
|
|
95
96
|
if (!files.length) return { status: drafts.length ? "draft" : "missing", files: [], drafts };
|
|
96
97
|
const confirmed = files.every((f) => confirmedIn(dir, f, null));
|
|
97
98
|
return { status: drafts.length ? "draft" : confirmed ? "confirmed" : "agent", files, drafts };
|
|
@@ -174,7 +175,7 @@ const LEAD = ["name", "title", "who", "statement", "question", "label", "id"];
|
|
|
174
175
|
const lead = (o) => { const k = LEAD.find((k) => typeof o[k] === "string"); return k ? o[k] : ""; };
|
|
175
176
|
const rest = (o) => { const k = LEAD.find((k) => typeof o[k] === "string"); return Object.fromEntries(Object.entries(o).filter(([x]) => x !== k)); };
|
|
176
177
|
const body = (dir, f) => {
|
|
177
|
-
const t = readFileSync(
|
|
178
|
+
const t = readFileSync(ventureFile(dir, f), "utf8");
|
|
178
179
|
if (f.endsWith(".json")) return md(JSON.parse(t));
|
|
179
180
|
return t.replace(/^---\n[\s\S]*?\n---\n/, "").replace(/^#\s.*\n/, "").trim();
|
|
180
181
|
};
|
|
@@ -183,7 +184,7 @@ const STATE = { complete: "complete: its automation may run", review: "every rec
|
|
|
183
184
|
|
|
184
185
|
/** A new version each time a package completes or the phase changes; earlier versions are kept. */
|
|
185
186
|
export function versions(dir, r) {
|
|
186
|
-
const f = join(dir, OUT, "venture-package.json");
|
|
187
|
+
const f = join(ventureFile(dir, OUT), "venture-package.json");
|
|
187
188
|
const before = existsSync(f) ? load(f) : null;
|
|
188
189
|
const list = before?.versions ?? [];
|
|
189
190
|
const last = list.at(-1);
|
|
@@ -196,7 +197,7 @@ export function versions(dir, r) {
|
|
|
196
197
|
|
|
197
198
|
/** Write the readable view: the manifest, the index, and one document per open core package. */
|
|
198
199
|
export function write(dir, r) {
|
|
199
|
-
const out =
|
|
200
|
+
const out = ventureFile(dir, OUT);
|
|
200
201
|
mkdirSync(out, { recursive: true });
|
|
201
202
|
r.versions = versions(dir, r);
|
|
202
203
|
writeFileSync(join(out, "venture-package.json"), JSON.stringify(r, null, 2) + "\n");
|
package/schema/payouts.mjs
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
19
20
|
|
|
20
21
|
export function payouts(own, value) {
|
|
21
22
|
const classes = new Map(own.classes.map((c) => [c.id, c]));
|
|
@@ -74,8 +75,8 @@ export function payouts(own, value) {
|
|
|
74
75
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
75
76
|
const [dir, arg] = process.argv.slice(2);
|
|
76
77
|
if (!dir) { console.error("usage: node schema/payouts.mjs <venture dir> [value] [--json]"); process.exit(2); }
|
|
77
|
-
const own = JSON.parse(readFileSync(join(dir, "governance/ownership.json"), "utf8"));
|
|
78
|
-
const exitFile = join(dir, "governance/exit.json");
|
|
78
|
+
const own = JSON.parse(readFileSync(join(ventureFile(dir, "governance/ownership.json")), "utf8"));
|
|
79
|
+
const exitFile = join(ventureFile(dir, "governance/exit.json"));
|
|
79
80
|
const scenarios = arg && !arg.startsWith("--") ? [{ id: "given", value: Number(arg) }]
|
|
80
81
|
: existsSync(exitFile) ? JSON.parse(readFileSync(exitFile, "utf8")).scenarios ?? [] : [];
|
|
81
82
|
if (!scenarios.length) { console.error("no scenarios: pass a value or add scenarios to governance/exit.json"); process.exit(2); }
|
|
@@ -29,8 +29,7 @@
|
|
|
29
29
|
"required": [
|
|
30
30
|
"id",
|
|
31
31
|
"name",
|
|
32
|
-
"role"
|
|
33
|
-
"hats"
|
|
32
|
+
"role"
|
|
34
33
|
],
|
|
35
34
|
"additionalProperties": false,
|
|
36
35
|
"properties": {
|
|
@@ -65,7 +64,7 @@
|
|
|
65
64
|
]
|
|
66
65
|
},
|
|
67
66
|
"uniqueItems": true,
|
|
68
|
-
"description": "The functions this person owns (
|
|
67
|
+
"description": "The functions this person owns. In the structured layout (EE-086) hats are read from the org chart in venture.json and not stored here"
|
|
69
68
|
},
|
|
70
69
|
"founder_file": {
|
|
71
70
|
"type": "string",
|