naive-framework 0.2.0 → 0.3.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.
Files changed (76) hide show
  1. package/AGENTS.md +1 -1
  2. package/docs/decisions/decisions.json +15 -0
  3. package/evidence/skills/fidelity-ladder-audit/ladder-census.mjs +2 -1
  4. package/interface/adapters.mjs +7 -6
  5. package/interface/boundaries.mjs +5 -4
  6. package/interface/brand.mjs +6 -5
  7. package/interface/cli/mentura.mjs +4 -3
  8. package/interface/cli/naive.mjs +11 -6
  9. package/interface/context.mjs +7 -6
  10. package/interface/demo/board.mjs +9 -8
  11. package/interface/demo/decision-harness.mjs +4 -3
  12. package/interface/gate-hooks.mjs +2 -1
  13. package/interface/harness/evals.mjs +8 -7
  14. package/interface/harness/run.mjs +14 -13
  15. package/interface/instrumentation.mjs +4 -3
  16. package/interface/package-routes.mjs +4 -3
  17. package/interface/provenance.mjs +5 -4
  18. package/interface/reads.mjs +4 -3
  19. package/interface/release-card.mjs +2 -1
  20. package/interface/router.mjs +10 -9
  21. package/interface/vitals.mjs +3 -2
  22. package/knowledge/connectors/catalog.mjs +3 -2
  23. package/knowledge/connectors/filesystem.mjs +10 -5
  24. package/knowledge/connectors/index.mjs +3 -2
  25. package/operations/marketing/check-public.mjs +7 -6
  26. package/operations/marketing/site/plan.mjs +4 -3
  27. package/operations/marketing/site/presence.mjs +4 -3
  28. package/package.json +1 -1
  29. package/platform/integrations/rho.mjs +2 -1
  30. package/purpose/frameworks/idea-map/route.mjs +5 -4
  31. package/purpose/frameworks/product-anatomy/anatomy.mjs +2 -1
  32. package/schema/access.mjs +2 -1
  33. package/schema/add-department.mjs +3 -2
  34. package/schema/assess-problem.mjs +3 -2
  35. package/schema/assets.mjs +6 -5
  36. package/schema/brand.mjs +11 -10
  37. package/schema/caching.mjs +5 -4
  38. package/schema/check-config.mjs +4 -3
  39. package/schema/check-data-model.mjs +2 -1
  40. package/schema/check-data-packages.mjs +5 -4
  41. package/schema/check-execution.mjs +4 -3
  42. package/schema/check-people.mjs +22 -8
  43. package/schema/check-private.mjs +2 -1
  44. package/schema/check-ready.mjs +6 -5
  45. package/schema/check-sources.mjs +2 -1
  46. package/schema/check-where-used.mjs +4 -3
  47. package/schema/decision-analytics.mjs +2 -1
  48. package/schema/decisions.mjs +14 -11
  49. package/schema/foundation.mjs +8 -7
  50. package/schema/founder.schema.json +2 -2
  51. package/schema/frames.mjs +4 -3
  52. package/schema/freshness.mjs +13 -12
  53. package/schema/gate-drafts.mjs +8 -7
  54. package/schema/init-venture.mjs +38 -20
  55. package/schema/migrate-venture.mjs +8 -7
  56. package/schema/migrations/kit-to-package.mjs +4 -3
  57. package/schema/migrations/package-layout.mjs +9 -5
  58. package/schema/model-steps.mjs +8 -7
  59. package/schema/naive-config.mjs +4 -3
  60. package/schema/need-now.mjs +5 -4
  61. package/schema/packages.mjs +11 -10
  62. package/schema/payouts.mjs +3 -2
  63. package/schema/people.schema.json +2 -3
  64. package/schema/port-venture.mjs +206 -132
  65. package/schema/release-read.mjs +5 -4
  66. package/schema/releases.mjs +21 -20
  67. package/schema/run-handoff.mjs +5 -4
  68. package/schema/setup.mjs +5 -4
  69. package/schema/stale.mjs +6 -5
  70. package/schema/structure.mjs +5 -4
  71. package/schema/time-to-ready.mjs +7 -6
  72. package/schema/triggers.mjs +7 -6
  73. package/schema/upgrades.mjs +31 -30
  74. package/schema/venture-paths.mjs +85 -0
  75. package/schema/venture.schema.json +25 -17
  76. package/schema/workspace.schema.json +5 -1
@@ -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(join(dir, f)); return r && typeof r.id === "string" ? [[r.id, { file: f, record: r }]] : []; } catch { return []; }
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(join(dir, f))) return false;
587
- try { return load(join(dir, f)).by?.kind === "model"; } catch { return false; }
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(join(dir, x)))) {
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(join(dir, file)).kind === "draft") && last.route) {
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(join(dir, file))) throw new Expected(`no judgment ${id}`, "missing record");
624
- const j = load(join(dir, file));
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 };
@@ -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 && join(dir, CONFIG);
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 = join(dir, CONFIG);
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(join(dir, CONFIG), JSON.stringify(out, null, 2) + "\n");
153
+ writeFileSync(ventureFile(dir, CONFIG), JSON.stringify(out, null, 2) + "\n");
153
154
  return out;
154
155
  }
155
156
 
@@ -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(join(dir, p)) ? load(join(dir, p)) : null;
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 {
@@ -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(join(dir, f)) ? load(join(dir, f)) : null;
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(join(dir, f)));
81
- const dump = dumpFile ? readFileSync(join(dir, dumpFile), "utf8").slice(0, 2500) : null;
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(join(dir, f)) && !(f.endsWith(".json") && ["retired", "archived"].includes(json(dir, f)?.status)));
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(join(dir, `_drafts/${f.replace(/\//g, "__")}.draft`)));
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(join(dir, f), "utf8");
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 = join(dir, 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");
@@ -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 (operations/departments.json)"
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",